Completed
Push — master ( bf368b...88d0e2 )
by Arthur
01:43
created

Connection::encode()   C

Complexity

Conditions 12
Paths 40

Size

Total Lines 55

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 55
rs 6.9666
c 0
b 0
f 0
cc 12
nc 40
nop 3

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
namespace WSSC\Components;
4
5
use WSSC\Contracts\CommonsContract;
6
use WSSC\Contracts\ConnectionContract;
7
use WSSC\Contracts\WebSocketServerContract;
8
9
class Connection implements ConnectionContract, CommonsContract
10
{
11
12
    private $socketConnection;
13
    private $clients;
14
15
    /**
16
     * Connection constructor.
17
     *
18
     * @param $sockConn
19
     * @param array $clients
20
     */
21
    public function __construct($sockConn, array $clients = [])
22
    {
23
        $this->socketConnection = $sockConn;
24
        $this->clients = $clients;
25
    }
26
27
    /**
28
     * Closes clients socket stream
29
     *
30
     * @throws \Exception
31
     */
32
    public function close()
33
    {
34
        if (is_resource($this->socketConnection)) {
35
            fwrite($this->socketConnection, $this->encode('', self::EVENT_TYPE_CLOSE));
36
            fclose($this->socketConnection);
37
        }
38
    }
39
40
    /**
41
     * This method is invoked when user implementation call $conn->send($data)
42
     * writes data to the clients stream socket
43
     *
44
     * @param string $data pure decoded data from server
45
     * @throws \Exception
46
     */
47
    public function send(string $data)
48
    {
49
        fwrite($this->socketConnection, $this->encode($data));
50
    }
51
52
    /**
53
     * @param string $data data to send to clients
54
     * @throws \Exception
55
     */
56
    public function broadCast(string $data)
57
    {
58
        foreach ($this->clients as $client) {
59
            if (is_resource($client)) { // check if not yet closed/broken etc
60
                fwrite($client, $this->encode($data));
61
            }
62
        }
63
    }
64
65
    /**
66
     * Encodes data before writing to the client socket stream
67
     *
68
     * @param string $payload
69
     * @param string $type
70
     * @param boolean $masked
71
     * @return mixed
72
     * @throws \Exception
73
     */
74
    private function encode($payload, string $type = self::EVENT_TYPE_TEXT, bool $masked = false)
75
    {
76
        $frameHead = [];
77
        $payloadLength = strlen($payload);
78
79
        switch ($type) {
80
            case self::EVENT_TYPE_TEXT:
81
                // first byte indicates FIN, Text-Frame (10000001):
82
                $frameHead[0] = self::ENCODE_TEXT;
83
                break;
84
85
            case self::EVENT_TYPE_CLOSE:
86
                // first byte indicates FIN, Close Frame(10001000):
87
                $frameHead[0] = self::ENCODE_CLOSE;
88
                break;
89
90
            case self::EVENT_TYPE_PING:
91
                // first byte indicates FIN, Ping frame (10001001):
92
                $frameHead[0] = self::ENCODE_PING;
93
                break;
94
95
            case self::EVENT_TYPE_PONG:
96
                // first byte indicates FIN, Pong frame (10001010):
97
                $frameHead[0] = self::ENCODE_PONG;
98
                break;
99
        }
100
101
        // set mask and payload length (using 1, 3 or 9 bytes)
102
        if ($payloadLength > self::PAYLOAD_MAX_BITS) {
103
            $payloadLengthBin = str_split(sprintf('%064b', $payloadLength), self::PAYLOAD_CHUNK);
104
            $frameHead[1] = ($masked === true) ? self::MASK_255 : self::MASK_127;
105
106
            for ($i = 0; $i < 8; $i++) {
107
                $frameHead[$i + 2] = bindec($payloadLengthBin[$i]);
108
            }
109
110
            // most significant bit MUST be 0
111
            if ($frameHead[2] > self::MASK_127) {
112
                return [
113
                    'type'    => $type,
114
                    'payload' => $payload,
115
                    'error'   => WebSocketServerContract::ERR_FRAME_TOO_LARGE,
116
                ];
117
            }
118
        } elseif ($payloadLength > self::MASK_125) {
119
            $payloadLengthBin = str_split(sprintf('%016b', $payloadLength), self::PAYLOAD_CHUNK);
120
            $frameHead[1] = ($masked === true) ? self::MASK_254 : self::MASK_126;
121
            $frameHead[2] = bindec($payloadLengthBin[0]);
122
            $frameHead[3] = bindec($payloadLengthBin[1]);
123
        } else {
124
            $frameHead[1] = ($masked === true) ? $payloadLength + self::MASK_128 : $payloadLength;
125
        }
126
127
        return $this->getComposedFrame($frameHead, $payload, $payloadLength, $masked);
128
    }
129
130
    private function getComposedFrame(array $frameHead, string $payload, int $payloadLength, bool $masked)
131
    {
132
        // convert frame-head to string:
133
        foreach (array_keys($frameHead) as $i) {
134
            $frameHead[$i] = chr($frameHead[$i]);
135
        }
136
137
        // generate a random mask:
138
        $mask = [];
139
        if ($masked === true) {
140 View Code Duplication
            for ($i = 0; $i < 4; $i++) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
141
                $mask[$i] = chr(random_int(0, self::MASK_255));
142
            }
143
144
            $frameHead = array_merge($frameHead, $mask);
145
        }
146
        $frame = implode('', $frameHead);
147
148
        // append payload to frame:
149 View Code Duplication
        for ($i = 0; $i < $payloadLength; $i++) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
150
            $frame .= ($masked === true) ? $payload[$i] ^ $mask[$i % 4] : $payload[$i];
151
        }
152
153
        return $frame;
154
    }
155
156
    /**
157
     * Gets unique socket id from resource
158
     *
159
     * @return int
160
     */
161
    public function getUniqueSocketId(): int
162
    {
163
        return (int)$this->socketConnection;
164
    }
165
166
    /**
167
     *  Gets client socket address host/port or UNIX path
168
     *
169
     * @return string
170
     */
171
    public function getPeerName(): string
172
    {
173
        return stream_socket_get_name($this->socketConnection, true);
174
    }
175
}
176