Skip to content

Commit c2933ef

Browse files
authored
websocket: improve frame parsing (nodejs#3447)
1 parent 8044a43 commit c2933ef

File tree

2 files changed

+62
-28
lines changed

2 files changed

+62
-28
lines changed

lib/web/websocket/receiver.js

Lines changed: 61 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ const { PerMessageDeflate } = require('./permessage-deflate')
2424

2525
class ByteParser extends Writable {
2626
#buffers = []
27+
#fragmentsBytes = 0
2728
#byteOffset = 0
2829
#loop = false
2930

@@ -208,16 +209,14 @@ class ByteParser extends Writable {
208209
this.#state = parserStates.INFO
209210
} else {
210211
if (!this.#info.compressed) {
211-
this.#fragments.push(body)
212+
this.writeFragments(body)
212213

213214
// If the frame is not fragmented, a message has been received.
214215
// If the frame is fragmented, it will terminate with a fin bit set
215216
// and an opcode of 0 (continuation), therefore we handle that when
216217
// parsing continuation frames, not here.
217218
if (!this.#info.fragmented && this.#info.fin) {
218-
const fullMessage = Buffer.concat(this.#fragments)
219-
websocketMessageReceived(this.#handler, this.#info.binaryType, fullMessage)
220-
this.#fragments.length = 0
219+
websocketMessageReceived(this.#handler, this.#info.binaryType, this.consumeFragments())
221220
}
222221

223222
this.#state = parserStates.INFO
@@ -228,7 +227,7 @@ class ByteParser extends Writable {
228227
return
229228
}
230229

231-
this.#fragments.push(data)
230+
this.writeFragments(data)
232231

233232
if (!this.#info.fin) {
234233
this.#state = parserStates.INFO
@@ -237,11 +236,10 @@ class ByteParser extends Writable {
237236
return
238237
}
239238

240-
websocketMessageReceived(this.#handler, this.#info.binaryType, Buffer.concat(this.#fragments))
239+
websocketMessageReceived(this.#handler, this.#info.binaryType, this.consumeFragments())
241240

242241
this.#loop = true
243242
this.#state = parserStates.INFO
244-
this.#fragments.length = 0
245243
this.run(callback)
246244
})
247245

@@ -265,34 +263,70 @@ class ByteParser extends Writable {
265263
return emptyBuffer
266264
}
267265

268-
if (this.#buffers[0].length === n) {
269-
this.#byteOffset -= this.#buffers[0].length
266+
this.#byteOffset -= n
267+
268+
const first = this.#buffers[0]
269+
270+
if (first.length > n) {
271+
// replace with remaining buffer
272+
this.#buffers[0] = first.subarray(n, first.length)
273+
return first.subarray(0, n)
274+
} else if (first.length === n) {
275+
// prefect match
270276
return this.#buffers.shift()
277+
} else {
278+
let offset = 0
279+
// If Buffer.allocUnsafe is used, extra copies will be made because the offset is non-zero.
280+
const buffer = Buffer.allocUnsafeSlow(n)
281+
while (offset !== n) {
282+
const next = this.#buffers[0]
283+
const length = next.length
284+
285+
if (length + offset === n) {
286+
buffer.set(this.#buffers.shift(), offset)
287+
break
288+
} else if (length + offset > n) {
289+
buffer.set(next.subarray(0, n - offset), offset)
290+
this.#buffers[0] = next.subarray(n - offset)
291+
break
292+
} else {
293+
buffer.set(this.#buffers.shift(), offset)
294+
offset += length
295+
}
296+
}
297+
298+
return buffer
299+
}
300+
}
301+
302+
writeFragments (fragment) {
303+
this.#fragmentsBytes += fragment.length
304+
this.#fragments.push(fragment)
305+
}
306+
307+
consumeFragments () {
308+
const fragments = this.#fragments
309+
310+
if (fragments.length === 1) {
311+
// single fragment
312+
this.#fragmentsBytes = 0
313+
return fragments.shift()
271314
}
272315

273-
const buffer = Buffer.allocUnsafe(n)
274316
let offset = 0
317+
// If Buffer.allocUnsafe is used, extra copies will be made because the offset is non-zero.
318+
const output = Buffer.allocUnsafeSlow(this.#fragmentsBytes)
275319

276-
while (offset !== n) {
277-
const next = this.#buffers[0]
278-
const { length } = next
279-
280-
if (length + offset === n) {
281-
buffer.set(this.#buffers.shift(), offset)
282-
break
283-
} else if (length + offset > n) {
284-
buffer.set(next.subarray(0, n - offset), offset)
285-
this.#buffers[0] = next.subarray(n - offset)
286-
break
287-
} else {
288-
buffer.set(this.#buffers.shift(), offset)
289-
offset += next.length
290-
}
320+
for (let i = 0; i < fragments.length; ++i) {
321+
const buffer = fragments[i]
322+
output.set(buffer, offset)
323+
offset += buffer.length
291324
}
292325

293-
this.#byteOffset -= n
326+
this.#fragments = []
327+
this.#fragmentsBytes = 0
294328

295-
return buffer
329+
return output
296330
}
297331

298332
parseCloseBody (data) {

lib/web/websocket/util.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@ function toArrayBuffer (buffer) {
8787
if (buffer.byteLength === buffer.buffer.byteLength) {
8888
return buffer.buffer
8989
}
90-
return buffer.buffer.slice(buffer.byteOffset, buffer.byteOffset + buffer.byteLength)
90+
return new Uint8Array(buffer).buffer
9191
}
9292

9393
/**

0 commit comments

Comments
 (0)