UNPKG

walve

Version:

stream based WebSocket implementation

146 lines (115 loc) 3.76 kB
const { Transform } = require('readable-stream') const FIN = 0x80 const RSV1 = 0x40 const RSV2 = 0x20 const RSV3 = 0x10 const RSV_ALL = RSV1 | RSV2 | RSV3 const OPCODE = 0x0f const MASKED = 0x80 const LENGTH = 0x7f const LENGTH_EXT1 = 0x7e const LENGTH_EXT2 = 0x7f class Incoming extends Transform { constructor(options) { super(options) this.header = null this.inHeader = true this.needsHeadBase = true this.needsHeadExtended = false this.rawHead = [] // TODO: move this.headLength to headBytesRead this.totalBytesRead = 0 this.payloadBytesRead = 0 this.payloadBytesToRead = 0 } _cache(chunk) { this.rawHead.push(chunk) return Buffer.concat(this.rawHead) } _transform(chunk, encoding, done) { var header = this.header = this.header || {} if (this.inHeader) { chunk = this._cache(chunk) // buffer until we have two bytes to parse // and to calculated total head byte length if (chunk.length >= 2 && this.needsHeadBase) { if (RSV_ALL & chunk[0]) { this.emit('error', new Error('RSV flag should not be set.')) return } header.final = !!(FIN & chunk[0]) header.masked = !!(MASKED & chunk[1]) header.opcode = OPCODE & chunk[0] this.length = LENGTH & chunk[1] this.headLength = 2 if (header.masked) { this.headLength += 4 } if (this.length === LENGTH_EXT1) { this.headLength += 2 } if (this.length === LENGTH_EXT2) { this.headLength += 8 } this.totalBytesRead += 2 this.needsHeadBase = false this.needsHeadExtended = true } // buffer until we have all required head bytes and parse if (chunk.length >= this.headLength && this.needsHeadExtended) { header.length = this.length if (header.masked) { header.masking = chunk.slice(this.headLength - 4) } switch (this.length) { case LENGTH_EXT1: header.length = chunk.readUInt16BE(2, 2) break case LENGTH_EXT2: if (chunk.readUInt32BE(2, 4)) { this.emit('error', new Error('Payload length is too big.')) return } header.length = chunk.readUInt32BE(6, 4) break } this.totalBytesRead += chunk.length this.payloadBytesToRead += header.length this.inHeader = false this.needsHeadExtended = false this.emit('header', header) } // if we are finished with head bytes slice payload if in head length // else set chunk to null to avoid being consumed by stream if (!this.needsHeadBase && !this.needsHeadExtended) { chunk = chunk.slice(this.headLength) } } if (!this.inHeader) { // if we have more chunk then we need to read we probably got two frames // glued to each other. In this case we will flush them away. if (chunk.length > this.payloadBytesToRead) { this.emit('flush', chunk.slice(this.payloadBytesToRead)) chunk = chunk.slice(0, this.payloadBytesToRead) } // unmask the payload if (header.masked) { for (var i = 0; i < chunk.length; i++) { chunk[i] = chunk[i] ^ header.masking[(i + this.payloadBytesRead) % 4] } } this.push(chunk) // update all counters this.totalBytesRead += chunk.length this.payloadBytesRead += chunk.length this.payloadBytesToRead -= chunk.length if (!this.payloadBytesToRead) { this.push(null) } } done() } } module.exports = Incoming