/** * @coroutine * * @param int $maxSize Max frame size. * @param float|int $timeout * * @return \Generator * * @resolve \Icicle\WebSocket\Protocol\Rfc6455Frame * * @throws \Icicle\WebSocket\Exception\FrameException */ public function read(int $maxSize, float $timeout = 0) : \Generator { $buffer = new Buffer(); try { do { $buffer->push(yield from $this->socket->read(0, null, $timeout)); } while ($buffer->getLength() < 2); $bytes = unpack('Cflags/Clength', $buffer->shift(2)); $rsv = $bytes['flags'] & self::RSV_MASK; $opcode = $bytes['flags'] & self::OPCODE_MASK; $final = (bool) ($bytes['flags'] & self::FIN_MASK); $masked = (bool) ($bytes['length'] & self::MASK_FLAG_MASK); $size = $bytes['length'] & self::LENGTH_MASK; if ($masked === $this->masked) { throw new FrameException(sprintf('Received %s frame.', $masked ? 'masked' : 'unmasked')); } if ($size === self::TWO_BYTE_LENGTH_FLAG) { while ($buffer->getLength() < 2) { $buffer->push(yield from $this->socket->read(0, null, $timeout)); } $bytes = unpack('nlength', $buffer->shift(2)); $size = $bytes['length']; if ($size < self::TWO_BYTE_LENGTH_FLAG) { throw new FrameException('Frame format error.'); } } elseif ($size === self::EIGHT_BYTE_LENGTH_FLAG) { while ($buffer->getLength() < 8) { $buffer->push(yield from $this->socket->read(0, null, $timeout)); } $bytes = unpack('Nhigh/Nlow', $buffer->shift(8)); $size = $bytes['high'] << 32 | $bytes['low']; if ($size < self::TWO_BYTE_MAX_LENGTH) { throw new FrameException('Frame format error.'); } } if ($size > $maxSize) { throw new PolicyException('Frame size exceeded max allowed size.'); } if ($masked) { while ($buffer->getLength() < self::MASK_LENGTH) { $buffer->push(yield from $this->socket->read(0, null, $timeout)); } $mask = $buffer->shift(self::MASK_LENGTH); } while ($buffer->getLength() < $size) { $buffer->push(yield from $this->socket->read(0, null, $timeout)); } $data = $buffer->shift($size); if ($masked) { $data ^= str_repeat($mask, (int) (($size + self::MASK_LENGTH - 1) / self::MASK_LENGTH)); } } finally { if (!$buffer->isEmpty()) { $this->socket->unshift((string) $buffer); } } return new Frame($opcode, $data, $rsv, $final); }
/** * Returns bytes from the buffer based on the current length or current search byte. * * @return string */ private function remove() : string { if (null !== $this->byte && false !== ($position = $this->buffer->search($this->byte))) { if (0 === $this->length || $position < $this->length) { return $this->buffer->shift($position + 1); } return $this->buffer->shift($this->length); } if (0 === $this->length) { return $this->buffer->drain(); } return $this->buffer->shift($this->length); }
/** * @coroutine * * Reads and parses characters from the stream according to a format. * * The format string is of the same format as `sscanf()`. * * @param string $format The parse format. * @param float|int $timeout Number of seconds until the returned promise is rejected with a TimeoutException * if no data is received. Use 0 for no timeout. * * @return \Generator * * @resolve array An array of parsed values. * * @throws \Icicle\Awaitable\Exception\TimeoutException If the operation times out. * @throws \Icicle\Stream\Exception\UnreadableException If the stream is no longer readable. * @throws \Icicle\Stream\Exception\ClosedException If the stream is unexpectedly closed. * * @see http://php.net/sscanf */ public function scan(string $format, float $timeout = 0) : \Generator { // Read from the stream chunk by chunk, attempting to satisfy the format // string each time until the format successfully parses or the end of // the stream is reached. while (true) { $result = sscanf((string) $this->buffer, $format . '%n'); $length = $result ? array_pop($result) : null; // If the format string was satisfied, consume the used characters and // return the parsed results. if ($length !== null && $length < $this->buffer->getLength()) { $this->buffer->shift($length); return $result; } // Read more into the buffer if possible. if ($this->stream->isReadable()) { $this->buffer->push(yield from $this->stream->read(0, null, $timeout)); } else { // Format string can't be satisfied. return []; } } }
/** * @param \Icicle\Stream\Structures\Buffer $buffer * @param \Icicle\Socket\Socket $socket * @param float|int $timeout * * @return \Generator * * @throws \Icicle\Http\Exception\MessageException * @throws \Icicle\Http\Exception\ParseException */ protected function readHeaders(Buffer $buffer, Socket $socket, float $timeout = 0) : \Generator { $size = 0; $headers = []; do { while (false === ($position = $buffer->search("\r\n"))) { if ($buffer->getLength() >= $this->maxSize) { throw new MessageException(Response::REQUEST_HEADER_TOO_LARGE, sprintf('Message header exceeded maximum size of %d bytes.', $this->maxSize)); } $buffer->push(yield from $socket->read(0, null, $timeout)); } $length = $position + 2; $line = $buffer->shift($length); if (2 === $length) { return $headers; } $size += $length; $parts = explode(':', $line, 2); if (2 !== count($parts)) { throw new ParseException('Found header without colon.'); } list($name, $value) = $parts; $name = Message\decode($name); $value = Message\decode(trim($value)); // No check for case as Message class will automatically combine similarly named headers. if (!isset($headers[$name])) { $headers[$name] = [$value]; } else { $headers[$name][] = $value; } } while ($size < $this->maxSize); throw new MessageException(Response::REQUEST_HEADER_TOO_LARGE, sprintf('Message header exceeded maximum size of %d bytes.', $this->maxSize)); }