UNPKG

quic

Version:

A QUIC server/client implementation in Node.js.

370 lines 14.8 kB
'use strict'; Object.defineProperty(exports, "__esModule", { value: true }); // **Github:** https://github.com/fidm/quic // // **License:** MIT const util_1 = require("util"); const error_1 = require("./error"); const common_1 = require("./common"); const frame_1 = require("./frame"); const protocol_1 = require("./protocol"); // --- QUIC Public Packet Header // // 0 1 2 3 4 8 // +--------+--------+--------+--------+--------+--- ---+ // | Public | Connection ID (64) ... | -> // |Flags(8)| (optional) | // +--------+--------+--------+--------+--------+--- ---+ // // 9 10 11 12 // +--------+--------+--------+--------+ // | QUIC Version (32) | -> // | (optional) | // +--------+--------+--------+--------+ // // 13 14 15 16 17 18 19 20 // +--------+--------+--------+--------+--------+--------+--------+--------+ // | Diversification Nonce | -> // | (optional) | // +--------+--------+--------+--------+--------+--------+--------+--------+ // // 21 22 23 24 25 26 27 28 // +--------+--------+--------+--------+--------+--------+--------+--------+ // | Diversification Nonce Continued | -> // | (optional) | // +--------+--------+--------+--------+--------+--------+--------+--------+ // // 29 30 31 32 33 34 35 36 // +--------+--------+--------+--------+--------+--------+--------+--------+ // | Diversification Nonce Continued | -> // | (optional) | // +--------+--------+--------+--------+--------+--------+--------+--------+ // // 37 38 39 40 41 42 43 44 // +--------+--------+--------+--------+--------+--------+--------+--------+ // | Diversification Nonce Continued | -> // | (optional) | // +--------+--------+--------+--------+--------+--------+--------+--------+ // // 45 46 47 48 49 50 // +--------+--------+--------+--------+--------+--------+ // | Packet Number (8, 16, 32, or 48) | // | (variable length) | // +--------+--------+--------+--------+--------+--------+ // --- // // Public Flags: // 0x01 = PUBLIC_FLAG_VERSION. Interpretation of this flag depends on whether the packet // is sent by the server or the client. When sent by the client, setting it indicates that // the header contains a QUIC Version (see below)... // 0x02 = PUBLIC_FLAG_RESET. Set to indicate that the packet is a Public Reset packet. // 0x04 = Indicates the presence of a 32 byte diversification nonce in the header. // 0x08 = Indicates the full 8 byte Connection ID is present in the packet. // Two bits at 0x30 indicate the number of low-order-bytes of the packet number that // are present in each packet. The bits are only used for Frame Packets. For Public Reset // and Version Negotiation Packets (sent by the server) which don't have a packet number, // these bits are not used and must be set to 0. Within this 2 bit mask: // 0x30 indicates that 6 bytes of the packet number is present // 0x20 indicates that 4 bytes of the packet number is present // 0x10 indicates that 2 bytes of the packet number is present // 0x00 indicates that 1 byte of the packet number is present // 0x40 is reserved for multipath use. // 0x80 is currently unused, and must be set to 0. function parsePacket(bufv, packetSentBy) { bufv.walk(0); // align start and end const flag = bufv.buf.readUInt8(bufv.start); // 0x80, currently unused if (flag >= 127) { throw new error_1.QuicError('QUIC_INTERNAL_ERROR'); } // 0x08, connectionID if ((flag & 0b1000) === 0) { throw new error_1.QuicError('QUIC_INTERNAL_ERROR'); } if ((flag & 0b10) > 0) { // Reset Packet return ResetPacket.fromBuffer(bufv); } const hasVersion = (flag & 0b1) > 0; if (hasVersion && packetSentBy === protocol_1.SessionType.SERVER) { // Negotiation Packet return NegotiationPacket.fromBuffer(bufv); } return RegularPacket.fromBuffer(bufv, flag, packetSentBy); } exports.parsePacket = parsePacket; /** Packet representing a base Packet. */ class Packet { static fromBuffer(_bufv, _flag, _packetSentBy) { throw new Error(`class method "fromBuffer" is not implemented`); } constructor(connectionID, flag) { this.flag = flag; this.connectionID = connectionID; this.sentTime = 0; // timestamp, ms } isReset() { return this instanceof ResetPacket; } isNegotiation() { return this instanceof NegotiationPacket; } isRegular() { return this instanceof RegularPacket; } valueOf() { return { connectionID: this.connectionID.valueOf(), flag: this.flag, }; } toString() { return JSON.stringify(this.valueOf()); } [util_1.inspect.custom](_depth, _options) { return `<${this.constructor.name} ${this.toString()}>`; } } exports.Packet = Packet; /** ResetPacket representing a reset Packet. */ class ResetPacket extends Packet { // --- Public Reset Packet // 0 1 2 3 4 8 // +--------+--------+--------+--------+--------+-- --+ // | Public | Connection ID (64) ... | -> // |Flags(8)| | // +--------+--------+--------+--------+--------+-- --+ // // 9 10 11 12 13 14 // +--------+--------+--------+--------+--------+--------+--- // | Quic Tag (32) | Tag value map ... -> // | (PRST) | (variable length) // +--------+--------+--------+--------+--------+--------+--- static fromBuffer(bufv) { bufv.walk(1); // flag const connectionID = protocol_1.ConnectionID.fromBuffer(bufv); const quicTag = protocol_1.QuicTags.fromBuffer(bufv); if (quicTag.name !== protocol_1.Tag.PRST || !quicTag.has(protocol_1.Tag.RNON)) { throw new error_1.QuicError('QUIC_INVALID_PUBLIC_RST_PACKET'); } return new ResetPacket(connectionID, quicTag); } constructor(connectionID, tags) { super(connectionID, 0b00001010); this.tags = tags; this.packetNumber = null; this.socketAddress = null; const nonceProof = tags.get(protocol_1.Tag.RNON); if (nonceProof == null) { throw new error_1.QuicError('QUIC_INVALID_PUBLIC_RST_PACKET'); } this.nonceProof = nonceProof; const rseq = tags.get(protocol_1.Tag.RSEQ); if (rseq != null) { this.packetNumber = protocol_1.PacketNumber.fromBuffer(new common_1.BufferVisitor(rseq), rseq.length); } const cadr = tags.get(protocol_1.Tag.CADR); if (cadr != null) { this.socketAddress = protocol_1.SocketAddress.fromBuffer(new common_1.BufferVisitor(cadr)); } } valueOf() { return { connectionID: this.connectionID.valueOf(), flag: this.flag, packetNumber: this.packetNumber == null ? null : this.packetNumber.valueOf(), socketAddress: this.socketAddress == null ? null : this.socketAddress.valueOf(), nonceProof: this.nonceProof, }; } byteLen() { return 9 + this.tags.byteLen(); } writeTo(bufv) { bufv.walk(1); bufv.buf.writeUInt8(this.flag, bufv.start); this.connectionID.writeTo(bufv); this.tags.writeTo(bufv); return bufv; } } exports.ResetPacket = ResetPacket; /** NegotiationPacket representing a negotiation Packet. */ class NegotiationPacket extends Packet { // --- Version Negotiation Packet // 0 1 2 3 4 5 6 7 8 // +--------+--------+--------+--------+--------+--------+--------+--------+--------+ // | Public | Connection ID (64) | -> // |Flags(8)| | // +--------+--------+--------+--------+--------+--------+--------+--------+--------+ // // 9 10 11 12 13 14 15 16 17 // +--------+--------+--------+--------+--------+--------+--------+--------+---...--+ // | 1st QUIC version supported | 2nd QUIC version supported | ... // | by server (32) | by server (32) | // +--------+--------+--------+--------+--------+--------+--------+--------+---...--+ static fromConnectionID(connectionID) { return new NegotiationPacket(connectionID, protocol_1.getVersions()); } static fromBuffer(bufv) { bufv.walk(1); // flag const connectionID = protocol_1.ConnectionID.fromBuffer(bufv); const versions = []; while (bufv.length > bufv.end) { bufv.walk(4); const version = bufv.buf.toString('utf8', bufv.start, bufv.end); if (!protocol_1.isSupportedVersion(version)) { throw new error_1.QuicError('QUIC_INVALID_VERSION'); } versions.push(version); } return new NegotiationPacket(connectionID, versions); } constructor(connectionID, versions) { super(connectionID, 0b00001001); this.versions = versions; } valueOf() { return { connectionID: this.connectionID.valueOf(), flag: this.flag, versions: this.versions, }; } byteLen() { return 9 + 4 * this.versions.length; } writeTo(bufv) { bufv.walk(1); bufv.buf.writeUInt8(this.flag, bufv.start); this.connectionID.writeTo(bufv); for (const version of this.versions) { bufv.walk(4); bufv.buf.write(version, bufv.start, 4); } return bufv; } } exports.NegotiationPacket = NegotiationPacket; /** RegularPacket representing a regular Packet. */ class RegularPacket extends Packet { // --- Frame Packet // +--------+---...---+--------+---...---+ // | Type | Payload | Type | Payload | // +--------+---...---+--------+---...---+ static fromBuffer(bufv, flag, packetSentBy) { bufv.walk(1); // flag const connectionID = protocol_1.ConnectionID.fromBuffer(bufv); let version = ''; const hasVersion = (flag & 0b1) > 0; if (hasVersion) { bufv.walk(4); version = bufv.buf.toString('utf8', bufv.start, bufv.end); if (!protocol_1.isSupportedVersion(version)) { throw new error_1.QuicError('QUIC_INVALID_VERSION'); } } // Contrary to what the gQUIC wire spec says, the 0x4 bit only indicates the presence of // the diversification nonce for packets sent by the server. // It doesn't have any meaning when sent by the client. let nonce = null; if (packetSentBy === protocol_1.SessionType.SERVER && (flag & 0b100) > 0) { bufv.walk(32); nonce = bufv.buf.slice(bufv.start, bufv.end); if (nonce.length !== 32) { throw new error_1.QuicError('QUIC_INTERNAL_ERROR'); } } const packetNumber = protocol_1.PacketNumber.fromBuffer(bufv, protocol_1.PacketNumber.flagToByteLen((flag & 0b110000) >> 4)); const packet = new RegularPacket(connectionID, packetNumber, nonce); if (version !== '') { packet.setVersion(version); } return packet; } constructor(connectionID, packetNumber, nonce = null) { let flag = 0b00001000; flag |= (packetNumber.flagBits() << 4); if (nonce != null) { flag |= 0x04; } super(connectionID, flag); this.packetNumber = packetNumber; this.version = ''; // 4 byte, string this.nonce = nonce; // 32 byte, buffer this.frames = []; this.isRetransmittable = true; } valueOf() { return { connectionID: this.connectionID.valueOf(), flag: this.flag, version: this.version, packetNumber: this.packetNumber.valueOf(), nonce: this.nonce, frames: this.frames.map((val) => val.valueOf()), }; } setVersion(version) { this.flag |= 0b00000001; this.version = version; } setPacketNumber(packetNumber) { this.packetNumber = packetNumber; this.flag &= 0b11001111; this.flag |= (packetNumber.flagBits() << 4); } needAck() { for (const frame of this.frames) { if (frame.isRetransmittable()) { return true; } } return false; } parseFrames(bufv) { while (bufv.end < bufv.length) { this.addFrames(frame_1.parseFrame(bufv, this.packetNumber)); } } addFrames(...frames) { this.frames.push(...frames); return this; } headerLen() { let len = 9; if (this.version !== '') { len += 4; } if (this.nonce != null) { len += 32; } len += this.packetNumber.byteLen(); return len; } byteLen() { let len = this.headerLen(); for (const frame of this.frames) { len += frame.byteLen(); } return len; } writeTo(bufv) { bufv.walk(1); bufv.buf.writeUInt8(this.flag, bufv.start); this.connectionID.writeTo(bufv); if (this.version !== '') { bufv.walk(4); bufv.buf.write(this.version, bufv.start, 4); } if (this.nonce != null) { bufv.walk(32); this.nonce.copy(bufv.buf, bufv.start, 0, 32); } this.packetNumber.writeTo(bufv); for (const frame of this.frames) { frame.writeTo(bufv); } return bufv; } } exports.RegularPacket = RegularPacket; //# sourceMappingURL=packet.js.map