codekingpro/portable-devtools
114k
1'use strict'
2
3const { maxUnsigned16Bit } = require('./constants')
4
5const BUFFER_SIZE = 16386
6
7/** @type {import('crypto')} */
8let crypto
9let buffer = null
10let bufIdx = BUFFER_SIZE
11
12try {
13 crypto = require('node:crypto')
14/* c8 ignore next 3 */
15} catch {
16 crypto = {
17 // not full compatibility, but minimum.
18 randomFillSync: function randomFillSync (buffer, _offset, _size) {
19 for (let i = 0; i < buffer.length; ++i) {
20 buffer[i] = Math.random() * 255 | 0
21 }
22 return buffer
23 }
24 }
25}
26
27function generateMask () {
28 if (bufIdx === BUFFER_SIZE) {
29 bufIdx = 0
30 crypto.randomFillSync((buffer ??= Buffer.allocUnsafe(BUFFER_SIZE)), 0, BUFFER_SIZE)
31 }
32 return [buffer[bufIdx++], buffer[bufIdx++], buffer[bufIdx++], buffer[bufIdx++]]
33}
34
35class WebsocketFrameSend {
36 /**
37 * @param {Buffer|undefined} data
38 */
39 constructor (data) {
40 this.frameData = data
41 }
42
43 createFrame (opcode) {
44 const frameData = this.frameData
45 const maskKey = generateMask()
46 const bodyLength = frameData?.byteLength ?? 0
47
48 /** @type {number} */
49 let payloadLength = bodyLength // 0-125
50 let offset = 6
51
52 if (bodyLength > maxUnsigned16Bit) {
53 offset += 8 // payload length is next 8 bytes
54 payloadLength = 127
55 } else if (bodyLength > 125) {
56 offset += 2 // payload length is next 2 bytes
57 payloadLength = 126
58 }
59
60 const buffer = Buffer.allocUnsafe(bodyLength + offset)
61
62 // Clear first 2 bytes, everything else is overwritten
63 buffer[0] = buffer[1] = 0
64 buffer[0] |= 0x80 // FIN
65 buffer[0] = (buffer[0] & 0xF0) + opcode // opcode
66
67 /*! ws. MIT License. Einar Otto Stangvik <einaros@gmail.com> */
68 buffer[offset - 4] = maskKey[0]
69 buffer[offset - 3] = maskKey[1]
70 buffer[offset - 2] = maskKey[2]
71 buffer[offset - 1] = maskKey[3]
72
73 buffer[1] = payloadLength
74
75 if (payloadLength === 126) {
76 buffer.writeUInt16BE(bodyLength, 2)
77 } else if (payloadLength === 127) {
78 // Clear extended payload length
79 buffer[2] = buffer[3] = 0
80 buffer.writeUIntBE(bodyLength, 4, 6)
81 }
82
83 buffer[1] |= 0x80 // MASK
84
85 // mask body
86 for (let i = 0; i < bodyLength; ++i) {
87 buffer[offset + i] = frameData[i] ^ maskKey[i & 3]
88 }
89
90 return buffer
91 }
92}
93
94module.exports = {
95 WebsocketFrameSend
96}
97 