UNPKG

quico

Version:

A pure JavaScript implementation of QUIC, HTTP/3, QPACK, and WebTransport for Node.js

404 lines (327 loc) 9.7 kB
function writeVarInt(value) { if (value < 0x40) { // 1 byte, prefix 00 return new Uint8Array([value]); // אין צורך ב־& 0x3f } if (value < 0x4000) { // 2 bytes, prefix 01 return new Uint8Array([ 0x40 | (value >> 8), value & 0xff ]); } if (value < 0x40000000) { // 4 bytes, prefix 10 return new Uint8Array([ 0x80 | (value >> 24), (value >> 16) & 0xff, (value >> 8) & 0xff, value & 0xff ]); } if (value <= Number.MAX_SAFE_INTEGER) { const hi = Math.floor(value / 2 ** 32); const lo = value >>> 0; return new Uint8Array([ 0xC0 | (hi >> 24), (hi >> 16) & 0xff, (hi >> 8) & 0xff, hi & 0xff, (lo >> 24) & 0xff, (lo >> 16) & 0xff, (lo >> 8) & 0xff, lo & 0xff ]); } throw new Error("Value too large for QUIC VarInt"); } function writeVarInt2(value) { if (value < 0x40) { // 1 byte return new Uint8Array([value & 0x3f]); } if (value < 0x4000) { // 2 bytes return new Uint8Array([ 0x40 | ((value >> 8) & 0x3f), value & 0xff ]); } if (value < 0x40000000) { // 4 bytes return new Uint8Array([ 0x80 | ((value >> 24) & 0x3f), (value >> 16) & 0xff, (value >> 8) & 0xff, value & 0xff ]); } if (value <= Number.MAX_SAFE_INTEGER) { var hi = Math.floor(value / 2 ** 32); var lo = value >>> 0; return new Uint8Array([ 0xC0 | ((hi >> 24) & 0x3f), (hi >> 16) & 0xff, (hi >> 8) & 0xff, hi & 0xff, (lo >> 24) & 0xff, (lo >> 16) & 0xff, (lo >> 8) & 0xff, lo & 0xff ]); } throw new Error("Value too large for QUIC VarInt"); } function readVarInt(array, offset) { if (offset >= array.length) return null; const first = array[offset]; const prefix = first >> 6; if (prefix === 0b00) { return { value: first & 0x3f, byteLength: 1 }; } if (prefix === 0b01) { if (offset + 1 >= array.length) return null; const value = ((first & 0x3f) << 8) | array[offset + 1]; return { value, byteLength: 2 }; } if (prefix === 0b10) { if (offset + 3 >= array.length) return null; const value = ( ((first & 0x3F) << 24) | (array[offset + 1] << 16) | (array[offset + 2] << 8) | array[offset + 3] ) >>> 0; return { value, byteLength: 4 }; } if (prefix === 0b11) { if (offset + 7 >= array.length) return null; const hi = ( ((first & 0x3F) << 24) | (array[offset + 1] << 16) | (array[offset + 2] << 8) | array[offset + 3] ) >>> 0; const lo = ( (array[offset + 4] << 24) | (array[offset + 5] << 16) | (array[offset + 6] << 8) | array[offset + 7] ) >>> 0; const full = BigInt(hi) * 4294967296n + BigInt(lo); // 2^32 if (full <= BigInt(Number.MAX_SAFE_INTEGER)) { return { value: Number(full), byteLength: 8 }; } else { return { value: full, byteLength: 8 }; } } return null; } function concatUint8Arrays(arrays) { var totalLength = 0; for (var i = 0; i < arrays.length; i++) { totalLength += arrays[i].length; } var result = new Uint8Array(totalLength); var offset = 0; for (var i = 0; i < arrays.length; i++) { result.set(arrays[i], offset); offset += arrays[i].length; } return result; } function arraybufferEqual(buf1, buf2) { //if (buf1 === buf2) { //return true; //} if (buf1.byteLength !== buf2.byteLength) { return false; } var view1 = new DataView(buf1); var view2 = new DataView(buf2); for (let i = 0; i < buf1.byteLength; i++) { if (view1.getUint8(i) !== view2.getUint8(i)) { return false; } } return true; } function buildAckFrameFromPackets(packets, ecnStats, ackDelay) { if (!packets || packets.length === 0) return null; var sorted = packets.slice().sort((a, b) => b - a); var ranges = []; var rangeStart = sorted[0]; var rangeEnd = rangeStart; var lastPn = rangeStart; for (var i = 1; i < sorted.length; i++) { var pn = sorted[i]; if (pn === lastPn - 1) { lastPn = pn; } else { ranges.push({ start: lastPn, end: rangeEnd }); rangeEnd = pn; lastPn = pn; } } ranges.push({ start: lastPn, end: rangeEnd }); var firstRange = ranges[0].end - ranges[0].start; var ackRanges = []; for (var i = 1; i < ranges.length; i++) { var gap = ranges[i - 1].start - ranges[i].end - 1; var length = ranges[i].end - ranges[i].start; ackRanges.push({ gap: gap, length: length }); } var frame = { type: 'ack', largest: sorted[0], delay: ackDelay || 0, // ← כאן מכניסים את ה־delay שחושב firstRange: firstRange, ranges: ackRanges }; if (ecnStats) { frame.ecn = { ect0: ecnStats.ect0 || 0, ect1: ecnStats.ect1 || 0, ce: ecnStats.ce || 0 }; } return frame; } function build_ack_info_from_ranges(flatRanges, ecnStats, ackDelay) { if (!flatRanges || flatRanges.length === 0) return null; if (flatRanges.length % 2 !== 0) throw new Error("flatRanges must be in [from, to, ...] pairs"); var ranges = []; for (var i = 0; i < flatRanges.length; i += 2) { var from = flatRanges[i]; var to = flatRanges[i + 1]; if (to < from) throw new Error("Range end must be >= start"); ranges.push({ start: from, end: to }); } // Sort ranges from highest to lowest end ranges.sort((a, b) => b.end - a.end); // Merge overlapping or adjacent ranges var merged = [ranges[0]]; for (var i = 1; i < ranges.length; i++) { var last = merged[merged.length - 1]; var curr = ranges[i]; if (curr.end >= last.start - 1) { // Merge them last.start = Math.min(last.start, curr.start); } else { merged.push(curr); } } var largest = merged[0].end; var firstRange = largest - merged[0].start; var ackRanges = []; for (var i = 1; i < merged.length; i++) { var gap = merged[i - 1].start - merged[i].end - 1; var length = merged[i].end - merged[i].start; ackRanges.push({ gap: gap, length: length }); } return { type: 'ack', largest: largest, delay: ackDelay || 0, firstRange: firstRange, ranges: ackRanges, ecn: ecnStats ? { ect0: ecnStats.ect0 || 0, ect1: ecnStats.ect1 || 0, ce: ecnStats.ce || 0 } : null }; } function build_ack_info_from_ranges2(flatRanges, ecnStats, ackDelay) { if (!flatRanges || flatRanges.length === 0) return null; if (flatRanges.length % 2 !== 0) throw new Error("flatRanges must be in [from, to, ...] pairs"); // בניית טווחים מלאים var ranges = []; for (var i = 0; i < flatRanges.length; i += 2) { var from = flatRanges[i]; var to = flatRanges[i + 1]; if (to < from) throw new Error("Range end must be >= start"); ranges.push({ start: from, end: to }); } // ממיינים מהגדול לקטן לפי end ranges.sort(function (a, b) { return b.end - a.end; }); // הסרת טווחים חופפים או לא חוקיים for (var i = 1; i < ranges.length; i++) { if (ranges[i].end >= ranges[i - 1].start) { throw new Error("Overlapping ranges are not allowed"); } } // התחלת ack מהטווח הגבוה ביותר var largest = ranges[0].end; var firstRange = largest - ranges[0].start; var ackRanges = []; var runningEnd = ranges[0].start - 1; for (var i = 1; i < ranges.length; i++) { var gap = runningEnd - ranges[i].end - 1; var length = ranges[i].end - ranges[i].start; // בדיקה אם הבלוק הבא יגלוש מתחת ל־0 var nextEnd = runningEnd - (gap + 1 + length); if (nextEnd < 0) { console.warn("Skipped range due to underflow risk:", ranges[i]); continue; // לא מוסיפים את הטווח הזה } ackRanges.push({ gap: gap, length: length }); runningEnd = ranges[i].start - 1; } var frame = { type: 'ack', largest: largest, delay: ackDelay || 0, firstRange: firstRange, ranges: ackRanges, ecn: ecnStats ? { ect0: ecnStats.ect0 || 0, ect1: ecnStats.ect1 || 0, ce: ecnStats.ce || 0 } : null }; return frame; } function quic_acked_info_to_ranges(ackFrame) { var flatRanges = []; if (!ackFrame || ackFrame.type !== 'ack') return flatRanges; var largest = ackFrame.largest; var firstRange = ackFrame.firstRange; // טווח ראשון: [largest - firstRange, largest] var rangeEnd = largest; var rangeStart = rangeEnd - firstRange; flatRanges.push(rangeStart, rangeEnd); // נתחיל לבנות את שאר הטווחים לפי gap+length var ranges = ackFrame.ranges || []; for (var i = 0; i < ranges.length; i++) { var { gap, length } = ranges[i]; // מעבר אחורה לפי gap rangeEnd = rangeStart - 1 - gap; rangeStart = rangeEnd - length; flatRanges.push(rangeStart, rangeEnd); } return flatRanges; } module.exports = { concatUint8Arrays, arraybufferEqual, readVarInt, writeVarInt, quic_acked_info_to_ranges, build_ack_info_from_ranges };