xref: /plugin/pureldap/vendor/freedsx/socket/src/FreeDSx/Socket/SocketServer.php (revision dad993c57a70866aa1db59c43f043769c2eb7ed0)
10b3fd2d3SAndreas Gohr<?php
20b3fd2d3SAndreas Gohr/**
30b3fd2d3SAndreas Gohr * This file is part of the FreeDSx Socket package.
40b3fd2d3SAndreas Gohr *
50b3fd2d3SAndreas Gohr * (c) Chad Sikorra <Chad.Sikorra@gmail.com>
60b3fd2d3SAndreas Gohr *
70b3fd2d3SAndreas Gohr * For the full copyright and license information, please view the LICENSE
80b3fd2d3SAndreas Gohr * file that was distributed with this source code.
90b3fd2d3SAndreas Gohr */
100b3fd2d3SAndreas Gohr
110b3fd2d3SAndreas Gohrnamespace FreeDSx\Socket;
120b3fd2d3SAndreas Gohr
130b3fd2d3SAndreas Gohruse FreeDSx\Socket\Exception\ConnectionException;
140b3fd2d3SAndreas Gohr
150b3fd2d3SAndreas Gohr/**
160b3fd2d3SAndreas Gohr * TCP socket server to accept client connections.
170b3fd2d3SAndreas Gohr *
180b3fd2d3SAndreas Gohr * @author Chad Sikorra <Chad.Sikorra@gmail.com>
190b3fd2d3SAndreas Gohr */
200b3fd2d3SAndreas Gohrclass SocketServer extends Socket
210b3fd2d3SAndreas Gohr{
220b3fd2d3SAndreas Gohr    /**
23*dad993c5SAndreas Gohr     * Supported transport types.
24*dad993c5SAndreas Gohr     */
25*dad993c5SAndreas Gohr    public const TRANSPORTS = [
26*dad993c5SAndreas Gohr        'tcp',
27*dad993c5SAndreas Gohr        'udp',
28*dad993c5SAndreas Gohr        'unix',
29*dad993c5SAndreas Gohr    ];
30*dad993c5SAndreas Gohr
31*dad993c5SAndreas Gohr    /**
320b3fd2d3SAndreas Gohr     * @var array
330b3fd2d3SAndreas Gohr     */
340b3fd2d3SAndreas Gohr    protected $serverOpts = [
35*dad993c5SAndreas Gohr        'use_ssl' => false,
360b3fd2d3SAndreas Gohr        'ssl_cert' => null,
370b3fd2d3SAndreas Gohr        'ssl_cert_key' => null,
380b3fd2d3SAndreas Gohr        'ssl_cert_passphrase' => null,
390b3fd2d3SAndreas Gohr        'ssl_crypto_type' => STREAM_CRYPTO_METHOD_TLSv1_2_SERVER | STREAM_CRYPTO_METHOD_TLSv1_1_SERVER | STREAM_CRYPTO_METHOD_TLS_SERVER,
400b3fd2d3SAndreas Gohr        'ssl_validate_cert' => false,
410b3fd2d3SAndreas Gohr        'idle_timeout' => 600,
420b3fd2d3SAndreas Gohr    ];
430b3fd2d3SAndreas Gohr
440b3fd2d3SAndreas Gohr    /**
450b3fd2d3SAndreas Gohr     * @var Socket[]
460b3fd2d3SAndreas Gohr     */
470b3fd2d3SAndreas Gohr    protected $clients = [];
480b3fd2d3SAndreas Gohr
490b3fd2d3SAndreas Gohr    /**
500b3fd2d3SAndreas Gohr     * @param array $options
510b3fd2d3SAndreas Gohr     */
520b3fd2d3SAndreas Gohr    public function __construct(array $options = [])
530b3fd2d3SAndreas Gohr    {
54*dad993c5SAndreas Gohr        parent::__construct(
55*dad993c5SAndreas Gohr            null,
56*dad993c5SAndreas Gohr            \array_merge(
57*dad993c5SAndreas Gohr                $this->serverOpts,
58*dad993c5SAndreas Gohr                $options
59*dad993c5SAndreas Gohr            )
60*dad993c5SAndreas Gohr        );
61*dad993c5SAndreas Gohr        if (!\in_array($this->options['transport'], self::TRANSPORTS, true)) {
62*dad993c5SAndreas Gohr            throw new \RuntimeException(sprintf(
63*dad993c5SAndreas Gohr                'The transport "%s" is not valid. It must be one of: %s',
64*dad993c5SAndreas Gohr                $this->options['transport'],
65*dad993c5SAndreas Gohr                implode(',', self::TRANSPORTS)
66*dad993c5SAndreas Gohr            ));
67*dad993c5SAndreas Gohr        }
680b3fd2d3SAndreas Gohr    }
690b3fd2d3SAndreas Gohr
700b3fd2d3SAndreas Gohr    /**
710b3fd2d3SAndreas Gohr     * Create the socket server and bind to a specific port to listen for clients.
720b3fd2d3SAndreas Gohr     *
730b3fd2d3SAndreas Gohr     * @param string $ip
74*dad993c5SAndreas Gohr     * @param int|null $port
750b3fd2d3SAndreas Gohr     * @return $this
760b3fd2d3SAndreas Gohr     * @throws ConnectionException
770b3fd2d3SAndreas Gohr     * @internal param string $ip
780b3fd2d3SAndreas Gohr     */
79*dad993c5SAndreas Gohr    public function listen(string $ip, ?int $port): self
800b3fd2d3SAndreas Gohr    {
810b3fd2d3SAndreas Gohr        $flags = STREAM_SERVER_BIND;
820b3fd2d3SAndreas Gohr        if ($this->options['transport'] !== 'udp') {
830b3fd2d3SAndreas Gohr            $flags |= STREAM_SERVER_LISTEN;
840b3fd2d3SAndreas Gohr        }
85*dad993c5SAndreas Gohr
86*dad993c5SAndreas Gohr        $transport = $this->options['transport'];
87*dad993c5SAndreas Gohr        if ($transport === 'tcp' && $this->options['use_ssl'] === true) {
88*dad993c5SAndreas Gohr            $transport = 'ssl';
89*dad993c5SAndreas Gohr        }
90*dad993c5SAndreas Gohr
91*dad993c5SAndreas Gohr        if ($transport !== 'unix' && $port === null) {
92*dad993c5SAndreas Gohr            throw new ConnectionException('The port must be set if not using a unix based socket.');
93*dad993c5SAndreas Gohr        }
94*dad993c5SAndreas Gohr
95*dad993c5SAndreas Gohr        $uri = $transport.'://'.$ip;
96*dad993c5SAndreas Gohr        if ($port !== null && $transport !== 'unix') {
97*dad993c5SAndreas Gohr            $uri .= ':' . $port;
98*dad993c5SAndreas Gohr        }
99*dad993c5SAndreas Gohr
1000b3fd2d3SAndreas Gohr        $socket = @\stream_socket_server(
101*dad993c5SAndreas Gohr            $uri,
1020b3fd2d3SAndreas Gohr            $this->errorNumber,
1030b3fd2d3SAndreas Gohr            $this->errorMessage,
1040b3fd2d3SAndreas Gohr            $flags,
1050b3fd2d3SAndreas Gohr            $this->createSocketContext()
1060b3fd2d3SAndreas Gohr        );
1070b3fd2d3SAndreas Gohr        if ($socket === false) {
1080b3fd2d3SAndreas Gohr            throw new ConnectionException(sprintf(
1090b3fd2d3SAndreas Gohr                'Unable to open %s socket (%s): %s',
1100b3fd2d3SAndreas Gohr                \strtoupper($this->options['transport']),
1110b3fd2d3SAndreas Gohr                $this->errorNumber,
1120b3fd2d3SAndreas Gohr                $this->errorMessage
1130b3fd2d3SAndreas Gohr            ));
1140b3fd2d3SAndreas Gohr        }
1150b3fd2d3SAndreas Gohr        $this->socket = $socket;
1160b3fd2d3SAndreas Gohr
1170b3fd2d3SAndreas Gohr        return $this;
1180b3fd2d3SAndreas Gohr    }
1190b3fd2d3SAndreas Gohr
1200b3fd2d3SAndreas Gohr    /**
1210b3fd2d3SAndreas Gohr     * @param int $timeout
1220b3fd2d3SAndreas Gohr     * @return null|Socket
1230b3fd2d3SAndreas Gohr     */
124*dad993c5SAndreas Gohr    public function accept(int $timeout = -1): ?Socket
1250b3fd2d3SAndreas Gohr    {
1260b3fd2d3SAndreas Gohr        $socket = @\stream_socket_accept($this->socket, $timeout);
1270b3fd2d3SAndreas Gohr        if (\is_resource($socket)) {
1280b3fd2d3SAndreas Gohr            $socket = new Socket($socket, \array_merge($this->options, [
1290b3fd2d3SAndreas Gohr                'timeout_read' => $this->options['idle_timeout']
1300b3fd2d3SAndreas Gohr            ]));
1310b3fd2d3SAndreas Gohr            $this->clients[] = $socket;
1320b3fd2d3SAndreas Gohr        }
1330b3fd2d3SAndreas Gohr
1340b3fd2d3SAndreas Gohr        return $socket instanceof Socket ? $socket : null;
1350b3fd2d3SAndreas Gohr    }
1360b3fd2d3SAndreas Gohr
1370b3fd2d3SAndreas Gohr    /**
1380b3fd2d3SAndreas Gohr     * Receive data from a UDP based socket. Optionally get the IP address the data was received from.
1390b3fd2d3SAndreas Gohr     *
1400b3fd2d3SAndreas Gohr     * @todo Buffer size should be adjustable. Max UDP packet size is 65507. Currently this avoids possible truncation.
1410b3fd2d3SAndreas Gohr     * @param null $ipAddress
1420b3fd2d3SAndreas Gohr     * @return null|string
1430b3fd2d3SAndreas Gohr     */
1440b3fd2d3SAndreas Gohr    public function receive(&$ipAddress = null)
1450b3fd2d3SAndreas Gohr    {
1460b3fd2d3SAndreas Gohr        $this->block(true);
1470b3fd2d3SAndreas Gohr
148*dad993c5SAndreas Gohr        return \stream_socket_recvfrom(
149*dad993c5SAndreas Gohr            $this->socket,
150*dad993c5SAndreas Gohr            65507,
151*dad993c5SAndreas Gohr            0,
152*dad993c5SAndreas Gohr            $ipAddress
153*dad993c5SAndreas Gohr        );
1540b3fd2d3SAndreas Gohr    }
1550b3fd2d3SAndreas Gohr
1560b3fd2d3SAndreas Gohr    /**
1570b3fd2d3SAndreas Gohr     * @return Socket[]
1580b3fd2d3SAndreas Gohr     */
159*dad993c5SAndreas Gohr    public function getClients(): array
1600b3fd2d3SAndreas Gohr    {
1610b3fd2d3SAndreas Gohr        return $this->clients;
1620b3fd2d3SAndreas Gohr    }
1630b3fd2d3SAndreas Gohr
1640b3fd2d3SAndreas Gohr    /**
1650b3fd2d3SAndreas Gohr     * @param Socket $socket
1660b3fd2d3SAndreas Gohr     */
1670b3fd2d3SAndreas Gohr    public function removeClient(Socket $socket): void
1680b3fd2d3SAndreas Gohr    {
1690b3fd2d3SAndreas Gohr        if (($index = \array_search($socket, $this->clients, true)) !== false) {
1700b3fd2d3SAndreas Gohr            unset($this->clients[$index]);
1710b3fd2d3SAndreas Gohr        }
1720b3fd2d3SAndreas Gohr    }
1730b3fd2d3SAndreas Gohr
1740b3fd2d3SAndreas Gohr    /**
1750b3fd2d3SAndreas Gohr     * Create the socket server. Binds and listens on a specific port
1760b3fd2d3SAndreas Gohr     *
1770b3fd2d3SAndreas Gohr     * @param string $ip
178*dad993c5SAndreas Gohr     * @param int|null $port
1790b3fd2d3SAndreas Gohr     * @param array $options
1800b3fd2d3SAndreas Gohr     * @return SocketServer
1810b3fd2d3SAndreas Gohr     * @throws ConnectionException
1820b3fd2d3SAndreas Gohr     */
183*dad993c5SAndreas Gohr    public static function bind(
184*dad993c5SAndreas Gohr        string $ip,
185*dad993c5SAndreas Gohr        ?int $port,
186*dad993c5SAndreas Gohr        array $options = []
187*dad993c5SAndreas Gohr    ): SocketServer {
188*dad993c5SAndreas Gohr        return (new self($options))->listen(
189*dad993c5SAndreas Gohr            $ip,
190*dad993c5SAndreas Gohr            $port
191*dad993c5SAndreas Gohr        );
1920b3fd2d3SAndreas Gohr    }
1930b3fd2d3SAndreas Gohr
1940b3fd2d3SAndreas Gohr    /**
1950b3fd2d3SAndreas Gohr     * Create a TCP based socket server.
1960b3fd2d3SAndreas Gohr     *
1970b3fd2d3SAndreas Gohr     * @param string $ip
1980b3fd2d3SAndreas Gohr     * @param int $port
1990b3fd2d3SAndreas Gohr     * @param array $options
2000b3fd2d3SAndreas Gohr     * @return SocketServer
2010b3fd2d3SAndreas Gohr     * @throws ConnectionException
2020b3fd2d3SAndreas Gohr     */
203*dad993c5SAndreas Gohr    public static function bindTcp(
204*dad993c5SAndreas Gohr        string $ip,
205*dad993c5SAndreas Gohr        int $port,
206*dad993c5SAndreas Gohr        array $options = []
207*dad993c5SAndreas Gohr    ): SocketServer {
208*dad993c5SAndreas Gohr        return static::bind(
209*dad993c5SAndreas Gohr            $ip,
210*dad993c5SAndreas Gohr            $port,
211*dad993c5SAndreas Gohr            \array_merge(
212*dad993c5SAndreas Gohr                $options,
213*dad993c5SAndreas Gohr                ['transport' => 'tcp']
214*dad993c5SAndreas Gohr            )
215*dad993c5SAndreas Gohr        );
2160b3fd2d3SAndreas Gohr    }
2170b3fd2d3SAndreas Gohr
2180b3fd2d3SAndreas Gohr    /**
2190b3fd2d3SAndreas Gohr     * Created a UDP based socket server.
2200b3fd2d3SAndreas Gohr     *
2210b3fd2d3SAndreas Gohr     * @param string $ip
2220b3fd2d3SAndreas Gohr     * @param int $port
2230b3fd2d3SAndreas Gohr     * @param array $options
2240b3fd2d3SAndreas Gohr     * @return SocketServer
2250b3fd2d3SAndreas Gohr     * @throws ConnectionException
2260b3fd2d3SAndreas Gohr     */
227*dad993c5SAndreas Gohr    public static function bindUdp(
228*dad993c5SAndreas Gohr        string $ip,
229*dad993c5SAndreas Gohr        int $port,
230*dad993c5SAndreas Gohr        array $options = []
231*dad993c5SAndreas Gohr    ): SocketServer {
232*dad993c5SAndreas Gohr        return static::bind(
233*dad993c5SAndreas Gohr            $ip,
234*dad993c5SAndreas Gohr            $port,
235*dad993c5SAndreas Gohr            \array_merge(
236*dad993c5SAndreas Gohr                $options,
237*dad993c5SAndreas Gohr                ['transport' => 'udp']
238*dad993c5SAndreas Gohr            )
239*dad993c5SAndreas Gohr        );
240*dad993c5SAndreas Gohr    }
241*dad993c5SAndreas Gohr
242*dad993c5SAndreas Gohr    /**
243*dad993c5SAndreas Gohr     * Created a UNIX based socket server.
244*dad993c5SAndreas Gohr     *
245*dad993c5SAndreas Gohr     * @param string $socketFile
246*dad993c5SAndreas Gohr     * @param array $options
247*dad993c5SAndreas Gohr     * @return SocketServer
248*dad993c5SAndreas Gohr     * @throws ConnectionException
249*dad993c5SAndreas Gohr     */
250*dad993c5SAndreas Gohr    public static function bindUnix(
251*dad993c5SAndreas Gohr        string $socketFile,
252*dad993c5SAndreas Gohr        array $options = []
253*dad993c5SAndreas Gohr    ): SocketServer {
254*dad993c5SAndreas Gohr        return static::bind(
255*dad993c5SAndreas Gohr            $socketFile,
256*dad993c5SAndreas Gohr            null,
257*dad993c5SAndreas Gohr            \array_merge(
258*dad993c5SAndreas Gohr                $options,
259*dad993c5SAndreas Gohr                ['transport' => 'unix']
260*dad993c5SAndreas Gohr            )
261*dad993c5SAndreas Gohr        );
2620b3fd2d3SAndreas Gohr    }
2630b3fd2d3SAndreas Gohr}
264