feat(api): bind encrypted workspaces to accounts and devices
This commit is contained in:
1 parent
019b15041a
commit
c7d8ef15bc
49 files changed
+1904
-278
No files matched your search
@@ -32,7 +32,7 @@ const hkdf = (ikm, salt, info) => Buffer.from(hkdfSync('sha256', ikm, salt, Buff
|
||||
|
||||
// ───────────── 协议常量(protocol.md) ─────────────
|
||||
|
||||
const FLAG = { FINAL: 1, SNAPSHOT: 2, GATED: 4, ENC: 8 };
|
||||
const FLAG = { FINAL: 1, SNAPSHOT: 2, GATED: 4, ENC: 8, CONTROL: 16 };
|
||||
const TIERS = [1024, 4096, 16384, 65536, 262144, 1048576];
|
||||
const MAX_FRAME_DATA = 1048576 - 4;
|
||||
const LABEL = {
|
||||
@@ -111,11 +111,13 @@ function metaJson(m) {
|
||||
if (m.size !== undefined) o.size = m.size;
|
||||
o.ts = m.ts;
|
||||
if (m.dev !== undefined) o.dev = m.dev;
|
||||
if (m.threadId !== undefined) o.thread_id = m.threadId;
|
||||
if (m.folderId !== undefined) o.folder_id = m.folderId;
|
||||
return Buffer.from(JSON.stringify(o), 'utf8');
|
||||
}
|
||||
|
||||
/** 条目编码的参照实现:小内容单帧(非 GATED),放不下则多帧 GATED。 */
|
||||
function encodeItem({ itemId, meta, content, maxFrameData = MAX_FRAME_DATA, snapshot = false, forceGated = false }) {
|
||||
function encodeItem({ itemId, meta, content, maxFrameData = MAX_FRAME_DATA, snapshot = false, forceGated = false, control = false }) {
|
||||
const mj = metaJson(meta);
|
||||
const head = Buffer.concat([u16(mj.length), mj]);
|
||||
const cap0 = maxFrameData - head.length;
|
||||
@@ -123,7 +125,7 @@ function encodeItem({ itemId, meta, content, maxFrameData = MAX_FRAME_DATA, snap
|
||||
|
||||
const frames = [];
|
||||
if (content.length <= cap0 && !forceGated) {
|
||||
frames.push({ index: 0, flags: FLAG.FINAL | FLAG.ENC | (snapshot ? FLAG.SNAPSHOT : 0), data: Buffer.concat([head, content]) });
|
||||
frames.push({ index: 0, flags: FLAG.FINAL | FLAG.ENC | (snapshot ? FLAG.SNAPSHOT : 0) | (control ? FLAG.CONTROL : 0), data: Buffer.concat([head, content]) });
|
||||
return { gated: false, frames };
|
||||
}
|
||||
if (snapshot) throw new Error('快照不能是 GATED');
|
||||
@@ -216,6 +218,7 @@ vectors.frames = frameCases.map((c) => {
|
||||
// 条目向量:meta + 内容 + 分帧
|
||||
const TS = 1700000000000;
|
||||
const itemCases = [
|
||||
{ name: 'workspace_control', itemId: itemC, meta: { kind: 'other', ts: TS }, control: true, content: Buffer.from('{"app":"webdrop.workspace","v":1,"op":"thread.create"}', 'utf8') },
|
||||
{
|
||||
name: 'text_small',
|
||||
itemId: itemA,
|
||||
@@ -241,6 +244,13 @@ const itemCases = [
|
||||
meta: { kind: 'file', name: 'note.txt', mime: 'text/plain', size: 11, ts: TS },
|
||||
content: Buffer.from('hello world', 'utf8'),
|
||||
},
|
||||
{
|
||||
name: 'file_in_thread_folder',
|
||||
itemId: itemC,
|
||||
meta: { kind: 'file', name: 'readme.txt', mime: 'text/plain', size: 11, ts: TS, dev: 'Windows 11',
|
||||
threadId: '01234567-89ab-cdef-0123-456789abcdef', folderId: 'fedcba98-7654-3210-fedc-ba9876543210' },
|
||||
content: Buffer.from('thread test', 'utf8'),
|
||||
},
|
||||
{
|
||||
name: 'empty_text',
|
||||
itemId: itemB,
|
||||
@@ -296,6 +306,7 @@ vectors.items = itemCases.map((c) => {
|
||||
max_frame_data: c.maxFrameData ?? MAX_FRAME_DATA,
|
||||
snapshot: !!c.snapshot,
|
||||
force_gated: !!c.forceGated,
|
||||
control: !!c.control,
|
||||
gated: plan.gated,
|
||||
frames: envelopes.map(hex),
|
||||
};
|
||||
@@ -381,7 +392,7 @@ vectors.negative = [
|
||||
{ name: 'header_only', envelope: hex(base.subarray(0, 26)), expect: 'bad_envelope' },
|
||||
{ name: 'too_short', envelope: hex(base.subarray(0, 10)), expect: 'bad_envelope' },
|
||||
{ name: 'bad_version', envelope: mutate((b) => { b[0] = 2; }), expect: 'bad_envelope' },
|
||||
{ name: 'reserved_flag_bit', envelope: mutate((b) => { b[1] |= 0x10; }), expect: 'bad_envelope' },
|
||||
{ name: 'reserved_flag_bit', envelope: mutate((b) => { b[1] |= 0x20; }), expect: 'bad_envelope' },
|
||||
{ name: 'payload_len_mismatch', envelope: mutate((b) => { b[25] ^= 1; }), expect: 'bad_envelope' },
|
||||
// 没有 ENC 标志的帧,在 enc 会话里必须被拒绝(防降级:服务端不能注入明文帧)
|
||||
{
|
||||
|
||||
Reference in new issue
Block a user