@@ -48,201 +48,204 @@ class ByteParser extends Writable {
48
48
* or not enough bytes are buffered to parse.
49
49
*/
50
50
run ( callback ) {
51
- if ( this . #state === parserStates . INFO ) {
52
- // If there aren't enough bytes to parse the payload length, etc.
53
- if ( this . #byteOffset < 2 ) {
54
- return callback ( )
55
- }
51
+ while ( true ) {
52
+ if ( this . #state === parserStates . INFO ) {
53
+ // If there aren't enough bytes to parse the payload length, etc.
54
+ if ( this . #byteOffset < 2 ) {
55
+ return callback ( )
56
+ }
56
57
57
- const buffer = this . consume ( 2 )
58
+ const buffer = this . consume ( 2 )
58
59
59
- this . #info. fin = ( buffer [ 0 ] & 0x80 ) !== 0
60
- this . #info. opcode = buffer [ 0 ] & 0x0F
60
+ this . #info. fin = ( buffer [ 0 ] & 0x80 ) !== 0
61
+ this . #info. opcode = buffer [ 0 ] & 0x0F
61
62
62
- // If we receive a fragmented message, we use the type of the first
63
- // frame to parse the full message as binary/text, when it's terminated
64
- this . #info. originalOpcode ??= this . #info. opcode
63
+ // If we receive a fragmented message, we use the type of the first
64
+ // frame to parse the full message as binary/text, when it's terminated
65
+ this . #info. originalOpcode ??= this . #info. opcode
65
66
66
- this . #info. fragmented = ! this . #info. fin && this . #info. opcode !== opcodes . CONTINUATION
67
+ this . #info. fragmented = ! this . #info. fin && this . #info. opcode !== opcodes . CONTINUATION
67
68
68
- if ( this . #info. fragmented && this . #info. opcode !== opcodes . BINARY && this . #info. opcode !== opcodes . TEXT ) {
69
- // Only text and binary frames can be fragmented
70
- failWebsocketConnection ( this . ws , 'Invalid frame type was fragmented.' )
71
- return
72
- }
69
+ if ( this . #info. fragmented && this . #info. opcode !== opcodes . BINARY && this . #info. opcode !== opcodes . TEXT ) {
70
+ // Only text and binary frames can be fragmented
71
+ failWebsocketConnection ( this . ws , 'Invalid frame type was fragmented.' )
72
+ return
73
+ }
73
74
74
- const payloadLength = buffer [ 1 ] & 0x7F
75
+ const payloadLength = buffer [ 1 ] & 0x7F
75
76
76
- if ( payloadLength <= 125 ) {
77
- this . #info. payloadLength = payloadLength
78
- this . #state = parserStates . READ_DATA
79
- } else if ( payloadLength === 126 ) {
80
- this . #state = parserStates . PAYLOADLENGTH_16
81
- } else if ( payloadLength === 127 ) {
82
- this . #state = parserStates . PAYLOADLENGTH_64
83
- }
77
+ if ( payloadLength <= 125 ) {
78
+ this . #info. payloadLength = payloadLength
79
+ this . #state = parserStates . READ_DATA
80
+ } else if ( payloadLength === 126 ) {
81
+ this . #state = parserStates . PAYLOADLENGTH_16
82
+ } else if ( payloadLength === 127 ) {
83
+ this . #state = parserStates . PAYLOADLENGTH_64
84
+ }
84
85
85
- if ( this . #info. fragmented && payloadLength > 125 ) {
86
- // A fragmented frame can't be fragmented itself
87
- failWebsocketConnection ( this . ws , 'Fragmented frame exceeded 125 bytes.' )
88
- return
89
- } else if (
90
- ( this . #info. opcode === opcodes . PING ||
91
- this . #info. opcode === opcodes . PONG ||
92
- this . #info. opcode === opcodes . CLOSE ) &&
93
- payloadLength > 125
94
- ) {
95
- // Control frames can have a payload length of 125 bytes MAX
96
- failWebsocketConnection ( this . ws , 'Payload length for control frame exceeded 125 bytes.' )
97
- return
98
- } else if ( this . #info. opcode === opcodes . CLOSE ) {
99
- if ( payloadLength === 1 ) {
100
- failWebsocketConnection ( this . ws , 'Received close frame with a 1-byte body.' )
86
+ if ( this . #info. fragmented && payloadLength > 125 ) {
87
+ // A fragmented frame can't be fragmented itself
88
+ failWebsocketConnection ( this . ws , 'Fragmented frame exceeded 125 bytes.' )
101
89
return
102
- }
90
+ } else if (
91
+ ( this . #info. opcode === opcodes . PING ||
92
+ this . #info. opcode === opcodes . PONG ||
93
+ this . #info. opcode === opcodes . CLOSE ) &&
94
+ payloadLength > 125
95
+ ) {
96
+ // Control frames can have a payload length of 125 bytes MAX
97
+ failWebsocketConnection ( this . ws , 'Payload length for control frame exceeded 125 bytes.' )
98
+ return
99
+ } else if ( this . #info. opcode === opcodes . CLOSE ) {
100
+ if ( payloadLength === 1 ) {
101
+ failWebsocketConnection ( this . ws , 'Received close frame with a 1-byte body.' )
102
+ return
103
+ }
103
104
104
- const body = this . consume ( payloadLength )
105
+ const body = this . consume ( payloadLength )
106
+
107
+ this . #info. closeInfo = this . parseCloseBody ( false , body )
108
+
109
+ if ( ! this . ws [ kSentClose ] ) {
110
+ // If an endpoint receives a Close frame and did not previously send a
111
+ // Close frame, the endpoint MUST send a Close frame in response. (When
112
+ // sending a Close frame in response, the endpoint typically echos the
113
+ // status code it received.)
114
+ const body = Buffer . allocUnsafe ( 2 )
115
+ body . writeUInt16BE ( this . #info. closeInfo . code , 0 )
116
+ const closeFrame = new WebsocketFrameSend ( body )
117
+
118
+ this . ws [ kResponse ] . socket . write (
119
+ closeFrame . createFrame ( opcodes . CLOSE ) ,
120
+ ( err ) => {
121
+ if ( ! err ) {
122
+ this . ws [ kSentClose ] = true
123
+ }
124
+ }
125
+ )
126
+ }
105
127
106
- this . #info. closeInfo = this . parseCloseBody ( false , body )
128
+ // Upon either sending or receiving a Close control frame, it is said
129
+ // that _The WebSocket Closing Handshake is Started_ and that the
130
+ // WebSocket connection is in the CLOSING state.
131
+ this . ws [ kReadyState ] = states . CLOSING
132
+ this . ws [ kReceivedClose ] = true
107
133
108
- if ( ! this . ws [ kSentClose ] ) {
109
- // If an endpoint receives a Close frame and did not previously send a
110
- // Close frame, the endpoint MUST send a Close frame in response. (When
111
- // sending a Close frame in response, the endpoint typically echos the
112
- // status code it received.)
113
- const body = Buffer . allocUnsafe ( 2 )
114
- body . writeUInt16BE ( this . #info. closeInfo . code , 0 )
115
- const closeFrame = new WebsocketFrameSend ( body )
134
+ this . end ( )
116
135
117
- this . ws [ kResponse ] . socket . write (
118
- closeFrame . createFrame ( opcodes . CLOSE ) ,
119
- ( err ) => {
120
- if ( ! err ) {
121
- this . ws [ kSentClose ] = true
122
- }
123
- }
124
- )
125
- }
136
+ return
137
+ } else if ( this . #info. opcode === opcodes . PING ) {
138
+ // Upon receipt of a Ping frame, an endpoint MUST send a Pong frame in
139
+ // response, unless it already received a Close frame.
140
+ // A Pong frame sent in response to a Ping frame must have identical
141
+ // "Application data"
142
+
143
+ const body = this . consume ( payloadLength )
126
144
127
- // Upon either sending or receiving a Close control frame, it is said
128
- // that _The WebSocket Closing Handshake is Started_ and that the
129
- // WebSocket connection is in the CLOSING state.
130
- this . ws [ kReadyState ] = states . CLOSING
131
- this . ws [ kReceivedClose ] = true
145
+ if ( ! this . ws [ kReceivedClose ] ) {
146
+ const frame = new WebsocketFrameSend ( body )
132
147
133
- this . end ( )
148
+ this . ws [ kResponse ] . socket . write ( frame . createFrame ( opcodes . PONG ) )
134
149
135
- return
136
- } else if ( this . #info . opcode === opcodes . PING ) {
137
- // Upon receipt of a Ping frame, an endpoint MUST send a Pong frame in
138
- // response, unless it already received a Close frame.
139
- // A Pong frame sent in response to a Ping frame must have identical
140
- // "Application data"
150
+ if ( channels . ping . hasSubscribers ) {
151
+ channels . ping . publish ( {
152
+ payload : body
153
+ } )
154
+ }
155
+ }
141
156
142
- const body = this . consume ( payloadLength )
157
+ this . #state = parserStates . INFO
143
158
144
- if ( ! this . ws [ kReceivedClose ] ) {
145
- const frame = new WebsocketFrameSend ( body )
159
+ if ( this . #byteOffset > 0 ) {
160
+ continue
161
+ } else {
162
+ callback ( )
163
+ return
164
+ }
165
+ } else if ( this . #info. opcode === opcodes . PONG ) {
166
+ // A Pong frame MAY be sent unsolicited. This serves as a
167
+ // unidirectional heartbeat. A response to an unsolicited Pong frame is
168
+ // not expected.
146
169
147
- this . ws [ kResponse ] . socket . write ( frame . createFrame ( opcodes . PONG ) )
170
+ const body = this . consume ( payloadLength )
148
171
149
- if ( channels . ping . hasSubscribers ) {
150
- channels . ping . publish ( {
172
+ if ( channels . pong . hasSubscribers ) {
173
+ channels . pong . publish ( {
151
174
payload : body
152
175
} )
153
176
}
154
- }
155
-
156
- this . #state = parserStates . INFO
157
177
158
- if ( this . #byteOffset > 0 ) {
159
- return this . run ( callback )
160
- } else {
161
- callback ( )
162
- return
178
+ if ( this . #byteOffset > 0 ) {
179
+ continue
180
+ } else {
181
+ callback ( )
182
+ return
183
+ }
184
+ }
185
+ } else if ( this . #state === parserStates . PAYLOADLENGTH_16 ) {
186
+ if ( this . #byteOffset < 2 ) {
187
+ return callback ( )
163
188
}
164
- } else if ( this . #info. opcode === opcodes . PONG ) {
165
- // A Pong frame MAY be sent unsolicited. This serves as a
166
- // unidirectional heartbeat. A response to an unsolicited Pong frame is
167
- // not expected.
168
189
169
- const body = this . consume ( payloadLength )
190
+ const buffer = this . consume ( 2 )
170
191
171
- if ( channels . pong . hasSubscribers ) {
172
- channels . pong . publish ( {
173
- payload : body
174
- } )
192
+ this . #info. payloadLength = buffer . readUInt16BE ( 0 )
193
+ this . #state = parserStates . READ_DATA
194
+ } else if ( this . #state === parserStates . PAYLOADLENGTH_64 ) {
195
+ if ( this . #byteOffset < 8 ) {
196
+ return callback ( )
175
197
}
176
198
177
- if ( this . #byteOffset > 0 ) {
178
- return this . run ( callback )
179
- } else {
180
- callback ( )
199
+ const buffer = this . consume ( 8 )
200
+ const upper = buffer . readUInt32BE ( 0 )
201
+
202
+ // 2^31 is the maxinimum bytes an arraybuffer can contain
203
+ // on 32-bit systems. Although, on 64-bit systems, this is
204
+ // 2^53-1 bytes.
205
+ // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Errors/Invalid_array_length
206
+ // https://source.chromium.org/chromium/chromium/src/+/main:v8/src/common/globals.h;drc=1946212ac0100668f14eb9e2843bdd846e510a1e;bpv=1;bpt=1;l=1275
207
+ // https://source.chromium.org/chromium/chromium/src/+/main:v8/src/objects/js-array-buffer.h;l=34;drc=1946212ac0100668f14eb9e2843bdd846e510a1e
208
+ if ( upper > 2 ** 31 - 1 ) {
209
+ failWebsocketConnection ( this . ws , 'Received payload length > 2^31 bytes.' )
181
210
return
182
211
}
183
- }
184
- } else if ( this . #state === parserStates . PAYLOADLENGTH_16 ) {
185
- if ( this . #byteOffset < 2 ) {
186
- return callback ( )
187
- }
188
-
189
- const buffer = this . consume ( 2 )
190
212
191
- this . #info. payloadLength = buffer . readUInt16BE ( 0 )
192
- this . #state = parserStates . READ_DATA
193
- } else if ( this . #state === parserStates . PAYLOADLENGTH_64 ) {
194
- if ( this . #byteOffset < 8 ) {
195
- return callback ( )
196
- }
197
-
198
- const buffer = this . consume ( 8 )
199
- const upper = buffer . readUInt32BE ( 0 )
200
-
201
- // 2^31 is the maxinimum bytes an arraybuffer can contain
202
- // on 32-bit systems. Although, on 64-bit systems, this is
203
- // 2^53-1 bytes.
204
- // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Errors/Invalid_array_length
205
- // https://source.chromium.org/chromium/chromium/src/+/main:v8/src/common/globals.h;drc=1946212ac0100668f14eb9e2843bdd846e510a1e;bpv=1;bpt=1;l=1275
206
- // https://source.chromium.org/chromium/chromium/src/+/main:v8/src/objects/js-array-buffer.h;l=34;drc=1946212ac0100668f14eb9e2843bdd846e510a1e
207
- if ( upper > 2 ** 31 - 1 ) {
208
- failWebsocketConnection ( this . ws , 'Received payload length > 2^31 bytes.' )
209
- return
210
- }
213
+ const lower = buffer . readUInt32BE ( 4 )
211
214
212
- const lower = buffer . readUInt32BE ( 4 )
215
+ this . #info. payloadLength = ( upper << 8 ) + lower
216
+ this . #state = parserStates . READ_DATA
217
+ } else if ( this . #state === parserStates . READ_DATA ) {
218
+ if ( this . #byteOffset < this . #info. payloadLength ) {
219
+ // If there is still more data in this chunk that needs to be read
220
+ return callback ( )
221
+ } else if ( this . #byteOffset >= this . #info. payloadLength ) {
222
+ // If the server sent multiple frames in a single chunk
213
223
214
- this . #info. payloadLength = ( upper << 8 ) + lower
215
- this . #state = parserStates . READ_DATA
216
- } else if ( this . #state === parserStates . READ_DATA ) {
217
- if ( this . #byteOffset < this . #info. payloadLength ) {
218
- // If there is still more data in this chunk that needs to be read
219
- return callback ( )
220
- } else if ( this . #byteOffset >= this . #info. payloadLength ) {
221
- // If the server sent multiple frames in a single chunk
224
+ const body = this . consume ( this . #info. payloadLength )
222
225
223
- const body = this . consume ( this . #info . payloadLength )
226
+ this . #fragments . push ( body )
224
227
225
- this . #fragments. push ( body )
228
+ // If the frame is unfragmented, or a fragmented frame was terminated,
229
+ // a message was received
230
+ if ( ! this . #info. fragmented || ( this . #info. fin && this . #info. opcode === opcodes . CONTINUATION ) ) {
231
+ const fullMessage = Buffer . concat ( this . #fragments)
226
232
227
- // If the frame is unfragmented, or a fragmented frame was terminated,
228
- // a message was received
229
- if ( ! this . #info. fragmented || ( this . #info. fin && this . #info. opcode === opcodes . CONTINUATION ) ) {
230
- const fullMessage = Buffer . concat ( this . #fragments)
233
+ websocketMessageReceived ( this . ws , this . #info. originalOpcode , fullMessage )
231
234
232
- websocketMessageReceived ( this . ws , this . #info. originalOpcode , fullMessage )
235
+ this . #info = { }
236
+ this . #fragments. length = 0
237
+ }
233
238
234
- this . #info = { }
235
- this . #fragments. length = 0
239
+ this . #state = parserStates . INFO
236
240
}
237
-
238
- this . #state = parserStates . INFO
239
241
}
240
- }
241
242
242
- if ( this . #byteOffset > 0 ) {
243
- return this . run ( callback )
244
- } else {
245
- callback ( )
243
+ if ( this . #byteOffset > 0 ) {
244
+ continue
245
+ } else {
246
+ callback ( )
247
+ break
248
+ }
246
249
}
247
250
}
248
251
0 commit comments