summaryrefslogtreecommitdiffstats
path: root/vendor/clue/socket-raw
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/clue/socket-raw')
-rw-r--r--vendor/clue/socket-raw/LICENSE21
-rw-r--r--vendor/clue/socket-raw/composer.json23
-rw-r--r--vendor/clue/socket-raw/src/Exception.php91
-rw-r--r--vendor/clue/socket-raw/src/Factory.php282
-rw-r--r--vendor/clue/socket-raw/src/Socket.php562
5 files changed, 979 insertions, 0 deletions
diff --git a/vendor/clue/socket-raw/LICENSE b/vendor/clue/socket-raw/LICENSE
new file mode 100644
index 0000000..da15612
--- /dev/null
+++ b/vendor/clue/socket-raw/LICENSE
@@ -0,0 +1,21 @@
+The MIT License (MIT)
+
+Copyright (c) 2013 Christian Lück
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is furnished
+to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
diff --git a/vendor/clue/socket-raw/composer.json b/vendor/clue/socket-raw/composer.json
new file mode 100644
index 0000000..2add27e
--- /dev/null
+++ b/vendor/clue/socket-raw/composer.json
@@ -0,0 +1,23 @@
+{
+ "name": "clue/socket-raw",
+ "description": "Simple and lightweight OOP wrapper for PHP's low-level sockets extension (ext-sockets).",
+ "keywords": ["socket", "stream", "datagram", "dgram", "client", "server", "ipv6", "tcp", "udp", "icmp", "unix", "udg"],
+ "homepage": "https://github.com/clue/socket-raw",
+ "license": "MIT",
+ "authors": [
+ {
+ "name": "Christian Lück",
+ "email": "christian@clue.engineering"
+ }
+ ],
+ "autoload": {
+ "psr-4": {"Socket\\Raw\\": "src"}
+ },
+ "require": {
+ "ext-sockets": "*",
+ "php": ">=5.3"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^9.3 || ^5.7 || ^4.8.35"
+ }
+}
diff --git a/vendor/clue/socket-raw/src/Exception.php b/vendor/clue/socket-raw/src/Exception.php
new file mode 100644
index 0000000..bdabf78
--- /dev/null
+++ b/vendor/clue/socket-raw/src/Exception.php
@@ -0,0 +1,91 @@
+<?php
+
+namespace Socket\Raw;
+
+use RuntimeException;
+
+class Exception extends RuntimeException
+{
+ /**
+ * Create an Exception after a socket operation on the given $resource failed
+ *
+ * @param \Socket|resource $resource
+ * @param string $messagePrefix
+ * @return self
+ * @uses socket_last_error() to get last socket error code
+ * @uses socket_clear_error() to clear socket error code
+ * @uses self::createFromCode() to automatically construct exception with full error message
+ */
+ public static function createFromSocketResource($resource, $messagePrefix = 'Socket operation failed')
+ {
+ if (PHP_VERSION_ID >= 80000) {
+ try {
+ $code = socket_last_error($resource);
+ } catch (\Error $e) {
+ $code = SOCKET_ENOTSOCK;
+ }
+ } elseif (is_resource($resource)) {
+ $code = socket_last_error($resource);
+ socket_clear_error($resource);
+ } else {
+ // socket already closed, return fixed error code instead of operating on invalid handle
+ $code = SOCKET_ENOTSOCK;
+ }
+
+ return self::createFromCode($code, $messagePrefix);
+ }
+
+ /**
+ * Create an Exception after a global socket operation failed (like socket creation)
+ *
+ * @param string $messagePrefix
+ * @return self
+ * @uses socket_last_error() to get last global error code
+ * @uses socket_clear_error() to clear global error code
+ * @uses self::createFromCode() to automatically construct exception with full error message
+ */
+ public static function createFromGlobalSocketOperation($messagePrefix = 'Socket operation failed')
+ {
+ $code = socket_last_error();
+ socket_clear_error();
+
+ return self::createFromCode($code, $messagePrefix);
+ }
+
+ /**
+ * Create an Exception for given error $code
+ *
+ * @param int $code
+ * @param string $messagePrefix
+ * @return self
+ * @throws Exception if given $val is boolean false
+ * @uses self::getErrorMessage() to translate error code to error message
+ */
+ public static function createFromCode($code, $messagePrefix = 'Socket error')
+ {
+ return new self($messagePrefix . ': ' . self::getErrorMessage($code), $code);
+ }
+
+ /**
+ * get error message for given error code
+ *
+ * @param int $code error code
+ * @return string
+ * @uses socket_strerror() to translate error code to error message
+ * @uses get_defined_constants() to check for related error constant
+ */
+ protected static function getErrorMessage($code)
+ {
+ $string = socket_strerror($code);
+
+ // search constant starting with SOCKET_ for this error code
+ foreach (get_defined_constants() as $key => $value) {
+ if($value === $code && strpos($key, 'SOCKET_') === 0) {
+ $string .= ' (' . $key . ')';
+ break;
+ }
+ }
+
+ return $string;
+ }
+}
diff --git a/vendor/clue/socket-raw/src/Factory.php b/vendor/clue/socket-raw/src/Factory.php
new file mode 100644
index 0000000..a5068f9
--- /dev/null
+++ b/vendor/clue/socket-raw/src/Factory.php
@@ -0,0 +1,282 @@
+<?php
+
+namespace Socket\Raw;
+
+use \InvalidArgumentException;
+
+class Factory
+{
+ /**
+ * create client socket connected to given target address
+ *
+ * @param string $address target address to connect to
+ * @param null|float $timeout connection timeout (in seconds), default null = no limit
+ * @return \Socket\Raw\Socket
+ * @throws InvalidArgumentException if given address is invalid
+ * @throws Exception on error
+ * @uses self::createFromString()
+ * @uses Socket::connect()
+ * @uses Socket::connectTimeout()
+ */
+ public function createClient($address, $timeout = null)
+ {
+ $socket = $this->createFromString($address, $scheme);
+
+ try {
+ if ($timeout === null) {
+ $socket->connect($address);
+ } else {
+ // connectTimeout enables non-blocking mode, so turn blocking on again
+ $socket->connectTimeout($address, $timeout);
+ $socket->setBlocking(true);
+ }
+ } catch (Exception $e) {
+ $socket->close();
+ throw $e;
+ }
+
+ return $socket;
+ }
+
+ /**
+ * create server socket bound to given address (and start listening for streaming clients to connect to this stream socket)
+ *
+ * @param string $address address to bind socket to
+ * @return \Socket\Raw\Socket
+ * @throws Exception on error
+ * @uses self::createFromString()
+ * @uses Socket::bind()
+ * @uses Socket::listen() only for stream sockets (TCP/UNIX)
+ */
+ public function createServer($address)
+ {
+ $socket = $this->createFromString($address, $scheme);
+
+ try {
+ $socket->bind($address);
+
+ if ($socket->getType() === SOCK_STREAM) {
+ $socket->listen();
+ }
+ } catch (Exception $e) {
+ $socket->close();
+ throw $e;
+ }
+
+ return $socket;
+ }
+
+ /**
+ * create TCP/IPv4 stream socket
+ *
+ * @return \Socket\Raw\Socket
+ * @throws Exception on error
+ * @uses self::create()
+ */
+ public function createTcp4()
+ {
+ return $this->create(AF_INET, SOCK_STREAM, SOL_TCP);
+ }
+
+ /**
+ * create TCP/IPv6 stream socket
+ *
+ * @return \Socket\Raw\Socket
+ * @throws Exception on error
+ * @uses self::create()
+ */
+ public function createTcp6()
+ {
+ return $this->create(AF_INET6, SOCK_STREAM, SOL_TCP);
+ }
+
+ /**
+ * create UDP/IPv4 datagram socket
+ *
+ * @return \Socket\Raw\Socket
+ * @throws Exception on error
+ * @uses self::create()
+ */
+ public function createUdp4()
+ {
+ return $this->create(AF_INET, SOCK_DGRAM, SOL_UDP);
+ }
+
+ /**
+ * create UDP/IPv6 datagram socket
+ *
+ * @return \Socket\Raw\Socket
+ * @throws Exception on error
+ * @uses self::create()
+ */
+ public function createUdp6()
+ {
+ return $this->create(AF_INET6, SOCK_DGRAM, SOL_UDP);
+ }
+
+ /**
+ * create local UNIX stream socket
+ *
+ * @return \Socket\Raw\Socket
+ * @throws Exception on error
+ * @uses self::create()
+ */
+ public function createUnix()
+ {
+ return $this->create(AF_UNIX, SOCK_STREAM, 0);
+ }
+
+ /**
+ * create local UNIX datagram socket (UDG)
+ *
+ * @return \Socket\Raw\Socket
+ * @throws Exception on error
+ * @uses self::create()
+ */
+ public function createUdg()
+ {
+ return $this->create(AF_UNIX, SOCK_DGRAM, 0);
+ }
+
+ /**
+ * create raw ICMP/IPv4 datagram socket (requires root!)
+ *
+ * @return \Socket\Raw\Socket
+ * @throws Exception on error
+ * @uses self::create()
+ */
+ public function createIcmp4()
+ {
+ return $this->create(AF_INET, SOCK_RAW, getprotobyname('icmp'));
+ }
+
+ /**
+ * create raw ICMPv6 (IPv6) datagram socket (requires root!)
+ *
+ * @return \Socket\Raw\Socket
+ * @throws Exception on error
+ * @uses self::create()
+ */
+ public function createIcmp6()
+ {
+ return $this->create(AF_INET6, SOCK_RAW, 58 /*getprotobyname('icmp')*/);
+ }
+
+ /**
+ * create low level socket with given arguments
+ *
+ * @param int $domain
+ * @param int $type
+ * @param int $protocol
+ * @return \Socket\Raw\Socket
+ * @throws Exception if creating socket fails
+ * @throws \Error PHP 8 only: throws \Error when arguments are invalid
+ * @uses socket_create()
+ */
+ public function create($domain, $type, $protocol)
+ {
+ $sock = @socket_create($domain, $type, $protocol);
+ if ($sock === false) {
+ throw Exception::createFromGlobalSocketOperation('Unable to create socket');
+ }
+ return new Socket($sock);
+ }
+
+ /**
+ * create a pair of indistinguishable sockets (commonly used in IPC)
+ *
+ * @param int $domain
+ * @param int $type
+ * @param int $protocol
+ * @return \Socket\Raw\Socket[]
+ * @throws Exception if creating pair of sockets fails
+ * @throws \Error PHP 8 only: throws \Error when arguments are invalid
+ * @uses socket_create_pair()
+ */
+ public function createPair($domain, $type, $protocol)
+ {
+ $ret = @socket_create_pair($domain, $type, $protocol, $pair);
+ if ($ret === false) {
+ throw Exception::createFromGlobalSocketOperation('Unable to create pair of sockets');
+ }
+ return array(new Socket($pair[0]), new Socket($pair[1]));
+ }
+
+ /**
+ * create TCP/IPv4 stream socket and listen for new connections
+ *
+ * @param int $port
+ * @param int $backlog
+ * @return \Socket\Raw\Socket
+ * @throws Exception if creating listening socket fails
+ * @throws \Error PHP 8 only: throws \Error when arguments are invalid
+ * @uses socket_create_listen()
+ * @see self::createServer() as an alternative to bind to specific IP, IPv6, UDP, UNIX, UGP
+ */
+ public function createListen($port, $backlog = 128)
+ {
+ $sock = @socket_create_listen($port, $backlog);
+ if ($sock === false) {
+ throw Exception::createFromGlobalSocketOperation('Unable to create listening socket');
+ }
+ return new Socket($sock);
+ }
+
+ /**
+ * create socket for given address
+ *
+ * @param string $address (passed by reference in order to remove scheme, if present)
+ * @param string|null $scheme default scheme to use, defaults to TCP (passed by reference in order to update with actual scheme used)
+ * @return \Socket\Raw\Socket
+ * @throws InvalidArgumentException if given address is invalid
+ * @throws Exception in case creating socket failed
+ * @uses self::createTcp4() etc.
+ */
+ public function createFromString(&$address, &$scheme)
+ {
+ if ($scheme === null) {
+ $scheme = 'tcp';
+ }
+
+ $hasScheme = false;
+
+ $pos = strpos($address, '://');
+ if ($pos !== false) {
+ $scheme = substr($address, 0, $pos);
+ $address = substr($address, $pos + 3);
+ $hasScheme = true;
+ }
+
+ if (strpos($address, ':') !== strrpos($address, ':') && in_array($scheme, array('tcp', 'udp', 'icmp'))) {
+ // TCP/UDP/ICMP address with several colons => must be IPv6
+ $scheme .= '6';
+ }
+
+ if ($scheme === 'tcp') {
+ $socket = $this->createTcp4();
+ } elseif ($scheme === 'udp') {
+ $socket = $this->createUdp4();
+ } elseif ($scheme === 'tcp6') {
+ $socket = $this->createTcp6();
+ } elseif ($scheme === 'udp6') {
+ $socket = $this->createUdp6();
+ } elseif ($scheme === 'unix') {
+ $socket = $this->createUnix();
+ } elseif ($scheme === 'udg') {
+ $socket = $this->createUdg();
+ } elseif ($scheme === 'icmp') {
+ $socket = $this->createIcmp4();
+ } elseif ($scheme === 'icmp6') {
+ $socket = $this->createIcmp6();
+ if ($hasScheme) {
+ // scheme was stripped from address, resulting IPv6 must not
+ // have a port (due to ICMP) and thus must not be enclosed in
+ // square brackets
+ $address = trim($address, '[]');
+ }
+ } else {
+ throw new InvalidArgumentException('Invalid address scheme given');
+ }
+ return $socket;
+ }
+}
diff --git a/vendor/clue/socket-raw/src/Socket.php b/vendor/clue/socket-raw/src/Socket.php
new file mode 100644
index 0000000..67407f2
--- /dev/null
+++ b/vendor/clue/socket-raw/src/Socket.php
@@ -0,0 +1,562 @@
+<?php
+
+namespace Socket\Raw;
+
+/**
+ * Simple and lightweight OOP wrapper for the low-level sockets extension (ext-sockets)
+ *
+ * @author clue
+ * @link https://github.com/clue/php-socket-raw
+ */
+class Socket
+{
+ /**
+ * reference to actual socket resource
+ *
+ * @var \Socket|resource
+ */
+ private $resource;
+
+ /**
+ * instanciate socket wrapper for given socket resource
+ *
+ * should usually not be called manually, see Factory
+ *
+ * @param \Socket|resource $resource
+ * @see Factory as the preferred (and simplest) way to construct socket instances
+ */
+ public function __construct($resource)
+ {
+ $this->resource = $resource;
+ }
+
+ /**
+ * get actual socket resource
+ *
+ * @return \Socket|resource returns the socket resource (a `Socket` object as of PHP 8)
+ */
+ public function getResource()
+ {
+ return $this->resource;
+ }
+
+ /**
+ * accept an incomming connection on this listening socket
+ *
+ * @return \Socket\Raw\Socket new connected socket used for communication
+ * @throws Exception on error, if this is not a listening socket or there's no connection pending
+ * @throws \Error PHP 8 only: throws \Error when socket is invalid
+ * @see self::selectRead() to check if this listening socket can accept()
+ * @see Factory::createServer() to create a listening socket
+ * @see self::listen() has to be called first
+ * @uses socket_accept()
+ */
+ public function accept()
+ {
+ $resource = @socket_accept($this->resource);
+ if ($resource === false) {
+ throw Exception::createFromGlobalSocketOperation();
+ }
+ return new Socket($resource);
+ }
+
+ /**
+ * binds a name/address/path to this socket
+ *
+ * has to be called before issuing connect() or listen()
+ *
+ * @param string $address either of IPv4:port, hostname:port, [IPv6]:port, unix-path
+ * @return self $this (chainable)
+ * @throws Exception on error
+ * @throws \Error PHP 8 only: throws \Error when socket or arguments are invalid
+ * @uses socket_bind()
+ */
+ public function bind($address)
+ {
+ $ret = @socket_bind($this->resource, $this->unformatAddress($address, $port), $port);
+ if ($ret === false) {
+ throw Exception::createFromSocketResource($this->resource);
+ }
+ return $this;
+ }
+
+ /**
+ * close this socket
+ *
+ * ATTENTION: make sure to NOT re-use this socket instance after closing it!
+ * its socket resource remains closed and most further operations will fail!
+ *
+ * @return self $this (chainable)
+ * @throws \Error PHP 8 only: throws \Error when socket is invalid
+ * @see self::shutdown() should be called before closing socket
+ * @uses socket_close()
+ */
+ public function close()
+ {
+ socket_close($this->resource);
+ return $this;
+ }
+
+ /**
+ * initiate a connection to given address
+ *
+ * @param string $address either of IPv4:port, hostname:port, [IPv6]:port, unix-path
+ * @return self $this (chainable)
+ * @throws Exception on error
+ * @throws \Error PHP 8 only: throws \Error when socket or arguments are invalid
+ * @uses socket_connect()
+ */
+ public function connect($address)
+ {
+ $ret = @socket_connect($this->resource, $this->unformatAddress($address, $port), $port);
+ if ($ret === false) {
+ throw Exception::createFromSocketResource($this->resource);
+ }
+ return $this;
+ }
+
+ /**
+ * Initiates a new connection to given address, wait for up to $timeout seconds
+ *
+ * The given $timeout parameter is an upper bound, a maximum time to wait
+ * for the connection to be either accepted or rejected.
+ *
+ * The resulting socket resource will be set to non-blocking mode,
+ * regardless of its previous state and whether this method succedes or
+ * if it fails. Make sure to reset with `setBlocking(true)` if you want to
+ * continue using blocking calls.
+ *
+ * @param string $address either of IPv4:port, hostname:port, [IPv6]:port, unix-path
+ * @param float $timeout maximum time to wait (in seconds)
+ * @return self $this (chainable)
+ * @throws Exception on error
+ * @throws \Error PHP 8 only: throws \Error when socket or arguments are invalid
+ * @uses self::setBlocking() to enable non-blocking mode
+ * @uses self::connect() to initiate the connection
+ * @uses self::selectWrite() to wait for the connection to complete
+ * @uses self::assertAlive() to check connection state
+ */
+ public function connectTimeout($address, $timeout)
+ {
+ $this->setBlocking(false);
+
+ try {
+ // socket is non-blocking, so connect should emit EINPROGRESS
+ $this->connect($address);
+
+ // socket is already connected immediately?
+ return $this;
+ } catch (Exception $e) {
+ // non-blocking connect() should be EINPROGRESS (or EWOULDBLOCK on Windows) => otherwise re-throw
+ if ($e->getCode() !== SOCKET_EINPROGRESS && $e->getCode() !== SOCKET_EWOULDBLOCK) {
+ throw $e;
+ }
+
+ // connection should be completed (or rejected) within timeout: socket becomes writable on success or error
+ // Windows requires special care because it uses exceptfds for socket errors: https://github.com/reactphp/event-loop/issues/206
+ $r = null;
+ $w = array($this->resource);
+ $e = DIRECTORY_SEPARATOR === '\\' ? $w : null;
+ $ret = @socket_select($r, $w, $e, $timeout === null ? null : (int) $timeout, (int) (($timeout - floor($timeout)) * 1000000));
+
+ if ($ret === false) {
+ throw Exception::createFromGlobalSocketOperation('Failed to select socket for writing');
+ } elseif ($ret === 0) {
+ throw new Exception('Timed out while waiting for connection', SOCKET_ETIMEDOUT);
+ }
+
+ // confirm connection success (or fail if connected has been rejected)
+ $this->assertAlive();
+
+ return $this;
+ }
+ }
+
+ /**
+ * get socket option
+ *
+ * @param int $level
+ * @param int $optname
+ * @return mixed
+ * @throws Exception on error
+ * @throws \Error PHP 8 only: throws \Error when socket or arguments are invalid
+ * @uses socket_get_option()
+ */
+ public function getOption($level, $optname)
+ {
+ $value = @socket_get_option($this->resource, $level, $optname);
+ if ($value === false) {
+ throw Exception::createFromSocketResource($this->resource);
+ }
+ return $value;
+ }
+
+ /**
+ * get remote side's address/path
+ *
+ * @return string
+ * @throws Exception on error
+ * @throws \Error PHP 8 only: throws \Error when socket is invalid
+ * @uses socket_getpeername()
+ */
+ public function getPeerName()
+ {
+ $ret = @socket_getpeername($this->resource, $address, $port);
+ if ($ret === false) {
+ throw Exception::createFromSocketResource($this->resource);
+ }
+ return $this->formatAddress($address, $port);
+ }
+
+ /**
+ * get local side's address/path
+ *
+ * @return string
+ * @throws Exception on error
+ * @throws \Error PHP 8 only: throws \Error when socket is invalid
+ * @uses socket_getsockname()
+ */
+ public function getSockName()
+ {
+ $ret = @socket_getsockname($this->resource, $address, $port);
+ if ($ret === false) {
+ throw Exception::createFromSocketResource($this->resource);
+ }
+ return $this->formatAddress($address, $port);
+ }
+
+ /**
+ * start listen for incoming connections
+ *
+ * @param int $backlog maximum number of incoming connections to be queued
+ * @return self $this (chainable)
+ * @throws Exception on error
+ * @throws \Error PHP 8 only: throws \Error when socket or arguments are invalid
+ * @see self::bind() has to be called first to bind name to socket
+ * @uses socket_listen()
+ */
+ public function listen($backlog = 0)
+ {
+ $ret = @socket_listen($this->resource, $backlog);
+ if ($ret === false) {
+ throw Exception::createFromSocketResource($this->resource);
+ }
+ return $this;
+ }
+
+ /**
+ * read up to $length bytes from connect()ed / accept()ed socket
+ *
+ * The $type parameter specifies if this should use either binary safe reading
+ * (PHP_BINARY_READ, the default) or stop at CR or LF characters (PHP_NORMAL_READ)
+ *
+ * @param int $length maximum length to read
+ * @param int $type either of PHP_BINARY_READ (the default) or PHP_NORMAL_READ
+ * @return string
+ * @throws Exception on error
+ * @throws \Error PHP 8 only: throws \Error when socket or arguments are invalid
+ * @see self::recv() if you need to pass flags
+ * @uses socket_read()
+ */
+ public function read($length, $type = PHP_BINARY_READ)
+ {
+ $data = @socket_read($this->resource, $length, $type);
+ if ($data === false) {
+ throw Exception::createFromSocketResource($this->resource);
+ }
+ return $data;
+ }
+
+ /**
+ * receive up to $length bytes from connect()ed / accept()ed socket
+ *
+ * @param int $length maximum length to read
+ * @param int $flags
+ * @return string
+ * @throws Exception on error
+ * @throws \Error PHP 8 only: throws \Error when socket or arguments are invalid
+ * @see self::read() if you do not need to pass $flags
+ * @see self::recvFrom() if your socket is not connect()ed
+ * @uses socket_recv()
+ */
+ public function recv($length, $flags)
+ {
+ $ret = @socket_recv($this->resource, $buffer, $length, $flags);
+ if ($ret === false) {
+ throw Exception::createFromSocketResource($this->resource);
+ }
+ return $buffer;
+ }
+
+ /**
+ * receive up to $length bytes from socket
+ *
+ * @param int $length maximum length to read
+ * @param int $flags
+ * @param string $remote reference will be filled with remote/peer address/path
+ * @return string
+ * @throws Exception on error
+ * @throws \Error PHP 8 only: throws \Error when socket or arguments are invalid
+ * @see self::recv() if your socket is connect()ed
+ * @uses socket_recvfrom()
+ */
+ public function recvFrom($length, $flags, &$remote)
+ {
+ $ret = @socket_recvfrom($this->resource, $buffer, $length, $flags, $address, $port);
+ if ($ret === false) {
+ throw Exception::createFromSocketResource($this->resource);
+ }
+ $remote = $this->formatAddress($address, $port);
+ return $buffer;
+ }
+
+ /**
+ * check socket to see if a read/recv/revFrom will not block
+ *
+ * @param float|null $sec maximum time to wait (in seconds), 0 = immediate polling, null = no limit
+ * @return boolean true = socket ready (read will not block), false = timeout expired, socket is not ready
+ * @throws Exception on error
+ * @throws \Error PHP 8 only: throws \Error when socket or arguments are invalid
+ * @uses socket_select()
+ */
+ public function selectRead($sec = 0)
+ {
+ $usec = $sec === null ? 0 : (int) (($sec - floor($sec)) * 1000000);
+ $r = array($this->resource);
+ $n = null;
+ $ret = @socket_select($r, $n, $n, $sec === null ? null : (int) $sec, $usec);
+ if ($ret === false) {
+ throw Exception::createFromGlobalSocketOperation('Failed to select socket for reading');
+ }
+ return !!$ret;
+ }
+
+ /**
+ * check socket to see if a write/send/sendTo will not block
+ *
+ * @param float|null $sec maximum time to wait (in seconds), 0 = immediate polling, null = no limit
+ * @return boolean true = socket ready (write will not block), false = timeout expired, socket is not ready
+ * @throws Exception on error
+ * @throws \Error PHP 8 only: throws \Error when socket or arguments are invalid
+ * @uses socket_select()
+ */
+ public function selectWrite($sec = 0)
+ {
+ $usec = $sec === null ? 0 : (int) (($sec - floor($sec)) * 1000000);
+ $w = array($this->resource);
+ $n = null;
+ $ret = @socket_select($n, $w, $n, $sec === null ? null : (int) $sec, $usec);
+ if ($ret === false) {
+ throw Exception::createFromGlobalSocketOperation('Failed to select socket for writing');
+ }
+ return !!$ret;
+ }
+
+ /**
+ * send given $buffer to connect()ed / accept()ed socket
+ *
+ * @param string $buffer
+ * @param int $flags
+ * @return int number of bytes actually written (make sure to check against given buffer length!)
+ * @throws Exception on error
+ * @throws \Error PHP 8 only: throws \Error when socket or arguments are invalid
+ * @see self::write() if you do not need to pass $flags
+ * @see self::sendTo() if your socket is not connect()ed
+ * @uses socket_send()
+ */
+ public function send($buffer, $flags)
+ {
+ $ret = @socket_send($this->resource, $buffer, strlen($buffer), $flags);
+ if ($ret === false) {
+ throw Exception::createFromSocketResource($this->resource);
+ }
+ return $ret;
+ }
+
+ /**
+ * send given $buffer to socket
+ *
+ * @param string $buffer
+ * @param int $flags
+ * @param string $remote remote/peer address/path
+ * @return int number of bytes actually written
+ * @throws Exception on error
+ * @throws \Error PHP 8 only: throws \Error when socket or arguments are invalid
+ * @see self::send() if your socket is connect()ed
+ * @uses socket_sendto()
+ */
+ public function sendTo($buffer, $flags, $remote)
+ {
+ $ret = @socket_sendto($this->resource, $buffer, strlen($buffer), $flags, $this->unformatAddress($remote, $port), $port);
+ if ($ret === false) {
+ throw Exception::createFromSocketResource($this->resource);
+ }
+ return $ret;
+ }
+
+ /**
+ * enable/disable blocking/nonblocking mode (O_NONBLOCK flag)
+ *
+ * @param boolean $toggle
+ * @return self $this (chainable)
+ * @throws Exception on error
+ * @throws \Error PHP 8 only: throws \Error when socket or arguments are invalid
+ * @uses socket_set_block()
+ * @uses socket_set_nonblock()
+ */
+ public function setBlocking($toggle = true)
+ {
+ $ret = $toggle ? @socket_set_block($this->resource) : @socket_set_nonblock($this->resource);
+ if ($ret === false) {
+ throw Exception::createFromSocketResource($this->resource);
+ }
+ return $this;
+ }
+
+ /**
+ * set socket option
+ *
+ * @param int $level
+ * @param int $optname
+ * @param mixed $optval
+ * @return self $this (chainable)
+ * @throws Exception on error
+ * @throws \Error PHP 8 only: throws \Error when socket or arguments are invalid
+ * @see self::getOption()
+ * @uses socket_set_option()
+ */
+ public function setOption($level, $optname, $optval)
+ {
+ $ret = @socket_set_option($this->resource, $level, $optname, $optval);
+ if ($ret === false) {
+ throw Exception::createFromSocketResource($this->resource);
+ }
+ return $this;
+ }
+
+ /**
+ * shuts down socket for receiving, sending or both
+ *
+ * @param int $how 0 = shutdown reading, 1 = shutdown writing, 2 = shutdown reading and writing
+ * @return self $this (chainable)
+ * @throws Exception on error
+ * @throws \Error PHP 8 only: throws \Error when socket or arguments are invalid
+ * @see self::close()
+ * @uses socket_shutdown()
+ */
+ public function shutdown($how = 2)
+ {
+ $ret = @socket_shutdown($this->resource, $how);
+ if ($ret === false) {
+ throw Exception::createFromSocketResource($this->resource);
+ }
+ return $this;
+ }
+
+ /**
+ * write $buffer to connect()ed / accept()ed socket
+ *
+ * @param string $buffer
+ * @return int number of bytes actually written
+ * @throws Exception on error
+ * @throws \Error PHP 8 only: throws \Error when socket or arguments are invalid
+ * @see self::send() if you need to pass flags
+ * @uses socket_write()
+ */
+ public function write($buffer)
+ {
+ $ret = @socket_write($this->resource, $buffer);
+ if ($ret === false) {
+ throw Exception::createFromSocketResource($this->resource);
+ }
+ return $ret;
+ }
+
+ /**
+ * get socket type as passed to socket_create()
+ *
+ * @return int usually either SOCK_STREAM or SOCK_DGRAM
+ * @throws Exception on error
+ * @throws \Error PHP 8 only: throws \Error when socket is invalid
+ * @uses self::getOption()
+ */
+ public function getType()
+ {
+ return $this->getOption(SOL_SOCKET, SO_TYPE);
+ }
+
+ /**
+ * assert that this socket is alive and its error code is 0
+ *
+ * This will fetch and reset the current socket error code from the
+ * socket and options and will throw an Exception along with error
+ * message and code if the code is not 0, i.e. if it does indicate
+ * an error situation.
+ *
+ * Calling this method should not be needed in most cases and is
+ * likely to not throw an Exception. Each socket operation like
+ * connect(), send(), etc. will throw a dedicated Exception in case
+ * of an error anyway.
+ *
+ * @return self $this (chainable)
+ * @throws Exception if error code is not 0
+ * @throws \Error PHP 8 only: throws \Error when socket is invalid
+ * @uses self::getOption() to retrieve and clear current error code
+ * @uses self::getErrorMessage() to translate error code to
+ */
+ public function assertAlive()
+ {
+ $code = $this->getOption(SOL_SOCKET, SO_ERROR);
+ if ($code !== 0) {
+ throw Exception::createFromCode($code, 'Socket error');
+ }
+ return $this;
+ }
+
+ /**
+ * format given address/host/path and port
+ *
+ * @param string $address
+ * @param int $port
+ * @return string
+ */
+ protected function formatAddress($address, $port)
+ {
+ if ($port !== 0) {
+ if (strpos($address, ':') !== false) {
+ $address = '[' . $address . ']';
+ }
+ $address .= ':' . $port;
+ }
+ return $address;
+ }
+
+ /**
+ * format given address by splitting it into returned address and port set by reference
+ *
+ * @param string $address
+ * @param int $port
+ * @return string address with port removed
+ */
+ protected function unformatAddress($address, &$port)
+ {
+ // [::1]:2 => ::1 2
+ // test:2 => test 2
+ // ::1 => ::1
+ // test => test
+
+ $colon = strrpos($address, ':');
+
+ // there is a colon and this is the only colon or there's a closing IPv6 bracket right before it
+ if ($colon !== false && (strpos($address, ':') === $colon || strpos($address, ']') === ($colon - 1))) {
+ $port = (int)substr($address, $colon + 1);
+ $address = substr($address, 0, $colon);
+
+ // remove IPv6 square brackets
+ if (substr($address, 0, 1) === '[') {
+ $address = substr($address, 1, -1);
+ }
+ }
+ return $address;
+ }
+}