diff --git a/phpstan.neon b/phpstan.neon index 876ed33..7cde2a4 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -1,5 +1,5 @@ parameters: - level: 6 + level: max paths: - %currentWorkingDirectory%/src phpVersion: 80200 diff --git a/phpstan.tests.neon b/phpstan.tests.neon index 0987142..d79592f 100644 --- a/phpstan.tests.neon +++ b/phpstan.tests.neon @@ -1,5 +1,5 @@ parameters: treatPhpDocTypesAsCertain: false - level: 10 + level: max paths: - %currentWorkingDirectory%/tests diff --git a/src/FreeDSx/Socket/HasSocketOptions.php b/src/FreeDSx/Socket/HasSocketOptions.php index 0145408..34abab3 100644 --- a/src/FreeDSx/Socket/HasSocketOptions.php +++ b/src/FreeDSx/Socket/HasSocketOptions.php @@ -51,6 +51,9 @@ trait HasSocketOptions private int $timeoutRead = 15; + /** + * @var positive-int + */ private int $bufferSize = 8192; public function setTransport(Transport $transport): self @@ -231,6 +234,9 @@ public function setBufferSize(int $bufferSize): self return $this; } + /** + * @return positive-int + */ public function getBufferSize(): int { return $this->bufferSize; diff --git a/src/FreeDSx/Socket/Queue/Asn1MessageQueue.php b/src/FreeDSx/Socket/Queue/Asn1MessageQueue.php index abe3deb..5a881c8 100644 --- a/src/FreeDSx/Socket/Queue/Asn1MessageQueue.php +++ b/src/FreeDSx/Socket/Queue/Asn1MessageQueue.php @@ -15,9 +15,13 @@ use FreeDSx\Asn1\Encoder\EncoderInterface; use FreeDSx\Asn1\Exception\PartialPduException; +use FreeDSx\Asn1\Type\AbstractType; use FreeDSx\Socket\Exception\PartialMessageException; use FreeDSx\Socket\PduInterface; use FreeDSx\Socket\Socket; +use RuntimeException; +use UnexpectedValueException; +use function get_debug_type; /** * Represents an ASN.1 based message queue using the FreeDSx ASN.1 library. @@ -35,7 +39,7 @@ public function __construct( protected ?string $pduClass = null, ) { if ($pduClass !== null && !\is_subclass_of($pduClass, PduInterface::class)) { - throw new \RuntimeException(sprintf( + throw new RuntimeException(sprintf( 'The class "%s" must implement "%s", but it does not.', $pduClass, PduInterface::class, @@ -61,13 +65,17 @@ protected function constructMessage( ?int $id = null, ): mixed { if ($this->pduClass === null) { - throw new \RuntimeException('You must either define a PDU class or override getPdu().'); + throw new RuntimeException('You must either define a PDU class or override getPdu().'); } - $callable = [$this->pduClass, 'fromAsn1']; - if (!\is_callable($callable)) { - throw new \RuntimeException(sprintf('The class %s is not callable.', $this->pduClass)); + $asn1 = $message->getMessage(); + if (!$asn1 instanceof AbstractType) { + throw new UnexpectedValueException(sprintf( + 'Expected an instance of %s, got %s.', + AbstractType::class, + get_debug_type($asn1), + )); } - return \call_user_func($callable, $message->getMessage()); + return ($this->pduClass)::fromAsn1($asn1); } } diff --git a/src/FreeDSx/Socket/Socket.php b/src/FreeDSx/Socket/Socket.php index 74e6d3f..c34d6a8 100644 --- a/src/FreeDSx/Socket/Socket.php +++ b/src/FreeDSx/Socket/Socket.php @@ -61,16 +61,17 @@ public function __construct( public function read(bool $block = true): string|false { - stream_set_blocking($this->socket, $block); + $stream = $this->getStream(); + stream_set_blocking($stream, $block); $data = fread( - $this->socket, + $stream, $this->options->getBufferSize(), ); if (!$block) { stream_set_blocking( - $this->socket, + $stream, true, ); } @@ -82,14 +83,20 @@ public function read(bool $block = true): string|false public function write(string $data): static { - @fwrite($this->socket, $data); + @fwrite( + $this->getStream(), + $data, + ); return $this; } public function block(bool $block): static { - stream_set_blocking($this->socket, $block); + stream_set_blocking( + $this->getStream(), + $block + ); return $this; } @@ -134,13 +141,21 @@ public function close(): static */ public function encrypt(bool $encrypt): static { - stream_set_blocking($this->socket, true); + $stream = $this->getStream(); + + stream_set_blocking( + $stream, + true, + ); $result = stream_socket_enable_crypto( - $this->socket, + $stream, $encrypt, $this->options->getSslCryptoMethod(), ); - stream_set_blocking($this->socket, false); + stream_set_blocking( + $stream, + false, + ); if ($result !== true) { throw new ConnectionException(sprintf( @@ -179,8 +194,8 @@ public function connect(string $host): static STREAM_CLIENT_CONNECT, $this->createSocketContext(), ); - $this->errorNumber = $errorNumber; - $this->errorMessage = $errorMessage; + $this->errorNumber = $errorNumber ?? 0; + $this->errorMessage = $errorMessage ?? ''; if ($socket === false) { throw new ConnectionException(sprintf( @@ -208,9 +223,12 @@ public function getOptions(): SocketOptionsInterface */ public static function create( string $host, - ?SocketOptions $options = null, + SocketOptions $options = new SocketOptions(), ): Socket { - return (new self(null, $options))->connect($host); + return (new self( + null, + $options, + ))->connect($host); } /** @@ -283,8 +301,21 @@ protected function createSocketContext() protected function setStreamOpts(): void { stream_set_timeout( - $this->socket, + $this->getStream(), $this->options->getTimeoutRead(), ); } + + /** + * @return resource + * @throws ConnectionException + */ + protected function getStream() + { + if ($this->socket === null) { + throw new ConnectionException('The socket is not connected.'); + } + + return $this->socket; + } } diff --git a/src/FreeDSx/Socket/SocketOptionsInterface.php b/src/FreeDSx/Socket/SocketOptionsInterface.php index ad86eb1..d1e6f60 100644 --- a/src/FreeDSx/Socket/SocketOptionsInterface.php +++ b/src/FreeDSx/Socket/SocketOptionsInterface.php @@ -46,6 +46,9 @@ public function getTimeoutConnect(): int; public function getTimeoutRead(): int; + /** + * @return positive-int + */ public function getBufferSize(): int; /** diff --git a/src/FreeDSx/Socket/SocketServer.php b/src/FreeDSx/Socket/SocketServer.php index 75f4d88..6f9f039 100644 --- a/src/FreeDSx/Socket/SocketServer.php +++ b/src/FreeDSx/Socket/SocketServer.php @@ -14,9 +14,10 @@ namespace FreeDSx\Socket; use FreeDSx\Socket\Exception\ConnectionException; -use function array_search; +use function array_filter; use function array_values; use function is_resource; +use function is_string; use function stream_socket_accept; use function stream_socket_recvfrom; use function stream_socket_server; @@ -81,8 +82,8 @@ public function listen( $flags, $this->createSocketContext(), ); - $this->errorNumber = $errorNumber; - $this->errorMessage = $errorMessage; + $this->errorNumber = $errorNumber ?? 0; + $this->errorMessage = $errorMessage ?? ''; if ($socket === false) { throw new ConnectionException(sprintf( @@ -128,20 +129,27 @@ private static function optionsForAcceptedClient(SocketServerOptions $server): S /** * Receive data from a UDP based socket. Optionally get the IP address the data was received from. * + * @param-out string|null $ipAddress * @todo Buffer size should be adjustable. Max UDP packet size is 65507. Currently this avoids possible truncation. */ public function receive(?string &$ipAddress = null): ?string { $this->block(true); + $received = null; $data = stream_socket_recvfrom( - $this->socket, + $this->getStream(), 65507, 0, - $ipAddress, + $received, ); + $ipAddress = is_string($received) + ? $received + : null; - return $data === false ? null : $data; + return $data === false + ? null + : $data; } /** @@ -154,11 +162,10 @@ public function getClients(): array public function removeClient(Socket $socket): void { - $index = array_search($socket, $this->clients, true); - if ($index !== false) { - unset($this->clients[$index]); - $this->clients = array_values($this->clients); - } + $this->clients = array_values(array_filter( + $this->clients, + fn (Socket $client): bool => $client !== $socket, + )); } /**