Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 28 additions & 5 deletions lib/web/websocket/frame.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,31 @@ function generateMask () {
return [buffer[bufIdx++], buffer[bufIdx++], buffer[bufIdx++], buffer[bufIdx++]]
}

function maskPayload (source, target, targetOffset, maskKey) {
const length = source.byteLength
const mask0 = maskKey[0]
const mask1 = maskKey[1]
const mask2 = maskKey[2]
const mask3 = maskKey[3]
const unrolledEnd = length & ~7

let i = 0
for (; i < unrolledEnd; i += 8) {
target[targetOffset + i] = source[i] ^ mask0
target[targetOffset + i + 1] = source[i + 1] ^ mask1
target[targetOffset + i + 2] = source[i + 2] ^ mask2
target[targetOffset + i + 3] = source[i + 3] ^ mask3
target[targetOffset + i + 4] = source[i + 4] ^ mask0
target[targetOffset + i + 5] = source[i + 5] ^ mask1
target[targetOffset + i + 6] = source[i + 6] ^ mask2
target[targetOffset + i + 7] = source[i + 7] ^ mask3
}

for (; i < length; ++i) {
target[targetOffset + i] = source[i] ^ maskKey[i & 3]
}
}

class WebsocketFrameSend {
/**
* @param {Buffer|undefined} data
Expand Down Expand Up @@ -71,8 +96,8 @@ class WebsocketFrameSend {
buffer[1] |= 0x80 // MASK

// mask body
for (let i = 0; i < bodyLength; ++i) {
buffer[offset + i] = frameData[i] ^ maskKey[i & 3]
if (bodyLength !== 0) {
maskPayload(frameData, buffer, offset, maskKey)
}

return buffer
Expand All @@ -87,9 +112,7 @@ class WebsocketFrameSend {
const bodyLength = buffer.length

// mask body
for (let i = 0; i < bodyLength; ++i) {
buffer[i] ^= maskKey[i & 3]
}
maskPayload(buffer, buffer, 0, maskKey)

let payloadLength = bodyLength
let offset = 6
Expand Down
26 changes: 26 additions & 0 deletions test/websocket/frame.js
Original file line number Diff line number Diff line change
Expand Up @@ -37,3 +37,29 @@ test('Writing 16-bit frame length value at correct offset when buffer has a non-
t.assert.strictEqual(frame[3], payloadLength & 0xff)
t.assert.strictEqual(smallBuffer.length, 1) // ensure smallBuffer can't be garbage-collected too soon
})

test('Masks aligned and unaligned payloads without changing frame semantics', (t) => {
for (const length of [0, 1, 3, 4, 5, 7, 8, 9, 125, 126, 127, 65535, 65536]) {
const payload = Buffer.alloc(length)
for (let i = 0; i < payload.length; ++i) payload[i] = i & 0xff
const originalPayload = Buffer.from(payload)
const frame = new WebsocketFrameSend(payload).createFrame(opcodes.BINARY)
const offset = length > 65535 ? 14 : length > 125 ? 8 : 6
const mask = frame.subarray(offset - 4, offset)
const decoded = Buffer.alloc(length)
for (let i = 0; i < length; ++i) decoded[i] = frame[offset + i] ^ mask[i & 3]
t.assert.deepStrictEqual(decoded, originalPayload)
t.assert.deepStrictEqual(payload, originalPayload)
}

const text = Buffer.from('héllo websocket 🌍'.repeat(2))
const backing = Buffer.alloc(text.length + 1)
text.copy(backing, 1)
const view = backing.subarray(1)
const original = Buffer.from(view)
const [head, body] = WebsocketFrameSend.createFastTextFrame(view)
const mask = head.subarray(head.length - 4)
const decoded = Buffer.alloc(body.length)
for (let i = 0; i < body.length; ++i) decoded[i] = body[i] ^ mask[i & 3]
t.assert.deepStrictEqual(decoded, original)
})
Loading