diff --git a/src/DatagramSocket.php b/src/DatagramSocket.php index 1804be0..018102f 100644 --- a/src/DatagramSocket.php +++ b/src/DatagramSocket.php @@ -18,14 +18,14 @@ final class DatagramSocket /** @var string Watcher ID. */ private $watcher; - /** @var string|null Stream socket name */ + /** @var SocketAddress */ private $address; /** @var Deferred|null */ private $reader; /** - * @param resource $socket A bound udp socket resource + * @param resource $socket A bound udp socket resource * @param int $chunkSize Maximum chunk size for the * * @throws \Error If a stream resource is not given for $socket. @@ -37,7 +37,7 @@ public function __construct($socket, int $chunkSize = self::DEFAULT_CHUNK_SIZE) } $this->socket = $socket; - $this->address = Internal\cleanupSocketName(@\stream_socket_get_name($this->socket, false)); + $this->address = SocketAddress::fromLocalResource($socket); \stream_set_blocking($this->socket, false); @@ -59,7 +59,7 @@ public function __construct($socket, int $chunkSize = self::DEFAULT_CHUNK_SIZE) return; } - $deferred->resolve([Internal\cleanupSocketName($address), $data]); + $deferred->resolve([SocketAddress::fromSocketName($address), $data]); if (!$reader) { Loop::disable($watcher); @@ -82,7 +82,7 @@ public function __destruct() } /** - * @return Promise<[string $address, string $data]|null> Resolves with null if the socket is closed. + * @return Promise<[SocketAddress $address, string $data]|null> Resolves with null if the socket is closed. * * @throws PendingReceiveError If a receive request is already pending. */ @@ -112,8 +112,6 @@ public function receive(): Promise */ public function send(string $address, string $data): Promise { - \assert($this->isAddressValid($address), 'Invalid packet address'); - if (!$this->socket) { return new Failure(new SocketException('The endpoint is not writable')); } @@ -171,9 +169,9 @@ public function close(): void } /** - * @return string|null + * @return SocketAddress */ - public function getAddress(): ?string + public function getAddress(): SocketAddress { return $this->address; } @@ -189,24 +187,4 @@ private function free(): void $this->reader = null; } } - - /** - * Rough address validation to catch programming mistakes. - * - * @param string $address - * - * @return bool - */ - private function isAddressValid(string $address): bool - { - $position = \strrpos($address, ':'); - if ($position === false) { - return ($address[0] ?? '') === "\0"; // udg socket address. - } - - $ip = \trim(\substr($address, 0, $position), '[]'); - $port = (int) \substr($address, $position + 1); - - return \inet_pton($ip) !== false && $port > 0 && $port < 65536; - } } diff --git a/src/ResourceSocket.php b/src/ResourceSocket.php index 67a864f..629747f 100644 --- a/src/ResourceSocket.php +++ b/src/ResourceSocket.php @@ -15,7 +15,7 @@ final class ResourceSocket implements EncryptableSocket public const DEFAULT_CHUNK_SIZE = ResourceInputStream::DEFAULT_CHUNK_SIZE; /** - * @param resource $resource Stream resource. + * @param resource $resource Stream resource. * @param int $chunkSize Read and write chunk size. * * @return self @@ -26,7 +26,7 @@ public static function fromServerSocket($resource, int $chunkSize = self::DEFAUL } /** - * @param resource $resource Stream resource. + * @param resource $resource Stream resource. * @param int $chunkSize Read and write chunk size. * @param ClientTlsContext|null $tlsContext * @@ -59,7 +59,7 @@ public static function fromClientSocket( private $remoteAddress; /** - * @param resource $resource Stream resource. + * @param resource $resource Stream resource. * @param int $chunkSize Read and write chunk size. * @param ClientTlsContext|null $tlsContext */ @@ -71,8 +71,8 @@ private function __construct( $this->tlsContext = $tlsContext; $this->reader = new ResourceInputStream($resource, $chunkSize); $this->writer = new ResourceOutputStream($resource, $chunkSize); - $this->remoteAddress = $this->getAddress(true); - $this->localAddress = $this->getAddress(false); + $this->remoteAddress = SocketAddress::fromPeerResource($resource); + $this->localAddress = SocketAddress::fromLocalResource($resource); $this->tlsState = self::TLS_STATE_DISABLED; } @@ -177,7 +177,7 @@ public function unreference(): void } /** @inheritDoc */ - public function getLocalAddress(): ?string + public function getLocalAddress(): SocketAddress { return $this->localAddress; } @@ -189,7 +189,7 @@ public function getResource() } /** @inheritDoc */ - public function getRemoteAddress(): ?string + public function getRemoteAddress(): SocketAddress { return $this->remoteAddress; } @@ -205,27 +205,4 @@ public function isClosed(): bool { return $this->getResource() === null; } - - private function getAddress(bool $wantPeer): ?string - { - $resource = $this->getResource(); - - if ($resource === null) { - return null; - } - - $remoteCleaned = Internal\cleanupSocketName(@\stream_socket_get_name($resource, $wantPeer)); - - if ($remoteCleaned !== null) { - return $remoteCleaned; - } - - $meta = @\stream_get_meta_data($resource) ?? []; - - if (\array_key_exists('stream_type', $meta) && $meta['stream_type'] === 'unix_socket') { - return Internal\cleanupSocketName(@\stream_socket_get_name($resource, !$wantPeer)); - } - - return null; - } } diff --git a/src/Server.php b/src/Server.php index 4d3aae2..151efe7 100644 --- a/src/Server.php +++ b/src/Server.php @@ -38,7 +38,7 @@ public function __construct($socket, int $chunkSize = ResourceSocket::DEFAULT_CH $this->socket = $socket; $this->chunkSize = $chunkSize; - $this->address = Internal\cleanupSocketName(@\stream_socket_get_name($this->socket, false)); + $this->address = SocketAddress::fromLocalResource($socket); \stream_set_blocking($this->socket, false); @@ -150,9 +150,9 @@ final public function unreference(): void } /** - * @return string|null + * @return SocketAddress */ - public function getAddress(): ?string + public function getAddress(): SocketAddress { return $this->address; } diff --git a/src/Socket.php b/src/Socket.php index d1fc95e..3b522f8 100644 --- a/src/Socket.php +++ b/src/Socket.php @@ -34,12 +34,12 @@ public function close(): void; public function isClosed(): bool; /** - * @return string|null + * @return SocketAddress */ - public function getLocalAddress(): ?string; + public function getLocalAddress(): SocketAddress; /** - * @return string|null + * @return SocketAddress */ - public function getRemoteAddress(): ?string; + public function getRemoteAddress(): SocketAddress; } diff --git a/src/SocketAddress.php b/src/SocketAddress.php new file mode 100644 index 0000000..bbdfd34 --- /dev/null +++ b/src/SocketAddress.php @@ -0,0 +1,113 @@ + 65535)) { + throw new \Error('Port number must be null or an integer between 1 and 65535'); + } + + if (\strrpos($host, ':')) { + $host = \trim($host, '[]'); + } + + $this->host = $host; + $this->port = $port; + } + + /** + * @return string + */ + public function getHost(): string + { + return $this->host; + } + + /** + * @return int + */ + public function getPort(): ?int + { + return $this->port; + } + + public function __toString(): string + { + $host = $this->host; + + if (\strrpos($host, ':')) { + $host = '[' . $host . ']'; + } + + if ($this->port === null) { + return $host; + } + + return $host . ':' . $this->port; + } +} diff --git a/test/DatagramSocketTest.php b/test/DatagramSocketTest.php index 3763f99..a821336 100644 --- a/test/DatagramSocketTest.php +++ b/test/DatagramSocketTest.php @@ -25,7 +25,8 @@ public function testReceive() asyncCall(function () use ($endpoint, $remote) { while ([$address, $data] = yield $endpoint->receive()) { $this->assertSame('Hello!', $data); - $this->assertSame($remote, $address); + $this->assertSame($remote->getHost(), $address->getHost()); + $this->assertSame($remote->getPort(), $address->getPort()); } }); }); @@ -47,7 +48,8 @@ public function testSend() asyncCall(function () use ($endpoint, $remote) { while ([$address, $data] = yield $endpoint->receive()) { $this->assertSame('a', $data); - $this->assertSame($remote, $address); + $this->assertSame($remote->getHost(), $address->getHost()); + $this->assertSame($remote->getPort(), $address->getPort()); yield $endpoint->send($address, 'b'); } }); diff --git a/test/SocketTest.php b/test/SocketTest.php index b8219be..c675ac6 100644 --- a/test/SocketTest.php +++ b/test/SocketTest.php @@ -34,10 +34,10 @@ public function testSocketAddress(): void $serverSocket = Socket\ResourceSocket::fromServerSocket($s); $this->assertNotNull($clientSocket->getRemoteAddress()); - $this->assertSame(__DIR__ . '/socket.sock', $clientSocket->getLocalAddress()); - $this->assertSame($clientSocket->getRemoteAddress(), $clientSocket->getLocalAddress()); - $this->assertSame($serverSocket->getRemoteAddress(), $serverSocket->getLocalAddress()); - $this->assertSame($serverSocket->getRemoteAddress(), $clientSocket->getLocalAddress()); + $this->assertSame(__DIR__ . '/socket.sock', (string) $clientSocket->getLocalAddress()); + $this->assertEquals($clientSocket->getRemoteAddress(), $clientSocket->getLocalAddress()); + $this->assertEquals($serverSocket->getRemoteAddress(), $serverSocket->getLocalAddress()); + $this->assertEquals($serverSocket->getRemoteAddress(), $clientSocket->getLocalAddress()); } finally { @\unlink(__DIR__ . '/socket.sock'); } diff --git a/test/functionsTest.php b/test/functionsTest.php index 1fcafcd..26ee619 100644 --- a/test/functionsTest.php +++ b/test/functionsTest.php @@ -47,7 +47,7 @@ public function testListenIPv6(): void { try { $socket = Socket\listen('[::1]:0'); - $this->assertRegExp('(\[::1\]:\d+)', $socket->getAddress()); + $this->assertRegExp('(\[::1\]:\d+)', (string) $socket->getAddress()); } catch (Socket\SocketException $e) { if ($e->getMessage() === 'Could not create server tcp://[::1]:0: [Error: #0] Cannot assign requested address') { $this->markTestSkipped('Missing IPv6 support');