Skip to content

Commit

Permalink
Use object for socket addresses
Browse files Browse the repository at this point in the history
  • Loading branch information
trowski committed May 29, 2019
1 parent 30daaed commit 3c136d2
Show file tree
Hide file tree
Showing 8 changed files with 143 additions and 73 deletions.
36 changes: 7 additions & 29 deletions src/DatagramSocket.php
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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);

Expand All @@ -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);
Expand All @@ -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.
*/
Expand Down Expand Up @@ -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'));
}
Expand Down Expand Up @@ -171,9 +169,9 @@ public function close(): void
}

/**
* @return string|null
* @return SocketAddress
*/
public function getAddress(): ?string
public function getAddress(): SocketAddress
{
return $this->address;
}
Expand All @@ -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;
}
}
37 changes: 7 additions & 30 deletions src/ResourceSocket.php
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
*
Expand Down Expand Up @@ -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
*/
Expand All @@ -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;
}

Expand Down Expand Up @@ -177,7 +177,7 @@ public function unreference(): void
}

/** @inheritDoc */
public function getLocalAddress(): ?string
public function getLocalAddress(): SocketAddress
{
return $this->localAddress;
}
Expand All @@ -189,7 +189,7 @@ public function getResource()
}

/** @inheritDoc */
public function getRemoteAddress(): ?string
public function getRemoteAddress(): SocketAddress
{
return $this->remoteAddress;
}
Expand All @@ -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;
}
}
6 changes: 3 additions & 3 deletions src/Server.php
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand Down Expand Up @@ -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;
}
Expand Down
8 changes: 4 additions & 4 deletions src/Socket.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
113 changes: 113 additions & 0 deletions src/SocketAddress.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
<?php

namespace Amp\Socket;

final class SocketAddress
{
/** @var string */
private $host;

/** @var int|null */
private $port;

/**
* @param resource $resource
*
* @return self
*/
public static function fromPeerResource($resource): self
{
$name = @\stream_socket_get_name($resource, true);

if ($name === false || $name === "\0") {
return self::fromLocalResource($resource);
}

return self::fromSocketName($name);
}

/**
* @param resource $resource
*
* @return self
*/
public static function fromLocalResource($resource): self
{
$wantPeer = false;

do {
$name = @\stream_socket_get_name($resource, $wantPeer);

if ($name !== false && $name !== "\0") {
return self::fromSocketName($name);
}
} while ($wantPeer = !$wantPeer);

return new self('');
}

/**
* @param string $name
*
* @return SocketAddress|null
*/
public static function fromSocketName(string $name): self
{
if ($portStartPos = \strrpos($name, ':')) {
$host = \substr($name, 0, $portStartPos);
$port = (int) \substr($name, $portStartPos + 1);
return new self($host, $port);
}

return new self($name);
}

/**
* @param string $host
* @param int|null $port
*/
public function __construct(string $host, ?int $port = null)
{
if ($port !== null && ($port < 1 || $port > 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;
}
}
6 changes: 4 additions & 2 deletions test/DatagramSocketTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -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());
}
});
});
Expand All @@ -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');
}
});
Expand Down
8 changes: 4 additions & 4 deletions test/SocketTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -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');
}
Expand Down
2 changes: 1 addition & 1 deletion test/functionsTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -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');
Expand Down

0 comments on commit 3c136d2

Please sign in to comment.