chore(repo): initialize WebDrop monorepo
Signed-off-by: JustMe <mr.toing@outlook.com>
This commit is contained in:
commit
53768a195d
39 files changed
+9063
No files matched your search
@@ -0,0 +1,86 @@
|
||||
import { HEADER_SIZE, ID_SIZE } from './constants.js';
|
||||
import { readU32, readU64, toBase64Url, type Bytes } from './bytes.js';
|
||||
import { IntegrityError } from './errors.js';
|
||||
|
||||
/** 墓碑原因(protocol §8)。 */
|
||||
export type RemovedReason = 'deleted' | 'expired' | 'evicted' | 'download_limit' | 'superseded';
|
||||
|
||||
const REASONS: readonly RemovedReason[] = ['deleted', 'expired', 'evicted', 'download_limit', 'superseded'];
|
||||
|
||||
export type LogRecord =
|
||||
| { kind: 'frame'; seq: number; envelope: Bytes }
|
||||
| { kind: 'tombstone'; seq: number; itemId: string; reason: RemovedReason }
|
||||
| { kind: 'announce'; seq: number; itemId: string; flags: number; createdAt: number };
|
||||
|
||||
const KIND_FRAME = 1;
|
||||
const KIND_TOMBSTONE = 2;
|
||||
const KIND_ANNOUNCE = 3;
|
||||
|
||||
/** body 的字节数,不够返回 null。 */
|
||||
function bodyLength(kind: number, buf: Uint8Array, bodyStart: number): number | null {
|
||||
switch (kind) {
|
||||
case KIND_FRAME: {
|
||||
// 封装自带 payload_len:body 长度 = 26 + payload_len
|
||||
if (buf.length < bodyStart + HEADER_SIZE) return null;
|
||||
return HEADER_SIZE + readU32(buf, bodyStart + 22);
|
||||
}
|
||||
case KIND_TOMBSTONE:
|
||||
return ID_SIZE + 1;
|
||||
case KIND_ANNOUNCE:
|
||||
return ID_SIZE + 1 + 8;
|
||||
default:
|
||||
throw new IntegrityError('bad_envelope');
|
||||
}
|
||||
}
|
||||
|
||||
function parseOne(buf: Uint8Array, offset: number): { record: LogRecord; next: number } {
|
||||
if (buf.length < offset + 9) throw new IntegrityError('bad_envelope');
|
||||
const seq = readU64(buf, offset);
|
||||
const kind = buf[offset + 8] as number;
|
||||
const bodyStart = offset + 9;
|
||||
|
||||
const len = bodyLength(kind, buf, bodyStart);
|
||||
if (len === null || buf.length < bodyStart + len) throw new IntegrityError('bad_envelope');
|
||||
const body = buf.subarray(bodyStart, bodyStart + len);
|
||||
const next = bodyStart + len;
|
||||
|
||||
switch (kind) {
|
||||
case KIND_FRAME:
|
||||
return { record: { kind: 'frame', seq, envelope: body.slice() }, next };
|
||||
case KIND_TOMBSTONE: {
|
||||
const reason = REASONS[body[ID_SIZE] as number];
|
||||
if (!reason) throw new IntegrityError('bad_envelope');
|
||||
return { record: { kind: 'tombstone', seq, itemId: toBase64Url(body.subarray(0, ID_SIZE)), reason }, next };
|
||||
}
|
||||
default:
|
||||
return {
|
||||
record: {
|
||||
kind: 'announce',
|
||||
seq,
|
||||
itemId: toBase64Url(body.subarray(0, ID_SIZE)),
|
||||
flags: body[ID_SIZE] as number,
|
||||
createdAt: readU64(body, ID_SIZE + 1),
|
||||
},
|
||||
next,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/** 解析 log 接口返回的记录拼接(长轮询的响应体)。 */
|
||||
export function parseLogRecords(data: Uint8Array): LogRecord[] {
|
||||
const out: LogRecord[] = [];
|
||||
let offset = 0;
|
||||
while (offset < data.length) {
|
||||
const { record, next } = parseOne(data, offset);
|
||||
out.push(record);
|
||||
offset = next;
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
/** 解析恰好一条记录(WebSocket 的一条二进制消息)。多余或缺少字节都视为非法。 */
|
||||
export function parseLogRecord(data: Uint8Array): LogRecord {
|
||||
const { record, next } = parseOne(data, 0);
|
||||
if (next !== data.length) throw new IntegrityError('bad_envelope');
|
||||
return record;
|
||||
}
|
||||
Reference in new issue
Block a user