Skip to content

Protocol

Lisselde_E edited this page Sep 30, 2026 · 2 revisions

协议

本文面向开发者,描述 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 端投递不依赖主机。

一、消息帧格式

头部(25 字节)

类型(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=应用后的权限

去中心化(mesh/直连,content=JSON)

值 名称 方向 说明
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)

去中心化对端发现(Pro,主机仅"介绍一次")

主机只负责首次引导(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,连接异常立即返回,不无限重试。

Protocol (English)

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.

1. Message Frame Format

Header (25 bytes)

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).

Body

header(25B) + filename(utf-8, filename_len) + content(length below)
  • No-content types: the following ignore file_size and are parsed with content_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.loads it;
  • Other types carry raw bytes (parsed by the caller).

2. Message Type Reference

Basics & File Transfer

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

Clipboard & Delivery

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)

Permission & Mode

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

Decentralized (mesh/direct, content=JSON)

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)

Decentralized Peer Discovery (Pro, host "introduces" once only)

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 put room_code and auth = sha256(room_code:sha256(password)) into END_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_REQ compares 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 existing AUTH_REQ (the LAN threat model is unchanged); a password-less room degrades to room binding only.
  • UDP self-discovery: each end's RoomResponder appends end_id/name/mesh_port and peers (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) fetches ROOM_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_list is the host entry with is_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 host END_INFO, a HOST_INFO redirect); an is_host claim 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, MeshPeerDiscovery runs 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).

3. Parsing & Safety Limits

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_DATA content 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() raises ProtocolError instead of waiting pointlessly.

Special Conventions

  • FILE_CANCEL is 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).

4. Network Parameters

  • 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 FileProvider starting from DEFAULT_START_PORT = 21300;
  • All socket transfers time out at 1s; on a connection error the operation returns immediately, no infinite retries.