协议

你的代码、电脑端服务器和手机之间的通信格式。每行一个 JSON 对象,因此任何语言都能驱动手机。

本页为译文。如与英文版不一致,以英文版为准。 English

本页是协议的摘要。规范性文本,包括密钥调度和中继帧,见仓库中的 PROTOCOL.md;命令本身定义在 commands.json 中。

端口

端口协议监听地址用途
8778UDP所有网络接口手机在 Wi-Fi 上查找电脑
8779TCP所有网络接口手机:明文、TLS 或 WebSocket,共用同一端口
8780TCP127.0.0.1你的代码:NDJSON、HTTP 或 WebSocket,共用同一端口

从你的代码到服务器

UTF-8 编码的 JSON,每行一个对象。每个请求都有一个由你选定的 id 和一个 cmd:

{"id":1,"cmd":"touch","device":"shelf-01","by":"id","value":"login"}
{"id":1,"ok":true,"via":"node","ms":412}
  • 只有一部手机在线时,可以省略 device。
  • 参数名与参考中一致。也可以按位置传值:{"id":2,"cmd":"tap","args":[540,1200]}。
  • touchById 这类简写也可以用作 cmd。
  • 单个结果值放在 value 中返回:{"id":3,"ok":true,"value":false}。
  • 错误:{"id":4,"ok":false,"error":"NOT_FOUND","msg":"…","retryable":true,"screen":"com.example/.Main"}。
  • 请求可以流水线式连续发送。发给不同手机的请求,回复可能乱序到达;同一部手机的回复保持顺序。

事件

发送 {"id":9,"cmd":"subscribe","events":["notification","device"]} 之后,该连接还会收到不带 id 的数据行:

{"event":"device","device":"k7d2q9xa","name":"shelf-01","state":"online","route":"lan"}
{"event":"notification","device":"k7d2q9xa","key":"0|com.kakao.talk|1|null|10123","package":"com.kakao.talk","title":"Kim","text":"See you at 3"}

类型:device、notification、screen(前台应用变化)、toast,以及 result(之前回复了 accepted 的命令的延迟结果)。

会切断网络的命令

data(false)、wifi(false)、airplane(true) 以及包含它们的 batch 会先回复 {"ok":true,"accepted":true},等手机重新连上后再发送 {"event":"result","id":…}。加上 "wait":true,最终结果就会作为回复返回。"offline_wait": seconds 用于覆盖命令等待离线手机的时长。

从服务器到手机

手机连接电脑后,发送两行明文握手:它的临时 P-256 密钥和一个 nonce,服务器以自己的这两项回应。随后双方用 HKDF-SHA256 从两个 ECDH 结果派生密钥:一个是临时密钥与临时密钥之间的(前向保密),一个是静态密钥与静态密钥之间的(双方证明各自的身份)。二维码配对时还会混入一个一次性令牌;代码配对时,双方都会显示一个由同一组密钥派生出的 6 位代码。

此后的每一行都是一个信封:

{"seq":0,"blob":"<base64url AES-256-GCM ciphertext>"}

seq 在每个方向上逐一递增,同时用作 nonce,所以重放或乱序的行会被拒绝。超过 512 KiB 的 blob 会拆分成多个部分,并带有 "more":true。

在信封内部,服务器发送带有自己 id 的命令,手机则给它发送的每条消息编上 n:

{"id":17,"cmd":"touch","by":"id","value":"com.kakao.talk:id/login","nth":0,"timeout":10}
{"id":17,"n":42,"ok":true,"via":"node","ms":412}

手机会保留消息,直到服务器确认 n,并记住最近 2000 个命令 ID,因此重连时既不会丢失,也不会重复。保活 ping 每 25 秒发送一次。

发现

{"droidline":"discover","v":1,"server":"k3j9d0a2mq"}
{"droidline":"here","v":1,"server":"k3j9d0a2mq","name":"OFFICE-PC","port":8779,"pub":"BD3x…","pairing":true}

这个回复本身不被信任;握手会核验电脑的密钥。

测试向量

test-vectors.json 包含固定的密钥、握手行、派生密钥、6 位代码和加密信封。Go 服务器、Android 应用和中继都用它做测试;用其他语言编写的实现也可以。