/** * @coroutine * * Flushes the contents of the internal buffer to the underlying stream. * * @return \Generator * * @resolve int Number of bytes written to the stream. * * @throws \Icicle\Awaitable\Exception\TimeoutException If the operation times out. * @throws \Icicle\Stream\Exception\UnwritableException If the stream is no longer writable. * @throws \Icicle\Stream\Exception\ClosedException If the stream is unexpectedly closed. */ public function flush() : \Generator { if ($this->buffer->isEmpty()) { return 0; } return yield from $this->stream->write($this->buffer->drain(), $this->timeout); }
/** * @coroutine * * @param string $data * @param float|int $timeout * @param bool $end * * @return \Generator * * @resolve int Number of bytes written to the stream. * * @throws \Icicle\Stream\Exception\UnwritableException If the stream is no longer writable. */ protected function send(string $data, float $timeout = 0, bool $end = false) : \Generator { if (!$this->isWritable()) { throw new UnwritableException('The stream is no longer writable.'); } $this->buffer->push($data); if (null !== $this->delayed && !$this->buffer->isEmpty()) { $delayed = $this->delayed; $this->delayed = null; $delayed->resolve($this->remove()); } if ($end) { if ($this->buffer->isEmpty()) { $this->free(); } else { $this->writable = false; } } if (0 !== $this->hwm && $this->buffer->getLength() > $this->hwm) { $awaitable = new Delayed($this->onCancelled = $this->onCancelled ?: function () { $this->free(); }); $this->queue->push($awaitable); if ($timeout) { $awaitable = $awaitable->timeout($timeout); } (yield $awaitable); } return strlen($data); }
/** * {@inheritdoc} * * @throws \Icicle\Http\Exception\MessageException If an invalid chunk length is found. */ protected function send(string $data, float $timeout = 0, bool $end = false) : \Generator { $this->buffer->push($data); $data = ''; while (!$this->buffer->isEmpty()) { if (0 === $this->length) { // Read chunk length. if (false === ($position = $this->buffer->search("\r\n"))) { return yield from parent::send($data, $timeout, $end); } $length = rtrim($this->buffer->remove($position + 2), "\r\n"); if ($position = strpos($length, ';')) { $length = substr($length, 0, $position); } if (!preg_match('/^[a-f0-9]+$/i', $length)) { yield from parent::send('', $timeout, true); throw new MessageException(Response::BAD_REQUEST, 'Invalid chunk length.'); } $this->length = hexdec($length) + 2; if (2 === $this->length) { // Termination chunk. $end = true; } } if (2 < $this->length) { // Read chunk. $buffer = $this->buffer->remove($this->length - 2); $this->length -= strlen($buffer); $data .= $buffer; } if (2 >= $this->length) { // Remove \r\n after chunk. $this->length -= strlen($this->buffer->remove($this->length)); } } return yield from parent::send($data, $timeout, $end); }
/** * @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); }