02 二维码与深链 lom://pair
二维码与深链 lom://pair 的逐字段格式与手机端校验步骤。
docs/open/phone-access/ 的原文,提交 92044e2;本页对应 02-qr.md。以 GitHub 上的最新版为准。桌面添加手机时显示一个二维码。二维码内容就是一个普通字符串(下称「配对链接」),格式固定:
lom://pair?v=1&h=<h>&p=<p>&t=<t>&k=<k>&f=<f>
链接的来源不影响协议:相机实时扫描、从相册图片解码、系统相机扫到后通过 lom:// 深链跳转进 App,得到的都是同一个字符串,走同一套校验和同一套配对流程。无论来源是什么,手机端都必须:完整校验链接 → 核对桌面公钥指纹 → 让用户在手机上看到桌面名称与脱敏账户后明确点「确认连接」(见 03)。
1. 二维码本身
| 项 | 取值 |
|---|---|
| 内容编码 | UTF-8 文本(全是 ASCII) |
| 纠错级别 | M |
| 典型长度 | 约 180–190 字符(官方主机名下 186 字符) |
| 解析上限 | 建议拒绝长度超过 2048 的输入 |
2. 字段
链接 = scheme lom + authority pair + 查询串。查询串里恰好有下表六个字段,每个字段出现一次,顺序不限;缺字段、多字段、重复字段、未知字段一律视为无效。
| 字段 | 含义 | 原始字节数 | 编码后字符数 | 编码 |
|---|---|---|---|---|
v | 协议版本 | — | 1 | 十进制文本,当前必须为 1 |
h | 云端主机名 | 可变(UTF-8 文本) | 可变 | 主机名的 UTF-8 字节做 base64url,不是明文 |
p | 配对会话 ID pairing_id | 16 | 22 | base64url |
t | 认领令牌 claim_token | 32 | 43 | base64url |
k | 预共享密钥 psk | 32 | 43 | base64url |
f | 桌面公钥指纹 | 16 | 22 | base64url |
编码统一规则(全协议通用,详见 04):base64url(RFC 4648 §5 字母表 A–Z a–z 0–9 - _),不带 = 填充,且必须是规范编码——解码后再编码必须与原文一字不差,否则视为无效。
注意两点:
p、t在后续 HTTP 请求里按原字符串使用(不要解码后再重编码,也不要大小写转换);k(psk)要解码成 32 字节再作为 HMAC 密钥使用;f要解码成 16 字节再与自己算出的指纹比较。
示例(取自测试向量,非真实会话)
lom://pair?v=1&h=bG9tLmVudGVybW9kZXR3by5jb20&p=OgX7dsvX6Pp6hNZsT6w8Sw&t=FQw1yQZ_QDI9gaoYuU4eRwQdFFjrO-YJZWp5v_TVKaE&k=3yUTIoVtslOrzFoP_u6Mv94XCSibzlDW7C6unULIp3o&f=rp1mfT5Bdyg1iFGecQecVQ
其中 h=bG9tLmVudGVybW9kZXR3by5jb20 解码为 lom.entermodetwo.com。
3. 手机端校验步骤
任何一步失败都直接中止,提示「这不是有效的麓鸣配对码」(错误码 pairing.qr_invalid,此时不得发出任何网络请求):
- 总长度 ≤ 2048;
- 能按 URI 解析,且 scheme 为
lom、authority 为pair、路径为空、没有 fragment(#…); - 查询串按
&分割、每段按第一个=分成键值,键恰好是v h p t k f六个且无重复; v == "1"(其他版本见 08 版本策略);h是合法规范 base64url,解码结果是合法 UTF-8;主机名匹配[a-z0-9]+([.-][a-z0-9]+)*(小写字母、数字、.、-);- 主机名在 App 内置的白名单内。白名单目前只有
lom.entermodetwo.com。这是防止伪造二维码把手机引到别的服务器的关键检查,必须在 App 里写死,不得从二维码或网络动态取; p解码后恰为 16 字节,t为 32 字节,k为 32 字节,f为 16 字节,且全部是规范 base64url。
通过校验之后,才能用 https://<h 解码出的主机名> 发起第一个请求(03)。只允许 HTTPS,证书按系统默认校验。
4. 深链接入(Android)
在 AndroidManifest.xml 里给配对 Activity 声明:
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="lom" android:host="pair" />
</intent-filter>
收到的 Intent.data 就是配对链接字符串。系统相机、第三方扫码工具、浏览器点链接都可能触发它,所以深链进来的内容与手动输入一样不可信:走完整校验,并且必须停在「确认连接」页让用户确认,不能因为是深链就自动完成配对。
5. 配对码路径(没有二维码时)
扫不了码时,用户可以在手机上输入桌面显示的配对码。它不经过上面的链接,见 03 §3。输入规范化:
- 配对码是 8 个字符,显示为
XXXX-XXXX; - 字母表是 31 个字符:
23456789ABCDEFGHJKMNPQRSTUVWXYZ(去掉了易混淆的0 O 1 I L); - 用户输入不区分大小写,可以不输连字符,也可以带空格:去掉
-和空格、转大写后必须恰好 8 个字符且都在字母表内,否则本地提示「配对码不正确」(pairing.code_invalid),不必请求云端; - 提交给云端的
manual_code建议规范化成大写、带连字符的XXXX-XXXX形式(云端也接受不带连字符的形式)。
6. 局域网模式的区别
社区版桌面的局域网模式也使用 lom://pair?v=1&…,唯一区别是 h 解码后是 内网IPv4:端口(而不是云端主机名),且没有配对码路径。详见公开仓库 protocol/docs/LAN-PAIR-01.md。本文只覆盖官方云端模式。