true, 'opcode' => null, 'masked' => false, 'mask' => null, 'rsv1' => false, 'rsv2' => false, 'rsv3' => false, 'payload' => null ], ...$frame ]; $len = strlen($frame['payload']); if ($len > 65535) { $size0 = 127; $size1 = ($len >> 24) & 0xFF; $size2 = ($len >> 16) & 0xFF; $size3 = ($len >> 8) & 0xFF; $size4 = $len & 0xFF; } elseif ($len > 126) { $size0 = 126; $size1 = ($len >> 8) & 0xFF; $size2 = $len & 0xFF; $size3 = null; } else { $size0 = $len; $size1 = null; $size3 = null; } $encoded .= chr(($frame['final']?0x80:0x00) | ($frame['rsv1']?0x40:0x00) | ($frame['rsv2']?0x20:0x00) | ($frame['rsv3']?0x10:0x00) | ($frame['opcode'] & 0xF)); $encoded .= chr(($frame['masked']?0x80:0x00) | ($size0 & 0x7F)); if ($size1 !== null) { $encoded .= chr($size1) . chr($size2); } if ($size3 !== null) { $encoded .= chr($size3) . chr($size4); } if ($frame['masked'] === true) { if ($frame['mask'] === null || strlen($frame['mask']) !== 4) { $frame['mask'] = chr(mt_rand(0,255)).chr(mt_rand(0,255)).chr(mt_rand(0,255)).chr(mt_rand(0,255)); } $encoded .= $frame['mask']; $encoded .= $this->mask($frame['payload'], $frame['mask']); } else { $encoded .= $frame['payload']; } //$this->hexdump($encoded); return $encoded; } /** * Decode a websocket frame and return an array with the keys: * opcode (int) - the opcode * fin (bool) - final frame * rsv1-3 (bool) - reserved bits * masked (bool) - if the frame was masked * length (int) - length of payload * payload (string) - the payload */ public function decode($frame): array { // Decoded frame $decoded = []; // Keep track of the number of bytes in the header $header = 2; // Peek at the first byte, holding flags and opcode $byte0 = ord($frame[0]); $decoded['final'] = !!($byte0 & 0x80); $decoded['opcode'] = $byte0 & 0x0F; // Peek at the second byte, holding mask bit and len $byte1 = ord($frame[1]); $decoded['masked'] = $masked = !!($byte1 & 0x80); $decoded['rsv1'] = !!($byte1 & 0x40); $decoded['rsv2'] = !!($byte1 & 0x20); $decoded['rsv3'] = !!($byte1 & 0x10); $len = $byte1 & 0x7F; // Read extended length if present if ($len == 126) { $len = (ord($frame[$header+0]) << 8) | (ord($frame[$header+1])); $header += 2; } elseif ($len == 127) { $len = (ord($frame[$header+0]) << 24) | (ord($frame[$header+1]) << 16) | (ord($frame[$header+2]) << 8) | (ord($frame[$header+3])); $header += 4; } // Now for the masking if ($masked) { $mask = substr($frame, $header, 4); $header += 4; } // Extract and unmask payload $payload = substr($frame, $header, $len); if ($masked) { $payload = $this->mask($payload, $mask); $decoded['mask'] = $mask; } $decoded['length'] = $len; $decoded['payload'] = $payload; return $decoded; } private function mask(string $payload, string $mask): string { $payloadData = array_map("ord", str_split($payload,1)); $maskData = array_map("ord", str_split($mask,1)); //printf("Mask: %02x %02x %02x %02x\n", ...$maskData); $unmasked = []; for ($n = 0; $n < count($payloadData); $n++) { $unmasked[] = $payloadData[$n] ^ $maskData[$n % 4]; } return join("", array_map("chr", $unmasked)); } private function hexdump($data): void { printf("%4d .\n", strlen($data)); $rows = str_split($data, 16); $offs = 0; foreach ($rows as $row) { $h = []; $a = []; for ($n = 0; $n < 16; $n++) { if ($n < strlen($row)) { $h[] = sprintf("%02x%s", ord($row[$n]), ($n==7)?" ":" "); $a[] = sprintf("%s%s", (ctype_print($row[$n])?$row[$n]:"."), ($n==7)?" ":""); } else { $h[] = (($n==7)?" ":" "); $a[] = (($n==7)?" ":" "); } } printf("%04x | %s | %s\n", 16 * $offs++, join("", $h), join("", $a)); } } }