Passed
Push — master ( a91d54...875978 )
by y
02:08
created

Frame   A

Complexity

Total Complexity 26

Size/Duplication

Total Lines 212
Duplicated Lines 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 53
dl 0
loc 212
rs 10
c 1
b 0
f 0
wmc 26

20 Methods

Rating   Name   Duplication   Size   Complexity  
A getOpCode() 0 2 1
A isClose() 0 2 1
A hasRsv1() 0 2 1
A isText() 0 2 1
A hasRsv2() 0 2 1
A isPong() 0 2 1
A __toString() 0 5 2
A isPing() 0 2 1
A isFinal() 0 2 1
A hasRsv3() 0 2 1
A validate() 0 13 5
A isControl() 0 2 1
A getRsv() 0 2 1
A isContinue() 0 2 1
A __construct() 0 5 1
A getCloseReason() 0 2 1
A getCloseCode() 0 5 2
A getPayload() 0 2 1
A getLength() 0 2 1
A isBinary() 0 2 1
1
<?php
2
3
namespace Helix\Socket\WebSocket;
4
5
/**
6
 * A WebSocket frame.
7
 *
8
 * @link https://tools.ietf.org/html/rfc6455#section-5.2 Base Framing Protocol
9
 * @link https://tools.ietf.org/html/rfc6455#section-7.4.1 Defined Status Codes
10
 */
11
class Frame {
12
13
    const OP_CONTINUE = 0x00;
14
    const OP_TEXT = 0x01;
15
    const OP_BINARY = 0x02;
16
    const OP_CLOSE = 0x08;
17
    const OP_PING = 0x09;
18
    const OP_PONG = 0x0a;
19
20
    const CLOSE_NORMAL = 1000;              // mutual closure
21
    const CLOSE_INTERRUPT = 1001;           // abrupt closure due to hangups, reboots, "going away"
22
    const CLOSE_PROTOCOL_ERROR = 1002;      // invalid behavior / framing
23
    const CLOSE_UNHANDLED_DATA = 1003;      // message handler doesn't want the payload
24
    const CLOSE_BAD_DATA = 1007;            // message handler can't understand the payload
25
    const CLOSE_POLICY_VIOLATION = 1008;    // generic "access denied"
26
    const CLOSE_TOO_LARGE = 1009;           // unacceptable payload size
27
    const CLOSE_EXPECTATION = 1010;         // peer closed because it wants extensions (listed in the reason)
28
    const CLOSE_INTERNAL_ERROR = 1011;      // like http 500
29
30
    /**
31
     * @var bool
32
     */
33
    protected $final;
34
35
    /**
36
     * @var int
37
     */
38
    protected $opCode;
39
40
    /**
41
     * @var string
42
     */
43
    protected $payload;
44
45
    /**
46
     * @var int
47
     */
48
    protected $rsv;
49
50
    /**
51
     * @param bool $final
52
     * @param int $rsv
53
     * @param int $opCode
54
     * @param string $payload
55
     */
56
    public function __construct (bool $final, int $rsv, int $opCode, string $payload) {
57
        $this->final = $final;
58
        $this->rsv = $rsv;
59
        $this->opCode = $opCode;
60
        $this->payload = $payload;
61
    }
62
63
    /**
64
     * The payload or close reason.
65
     *
66
     * @return string
67
     */
68
    final public function __toString () {
69
        if ($this->isClose()) {
70
            return $this->getCloseReason();
71
        }
72
        return $this->payload;
73
    }
74
75
    /**
76
     * The `CLOSE` code.
77
     *
78
     * https://tools.ietf.org/html/rfc6455#section-5.5.1
79
     *
80
     * @return int
81
     */
82
    public function getCloseCode (): int {
83
        if ($this->getLength() >= 2) {
84
            return unpack('n', substr($this->payload, 0, 2))[1];
85
        }
86
        return self::CLOSE_NORMAL;
87
    }
88
89
    /**
90
     * The `CLOSE` reason.
91
     *
92
     * https://tools.ietf.org/html/rfc6455#section-5.5.1
93
     *
94
     * @return string
95
     */
96
    public function getCloseReason (): string {
97
        return substr($this->payload, 2);
98
    }
99
100
    /**
101
     * @return int
102
     */
103
    final public function getLength (): int {
104
        return strlen($this->payload);
105
    }
106
107
    /**
108
     * @return int
109
     */
110
    final public function getOpCode (): int {
111
        return $this->opCode;
112
    }
113
114
    /**
115
     * @return string
116
     */
117
    final public function getPayload (): string {
118
        return $this->payload;
119
    }
120
121
    /**
122
     * @return int
123
     */
124
    final public function getRsv (): int {
125
        return $this->rsv;
126
    }
127
128
    /**
129
     * @return bool
130
     */
131
    final public function hasRsv1 (): bool {
132
        return (bool)($this->rsv & 0b100);
133
    }
134
135
    /**
136
     * @return bool
137
     */
138
    final public function hasRsv2 (): bool {
139
        return (bool)($this->rsv & 0b010);
140
    }
141
142
    /**
143
     * @return bool
144
     */
145
    final public function hasRsv3 (): bool {
146
        return (bool)($this->rsv & 0b001);
147
    }
148
149
    /**
150
     * @return bool
151
     */
152
    final public function isBinary (): bool {
153
        return $this->opCode === self::OP_BINARY;
154
    }
155
156
    /**
157
     * @return bool
158
     */
159
    final public function isClose (): bool {
160
        return $this->opCode === self::OP_CLOSE;
161
    }
162
163
    /**
164
     * @return bool
165
     */
166
    final public function isContinue (): bool {
167
        return $this->opCode === self::OP_CONTINUE;
168
    }
169
170
    /**
171
     * @return bool
172
     */
173
    final public function isControl (): bool {
174
        return (bool)($this->opCode & 0b1000);
175
    }
176
177
    /**
178
     * @return bool
179
     */
180
    final public function isFinal (): bool {
181
        return $this->final;
182
    }
183
184
    /**
185
     * @return bool
186
     */
187
    final public function isPing (): bool {
188
        return $this->opCode === self::OP_PING;
189
    }
190
191
    /**
192
     * @return bool
193
     */
194
    final public function isPong (): bool {
195
        return $this->opCode === self::OP_PONG;
196
    }
197
198
    /**
199
     * @return bool
200
     */
201
    final public function isText (): bool {
202
        return $this->opCode === self::OP_TEXT;
203
    }
204
205
    /**
206
     * Throws if invalid. Doesn't validate the payload.
207
     *
208
     * @throws WebSocketError
209
     */
210
    public function validate (): void {
211
        if ($this->isControl()) {
212
            if ($this->opCode > self::OP_PONG) {
213
                throw new WebSocketError(self::CLOSE_PROTOCOL_ERROR, "Reserved control opcode ($this->opCode).");
214
            }
215
            // https://tools.ietf.org/html/rfc6455#section-5.4
216
            // Page 34: "Control frames themselves MUST NOT be fragmented."
217
            if (!$this->isFinal()) {
218
                throw new WebSocketError(self::CLOSE_PROTOCOL_ERROR, "Fragmented control frame.");
219
            }
220
        }
221
        elseif ($this->opCode > self::OP_BINARY) {
222
            throw new WebSocketError(self::CLOSE_PROTOCOL_ERROR, "Reserved data opcode ($this->opCode).");
223
        }
224
    }
225
226
}