// link.js - 链路层:帧边界 / nullmap / 明文协商 // // 帧 = [2 或 4 字节大端长度][帧体] // 长度首字节 bit7=1 → 4 字节头;bit6=1 → 帧体 zlib 压缩;低 6 位参与长度 // 帧体 = [nullmap][payload] // payload = [1 字节入口命令 ID][命令体](entry.js) // | [8 字节目标 id][空间命令记录…](dispatch.js) // nullmap:长度 = NullmapSize()。本服务器发出的帧一律 1 字节 0x00(没有空字段); // 读入按同一函数算长度,兼容抓包里的变长 nullmap。 // 加密:自变体 XOR(k[i] = K ^ (i<<3),上行再 ^87)。本服务器在 Hash Response 里把 // 「是否启用链路加密」置 0,整条链路走明文;解密只在 relay 模式做(见 ../wss_server.js)。 'use strict'; const zlib = require('zlib'); // 一帧的头部信息 → { TotalSize, HeaderSize, Compressed };字节不够返回 null function PeekFrame(Buf, Offset) { const Off = Offset || 0; if (Buf.length - Off < 2) return null; const B0 = Buf[Off]; if (B0 & 0x80) { if (Buf.length - Off < 4) return null; const Size = ((((B0 & 0x3f) << 24) | (Buf[Off + 1] << 16) | (Buf[Off + 2] << 8) | Buf[Off + 3]) >>> 0) + 4; return { TotalSize: Size, HeaderSize: 4, Compressed: (B0 & 0x40) !== 0 }; } return { TotalSize: 2 + (((B0 & 0x3f) << 8) | Buf[Off + 1]), HeaderSize: 2, Compressed: (B0 & 0x40) !== 0 }; } // 把一串字节拆成整帧;有任何残余(说明不是整帧流)返回 null function ParseFrames(Buf) { const Frames = []; let Off = 0; while (Off < Buf.length) { const Info = PeekFrame(Buf, Off); if (!Info || Off + Info.TotalSize > Buf.length) return null; Frames.push({ Body: Buf.slice(Off + Info.HeaderSize, Off + Info.TotalSize), Compressed: Info.Compressed }); Off += Info.TotalSize; } return Frames.length ? Frames : null; } // nullmap 长度(对应客户端的 x7g) // 长格式实测:`c0 <2 字节长度>` ⇒ 总长 = 3 + 长度(**不是 4 +**)。 // 这一字节之差会让「大 nullmap」的帧整体偏 1 字节,进而所有后续解析全错—— // 车库那几帧(16771B / 137119B)的 nullmap 分别是 800B / 269B,正是踩在这个坑上。 function NullmapSize(Buf, Offset) { const R = Buf[Offset]; if (R === undefined) return 0; if (R & 0x80) { if (R & 0x40) return 3 + (((R & 0x3f) << 16) | (Buf[Offset + 1] << 8) | Buf[Offset + 2]); return 1 + (R & 0x3f); } return 1 + ((R & 0x60) >>> 5); } function DecodeFrame(Frame) { return Frame.Compressed ? zlib.inflateSync(Frame.Body) : Frame.Body; } // 组帧:Length < 0x4000 用 2 字节头,否则 4 字节头(bit7) function EncodeFrame(Payload, Nullmap) { const Nm = Nullmap || Buffer.from([0x00]); const Length = Nm.length + Payload.length; if (Length < 0x4000) { return Buffer.concat([Buffer.from([(Length >> 8) & 0xff, Length & 0xff]), Nm, Payload]); } return Buffer.concat([ Buffer.from([((Length >>> 24) & 0x3f) | 0x80, (Length >>> 16) & 0xff, (Length >>> 8) & 0xff, Length & 0xff]), Nm, Payload, ]); } // 保活帧 = 负载为空的帧(实测 3 字节:00 01 00) function PingFrame() { return EncodeFrame(Buffer.alloc(0)); } // 帧体只有 nullmap、没有 payload function IsPingFrame(Frame) { return Frame.Body.length > 0 && NullmapSize(Frame.Body, 0) >= Frame.Body.length; } module.exports = { PeekFrame, ParseFrames, NullmapSize, DecodeFrame, EncodeFrame, PingFrame, IsPingFrame };