Code cleanup, return value fix, comments

This commit is contained in:
Chris 2024-02-24 13:59:39 +01:00
parent d603870d7f
commit 899dd3b7e4
4 changed files with 73 additions and 17 deletions

View File

@ -221,19 +221,33 @@ class WebSocketConnection implements WebSocketInterface
/**
* {@inheritDoc}
*
* @see writeBinary() to write binary frames as opposed to text frames.
*/
public function write($data)
{
return $this->send(self::OP_FRAME_TEXT, $data);
}
/**
* Write binary frames.
*
* @param string $data
* @return bool
*/
public function writeBinary($data)
{
return $this->send(self::OP_FRAME_BINARY, $data);
}
/**
* Encode and send a frame.
*
* @param int $opcode
* @param string $data
* @param bool $final
* @param bool $masked
* @return bool
*/
public function send(int $opcode, string $data, bool $final = true, bool $masked = false)
{
@ -245,9 +259,7 @@ class WebSocketConnection implements WebSocketInterface
'masked' => $masked
]);
$this->outStream->write($frame);
return true;
return $this->outStream->write($frame);
}
/**
@ -261,7 +273,10 @@ class WebSocketConnection implements WebSocketInterface
$this->emit('close', []);
}
public function closeWithReason(string $reason, int $code=1000)
/**
* {@inheritDoc}
*/
public function closeWithReason(string $reason, int $code=1000): void
{
$payload = chr(($code >> 8) & 0xFF) . chr($code & 0xFF) . $reason;
$this->send(self::OP_CLOSE, $payload);

View File

@ -16,15 +16,46 @@ interface WebSocketInterface extends ConnectionInterface
const EVENT_GROUP_JOIN = 'join';
const EVENT_GROUP_LEAVE = 'leave';
/**
* Close the connection with a reason and code.
*
* @param string $reason
* @param int $code
* @return void
*/
public function closeWithReason(string $reason, int $code=1000): void;
public function setGroup(?string $name): void;
public function getGroupName(): ?string;
public function getGroup(): ?ConnectionGroup;
public function closeWithReason(string $reason, int $code=1000);
/**
* Get the initial HTTP request sent to the server.
*
* @return ServerRequestInterface
*/
public function getServerRequest(): ServerRequestInterface;
}
/**
* Assign this connection to a connection group. If the connection is already
* part of a group, it will leave the current group before joining the new
* group.
*
* @param null|string $name The group name to join
* @return void
*/
public function setGroup(?string $name): void;
/**
* Get the current connection group.
*
* @see getGroupName() if you want the name of the group.
*
* @return null|ConnectionGroup
*/
public function getGroup(): ?ConnectionGroup;
/**
* Get the name of the current connection group
*
* @return null|string
*/
public function getGroupName(): ?string;
}

View File

@ -18,6 +18,8 @@ class WebSocketMiddleware implements EventEmitterInterface
const MAGIC = '258EAFA5-E914-47DA-95CA-C5AB0DC85B11';
const VERSION = 13;
use EventEmitterTrait;
private GroupManager $groupManager;
@ -32,7 +34,7 @@ class WebSocketMiddleware implements EventEmitterInterface
public function addRoute(string $path, callable $handler, array $allowedOrigins=[]): void
{
// TODO implement or remove
}
public function __invoke(ServerRequestInterface $request, callable $next)
@ -62,12 +64,14 @@ class WebSocketMiddleware implements EventEmitterInterface
$this->emit(self::EVENT_CONNECTION, [ $websocket ]);
//});
// TODO would it be possible or rather useful for the 'connection' event to set additional response headers to be sent here?
return new Response(
Response::STATUS_SWITCHING_PROTOCOLS,
array(
'Upgrade' => 'websocket',
'Connection' => 'upgrade',
'Sec-WebSocket-Accept' => $handshakeResponse
'Sec-WebSocket-Accept' => $handshakeResponse,
'Sec-WebSocket-Version' => self::VERSION
),
$stream
);

View File

@ -125,6 +125,7 @@ class WebSocketProtocol
$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);
@ -146,14 +147,16 @@ class WebSocketProtocol
$header += 4;
}
// Now for the masking
// Now for the masking, if present.
if ($masked) {
$mask = substr($frame, $header, 4);
$header += 4;
}
// Extract and unmask payload
// Extract the payload, and unmask it if needed. The mask() function handles
// both masking and unmasing as the algorithm uses xor.
$payload = substr($frame, $header, $len);
// TODO check that extracted payload len equals expected len
if ($masked) {
$payload = $this->mask($payload, $mask);
$decoded['mask'] = $mask;
@ -174,14 +177,17 @@ class WebSocketProtocol
*/
private function mask(string $payload, string $mask): string
{
// Unpack the payload and mask into byte values
$payloadData = array_map("ord", str_split($payload,1));
$maskData = array_map("ord", str_split($mask,1));
// TODO check that mask len==4
$unmasked = [];
for ($n = 0; $n < count($payloadData); $n++) {
$unmasked[] = $payloadData[$n] ^ $maskData[$n % 4];
}
// Return the masked byte values packed into a string
return join("", array_map("chr", $unmasked));
}