Completed
Push — master ( 0b32dd...f0bf18 )
by Arthur
01:30
created

WssMain::cleanSocketResources()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 8

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 8
rs 10
c 0
b 0
f 0
cc 3
nc 3
nop 1
1
<?php
2
3
4
namespace WSSC\Components;
5
6
7
use WSSC\Contracts\CommonsContract;
8
use WSSC\Contracts\WebSocketServerContract;
9
10
/**
11
 * Class WssMain
12
 *
13
 * @package WSSC\Components
14
 *
15
 * @property ServerConfig config
16
 */
17
class WssMain implements CommonsContract
18
{
19
    private $isPcntlLoaded = false;
20
21
    /**
22
     * Message frames decoder
23
     *
24
     * @param string $data
25
     * @return mixed null on empty data|false on improper data|array - on success
26
     */
27
    protected function decode(string $data)
28
    {
29
        if (empty($data)) {
30
            return null; // close has been sent
31
        }
32
33
        $unmaskedPayload = '';
34
        $decodedData = [];
35
36
        // estimate frame type:
37
        $firstByteBinary = sprintf('%08b', ord($data[0]));
38
        $secondByteBinary = sprintf('%08b', ord($data[1]));
39
        $isMasked = $secondByteBinary[0] === '1';
40
        $payloadLength = ord($data[1]) & self::MASK_127;
41
42
        // unmasked frame is received:
43
        if (!$isMasked) {
44
            return ['type' => '', 'payload' => '', 'error' => WebSocketServerContract::ERR_PROTOCOL];
45
        }
46
47
        $this->getTypeByOpCode($firstByteBinary, $decodedData);
48
        if (empty($decodedData['type'])) {
49
            return ['type' => '', 'payload' => '', 'error' => WebSocketServerContract::ERR_UNKNOWN_OPCODE];
50
        }
51
52
        $mask = substr($data, 2, 4);
53
        $payloadOffset = WebSocketServerContract::PAYLOAD_OFFSET_6;
54
        $dataLength = $payloadLength + $payloadOffset;
55
        if ($payloadLength === self::MASK_126) {
56
            $mask = substr($data, 4, 4);
57
            $payloadOffset = WebSocketServerContract::PAYLOAD_OFFSET_8;
58
            $dataLength = bindec(sprintf('%08b', ord($data[2])) . sprintf('%08b', ord($data[3]))) + $payloadOffset;
59
        } elseif ($payloadLength === self::MASK_127) {
60
            $mask = substr($data, 10, 4);
61
            $payloadOffset = WebSocketServerContract::PAYLOAD_OFFSET_14;
62
            $tmp = '';
63
            for ($i = 0; $i < 8; $i++) {
64
                $tmp .= sprintf('%08b', ord($data[$i + 2]));
65
            }
66
            $dataLength = bindec($tmp) + $payloadOffset;
67
            unset($tmp);
68
        }
69
70
        /**
71
         * We have to check for large frames here. socket_recv cuts at 1024 bytes
72
         * so if websocket-frame is > 1024 bytes we have to wait until whole
73
         * data is transferd.
74
         */
75
        if (strlen($data) < $dataLength) {
76
            return false;
77
        }
78
79
        if ($isMasked) {
80
            for ($i = $payloadOffset; $i < $dataLength; $i++) {
81
                $j = $i - $payloadOffset;
82
                if (isset($data[$i])) {
83
                    $unmaskedPayload .= $data[$i] ^ $mask[$j % 4];
84
                }
85
            }
86
            $decodedData['payload'] = $unmaskedPayload;
87
        } else {
88
            $payloadOffset -= 4;
89
            $decodedData['payload'] = substr($data, $payloadOffset);
90
        }
91
92
        return $decodedData;
93
    }
94
95
    /**
96
     * Returns true if pcntl ext loaded and false otherwise
97
     *
98
     * @return bool
99
     */
100
    protected function isPcntlLoaded(): bool
101
    {
102
        return $this->isPcntlLoaded;
103
    }
104
105
    /**
106
     * Sets pre-loaded pcntl state
107
     *
108
     * @param bool $isPcntlLoaded
109
     */
110
    protected function setIsPcntlLoaded(bool $isPcntlLoaded): void
111
    {
112
        $this->isPcntlLoaded = $isPcntlLoaded;
113
    }
114
115
    /**
116
     * Detects decode data type
117
     *
118
     * @param string $firstByteBinary
119
     * @param array $decodedData
120
     */
121
    private function getTypeByOpCode(string $firstByteBinary, array &$decodedData)
122
    {
123
        $opcode = bindec(substr($firstByteBinary, 4, 4));
124
        switch ($opcode) {
125
            // text frame:
126
            case self::DECODE_TEXT:
127
                $decodedData['type'] = self::EVENT_TYPE_TEXT;
128
                break;
129
            case self::DECODE_BINARY:
130
                $decodedData['type'] = self::EVENT_TYPE_BINARY;
131
                break;
132
            // connection close frame:
133
            case self::DECODE_CLOSE:
134
                $decodedData['type'] = self::EVENT_TYPE_CLOSE;
135
                break;
136
            // ping frame:
137
            case self::DECODE_PING:
138
                $decodedData['type'] = self::EVENT_TYPE_PING;
139
                break;
140
            // pong frame:
141
            case self::DECODE_PONG:
142
                $decodedData['type'] = self::EVENT_TYPE_PONG;
143
                break;
144
            default:
145
                $decodedData['type'] = '';
146
                break;
147
        }
148
    }
149
150
    /**
151
     * Checks if there are less connections for amount of processes
152
     * @param int $totalClients
153
     * @param int $maxClients
154
     */
155
    protected function lessConnThanProc(int $totalClients, int $maxClients): void
156
    {
157
        if ($totalClients !== 0 && $maxClients > $totalClients
158
            && $totalClients % $this->config->getClientsPerFork() === 0) {
159
            exit(1);
160
        }
161
    }
162
163
    /**
164
     * Clean socket resources that were closed,
165
     * thus avoiding (stream_select(): supplied resource is not a valid stream resource)
166
     * @param array $readSocks
167
     */
168
    protected function cleanSocketResources(array &$readSocks): void
169
    {
170
        foreach ($readSocks as $k => $sock) {
171
            if (!is_resource($sock)) {
172
                unset($readSocks[$k]);
173
            }
174
        }
175
    }
176
}
177