-
Notifications
You must be signed in to change notification settings - Fork 0
Protocol
本文面向开发者,描述 LANSyncBox 的二进制通信协议。协议为消息头 + 文件名 + 内容的定长头流式帧结构,TCP 承载,全异步收发。
协议代码(Protocol / MessageReceiver)为两版共享,帧格式与解析规则完全相同;消息类型按版本划分:
- 两版共用(0x01–0x1F):认证、心跳、文件流式传输(0x0C/0x0D/0x0E)、删除/目录/重命名、同步请求与结果、剪贴板文本广播、权限与模式、延迟探测——数据路径按版本不同(标准版经主机中继,Pro 端到端直连);
- 仅 Pro(0x20–0x27、0x29、0x2A–0x2E):mesh 端间链路——分发信号、状态交换、同步拉取、端身份、对端清单引导/互换、主机信息指示;房间探活帧(0x2A/0x2B,UDP 摸不到时的 TCP 定向确认);文本投递帧(0x2C,投递全面去中心化);对端发现帧(0x2D/0x2E,主机仅"介绍一次",之后各端自发现、自扩散);
- 投递双通道(文本 + 文件通知):0x16/0x17 走管理面(主机转发,两版代码均保留处理,兼容旧端);0x2C(文本)与 0x28(文件通知)为 Pro 的 mesh 直连版本,替换主机转发——Pro 端投递不依赖主机。
类型(4B) | 文件名长度(4B) | 文件大小(8B) | 修改时间(8B, double) | 隐藏标记(1B)
格式串 !I I Q d B(Protocol.HEADER_FORMAT),unpack_header() 解出 (msg_type, filename_len, file_size, mtime, hide_flag)。
头部(25B) + 文件名(utf-8, filename_len) + 内容(content, 长度见下)
-
无 content 类型:以下类型即使
file_size非 0 也不携带 content,接收端一律按content_size = 0解析:FILE_BEGIN/FILE_END/FILE_LIST_REQ/FILE_REQUEST/FILE_CANCEL/FILE_NOTIFY/FILE_REQUEST_FORWARD/SYNC_REQUEST; -
FILE_DATA 特例:content =
块索引(4B, !I)+ 块数据,file_size= 块数据长度 + 4; -
FILE_LIST_RESP:content = JSON 数组(
[{filename, size, mtime}, ...]); -
JSON content 类型:0x20–0x28、0x29 及 0x2E 的 content 一律为 JSON 字典,接收端
get_message()自动json.loads为 dict; - 其余类型 content 为原始字节(调用方自行解析)。
| 值 | 名称 | 方向 | 说明 |
|---|---|---|---|
| 0x01 | FILE | — | 文件传输(已废弃,保留向后兼容) |
| 0x02 | DELETE | 任意→主机→各端 | 删除指令(filename=相对路径) |
| 0x03 | AUTH_REQ | 连接端→主机 | content=sync_version:room_code:password_hash(密码 sha256) |
| 0x04 | AUTH_RESP | 主机→连接端 | content=状态:消息,状态三态:1=成功;0=永久失败(密码/房间号/同步逻辑版本不符);2=暂时不可用(瞬时,如复用监听处主机暂不在),客户端不得视为永久拒绝 |
| 0x05 | RENAME | — | 重命名(content=`old |
| 0x07 | HEARTBEAT | 双向 | 心跳,无 content |
| 0x08 | FILE_LIST_REQ | 连接端→主机 | 请求文件列表 |
| 0x09 | FILE_LIST_RESP | 任意→主机 | JSON 文件列表上报 |
| 0x0A | FILE_REQUEST | 连接端→主机 | 请求特定文件 |
| 0x0B | DIR_CREATE | 任意→各端 | 目录创建(filename=相对路径) |
| 0x0C | FILE_BEGIN | 发送端→接收端 | 流式传输开始(filename=相对路径,file_size=文件大小,mtime=修改时间) |
| 0x0D | FILE_DATA | 发送端→接收端 | 流式数据块(content=4B 块索引+块数据,分块 64KB) |
| 0x0E | FILE_END | 发送端→接收端 | 流式传输结束(filename/size/mtime 与 FILE_BEGIN 一致) |
| 0x0F | FILE_CANCEL | 任意→发送端 | 取消传输。无 content,路由信息编码在 filename(投递取消时 filename=session_id\x1fname) |
| 0x10 | FILE_NOTIFY | 主机→各端 | 文件通知(静默告知有新文件) |
| 0x11 | FILE_REQUEST_FORWARD | 连接端→主机 | 请求主机转发文件 |
| 0x12 | SYNC_REQUEST | 主机→连接端 | 触发重新上报并差异同步(无 content) |
| 0x13 | SYNC_RESULT | 主机→连接端 | content='1'=有差异(正在补齐)/'0'=一致 |
| 0x14 | PING | 发起端→对端 | 延迟探测,content=发送时刻(!d) |
| 0x15 | PONG | 对端→发起端 | 原样带回 PING 的发送时刻,用于计算 RTT |
| 值 | 名称 | 方向 | 说明 |
|---|---|---|---|
| 0x16 | CLIPBOARD_DATA | 复制端→主机→各端 | 文本剪贴板(filename=text,content=utf-8 文本);图片/文件不走此通道 |
| 0x17 | CLIPBOARD_FILES_NOTIFY | 复制端→主机→其余端 | 文件会话通知(filename=clipboard_files,content=JSON 会话元数据) |
| 0x18 | CLIPBOARD_FILE_PULL_REQ | 接收端→复制端 | 投递拉取请求(filename=条目名,content=JSON {session_id, token, name, offset}) |
| 0x19 | P2P_FILE_DATA | — | 预留常量,不再使用(投递已改走 0x0C/0x0D/0x0E 端到端流式) |
| 0x28 | CLIPBOARD_NOTIFY_SIGNAL | 端→网状各直连对端 | 投递通知(content=JSON 会话元数据,不经主机,走 mesh 直连) |
| 值 | 名称 | 方向 | 说明 |
|---|---|---|---|
| 0x1A | MODE_SWITCH | 主机→连接端 | content=sync/collect
|
| 0x1B | MODE_ACK | 连接端→主机 | content=切换后的模式 |
| 0x1C | MODE_REQ | 连接端→主机 | 认证后请求当前模式 |
| 0x1D | MODE_RESP | 主机→连接端 | content=sync/collect
|
| 0x1E | PERM_UPDATE | 主机→连接端 | content=rw/ro
|
| 0x1F | PERM_ACK | 连接端→主机 | content=应用后的权限 |
| 值 | 名称 | 方向 | 说明 |
|---|---|---|---|
| 0x20 | DISTRIBUTE_SIGNAL | 端→网状各直连对端 | 分发信号:{src_id, op_no, op, file, state, clock, ts, dst?};op=add/delete/rename/move(rename/move 带 old/new) |
| 0x21 | FILE_STATE_REQ | 端→对端 | 状态请求:{src_id, dirs?};dirs(26.9C2 可选)= 本端存在的目录名清单(空清单也显式带上,用于区分「本端无目录」与旧端) |
| 0x22 | FILE_STATE_RESP | 对端→端 |
{src_id, entries:[{name, op_no, state, exists, clock, ts}], session?, dirs_missing?};session={session_id, token, host, port} 供拉取方直连 FileProvider;dirs_missing(26.9C2 可选)= 本端有而请求方清单无的目录名,由请求方本地判定补建(删除优先,空目录对账兜底) |
| 0x23 | SYNC_PULL_REQ | 端→对端 FileProvider | 同步拉取请求(filename=条目名,content={session_id, token, name, offset},与 0x18 同构) |
| 0x24 | END_INFO | 握手后交换 |
{end_id, name, mesh_port, mgmt_port, room_code, auth}(filename=end_id);网状握手携带 room_code/auth 作准入校验,管理链路留空 |
| 0x25 | MESH_PEER_LIST | 主机→新加入端 / 端↔端 | 对端清单(地址线索):{peers:[{end_id, name, ip, mesh_port, mgmt_port, is_host?}]},首条为主机条目(is_host: true),其余为发送方当前直连快照(主机引导下发;mesh 建连后亦用于端间互换,实现"互相介绍"自扩散) |
| 0x26 | MESH_PEER_JOIN | 主机→各端 | 新端加入通告:{peer:{...}}
|
| 0x27 | MESH_PEER_LEAVE | 主机→各端 | 端离线通告:{end_id}
|
| 0x29 | HOST_INFO | 连接端(复用管理监听)→接入的新端 | 主机信息指示:{host_id, ip, port},令接入端把管理连接换接到真主机。接入端拨到复用监听时总先回一条 AUTH_RESP 结论再断开:主机已知→1+本帧;主机不可用→2("主机暂不可用,请稍后加入",瞬时码,接入端不视为永久拒绝、不停重连) |
| 0x2A | ROOM_PROBE | 探测端→目标端点 | 房间探活请求(content=sync_version:room_code,免认证、只回状态,不注册客户端/不写日志) |
| 0x2B | ROOM_PROBE_RESP | 目标端点→探测端 | 房间探活响应(content=单字符状态码:1 在线、2 端点存活但主机暂不可用、3 非本房间、4 同步逻辑版本不匹配);filename=宿主 end_id(仅 1 在线时回带,其余状态为空串;探测端据此判定"宿主即本机") |
| 0x2C | CLIPBOARD_TEXT_SIGNAL | 端→网状各直连对端 | 文本投递(filename=text,content=utf-8 文本,不经主机,走 mesh 直连);Pro 端复制文本走此通道,主机仅作为 mesh 对端之一接收 |
| 0x2D | ROOM_PEER_QUERY | 探测端→房间任一存活端 | 对端查询请求(content=sync_version:room_code,免认证、零日志,只回清单,不注册客户端、不下发权限) |
| 0x2E | ROOM_PEER_LIST | 应答方→探测端 | 对端清单:{peers:[{end_id, name, ip, mesh_port, mgmt_port, is_host?}]},首条为主机条目(is_host: true),其余为当前直连快照;用于对端查询(0x2D)应答与探活(0x2A)顺带推送(mesh 建连后的端间互换走 0x25) |
主机只负责首次引导(MESH_PEER_LIST/MESH_PEER_JOIN),此后各端自行发现与扩散,主机离线不影响对端发现。对端表语义为「此刻活跃直连的快照 + 地址线索/拨号缓存」:清单只作地址线索互换,接收方自行拨号验证,不得解读为"某端在线";各端只辐射自身直连快照,不传播在线状态,也不使用墓碑(连不上即本地出表,收到线索再拨)。
-
网状准入(随
END_INFO携带):直连握手时双方在END_INFO中带room_code与auth = sha256(房间号:sha256(密码));房间号或凭据不符即静默关闭、不注册、不发断开信号。不做旧端放行——版本不同者在认证面(AUTH_REQ比对同步逻辑版本)已被挡在房间之外,成不了本房间成员。发现帧(UDP/TCP)仍免认证,只传地址线索,拿到线索也过不了准入。已知局限:凭据为静态哈希、明文上链,强度与既有AUTH_REQ一致(局域网威胁模型不变);无密码房间退化为仅房间绑定。 -
UDP 自发现:各端
RoomResponder的发现应答在原有字段外追加end_id/name/mesh_port与peers(自身端点 + 当前直连快照),收到即可直接建 mesh 直连;旧端忽略未知字段(混版安全)。 -
TCP 定向查询:UDP 摸不到的已知 IP(跨网段、防火墙、发现端口被占)用
ROOM_PEER_QUERY(0x2D)取回ROOM_PEER_LIST(0x2E);ROOM_PROBE(0x2A)在房间/版本匹配时也顺带推送该清单。 -
建连互换(gossip):mesh 直连建立后双方用
MESH_PEER_LIST(0x25)互换地址线索(当前直连快照),新端据此继续拨号建连;已连通端不重复入表。 -
清单置顶主机条目:
peer_list首条为主机条目并附加is_host: true,供主机回归后重新统领全局权限。主机权威身份只由管理面认证路径写入(自身、主机END_INFO、HOST_INFO重定向);他人清单/发现应答里的is_host声明须与该身份一致才采信,否则忽略(防任意端置顶冒充主机)。 -
运行期轮询:房间就绪后启动
MeshPeerDiscovery,每 10 秒一轮(UDP 广播 + 已知 IP 的 TCP 查询),新对端自动并入拨号缓存,重连采用有界指数退避,达上限即停止重试(线索保留,待其自报或被重新介绍时重启)。
MessageReceiver 负责 TCP 流式数据分包:
-
MAX_BUFFER_SIZE = 8MB:缓冲超限即判协议垃圾,抛ProtocolError; -
MAX_NAME_LEN = 64KB:文件名/路径长度上限; -
MAX_CONTENT_SIZE = 8MB:单条 content 上限(需能装入缓冲); -
FILE_DATA的 content 不足 4 字节块索引同样判定非法; - 以上非法情形一律 fail-closed:接收循环断连并复位,防止垃圾流导致缓冲无界增长(内存泄漏);
- 头长度非法时
has_complete_message()抛ProtocolError,不做无谓等待。
-
FILE_CANCEL属于无 content 类型:投递取消时路由信息(session_id、name)以\x1f拼接编码到 filename 字段,content 留空,避免 content 残留导致下一条消息解析错乱; - JSON content 自动解析仅限去中心化新类型(0x20–0x28、0x29、0x2E);0x17/0x18 剪贴板会话类、0x2B 探活响应、0x2C 文本投递与 0x2D 对端查询保持原始 bytes,由调用方自行处理(0x2B 为单字符状态码 + filename 宿主 end_id、0x2C 为原始 utf-8 文本、0x2D 为
sync_version:room_code,均不解析)。
- 管理端口
DEFAULT_PORT = 9527; - 收发缓冲
BUFFER_SIZE = 65536(64KB,与流式分块一致); - 单文件上限
MAX_FILE_SIZE = 1GB; - 投递/同步数据端口由
FileProvider自DEFAULT_START_PORT = 21300起分配; - 所有 socket 传输超时 1s,连接异常立即返回,不无限重试。
This page is for developers and describes the binary communication protocol of LANSyncBox. Frames are fixed-length header + filename + content, streamed over TCP, fully asynchronous.
The protocol code (Protocol / MessageReceiver) is shared by both editions; framing and parsing rules are identical. Message types are split by edition:
- Both editions (0x01–0x1F): auth, heartbeat, streaming transfer (0x0C/0x0D/0x0E), delete/dir/rename, sync request & result, clipboard text broadcast, permissions & modes, latency probe — the data path differs by edition (Standard relayed by the host, Pro end-to-end);
- Pro only (0x20–0x27, 0x29, 0x2A–0x2E): mesh inter-end links — distribute signal, state exchange, sync pull, end info, peer list guidance/interchange, host info; the room liveness probe frames (0x2A/0x2B, TCP confirmation when UDP is unreachable); the clipboard text delivery frame (0x2C, delivery fully decentralized); and the peer discovery frames (0x2D/0x2E, the host "introduces" once, after which each end discovers and spreads peers on its own);
- Delivery has two channels (text + file notify): 0x16/0x17 go via the management plane (host-forwarded; both editions keep handling them for old peers); 0x2C (text) and 0x28 (file notify) are Pro's mesh-direct versions replacing host forwarding — Pro delivery does not depend on the host.
type(4B) | filename_len(4B) | file_size(8B) | mtime(8B, double) | hide_flag(1B)
Format string !I I Q d B (Protocol.HEADER_FORMAT); unpack_header() yields (msg_type, filename_len, file_size, mtime, hide_flag).
header(25B) + filename(utf-8, filename_len) + content(length below)
-
No-content types: the following ignore
file_sizeand are parsed withcontent_size = 0:FILE_BEGIN/FILE_END/FILE_LIST_REQ/FILE_REQUEST/FILE_CANCEL/FILE_NOTIFY/FILE_REQUEST_FORWARD/SYNC_REQUEST; -
FILE_DATA special: content =
chunk_index(4B, !I)+ chunk data;file_size= chunk data length + 4; -
FILE_LIST_RESP: content = JSON array (
[{filename, size, mtime}, ...]); -
JSON-content types: content of 0x20–0x28, 0x29 and 0x2E is always a JSON dict;
get_message()auto-json.loadsit; - Other types carry raw bytes (parsed by the caller).
| Val | Name | Direction | Description |
|---|---|---|---|
| 0x01 | FILE | — | Legacy file transfer (deprecated, kept for backward compat) |
| 0x02 | DELETE | any→host→ends | Delete command (filename = relative path) |
| 0x03 | AUTH_REQ | client→host | content=sync_version:room_code:password_hash (sha256) |
| 0x04 | AUTH_RESP | host→client | content=state:message, three states: 1=success; 0=permanent failure (wrong password/room/sync-logic version); 2=temporarily unavailable (transient, e.g. the host is momentarily absent behind a reused listener) — clients must not treat it as a permanent rejection |
| 0x05 | RENAME | — | Rename (content=`old |
| 0x07 | HEARTBEAT | both | Heartbeat, no content |
| 0x08 | FILE_LIST_REQ | client→host | Request file list |
| 0x09 | FILE_LIST_RESP | any→host | JSON file list report |
| 0x0A | FILE_REQUEST | client→host | Request a specific file |
| 0x0B | DIR_CREATE | any→ends | Directory creation (filename = relative path) |
| 0x0C | FILE_BEGIN | sender→receiver | Stream start (filename = rel path, file_size, mtime) |
| 0x0D | FILE_DATA | sender→receiver | Data chunk (content = 4B index + chunk, 64KB) |
| 0x0E | FILE_END | sender→receiver | Stream end (same filename/size/mtime as FILE_BEGIN) |
| 0x0F | FILE_CANCEL | any→sender | Cancel transfer. No content; routing encoded in filename (session_id\x1fname for delivery cancel) |
| 0x10 | FILE_NOTIFY | host→ends | Silent file notification |
| 0x11 | FILE_REQUEST_FORWARD | client→host | Ask host to forward a file |
| 0x12 | SYNC_REQUEST | host→client | Trigger re-report and diff sync (no content) |
| 0x13 | SYNC_RESULT | host→client | content='1'=diff (filling gaps)/'0'=consistent |
| 0x14 | PING | initiator→peer | Latency probe, content = send time (!d) |
| 0x15 | PONG | peer→initiator | Echoes PING send time for RTT |
| Val | Name | Direction | Description |
|---|---|---|---|
| 0x16 | CLIPBOARD_DATA | copier→host→ends | Text clipboard (filename=text, content=utf-8); images/files use the file path instead |
| 0x17 | CLIPBOARD_FILES_NOTIFY | copier→host→other ends | File session notify (filename=clipboard_files, content=JSON session metadata) |
| 0x18 | CLIPBOARD_FILE_PULL_REQ | receiver→copier | Delivery pull request (filename=item name, content=JSON {session_id, token, name, offset}) |
| 0x19 | P2P_FILE_DATA | — | Reserved constant, unused (delivery now uses 0x0C/0x0D/0x0E end-to-end streaming) |
| 0x28 | CLIPBOARD_NOTIFY_SIGNAL | end→mesh peers | Delivery notify (content=JSON session metadata, via mesh, not the host) |
| Val | Name | Direction | Description |
|---|---|---|---|
| 0x1A | MODE_SWITCH | host→client | content=sync/collect
|
| 0x1B | MODE_ACK | client→host | content=switched mode |
| 0x1C | MODE_REQ | client→host | Request current mode after auth |
| 0x1D | MODE_RESP | host→client | content=sync/collect
|
| 0x1E | PERM_UPDATE | host→client | content=rw/ro
|
| 0x1F | PERM_ACK | client→host | content=applied permission |
| Val | Name | Direction | Description |
|---|---|---|---|
| 0x20 | DISTRIBUTE_SIGNAL | end→mesh peers | Distribute signal: {src_id, op_no, op, file, state, clock, ts, dst?}; op=add/delete/rename/move (rename/move carry old/new) |
| 0x21 | FILE_STATE_REQ | end→peer | State request: {src_id, dirs?}; dirs (optional since 26.9C2) = names of directories present on this end (an empty list is still sent explicitly, to distinguish "no directories here" from a legacy peer) |
| 0x22 | FILE_STATE_RESP | peer→end |
{src_id, entries:[{name, op_no, state, exists, clock, ts}], session?, dirs_missing?}; session={session_id, token, host, port} lets the puller connect to the FileProvider; dirs_missing (optional since 26.9C2) = directories this end has that the requester lacks, applied locally by the requester (delete wins; empty-directory reconciliation fallback) |
| 0x23 | SYNC_PULL_REQ | end→peer FileProvider | Sync pull request (filename=item name, content={session_id, token, name, offset}, same shape as 0x18) |
| 0x24 | END_INFO | after handshake |
{end_id, name, mesh_port, mgmt_port, room_code, auth} (filename=end_id); a mesh handshake carries room_code/auth for admission, while the management link leaves them empty |
| 0x25 | MESH_PEER_LIST | host→new end / end↔end | Peer list (address hints): {peers:[{end_id, name, ip, mesh_port, mgmt_port, is_host?}]}, first entry is the host (is_host: true) and the rest are the sender's current direct-link snapshot (host guidance; also swapped between ends after a mesh link is up, for "mutual introduction" self-spread) |
| 0x26 | MESH_PEER_JOIN | host→ends | New end joined: {peer:{...}}
|
| 0x27 | MESH_PEER_LEAVE | host→ends | End left: {end_id}
|
| 0x29 | HOST_INFO | connected end (reusing its management listener)→new end | Host info: {host_id, ip, port}; directs the new end to switch its management connection to the real host. A new end dialing a reused listener always gets an AUTH_RESP verdict first, then the connection closes: host known→1+this frame; host unavailable→2 ("host temporarily unavailable, join later" — a transient code, so the dialing end does not treat it as a permanent rejection nor stop reconnecting) |
| 0x2A | ROOM_PROBE | prober→target endpoint | Room liveness probe (content=sync_version:room_code; auth-free, status-only, no client registration, no log) |
| 0x2B | ROOM_PROBE_RESP | target endpoint→prober | Room liveness response (content = one-char state: 1 online, 2 endpoint alive but host unavailable, 3 not this room, 4 sync-logic version mismatch); filename = host end_id (sent only when the state is 1 online, empty string otherwise; the prober uses it to detect "the host is myself") |
| 0x2C | CLIPBOARD_TEXT_SIGNAL | end→mesh peers | Text delivery (filename=text, content=utf-8, via mesh, not the host); Pro text copy uses this channel, with the host receiving as just one mesh peer |
| 0x2D | ROOM_PEER_QUERY | prober→any live end in the room | Peer query request (content=sync_version:room_code; auth-free, zero-log, list-only, no client registration, no permission push) |
| 0x2E | ROOM_PEER_LIST | responder→prober | Peer list: {peers:[{end_id, name, ip, mesh_port, mgmt_port, is_host?}]}, first entry is the host (is_host: true) and the rest are the current direct-link snapshot; used as the peer-query (0x2D) response and pushed alongside the liveness probe (0x2A) (the post-connect end-to-end swap uses 0x25) |
The host only does the first-time guidance (MESH_PEER_LIST/MESH_PEER_JOIN); afterwards each end discovers and spreads peers on its own, so peer discovery keeps working when the host is offline. The peer table means "a snapshot of the currently active direct links + an address-hint/dial cache": a list is only an address-hint swap, the receiver dials to verify, and it must not be read as "that end is online"; each end spreads only its own direct-link snapshot, with no online-state propagation and no tombstones (unreachable ends drop out locally and get dialed again when a hint arrives).
-
Mesh admission (carried in
END_INFO): at handshake both ends putroom_codeandauth = sha256(room_code:sha256(password))intoEND_INFO; a room or credential mismatch means an immediate silent close — no registration, no disconnect signal. There is no legacy pass-through: ends on a different version are already kept out of the room by the auth plane (AUTH_REQcompares the sync-logic version), so they can never become members. Discovery frames (UDP/TCP) stay auth-free and only carry address hints — having a hint does not get you past admission. Known limitation: the credential is a static hash sent in the clear, matching the strength of the existingAUTH_REQ(the LAN threat model is unchanged); a password-less room degrades to room binding only. -
UDP self-discovery: each end's
RoomResponderappendsend_id/name/mesh_portandpeers(its own endpoint plus the current direct-link snapshot) to the discovery response, so a receiver can dial mesh links directly; legacy ends ignore the unknown fields (mix-version safe). -
TCP targeted query: for a known IP that UDP cannot reach (cross-subnet, firewall, discovery port taken),
ROOM_PEER_QUERY(0x2D) fetchesROOM_PEER_LIST(0x2E); when room and version match,ROOM_PROBE(0x2A) also pushes that list along with its response. -
Post-connect swap (gossip): once a mesh link is up, both ends swap their address hints (the current direct-link snapshot) via
MESH_PEER_LIST(0x25) and the new end keeps dialing; an already-connected end is not re-added. -
Host pinned at the top of the list: the first entry of
peer_listis the host entry withis_host: true, so the host can resume and hold global authority after it returns. The host's authoritative identity is written only by the authenticated management path (itself, the hostEND_INFO, aHOST_INFOredirect); anis_hostclaim in another end's list or a discovery response is honoured only when it matches that identity, and ignored otherwise (so no end can pin itself as host by claiming it). -
Runtime polling: after the room is ready,
MeshPeerDiscoveryruns every 10 seconds (UDP broadcast + TCP query to known IPs); new peers are merged into the dial cache automatically, and reconnects use bounded exponential backoff that stops retrying once the cap is reached (the hint is kept, and retrying restarts when the end announces itself or is reintroduced).
MessageReceiver splits the TCP stream:
-
MAX_BUFFER_SIZE = 8MB: buffer overflow is treated as protocol garbage →ProtocolError; -
MAX_NAME_LEN = 64KB: filename/path length limit; -
MAX_CONTENT_SIZE = 8MB: per-message content limit (must fit the buffer); -
FILE_DATAcontent shorter than 4 bytes (no chunk index) is also invalid; - All invalid cases are fail-closed: the receive loop disconnects and resets, preventing unbounded buffer growth (memory leak);
- On an illegal header length,
has_complete_message()raisesProtocolErrorinstead of waiting pointlessly.
-
FILE_CANCELis a no-content type: for delivery cancellation the routing info (session_id, name) is encoded in the filename field joined by\x1f, with content left empty, so leftover content bytes cannot corrupt parsing of the next message; - Auto-JSON parsing applies only to the decentralized types (0x20–0x28, 0x29, 0x2E); the clipboard session types 0x17/0x18, the 0x2B liveness response, the 0x2C text delivery and the 0x2D peer query keep raw bytes and are handled by the caller (0x2B is a one-char state + host end_id in filename, 0x2C is raw utf-8 text, 0x2D is
sync_version:room_code; none is parsed).
- Management port
DEFAULT_PORT = 9527; - Send/receive buffer
BUFFER_SIZE = 65536(64KB, matching stream chunks); - Max file size
MAX_FILE_SIZE = 1GB; - Delivery/sync data ports are allocated by
FileProviderstarting fromDEFAULT_START_PORT = 21300; - All socket transfers time out at 1s; on a connection error the operation returns immediately, no infinite retries.