Completed
Push — master ( 1747a0...0cf576 )
by Arthur
04:10
created

WSClientTrait::sendFragment()   C

Complexity

Conditions 10
Paths 144

Size

Total Lines 48

Duplication

Lines 6
Ratio 12.5 %

Importance

Changes 0
Metric Value
dl 6
loc 48
rs 6.9745
c 0
b 0
f 0
cc 10
nc 144
nop 4

How to fix   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\Exceptions\BadOpcodeException;
7
use WSSC\Exceptions\BadUriException;
8
use WSSC\Exceptions\ConnectionException;
9
10
trait WSClientTrait
11
{
12
13
    /**
14
     * Validates whether server sent valid upgrade response
15
     *
16
     * @param ClientConfig $config
17
     * @param string $pathWithQuery
18
     * @param string $key
19
     * @throws ConnectionException
20
     */
21
    private function validateResponse(ClientConfig $config, string $pathWithQuery, string $key)
22
    {
23
        $response = stream_get_line($this->socket, self::DEFAULT_RESPONSE_HEADER, "\r\n\r\n");
0 ignored issues
show
Bug introduced by
The property socket does not exist. Did you maybe forget to declare it?

In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:

class MyClass { }

$x = new MyClass();
$x->foo = true;

Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion:

class MyClass {
    public $foo;
}

$x = new MyClass();
$x->foo = true;
Loading history...
24
        if (!preg_match(self::SEC_WEBSOCKET_ACCEPT_PTTRN, $response, $matches)) {
25
            $address = $config->getScheme() . '://' . $config->getHost() . $pathWithQuery;
26
            throw new ConnectionException(
27
                "Connection to '{$address}' failed: Server sent invalid upgrade response:\n"
28
                . $response, CommonsContract::CLIENT_INVALID_UPGRADE_RESPONSE
29
            );
30
        }
31
32
        $keyAccept = trim($matches[1]);
33
        $expectedResonse = base64_encode(pack('H*', sha1($key . self::SERVER_KEY_ACCEPT)));
34
        if ($keyAccept !== $expectedResonse) {
35
            throw new ConnectionException('Server sent bad upgrade response.',
36
                CommonsContract::CLIENT_INVALID_UPGRADE_RESPONSE);
37
        }
38
    }
39
40
    /**
41
     *  Gets host uri based on protocol
42
     *
43
     * @param ClientConfig $config
44
     * @return string
45
     * @throws BadUriException
46
     */
47
    private function getHostUri(ClientConfig $config): string
48
    {
49
        if (in_array($config->getScheme(), ['ws', 'wss'], true) === false) {
50
            throw new BadUriException(
51
                "Url should have scheme ws or wss, not '{$config->getScheme()}' from URI '$this->socketUrl' .",
0 ignored issues
show
Bug introduced by
The property socketUrl does not seem to exist. Did you mean socket?

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
52
                CommonsContract::CLIENT_INCORRECT_SCHEME
53
            );
54
        }
55
56
        return ($config->getScheme() === 'wss' ? 'ssl' : 'tcp') . '://' . $config->getHost();
57
    }
58
59
    /**
60
     * @param string $data
61
     * @return float|int
62
     * @throws ConnectionException
63
     */
64
    private function getPayloadLength(string $data)
65
    {
66
        $payloadLength = (int)ord($data[1]) & self::MASK_127; // Bits 1-7 in byte 1
67
        if ($payloadLength > self::MASK_125) {
68
            if ($payloadLength === self::MASK_126) {
69
                $data = $this->read(2); // 126: Payload is a 16-bit unsigned int
0 ignored issues
show
Bug introduced by
It seems like read() must be provided by classes using this trait. How about adding it as abstract method to this trait?

This check looks for methods that are used by a trait but not required by it.

To illustrate, let’s look at the following code example

trait Idable {
    public function equalIds(Idable $other) {
        return $this->getId() === $other->getId();
    }
}

The trait Idable provides a method equalsId that in turn relies on the method getId(). If this method does not exist on a class mixing in this trait, the method will fail.

Adding the getId() as an abstract method to the trait will make sure it is available.

Loading history...
70
            } else {
71
                $data = $this->read(8); // 127: Payload is a 64-bit unsigned int
0 ignored issues
show
Bug introduced by
It seems like read() must be provided by classes using this trait. How about adding it as abstract method to this trait?

This check looks for methods that are used by a trait but not required by it.

To illustrate, let’s look at the following code example

trait Idable {
    public function equalIds(Idable $other) {
        return $this->getId() === $other->getId();
    }
}

The trait Idable provides a method equalsId that in turn relies on the method getId(). If this method does not exist on a class mixing in this trait, the method will fail.

Adding the getId() as an abstract method to the trait will make sure it is available.

Loading history...
72
            }
73
            $payloadLength = bindec(self::sprintB($data));
74
        }
75
76
        return $payloadLength;
77
    }
78
79
    /**
80
     * @param string $data
81
     * @param int $payloadLength
82
     * @return string
83
     * @throws ConnectionException
84
     */
85
    private function getPayloadData(string $data, int $payloadLength): string
86
    {
87
        // Masking?
88
        $mask = (bool)(ord($data[1]) >> 7);  // Bit 0 in byte 1
89
        $payload = '';
90
        $maskingKey = '';
91
92
        // Get masking key.
93
        if ($mask) {
94
            $maskingKey = $this->read(4);
0 ignored issues
show
Bug introduced by
It seems like read() must be provided by classes using this trait. How about adding it as abstract method to this trait?

This check looks for methods that are used by a trait but not required by it.

To illustrate, let’s look at the following code example

trait Idable {
    public function equalIds(Idable $other) {
        return $this->getId() === $other->getId();
    }
}

The trait Idable provides a method equalsId that in turn relies on the method getId(). If this method does not exist on a class mixing in this trait, the method will fail.

Adding the getId() as an abstract method to the trait will make sure it is available.

Loading history...
95
        }
96
97
        // Get the actual payload, if any (might not be for e.g. close frames.
98
        if ($payloadLength > 0) {
99
            $data = $this->read($payloadLength);
0 ignored issues
show
Bug introduced by
It seems like read() must be provided by classes using this trait. How about adding it as abstract method to this trait?

This check looks for methods that are used by a trait but not required by it.

To illustrate, let’s look at the following code example

trait Idable {
    public function equalIds(Idable $other) {
        return $this->getId() === $other->getId();
    }
}

The trait Idable provides a method equalsId that in turn relies on the method getId(). If this method does not exist on a class mixing in this trait, the method will fail.

Adding the getId() as an abstract method to the trait will make sure it is available.

Loading history...
100
101
            if ($mask) {
102
                // Unmask payload.
103
                for ($i = 0; $i < $payloadLength; $i++) {
104
                    $payload .= ($data[$i] ^ $maskingKey[$i % 4]);
105
                }
106
            } else {
107
                $payload = $data;
108
            }
109
        }
110
111
        return $payload;
112
    }
113
114
    /**
115
     * @return null|string
116
     * @throws \WSSC\Exceptions\BadOpcodeException
117
     * @throws \InvalidArgumentException
118
     * @throws BadOpcodeException
119
     * @throws BadUriException
120
     * @throws ConnectionException
121
     * @throws \Exception
122
     */
123
    protected function receiveFragment()
124
    {
125
        // Just read the main fragment information first.
126
        $data = $this->read(2);
0 ignored issues
show
Bug introduced by
It seems like read() must be provided by classes using this trait. How about adding it as abstract method to this trait?

This check looks for methods that are used by a trait but not required by it.

To illustrate, let’s look at the following code example

trait Idable {
    public function equalIds(Idable $other) {
        return $this->getId() === $other->getId();
    }
}

The trait Idable provides a method equalsId that in turn relies on the method getId(). If this method does not exist on a class mixing in this trait, the method will fail.

Adding the getId() as an abstract method to the trait will make sure it is available.

Loading history...
127
128
        // Is this the final fragment?  // Bit 0 in byte 0
129
        /// @todo Handle huge payloads with multiple fragments.
130
        $final = (bool)(ord($data[0]) & 1 << 7);
131
132
        // Parse opcode
133
        $opcode_int = ord($data[0]) & 31; // Bits 4-7
134
        $opcode_ints = array_flip(self::$opcodes);
135
        if (!array_key_exists($opcode_int, $opcode_ints)) {
136
            throw new ConnectionException("Bad opcode in websocket frame: $opcode_int",
137
                CommonsContract::CLIENT_BAD_OPCODE);
138
        }
139
140
        $opcode = $opcode_ints[$opcode_int];
141
142
        // record the opcode if we are not receiving a continutation fragment
143
        if ($opcode !== 'continuation') {
144
            $this->lastOpcode = $opcode;
0 ignored issues
show
Bug introduced by
The property lastOpcode does not exist. Did you maybe forget to declare it?

In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:

class MyClass { }

$x = new MyClass();
$x->foo = true;

Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion:

class MyClass {
    public $foo;
}

$x = new MyClass();
$x->foo = true;
Loading history...
145
        }
146
147
        $payloadLength = $this->getPayloadLength($data);
148
        $payload = $this->getPayloadData($data, $payloadLength);
149
150
        if ($opcode === CommonsContract::EVENT_TYPE_CLOSE) {
151
            // Get the close status.
152
            if ($payloadLength >= 2) {
153
                $statusBin = $payload[0] . $payload[1];
154
                $status = bindec(sprintf('%08b%08b', ord($payload[0]), ord($payload[1])));
155
                $this->closeStatus = $status;
0 ignored issues
show
Bug introduced by
The property closeStatus does not exist. Did you maybe forget to declare it?

In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:

class MyClass { }

$x = new MyClass();
$x->foo = true;

Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion:

class MyClass {
    public $foo;
}

$x = new MyClass();
$x->foo = true;
Loading history...
156
                $payload = substr($payload, 2);
157
158
                if (!$this->isClosing) {
0 ignored issues
show
Bug introduced by
The property isClosing does not exist. Did you maybe forget to declare it?

In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:

class MyClass { }

$x = new MyClass();
$x->foo = true;

Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion:

class MyClass {
    public $foo;
}

$x = new MyClass();
$x->foo = true;
Loading history...
159
                    $this->send($statusBin . 'Close acknowledged: ' . $status,
0 ignored issues
show
Bug introduced by
It seems like send() must be provided by classes using this trait. How about adding it as abstract method to this trait?

This check looks for methods that are used by a trait but not required by it.

To illustrate, let’s look at the following code example

trait Idable {
    public function equalIds(Idable $other) {
        return $this->getId() === $other->getId();
    }
}

The trait Idable provides a method equalsId that in turn relies on the method getId(). If this method does not exist on a class mixing in this trait, the method will fail.

Adding the getId() as an abstract method to the trait will make sure it is available.

Loading history...
160
                        CommonsContract::EVENT_TYPE_CLOSE); // Respond.
161
                }
162
            }
163
164
            if ($this->isClosing) {
165
                $this->isClosing = false; // A close response, all done.
166
            }
167
168
            fclose($this->socket);
169
            $this->isConnected = false;
0 ignored issues
show
Bug introduced by
The property isConnected does not exist. Did you maybe forget to declare it?

In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:

class MyClass { }

$x = new MyClass();
$x->foo = true;

Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion:

class MyClass {
    public $foo;
}

$x = new MyClass();
$x->foo = true;
Loading history...
170
        }
171
172
        if (!$final) {
173
            $this->hugePayload .= $payload;
0 ignored issues
show
Bug introduced by
The property hugePayload does not exist. Did you maybe forget to declare it?

In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:

class MyClass { }

$x = new MyClass();
$x->foo = true;

Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion:

class MyClass {
    public $foo;
}

$x = new MyClass();
$x->foo = true;
Loading history...
174
175
            return NULL;
176
        } // this is the last fragment, and we are processing a huge_payload
177
178
        if ($this->hugePayload) {
179
            $payload = $this->hugePayload .= $payload;
180
            $this->hugePayload = NULL;
181
        }
182
183
        return $payload;
184
    }
185
186
    /**
187
     * @param $final
188
     * @param $payload
189
     * @param $opcode
190
     * @param $masked
191
     * @throws ConnectionException
192
     * @throws \Exception
193
     */
194
    protected function sendFragment($final, $payload, $opcode, $masked)
195
    {
196
        // Binary string for header.
197
        $frameHeadBin = '';
198
        // Write FIN, final fragment bit.
199
        $frameHeadBin .= (bool)$final ? '1' : '0';
200
        // RSV 1, 2, & 3 false and unused.
201
        $frameHeadBin .= '000';
202
        // Opcode rest of the byte.
203
        $frameHeadBin .= sprintf('%04b', self::$opcodes[$opcode]);
204
        // Use masking?
205
        $frameHeadBin .= $masked ? '1' : '0';
206
207
        // 7 bits of payload length...
208
        $payloadLen = strlen($payload);
209
        if ($payloadLen > self::MAX_BYTES_READ) {
210
            $frameHeadBin .= decbin(self::MASK_127);
211
            $frameHeadBin .= sprintf('%064b', $payloadLen);
212
        } else if ($payloadLen > self::MASK_125) {
213
            $frameHeadBin .= decbin(self::MASK_126);
214
            $frameHeadBin .= sprintf('%016b', $payloadLen);
215
        } else {
216
            $frameHeadBin .= sprintf('%07b', $payloadLen);
217
        }
218
219
        $frame = '';
220
221
        // Write frame head to frame.
222
        foreach (str_split($frameHeadBin, 8) as $binstr) {
223
            $frame .= chr(bindec($binstr));
224
        }
225
        // Handle masking
226
        if ($masked) {
227
            // generate a random mask:
228
            $mask = '';
229 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...
230
                $mask .= chr(random_int(0, 255));
231
            }
232
            $frame .= $mask;
233
        }
234
235
        // Append payload to frame:
236 View Code Duplication
        for ($i = 0; $i < $payloadLen; $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...
237
            $frame .= ($masked === true) ? $payload[$i] ^ $mask[$i % 4] : $payload[$i];
0 ignored issues
show
Bug introduced by
The variable $mask does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
238
        }
239
240
        $this->write($frame);
0 ignored issues
show
Bug introduced by
It seems like write() must be provided by classes using this trait. How about adding it as abstract method to this trait?

This check looks for methods that are used by a trait but not required by it.

To illustrate, let’s look at the following code example

trait Idable {
    public function equalIds(Idable $other) {
        return $this->getId() === $other->getId();
    }
}

The trait Idable provides a method equalsId that in turn relies on the method getId(). If this method does not exist on a class mixing in this trait, the method will fail.

Adding the getId() as an abstract method to the trait will make sure it is available.

Loading history...
241
    }
242
}