Passed
Push — master ( f0b67b...ec9c11 )
by y
01:29
created

Frame::validate()   A

Complexity

Conditions 5
Paths 5

Size

Total Lines 13
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 5
eloc 7
nc 5
nop 0
dl 0
loc 13
rs 9.6111
c 0
b 0
f 0

2 Methods

Rating   Name   Duplication   Size   Complexity  
A Frame::isData() 0 2 1
A Frame::isFinal() 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 RSV123 = 0x70;
14
    const RSV1 = 0x40;
15
    const RSV2 = 0x20;
16
    const RSV3 = 0x10;
17
18
    const OP_CONTINUE = 0x00;
19
    const OP_TEXT = 0x01;
20
    const OP_BINARY = 0x02;
21
    const OP_CLOSE = 0x08;
22
    const OP_PING = 0x09;
23
    const OP_PONG = 0x0a;
24
25
    const NAMES = [
26
        self::OP_CONTINUE => 'CONTINUE',
27
        self::OP_TEXT => 'TEXT',
28
        self::OP_BINARY => 'BINARY',
29
        0x03 => 'RESERVED DATA 0x03',
30
        0x04 => 'RESERVED DATA 0x04',
31
        0x05 => 'RESERVED DATA 0x05',
32
        0x06 => 'RESERVED DATA 0x06',
33
        0x07 => 'RESERVED DATA 0x07',
34
        self::OP_CLOSE => 'CLOSE',
35
        self::OP_PING => 'PING',
36
        self::OP_PONG => 'PONG',
37
        0x0b => 'RESERVED CONTROL 0x0b',
38
        0x0c => 'RESERVED CONTROL 0x0c',
39
        0x0d => 'RESERVED CONTROL 0x0d',
40
        0x0e => 'RESERVED CONTROL 0x0e',
41
        0x0f => 'RESERVED CONTROL 0x0f',
42
    ];
43
44
    const CLOSE_NORMAL = 1000;              // mutual closure
45
    const CLOSE_INTERRUPT = 1001;           // abrupt closure due to hangups, reboots, "going away"
46
    const CLOSE_PROTOCOL_ERROR = 1002;      // invalid behavior / framing
47
    const CLOSE_UNHANDLED_DATA = 1003;      // message handler doesn't want the payload
48
    const CLOSE_BAD_DATA = 1007;            // message handler can't understand the payload
49
    const CLOSE_POLICY_VIOLATION = 1008;    // generic "access denied"
50
    const CLOSE_TOO_LARGE = 1009;           // unacceptable payload size
51
    const CLOSE_EXPECTATION = 1010;         // peer closed because it wants extensions (listed in the reason)
52
    const CLOSE_INTERNAL_ERROR = 1011;      // like http 500
53
54
    /**
55
     * @var bool
56
     */
57
    protected $final;
58
59
    /**
60
     * @var int
61
     */
62
    protected $opCode;
63
64
    /**
65
     * @var string
66
     */
67
    protected $payload;
68
69
    /**
70
     * The RSV bits masked out of the first byte of the frame, as-is.
71
     *
72
     * @var int
73
     */
74
    protected $rsv;
75
76
    /**
77
     * @param bool $final
78
     * @param int $rsv
79
     * @param int $opCode
80
     * @param string $payload
81
     */
82
    public function __construct (bool $final, int $rsv, int $opCode, string $payload) {
83
        $this->final = $final;
84
        $this->rsv = $rsv;
85
        $this->opCode = $opCode;
86
        $this->payload = $payload;
87
    }
88
89
    /**
90
     * The payload, or `CLOSE` reason.
91
     *
92
     * @return string
93
     */
94
    public function __toString () {
95
        if ($this->isClose()) {
96
            return $this->getCloseReason();
97
        }
98
        return $this->payload;
99
    }
100
101
    /**
102
     * The `CLOSE` code.
103
     *
104
     * https://tools.ietf.org/html/rfc6455#section-5.5.1
105
     *
106
     * @return int
107
     */
108
    final public function getCloseCode (): int {
109
        $code = substr($this->payload, 0, 2);
110
        return isset($code[1]) ? unpack('n', $code)[1] : self::CLOSE_NORMAL;
111
    }
112
113
    /**
114
     * The `CLOSE` reason.
115
     *
116
     * https://tools.ietf.org/html/rfc6455#section-5.5.1
117
     *
118
     * @return string
119
     */
120
    final public function getCloseReason (): string {
121
        return substr($this->payload, 2);
122
    }
123
124
    /**
125
     * @return int
126
     */
127
    final public function getLength (): int {
128
        return strlen($this->payload);
129
    }
130
131
    /**
132
     * @return string
133
     */
134
    public function getName (): string {
135
        return self::NAMES[$this->opCode];
136
    }
137
138
    /**
139
     * @return int
140
     */
141
    final public function getOpCode (): int {
142
        return $this->opCode;
143
    }
144
145
    /**
146
     * @return string
147
     */
148
    final public function getPayload (): string {
149
        return $this->payload;
150
    }
151
152
    /**
153
     * @return int
154
     */
155
    final public function getRsv (): int {
156
        return $this->rsv;
157
    }
158
159
    /**
160
     * @return bool
161
     */
162
    final public function hasRsv1 (): bool {
163
        return (bool)($this->rsv & self::RSV1);
164
    }
165
166
    /**
167
     * @return bool
168
     */
169
    final public function hasRsv2 (): bool {
170
        return (bool)($this->rsv & self::RSV2);
171
    }
172
173
    /**
174
     * @return bool
175
     */
176
    final public function hasRsv3 (): bool {
177
        return (bool)($this->rsv & self::RSV3);
178
    }
179
180
    /**
181
     * @return bool
182
     */
183
    final public function isBinary (): bool {
184
        return $this->opCode === self::OP_BINARY;
185
    }
186
187
    /**
188
     * @return bool
189
     */
190
    final public function isClose (): bool {
191
        return $this->opCode === self::OP_CLOSE;
192
    }
193
194
    /**
195
     * @return bool
196
     */
197
    final public function isContinue (): bool {
198
        return $this->opCode === self::OP_CONTINUE;
199
    }
200
201
    /**
202
     * @return bool
203
     */
204
    final public function isControl (): bool {
205
        return $this->opCode >= self::OP_CLOSE;
206
    }
207
208
    /**
209
     * @return bool
210
     */
211
    final public function isData (): bool {
212
        return $this->opCode < self::OP_CLOSE;
213
    }
214
215
    /**
216
     * @return bool
217
     */
218
    final public function isFinal (): bool {
219
        return $this->final;
220
    }
221
222
    /**
223
     * @return bool
224
     */
225
    final public function isPing (): bool {
226
        return $this->opCode === self::OP_PING;
227
    }
228
229
    /**
230
     * @return bool
231
     */
232
    final public function isPong (): bool {
233
        return $this->opCode === self::OP_PONG;
234
    }
235
236
    /**
237
     * @return bool
238
     */
239
    final public function isText (): bool {
240
        return $this->opCode === self::OP_TEXT;
241
    }
242
243
}