1<?php
2
3namespace React\Datagram;
4
5use React\EventLoop\LoopInterface;
6use Evenement\EventEmitter;
7use Exception;
8
9class Socket extends EventEmitter implements SocketInterface
10{
11    protected $loop;
12    protected $socket;
13
14    protected $buffer;
15
16    public $bufferSize = 65536;
17
18    public function __construct(LoopInterface $loop, $socket, Buffer $buffer = null)
19    {
20        $this->loop = $loop;
21        $this->socket = $socket;
22
23        if ($buffer === null) {
24            $buffer = new Buffer($loop, $socket);
25        }
26        $this->buffer = $buffer;
27
28        $that = $this;
29        $this->buffer->on('error', function ($error) use ($that) {
30            $that->emit('error', array($error, $that));
31        });
32        $this->buffer->on('close', array($this, 'close'));
33
34        $this->resume();
35    }
36
37    public function getLocalAddress()
38    {
39        if ($this->socket === false) {
40            return null;
41        }
42
43        return $this->sanitizeAddress(@\stream_socket_get_name($this->socket, false));
44    }
45
46    public function getRemoteAddress()
47    {
48        if ($this->socket === false) {
49            return null;
50        }
51
52        return $this->sanitizeAddress(@\stream_socket_get_name($this->socket, true));
53    }
54
55    public function send($data, $remoteAddress = null)
56    {
57        $this->buffer->send($data, $remoteAddress);
58    }
59
60    public function pause()
61    {
62        $this->loop->removeReadStream($this->socket);
63    }
64
65    public function resume()
66    {
67        if ($this->socket !== false) {
68            $this->loop->addReadStream($this->socket, array($this, 'onReceive'));
69        }
70    }
71
72    public function onReceive()
73    {
74        try {
75            $data = $this->handleReceive($peer);
76        }
77        catch (Exception $e) {
78            // emit error message and local socket
79            $this->emit('error', array($e, $this));
80            return;
81        }
82
83        $this->emit('message', array($data, $peer, $this));
84    }
85
86    public function close()
87    {
88        if ($this->socket === false) {
89            return;
90        }
91
92        $this->emit('close', array($this));
93        $this->pause();
94
95        $this->handleClose();
96        $this->socket = false;
97        $this->buffer->close();
98
99        $this->removeAllListeners();
100    }
101
102    public function end()
103    {
104        $this->buffer->end();
105    }
106
107    private function sanitizeAddress($address)
108    {
109        if ($address === false) {
110            return null;
111        }
112
113        // check if this is an IPv6 address which includes multiple colons but no square brackets (PHP < 7.3)
114        $pos = \strrpos($address, ':');
115        if ($pos !== false && \strpos($address, ':') < $pos && \substr($address, 0, 1) !== '[') {
116            $address = '[' . \substr($address, 0, $pos) . ']:' . \substr($address, $pos + 1); // @codeCoverageIgnore
117        }
118        return $address;
119    }
120
121    protected function handleReceive(&$peerAddress)
122    {
123        $data = \stream_socket_recvfrom($this->socket, $this->bufferSize, 0, $peerAddress);
124
125        if ($data === false) {
126            // receiving data failed => remote side rejected one of our packets
127            // due to the nature of UDP, there's no way to tell which one exactly
128            // $peer is not filled either
129
130            throw new Exception('Invalid message');
131        }
132
133        $peerAddress = $this->sanitizeAddress($peerAddress);
134
135        return $data;
136    }
137
138    protected function handleClose()
139    {
140        \fclose($this->socket);
141    }
142}
143