|
| 1 | +import {StreamingOctetReader} from '@jsonjoy.com/util/lib/buffers/StreamingOctetReader'; |
| 2 | +import {WsFrameOpcode} from './constants'; |
| 3 | +import {WsFrameDecodingError} from './errors'; |
| 4 | +import {WsCloseFrame, WsFrameHeader, WsPingFrame, WsPongFrame} from './frames'; |
| 5 | + |
| 6 | +export class WsFrameDecoder { |
| 7 | + public readonly reader = new StreamingOctetReader(); |
| 8 | + |
| 9 | + public push(uint8: Uint8Array): void { |
| 10 | + this.reader.push(uint8); |
| 11 | + } |
| 12 | + |
| 13 | + public readFrameHeader(): WsFrameHeader | undefined { |
| 14 | + try { |
| 15 | + const reader = this.reader; |
| 16 | + if (reader.size() < 2) return undefined; |
| 17 | + const b0 = reader.u8(); |
| 18 | + const b1 = reader.u8(); |
| 19 | + const fin = <0 | 1>(b0 >>> 7); |
| 20 | + const opcode = b0 & 0b1111; |
| 21 | + const maskBit = b1 >>> 7; |
| 22 | + let length = b1 & 0b01111111; |
| 23 | + if (length === 126) { |
| 24 | + if (reader.size() < 2) return undefined; |
| 25 | + length = (reader.u8() << 8) | reader.u8(); |
| 26 | + } else if (length === 127) { |
| 27 | + if (reader.size() < 8) return undefined; |
| 28 | + reader.skip(4); |
| 29 | + length = reader.u32(); |
| 30 | + } |
| 31 | + let mask: undefined | [number, number, number, number]; |
| 32 | + if (maskBit) { |
| 33 | + if (reader.size() < 4) return undefined; |
| 34 | + mask = [reader.u8(), reader.u8(), reader.u8(), reader.u8()]; |
| 35 | + } |
| 36 | + if (opcode >= WsFrameOpcode.MIN_CONTROL_OPCODE) { |
| 37 | + switch (opcode) { |
| 38 | + case WsFrameOpcode.CLOSE: { |
| 39 | + return new WsCloseFrame(fin, opcode, length, mask, 0, ''); |
| 40 | + } |
| 41 | + case WsFrameOpcode.PING: { |
| 42 | + if (length > 125) throw new WsFrameDecodingError(); |
| 43 | + const data = mask ? reader.bufXor(length, mask, 0) : reader.buf(length); |
| 44 | + return new WsPingFrame(fin, opcode, length, mask, data); |
| 45 | + } |
| 46 | + case WsFrameOpcode.PONG: { |
| 47 | + if (length > 125) throw new WsFrameDecodingError(); |
| 48 | + const data = mask ? reader.bufXor(length, mask, 0) : reader.buf(length); |
| 49 | + return new WsPongFrame(fin, opcode, length, mask, data); |
| 50 | + } |
| 51 | + default: { |
| 52 | + throw new WsFrameDecodingError(); |
| 53 | + } |
| 54 | + } |
| 55 | + } |
| 56 | + return new WsFrameHeader(fin, opcode, length, mask); |
| 57 | + } catch (err) { |
| 58 | + if (err instanceof RangeError) return undefined; |
| 59 | + throw err; |
| 60 | + } |
| 61 | + } |
| 62 | + |
| 63 | + /** |
| 64 | + * Read application data of a frame and copy it to the destination buffer. |
| 65 | + * Receives the frame header and the number of bytes that still need to be |
| 66 | + * copied, returns back the number of bytes that still need to be copied in |
| 67 | + * subsequent calls. |
| 68 | + * |
| 69 | + * @param frame Frame header. |
| 70 | + * @param remaining How many bytes are remaining to be copied. |
| 71 | + * @param dst The destination buffer to write to. |
| 72 | + * @param pos Position in the destination buffer to start writing to. |
| 73 | + * @returns The number of bytes that still need to be copied in the next call. |
| 74 | + */ |
| 75 | + public readFrameData(frame: WsFrameHeader, remaining: number, dst: Uint8Array, pos: number): number { |
| 76 | + const reader = this.reader; |
| 77 | + const mask = frame.mask; |
| 78 | + const readSize = Math.min(reader.size(), remaining); |
| 79 | + if (!mask) reader.copy(readSize, dst, pos); |
| 80 | + else { |
| 81 | + const alreadyRead = frame.length - remaining; |
| 82 | + reader.copyXor(readSize, dst, pos, mask, alreadyRead); |
| 83 | + } |
| 84 | + return remaining - readSize; |
| 85 | + } |
| 86 | + |
| 87 | + public copyFrameData(frame: WsFrameHeader, dst: Uint8Array, pos: number): void { |
| 88 | + const reader = this.reader; |
| 89 | + const mask = frame.mask; |
| 90 | + const readSize = frame.length; |
| 91 | + if (!mask) reader.copy(readSize, dst, pos); |
| 92 | + else reader.copyXor(readSize, dst, pos, mask, 0); |
| 93 | + } |
| 94 | + |
| 95 | + /** |
| 96 | + * Reads application data of the CLOSE frame and sets the code and reason |
| 97 | + * properties of the frame. |
| 98 | + * |
| 99 | + * @param frame Close frame. |
| 100 | + */ |
| 101 | + public readCloseFrameData(frame: WsCloseFrame): void { |
| 102 | + let length = frame.length; |
| 103 | + if (length > 125) throw new WsFrameDecodingError(); |
| 104 | + let code = 0; |
| 105 | + let reason = ''; |
| 106 | + if (length > 0) { |
| 107 | + if (length < 2) throw new WsFrameDecodingError(); |
| 108 | + const reader = this.reader; |
| 109 | + const mask = frame.mask; |
| 110 | + const octet1 = reader.u8() ^ (mask ? mask[0] : 0); |
| 111 | + const octet2 = reader.u8() ^ (mask ? mask[1] : 0); |
| 112 | + code = (octet1 << 8) | octet2; |
| 113 | + length -= 2; |
| 114 | + if (length) reason = reader.utf8(length, mask ?? [0, 0, 0, 0], 2); |
| 115 | + } |
| 116 | + frame.code = code; |
| 117 | + frame.reason = reason; |
| 118 | + } |
| 119 | +} |
0 commit comments