AttachWebsocketStream   A
last analyzed

Complexity

Total Complexity 23

Size/Duplication

Total Lines 165
Duplicated Lines 7.27 %

Coupling/Cohesion

Components 1
Dependencies 1

Test Coverage

Coverage 76.14%

Importance

Changes 1
Bugs 0 Features 1
Metric Value
wmc 23
c 1
b 0
f 1
lcom 1
cbo 1
dl 12
loc 165
ccs 67
cts 88
cp 0.7614
rs 10

5 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 4 1
A socketWrite() 0 4 1
C write() 6 51 8
C read() 6 58 10
A socketRead() 0 10 3

How to fix   Duplicated Code   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

1
<?php
2
3
namespace Docker\Stream;
4
use Psr\Http\Message\StreamInterface;
5
6
/**
7
 * An interactive stream is used when communicating with an attached docker container
8
 *
9
 * It helps dealing with encoding and decoding frame from websocket protocol (hybi 10)
10
 *
11
 * @see https://tools.ietf.org/html/rfc6455#section-5.2
12
 */
13
class AttachWebsocketStream
14
{
15
    /** @var resource The underlying socket */
16
    private $socket;
17
18 1
    public function __construct(StreamInterface $stream)
19
    {
20 1
        $this->socket = $stream->detach();
21 1
    }
22
23
    /**
24
     * Send input to the container
25
     *
26
     * @param string $data Data to send
27
     */
28 1
    public function write($data)
29
    {
30 1
        $rand  = rand(0, 28);
31
        $frame = [
32 1
            'fin'      => 1,
33 1
            'rsv1'     => 0,
34 1
            'rsv2'     => 0,
35 1
            'rsv3'     => 0,
36 1
            'opcode'   => 1, // We always send text
37 1
            'mask'     => 1,
38 1
            'len'      => strlen($data),
39 1
            'mask_key' => substr(md5(uniqid()), $rand, 4),
40 1
            'data'     => $data,
41 1
        ];
42
43 1 View Code Duplication
        if ($frame['mask'] == 1) {
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...
44 1
            for ($i = 0; $i < $frame['len']; $i++) {
45 1
                $frame['data']{$i}
46 1
                    = chr(ord($frame['data']{$i}) ^ ord($frame['mask_key']{$i % 4}));
47 1
            }
48 1
        }
49
50 1
        if ($frame['len'] > pow(2, 16)) {
51
            $len = 127;
52 1
        } elseif ($frame['len'] > 125) {
53
            $len = 126;
54
        } else {
55 1
            $len = $frame['len'];
56
        }
57
58 1
        $firstByte  = ($frame['fin'] << 7) | (($frame['rsv1'] << 7) >> 1) | (($frame['rsv2'] << 7) >> 2) | (($frame['rsv3'] << 7) >> 3) | (($frame['opcode'] << 4) >> 4);
59 1
        $secondByte = ($frame['mask'] << 7) | (($len << 1) >> 1);
60
61 1
        $this->socketWrite(chr($firstByte));
62 1
        $this->socketWrite(chr($secondByte));
63
64 1
        if ($len == 126) {
65
            $this->socketWrite(pack('n', $frame['len']));
66 1
        } elseif ($len == 127) {
67
            $higher = $frame['len'] >> 32;
68
            $lower  = ($frame['len'] << 32) >> 32;
69
            $this->socketWrite(pack('N', $higher));
70
            $this->socketWrite(pack('N', $lower));
71
        }
72
73 1
        if ($frame['mask'] == 1) {
74 1
            $this->socketWrite($frame['mask_key']);
75 1
        }
76
77 1
        $this->socketWrite($frame['data']);
78 1
    }
79
80
    /**
81
     * Block until it receive a frame from websocket or return null if no more connexion
82
     *
83
     * @param int     $waitTime      Time to wait in seconds before return false
84
     * @param int     $waitMicroTime Time to wait in microseconds before return false
85
     * @param boolean $getFrame      Whether to return the frame of websocket or only the data
86
     *
87
     * @return null|false|string|array Null for socket not available, false for no message, string for the last message and the frame array if $getFrame is set to true
88
     */
89 1
    public function read($waitTime = 0, $waitMicroTime = 200000, $getFrame = false)
90
    {
91 1
        if (!is_resource($this->socket) || feof($this->socket)) {
92
            return null;
93
        }
94
95 1
        $read   = [$this->socket];
96 1
        $write  = null;
97 1
        $expect = null;
98
99 1
        if (stream_select($read, $write, $expect, $waitTime, $waitMicroTime) == 0) {
100 1
            return false;
101
        }
102
103 1
        $firstByte  = $this->socketRead(1);
104 1
        $frame      = [];
105 1
        $firstByte  = ord($firstByte);
106 1
        $secondByte = ord($this->socketRead(1));
107
108
        // First byte decoding
109 1
        $frame['fin']    = ($firstByte & 128) >> 7;
110 1
        $frame['rsv1']   = ($firstByte & 64)  >> 6;
111 1
        $frame['rsv2']   = ($firstByte & 32)  >> 5;
112 1
        $frame['rsv3']   = ($firstByte & 16)  >> 4;
113 1
        $frame['opcode'] = ($firstByte & 15);
114
115
        // Second byte decoding
116 1
        $frame['mask'] = ($secondByte & 128) >> 7;
117 1
        $frame['len']  = ($secondByte & 127);
118
119
        // Get length of the frame
120 1
        if ($frame['len'] == 126) {
121
            $frame['len'] = unpack('n', $this->socketRead(2))[1];
122 1
        } elseif ($frame['len'] == 127) {
123
            list($higher, $lower) = array_values(unpack('N2', $this->socketRead(8)));
124
            $frame['len'] = ($higher << 32) | $lower;
125
        }
126
127
        // Get the mask key if needed
128 1
        if ($frame['mask'] == 1) {
129
            $frame['mask_key'] = $this->socketRead(4);
130
        }
131
132 1
        $frame['data'] = $this->socketRead($frame['len']);
133
134
        // Decode data if needed
135 1 View Code Duplication
        if ($frame['mask'] == 1) {
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...
136
            for ($i = 0; $i < $frame['len']; $i++) {
137
                $frame['data']{$i} = chr(ord($frame['data']{$i}) ^ ord($frame['mask_key']{$i % 4}));
138
            }
139
        }
140
141 1
        if ($getFrame) {
142
            return $frame;
143
        }
144
145 1
        return (string)$frame['data'];
146
    }
147
148
    /**
149
     * Force to have something of the expected size (block)
150
     *
151
     * @param $length
152
     *
153
     * @return string
154
     */
155 1
    private function socketRead($length)
156
    {
157 1
        $read = "";
158
159
        do {
160 1
            $read .= fread($this->socket, $length - strlen($read));
161 1
        } while (strlen($read) < $length && !feof($this->socket));
162
163 1
        return $read;
164
    }
165
166
    /**
167
     * Write to the socket
168
     *
169
     * @param $data
170
     *
171
     * @return int
172
     */
173 1
    private function socketWrite($data)
174
    {
175 1
        return fwrite($this->socket, $data);
176
    }
177
}
178