AttachWebsocketStream::write()   C
last analyzed

Complexity

Conditions 8
Paths 36

Size

Total Lines 51
Code Lines 36

Duplication

Lines 6
Ratio 11.76 %

Code Coverage

Tests 32
CRAP Score 8.6768

Importance

Changes 1
Bugs 0 Features 1
Metric Value
cc 8
eloc 36
c 1
b 0
f 1
nc 36
nop 1
dl 6
loc 51
ccs 32
cts 41
cp 0.7805
crap 8.6768
rs 6.5978

How to fix   Long Method   

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 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