WssMain::decode()   B
last analyzed

Complexity

Conditions 10
Paths 15

Size

Total Lines 61
Code Lines 36

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 10
eloc 36
nc 15
nop 1
dl 0
loc 61
rs 7.6666
c 1
b 0
f 0

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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
    /**
20
     * @var bool
21
     */
22
    private bool $isPcntlLoaded = false;
23
24
    /**
25
     * Message frames decoder
26
     *
27
     * @param string $data
28
     * @return mixed null on empty data|false on improper data|array - on success
29
     */
30
    protected function decode(string $data)
31
    {
32
        if (empty($data)) {
33
            return null; // close has been sent
34
        }
35
36
        $unmaskedPayload = '';
37
        $decodedData = [];
38
39
        // estimate frame type:
40
        $firstByteBinary = sprintf('%08b', ord($data[0]));
41
        $secondByteBinary = sprintf('%08b', ord($data[1]));
42
        $isMasked = $secondByteBinary[0] === '1';
43
        $payloadLength = ord($data[1]) & self::MASK_127;
44
45
        // unmasked frame is received:
46
        if (!$isMasked) {
47
            return ['type' => '', 'payload' => '', 'error' => WebSocketServerContract::ERR_PROTOCOL];
48
        }
49
50
        $this->getTypeByOpCode($firstByteBinary, $decodedData);
51
        if (empty($decodedData['type'])) {
52
            return ['type' => '', 'payload' => '', 'error' => WebSocketServerContract::ERR_UNKNOWN_OPCODE];
53
        }
54
55
        $mask = substr($data, 2, 4);
56
        $payloadOffset = WebSocketServerContract::PAYLOAD_OFFSET_6;
57
        $dataLength = $payloadLength + $payloadOffset;
58
        if ($payloadLength === self::MASK_126) {
59
            $mask = substr($data, 4, 4);
60
            $payloadOffset = WebSocketServerContract::PAYLOAD_OFFSET_8;
61
            $dataLength = bindec(sprintf('%08b', ord($data[2])) . sprintf('%08b', ord($data[3]))) + $payloadOffset;
62
        } elseif ($payloadLength === self::MASK_127) {
63
            $mask = substr($data, 10, 4);
64
            $payloadOffset = WebSocketServerContract::PAYLOAD_OFFSET_14;
65
            $tmp = '';
66
            for ($i = 0; $i < 8; $i++) {
67
                $tmp .= sprintf('%08b', ord($data[$i + 2]));
68
            }
69
            $dataLength = bindec($tmp) + $payloadOffset;
70
            unset($tmp);
71
        }
72
73
        /**
74
         * We have to check for large frames here. socket_recv cuts at 1024 bytes
75
         * so if websocket-frame is > 1024 bytes we have to wait until whole
76
         * data is transferd.
77
         */
78
        if (strlen($data) < $dataLength) {
79
            return false;
80
        }
81
82
        for ($i = $payloadOffset; $i < $dataLength; $i++) {
83
            $j = $i - $payloadOffset;
84
            if (isset($data[$i])) {
85
                $unmaskedPayload .= $data[$i] ^ $mask[$j % 4];
86
            }
87
        }
88
        $decodedData['payload'] = $unmaskedPayload;
89
90
        return $decodedData;
91
    }
92
93
    /**
94
     * Returns true if pcntl ext loaded and false otherwise
95
     *
96
     * @return bool
97
     */
98
    protected function isPcntlLoaded(): bool
99
    {
100
        return $this->isPcntlLoaded;
101
    }
102
103
    /**
104
     * Sets pre-loaded pcntl state
105
     *
106
     * @param bool $isPcntlLoaded
107
     */
108
    protected function setIsPcntlLoaded(bool $isPcntlLoaded): void
109
    {
110
        $this->isPcntlLoaded = $isPcntlLoaded;
111
    }
112
113
    /**
114
     * Detects decode data type
115
     *
116
     * @param string $firstByteBinary
117
     * @param array $decodedData
118
     */
119
    private function getTypeByOpCode(string $firstByteBinary, array &$decodedData)
120
    {
121
        $opcode = bindec(substr($firstByteBinary, 4, 4));
122
        switch ($opcode) {
123
            // text frame:
124
            case self::DECODE_TEXT:
125
                $decodedData['type'] = self::EVENT_TYPE_TEXT;
126
                break;
127
            case self::DECODE_BINARY:
128
                $decodedData['type'] = self::EVENT_TYPE_BINARY;
129
                break;
130
            // connection close frame:
131
            case self::DECODE_CLOSE:
132
                $decodedData['type'] = self::EVENT_TYPE_CLOSE;
133
                break;
134
            // ping frame:
135
            case self::DECODE_PING:
136
                $decodedData['type'] = self::EVENT_TYPE_PING;
137
                break;
138
            // pong frame:
139
            case self::DECODE_PONG:
140
                $decodedData['type'] = self::EVENT_TYPE_PONG;
141
                break;
142
            default:
143
                $decodedData['type'] = '';
144
                break;
145
        }
146
    }
147
148
    /**
149
     * Checks if there are less connections for amount of processes
150
     * @param int $totalClients
151
     * @param int $maxClients
152
     */
153
    protected function lessConnThanProc(int $totalClients, int $maxClients): void
154
    {
155
        if ($totalClients !== 0 && $maxClients > $totalClients
156
            && $totalClients % $this->config->getClientsPerFork() === 0) {
157
            exit(1);
0 ignored issues
show
Best Practice introduced by
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
158
        }
159
    }
160
161
    /**
162
     * Clean socket resources that were closed,
163
     * thus avoiding (stream_select(): supplied resource is not a valid stream resource)
164
     * @param array $readSocks
165
     */
166
    protected function cleanSocketResources(array &$readSocks): void
167
    {
168
        foreach ($readSocks as $k => $sock) {
169
            if (!is_resource($sock)) {
170
                unset($readSocks[$k]);
171
            }
172
        }
173
    }
174
}
175