FMO (NFM Over Internet) 4.0 APRS 报文格式
面向读者:Web 地图/看板开发者、第三方客户端开发者、协议对接者。
目的:一份自包含的参考文档,用于解析和验证 APRS-IS 上的 FMO 消息。所有开发者均可通过根证书,验证一条FMO消息是否为真。
声明: FMO自V4后,将不再在APRS-IS广播任何加密内容,需身份确认内容,均使用数字签名技术保证其真实性。
目录
- 总览
- APRS 包头约定
- 通用 Payload 结构
- CERT Blob — 证书容器
- SIG — Ed25519 签名
- 消息类型:CQ 族
- 消息类型:BEACON 信标
- 消息类型:STATION 台站
- 消息类型:JOINT + EVENT 事件联合
- 消息类报文 (APFM00)
- 验证流程
- 常量与编码参考
- 完整示例
1. 总览
FMO(NFM Over Internet)设备使用全球 APRS-IS 网络作为发现媒介。设备广播结构化的 POSITION 和 STATUS 帧,携带身份、位置、能力和实时动作。V4 用 PKI 替代了共享密钥认证:每条广播包含用户证书(CERT blob)和 Ed25519 签名(SIG),私钥永不离开设备。
TOCALL 通道:
| 通道 | TOCALL | 用途 |
|---|---|---|
| 广播 | APFMO4 |
带 CERT + SIG 的 POSITION/STATUS 帧。发现、CQ、信标、台站、事件。 |
本文档详细介绍广播通道(APFMO4)。
2. APRS 包头约定
所有 V4 广播消息遵循此包头格式:
呼号-SSID>APFMO4,TCPIP*:有效载荷
| 组件 | 说明 |
|---|---|
呼号 |
发送设备的呼号(如 BG5ESN) |
-SSID |
APRS SSID,0–15。不属于证书身份字段,仅用于会话/路由上下文。 |
>APFMO4, |
TOCALL — V4 广播固定目标地址 |
TCPIP*: |
路径 — 表示消息来自 APRS-IS(非 RF) |
有效载荷 |
V4 payload,详见后续章节 |
使用的帧类型:
| 帧类型 | 前缀 | 使用场景 |
|---|---|---|
| POSITION | =<lat>N/<table><lon>E<code> |
CQ、BEACON、STATION、JOINT(除 EVENT STATUS 外的所有消息) |
| STATUS | > |
仅 EVENT STATUS(联合模式溢出容器) |
完整的 POSITION 帧(包头 + 坐标前缀 + payload)必须在 APRS-IS 512 字符限制之内。所有 V4 消息均在此范围内。
3. 通用 Payload 结构
Payload 部分(POSITION 坐标前缀之后)始终以以下格式开头:
FMO-V4,<TYPE>,...
Token 以逗号(,)分隔。逗号是保留分隔符,不得在文本字段中出现(EVENT.content 例外,见第 9 节)。
3.1 字段编码规则
| 数据类型 | 编码 | 示例 |
|---|---|---|
| 整数 (uid, port, filterKm 等) | 十进制 ASCII 文本 | 12345, 1883, 500 |
| 浮点数 (freq) | 固定 4 位小数 ASCII | 438.5000 |
| 文本 (name, url, rig, ant) | UTF-8 明文,不含逗号,不做 base64 | 安吉FMO中继 |
| 公钥 (32 字节) | Base64url,无 padding(=) |
K7gJ2XpQz9... |
| 签名 (64 字节) | Base64url,无 padding | d4Ef7GhJk1... |
| 哈希/指纹 (32 字节) | Base64url,无 padding | a1B2c3D4e5... |
| 坐标 (lat/lon) | APRS 原始字符串(度-分格式) | 3037.98N, 11932.20E |
3.2 Payload 中的关键前缀
| 前缀 | 含义 | 示例 |
|---|---|---|
FMO-V4, |
V4 协议标识 | — |
CERT: |
证书 blob(CBOR → base64url) | CERT:dXNLz5G... |
SIG: |
Ed25519 签名(base64url) | SIG:R8tY3UjHw5... |
SH: |
SHA-256 哈希(base64url),仅联合模式 | SH:y8xV4K... |
S |
服务器 UID 前缀 | S123 |
FREQ: |
工作频率 MHz | FREQ:438.5000 |
HEIGHT: |
天线高度 米 | HEIGHT:15 |
RIG: |
设备型号 | RIG:FT-5DR |
ANT: |
天线型号 | ANT:J型天线 |
P |
MQTT 端口前缀 | P1883 |
F |
覆盖范围 km,后缀 KM |
F500KM |
U |
在线/峰值用户数,格式 U<online>/<peak> |
U5/200 |
4. CERT Blob — 证书容器
这是最关键的结构。CERT blob 以紧凑二进制形式携带完整的自包含用户证书。
4.1 线上格式
CERT:<base64url( 10 元素 CBOR 数组 )>
生成步骤:
- 构造 10 元素的确定性 CBOR 数组(见下方)。
- 将 CBOR 字节编码为 base64url(无 padding)。
- 前面加上字符串
CERT:。
结果约 164–170 字符长(通常 168),例如:
CERT:dXNLz5G...Jq0A
4.2 CBOR 数组结构
CERT blob 是一个10 元素确定性 CBOR 数组。元素 0–8 构成 TBS(待签名)部分;元素 9 是 Intermediate CA 对元素 0–8 的 Ed25519 签名。
| 索引 | CBOR 类型 | 字段 | 说明 |
|---|---|---|---|
[0] |
text string (3) | protocol | 固定 "FMO" |
[1] |
unsigned integer | version | 固定 4 |
[2] |
text string (8) | messageType | 固定 "userCert" |
[3] |
unsigned integer | issuerSn | 签发 Intermediate CA 的序列号 |
[4] |
text string | callsign | 证书主体的呼号(大写 ASCII) |
[5] |
unsigned integer | uid | 用户唯一数字 ID |
[6] |
byte string (32) | publicKey | Ed25519 公钥(32 原始字节) |
[7] |
unsigned integer | iat | 签发时间戳(UTC Unix 秒) |
[8] |
unsigned integer | exp | 过期时间戳(UTC Unix 秒) |
[9] |
byte string (64) | caSignature | Intermediate CA 对元素 [0]–[8] 的 Ed25519 签名 |
TBS(待签名)数组 = 元素 [0] 至 [8]:
["FMO", 4, "userCert", issuerSn, callsign, uid, publicKey(32字节), iat, exp]
位于索引 [9] 的 caSignature 计算方式:
caSignature = Ed25519.sign(IntermediateCA私钥, 确定性CBOR编码(TBS数组))
4.3 certFingerprint 与 certBlobHash
| 哈希 | 定义 | 大小 | 用途 |
|---|---|---|---|
certFingerprint |
SHA-256(确定性CBOR编码(TBS数组)) = SHA-256(元素[0]–[8]的CBOR字节) |
32 字节 | SAS HTTP 认证(MQTT 密码)、CRL 查询 |
certBlobHash |
SHA-256(完整CERT blob原始CBOR字节) = SHA-256(元素[0]–[9]的CBOR字节) |
32 字节 | 所有消息类型的 SIG 签名数组 |
区别很重要:certFingerprint 仅覆盖 TBS(不含签名),而 certBlobHash 覆盖包含 CA 签名的完整 blob,防止 blob 替换攻击。
4.4 解析方法
# 1. 提取 "CERT:" 之后的 base64url 部分
b64 = payload.split("CERT:")[1].split(",")[0] # 取到下一个逗号前
# 2. Base64url 解码(无 padding)
cbor_bytes = base64url_decode(b64) # 约 125 原始字节
# 3. CBOR 解码为 10 元素数组
arr = cbor_decode(cbor_bytes) # 确定性 CBOR
assert arr[0] == "FMO"
assert arr[1] == 4
assert arr[2] == "userCert"
# 4. 提取字段
issuer_sn = arr[3] # 整数
callsign = arr[4] # 字符串(大写)
uid = arr[5] # 整数
public_key = arr[6] # bytes[32]
iat = arr[7] # 整数
exp = arr[8] # 整数
ca_sig = arr[9] # bytes[64]
# 5. 计算 certFingerprint
tbs_cbor = cbor_encode(arr[0:9]) # 仅元素 0-8
cert_fingerprint = sha256(tbs_cbor) # 32 字节
# 6. 计算 certBlobHash
cert_blob_hash = sha256(cbor_bytes) # 原始完整 CBOR 字节
CBOR 库建议:
- JavaScript:
cbor2(npm),cbor-x - Python:
cbor2(PyPI) - Go:
github.com/fxamacker/cbor - CBOR 为 RFC 8949。确定性编码指:整数用最小形式、文本/字节串用最短长度。
5. SIG — Ed25519 签名
每条广播 POSITION 帧都携带一个 SIG: token,包含 Ed25519 签名。签名不是对 APRS payload 文本的签名,而是对一个固定顺序的确定性 CBOR 数组(称为 TBS 数组)的签名,该数组重构了消息的语义字段。
5.1 域分离
所有签名数组以域分离前缀开始:
["FMO", 4, "<消息类型>", ...]
"FMO"— 协议标识(文本字符串)4— 协议版本(整数)"<消息类型>"— 以下之一:"CQ"、"OMCQ"、"VOCAL"、"ONLINE"、"BEACON"、"STATION"、"JOINT"、"userCert"(文本字符串)
这防止了跨上下文签名重放。
5.2 timeSalt — 防重放
每条消息级 SIG 包含一个 timeSalt 作为其最后一个元素:
timeSalt = floor(UTC Unix 时间戳 / 600)
- timeSalt 将时间划分为 10 分钟窗口。
- timeSalt 进入 CBOR 签名数组,但不出现在 APRS payload 文本中。
- 接收端用三个候选值验证:
timeSalt、timeSalt - 1、timeSalt + 1(±1 窗口容忍网络延迟和时钟偏差)。 - 消息在发送后最多 ~20 分钟内有效。
5.3 签验
对每种消息类型,接收端必须:
- 以完全相同的字段顺序重建完全相同的 CBOR 数组。
- 对该数组进行确定性 CBOR 编码。
- 验证
Ed25519.verify(来自cert的公钥, CBOR字节, 来自SIG token的签名)。
公钥来自 CERT blob(元素 [6]),在 cert 自身的 caSignature 已被受信任的 Intermediate CA 验证通过之后使用。
5.4 各消息类型的 TBS 数组
| 消息 | CBOR 数组元素 |
|---|---|
| caSig (CERT 内) | ["FMO", 4, "userCert", issuerSn(整数), callsign(文本), uid(整数), pubKey(bytes[32]), iat(整数), exp(整数)] — 9 元素 |
| CQ / OMCQ / VOCAL / ONLINE | ["FMO", 4, "<TYPE>", callsign(文本), ssid(整数), latStr(文本), lonStr(文本), certBlobHash(bytes[32]), serverUID(整数), timeSalt(整数)] — 10 元素 |
| BEACON | ["FMO", 4, "BEACON", callsign(文本), ssid(整数), latStr(文本), lonStr(文本), certBlobHash(bytes[32]), freq(文本"%.4f"), antHeight(整数,可选), rigName(文本,可选), antName(文本,可选), timeSalt(整数)] — 10–13 元素 |
| STATION | ["FMO", 4, "STATION", callsign(文本), ssid(整数), latStr(文本), lonStr(文本), certBlobHash(bytes[32]), cc(大写文本), name(文本), url(文本), port(整数), filterKm(整数), online(整数), peak(整数), timeSalt(整数)] — 16 元素 |
| JOINT | ["FMO", 4, "JOINT", callsign(文本), ssid(整数), latStr(文本), lonStr(文本), certBlobHash(bytes[32]), SH(bytes[32]), timeSalt(整数)] — 10 元素 |
5.5 进入 CBOR 编码前的重要规范化
| 字段 | 规范化 |
|---|---|
callsign |
大写 ASCII |
cc(国家码) |
大写,2 字符 |
latStr / lonStr |
APRS 原始字符串(如 "3037.98N"、"11932.20E") |
freq(频率) |
格式化为 "%.4f" → 文本字符串(如 "438.5000"),不是 CBOR float |
certBlobHash / SH |
原始 32 字节(不是 base64url) |
signature |
64 原始字节(不是 base64url) |
6. 消息类型:CQ 族
CQ、OMCQ、VOCAL、ONLINE 共用完全相同的线上格式 — 仅 <TYPE> 字段和接收端过滤策略不同。
6.1 线上格式
呼号-SSID>APFMO4,TCPIP*:=<lat>N/<table><lon>E<code> FMO-V4,<TYPE>,CERT:<blob>,S<serverUID>,SIG:<sig>
示例:
BG5ESN-15>APFMO4,TCPIP*:=3037.98N/11932.20EI FMO-V4,CQ,CERT:dXNLz5G...Jq0A,S123,SIG:R8tY3UjHw5...
6.2 字段参考
| Token | 位置 | 含义 |
|---|---|---|
FMO-V4 |
0 | 协议前缀 |
<TYPE> |
1 | CQ、OMCQ、VOCAL、ONLINE 之一 |
CERT:... |
2 | 证书 blob |
S<uid> |
3 | 发送者当前所连台站 UID(如 S123) |
SIG:... |
4(最后) | Ed25519 签名 |
| 类型 | 含义 | 触发方式 |
|---|---|---|
CQ |
通用呼叫 | 手动广播 |
OMCQ |
老友定向呼叫 | 手动广播 |
VOCAL |
语音活跃通知 | PTT ≥ 3 秒自动触发 |
ONLINE |
上线通知 | TNC 连接成功自动触发 |
6.3 TBS 签名数组
TBS = ["FMO", 4, msgType, # 域分离
callsign_upper, # 文本字符串(大写)
ssid, # 整数
latStr, # 文本字符串,如 "3037.98N"
lonStr, # 文本字符串,如 "11932.20E"
certBlobHash, # bytes[32]
serverUID, # 整数
timeSalt] # 整数, floor(utc/600)
数组长度:10 元素。
7. 消息类型:BEACON 信标
BEACON 广播设备的无线电配置。仅用于近距离发现(≤80 km)。
7.1 线上格式
呼号-SSID>APFMO4,TCPIP*:=<lat>N/<table><lon>E<code> FMO-V4,BEACON,CERT:<blob>,FREQ:<freq>,HEIGHT:<h>,RIG:<rig>,ANT:<ant>,SIG:<sig>
示例:
BG5ESN-15>APFMO4,TCPIP*:=3037.98N/11932.20EI FMO-V4,BEACON,CERT:dXNLz5G...Jq0A,FREQ:438.5000,HEIGHT:15,RIG:FT-5DR,ANT:J型天线,SIG:M5nL8VkPq2...
7.2 字段参考
| Token | 含义 | 可选? |
|---|---|---|
FREQ:<freq> |
工作频率 MHz(4 位小数) | 必填 |
HEIGHT:<h> |
天线高度 米(整数) | 可选 — 为 0 时省略 |
RIG:<rig> |
设备型号(UTF-8) | 可选 — 为空时省略 |
ANT:<ant> |
天线型号(UTF-8) | 可选 — 为空时省略 |
7.3 TBS 签名数组
TBS = ["FMO", 4, "BEACON",
callsign_upper, # 文本
ssid, # 整数
latStr, # 文本
lonStr, # 文本
certBlobHash, # bytes[32]
freqStr, # 文本 "%.4f",如 "438.5000"
# antHeight (整数) — 仅 > 0 时包含
# rigName (文本) — 仅非空时包含
# antName (文本) — 仅非空时包含
timeSalt] # 整数
基础长度:10 元素。可选字段会增加数组长度。
8. 消息类型:STATION 台站
STATION 是核心发现消息。台站管理员设备每 600 秒广播一次。携带其他设备用于连接的 MQTT 服务器地址和端口。
8.1 线上格式
呼号-SSID>APFMO4,TCPIP*:=<lat>N/<table><lon>E<code> FMO-V4,STATION,CERT:<blob>,<CC>,<name>,<url>,P<port>,F<km>KM,U<online>/<peak>,SIG:<sig>
示例:
BG5ESN-15>APFMO4,TCPIP*:=3037.98N/11932.20EI FMO-V4,STATION,CERT:dXNLz5G...Jq0A,CN,安吉FMO中继,fmo.bg5esn.com,P1883,F500KM,U5/200,SIG:H1zQ6RfWx8...
8.2 字段参考
| Token 顺序 | Token | 含义 | 格式 |
|---|---|---|---|
| 3 | CC |
国家码(ISO 3166-1 alpha-2) | 2 大写字符,如 CN、US、JP |
| 4 | name |
台站/服务器名称 | UTF-8 文本,不含逗号 |
| 5 | url |
MQTT broker 主机名或 IP | 如 fmo.bg5esn.com |
| 6 | P<port> |
MQTT broker 端口 | 如 P1883 |
| 7 | F<km>KM |
覆盖半径 km | 如 F500KM |
| 8 | U<online>/<peak> |
当前在线 / 历史峰值用户数 | 如 U5/200 |
| 最后 | SIG:... |
Ed25519 签名 | base64url |
8.3 TBS 签名数组
TBS = ["FMO", 4, "STATION",
callsign_upper, # 文本
ssid, # 整数
latStr, # 文本
lonStr, # 文本
certBlobHash, # bytes[32]
cc_upper, # 文本,2 字符
name, # 文本,UTF-8
url, # 文本
port, # 整数
filterKm, # 整数
online, # 整数
peak, # 整数
timeSalt] # 整数
数组长度:16 元素。
9. 消息类型:JOINT + EVENT 事件联合
EVENT 使用双帧联合模式:
- POSITION 帧(已签名)— 携带 CERT blob +
SH:(STATUS 内容的 SHA-256 哈希)+ SIG。 - STATUS 帧(未签名)— 携带实际事件内容。
STATUS 帧不单独签名;其完整性由 POSITION SIG 涵盖 SH 哈希来保护。
9.1 POSITION 帧
呼号-SSID>APFMO4,TCPIP*:=<lat>N/<table><lon>E<code> FMO-V4,JOINT,CERT:<blob>,SH:<hash>,SIG:<sig>
示例:
BG5ESN-15>APFMO4,TCPIP*:=3037.98N/11932.20EI FMO-V4,JOINT,CERT:dXNLz5G...Jq0A,SH:y8xV4K...mP2,SIG:Z3aB9CdEf1...
| Token | 含义 |
|---|---|
JOINT |
消息类型(POSITION 中始终为 JOINT,与事件类型无关) |
SH:<hash> |
SHA-256(STATUS payload 原始文本) → base64url(43 字符) |
9.2 STATUS 帧
呼号-SSID>APFMO4,TCPIP*:>FMO-V4,EVENT,<uid>,<topic>,<content>
示例:
BG5ESN-15>APFMO4,TCPIP*:>FMO-V4,EVENT,12345,WeatherAlert,QTH now heavy rain
| Token | 含义 | 约束 |
|---|---|---|
EVENT |
类型(在 STATUS 中,非 POSITION) | 固定 |
uid |
事件发布者 UID | 十进制整数 |
topic |
事件主题 | 最长 32 字符,不含逗号 |
content |
事件内容 | 最长 140 字符,可包含逗号(占据剩余全部行) |
9.3 联合模式流程
发送端:
1. 构造 STATUS raw = "FMO-V4,EVENT,<uid>,<topic>,<content>"
2. SH = SHA-256(STATUS raw) → base64url
3. 广播 POSITION(含 CERT + SH + SIG)
4. 广播 STATUS(纯文本,无 SIG)
接收端:
1. 收到 POSITION → 验证 caSig → 提取 pubkey → 验证 SIG → 存储 SH
2. 收到 STATUS → 计算 SHA-256(STATUS raw) → 与 SH 比对
3. SH 匹配 → 处理 EVENT(完整性由 POSITION SIG 保证)
9.4 TBS 签名数组
TBS = ["FMO", 4, "JOINT",
callsign_upper, # 文本
ssid, # 整数
latStr, # 文本
lonStr, # 文本
certBlobHash, # bytes[32]
SH, # bytes[32] — 原始 SHA-256,不是 base64url
timeSalt] # 整数
数组长度:10 元素。
10. 消息类报文 (APFM00)
点对点消息使用不同的 TOCALL(APFM00),不属于 V4 PKI 体系。不携带 CERT blob 或签名。受 APRS MESSAGE 67 字节限制。
线上格式:
呼号-SSID>APFM00,TCPIP*::目标9列 :<命令>,<参数...>{<序号>
关键约定:
::与:之间的目标字段固定 9 列,右侧空格填充。- 查询使用"无尾缀呼号"作为目标;回复使用"完整呼号"。
{INDEX为透明消息编号,应答/ACK 中原样回传。
| 命令 | 方向 | 用途 |
|---|---|---|
QTHQRY |
A → B | 查询好友状态 |
QTHANS |
B → A | 查询应答 |
CALL |
主叫 → 被叫 | 发起呼叫 |
CALLANS |
被叫 → 主叫 | 呼叫回执(RING / ACCEPT / REJECT / …) |
CALLCANCEL |
主叫 → 被叫 | 取消呼叫 |
ACK |
双向 | 消息确认 |
MSG |
双向 | 短消息 |
CONTROL |
控制端 → 设备 | 远程控制指令 |
CTLACK |
设备 → 控制端 | 控制应答 |
详见 docs/FMOAPRS消息协议.md。
11. 验证流程
接收端处理 V4 POSITION 帧的完整验证管道:
输入: 原始 APRS 字符串
1. 解析包头
- 从包头('>' 左侧)提取 callsign、ssid
- 从坐标前缀提取 latStr、lonStr
2. 解析 payload
- 按逗号分割
- 从 tokens[1] 识别消息类型
- 从以 "CERT:" 开头的 token 提取 CERT blob
- 从最后一个以 "SIG:" 开头的 token 提取签名
- 按消息类型提取其他字段
3. 解码 CERT blob
- 去掉 "CERT:" 前缀
- Base64url 解码 → 原始 CBOR 字节
- CBOR 解码 → 10 元素数组
- 提取: callsign, uid, pubKey, issuerSn, iat, exp, caSig
4. 呼号交叉验证
- cert.callsign(大写)必须 == 包头 callsign(大写,忽略 SSID)
- 失败 → CallMismatch,丢弃
5. 验证 caSig(证书链)
- 重建 TBS: ["FMO", 4, "userCert", issuerSn, callsign, uid, pubKey, iat, exp]
- 确定性 CBOR 编码 TBS
- Ed25519.verify(IntermediateCA.pubKey, TBS_CBOR, caSig)
- 失败 → CertChainInvalid,丢弃
6. 检查证书有效期
- iat ≤ nowUtc < exp
- 失败 → CertExpired,丢弃
7. 验证用户 SIG
- 按消息类型重建 TBS 数组(见第 5.4 节)
- 确定性 CBOR 编码 TBS
- 计算 timeSalt = floor(nowUtc / 600)
- 分别尝试 timeSalt, timeSalt-1, timeSalt+1
- Ed25519.verify(cert.pubKey, TBS_CBOR, sig_from_payload)
- 全部失败 → SigInvalid 或过期/重放,丢弃
8. (可选) 查询 CRL
- 在 Intermediate CRL 中查询 certFingerprint
- 在 Root CRL 中查询 issuerSn
- 找到 → CertRevoked,丢弃
9. 通过 → 处理业务逻辑
12. 常量与编码参考
12.1 加密常量
| 常量 | 值 | 说明 |
|---|---|---|
ED25519_PUBKEY_LEN |
32 字节 | Ed25519 公钥长度 |
ED25519_SIGN_LEN |
64 字节 | Ed25519 签名长度 |
CERT_FINGERPRINT_LEN |
32 字节 | SHA-256 输出(指纹 / blob hash) |
| SHA-256 输出 | 32 字节 | 用于指纹和 SH 的哈希算法 |
12.2 Base64url 编码
- 字母表:
A-Z、a-z、0-9、-、_(RFC 4648 §5) - 无 padding — 始终省略
=字符。 - 解码器应同时兼容带 padding 和不带 padding 的输入。
12.3 消息类型常量
| 常量 | 线上值 | 上下文 |
|---|---|---|
| TOCALL 广播 | APFMO4 |
V4 广播帧 |
| TOCALL 消息 | APFM00 |
点对点消息帧 |
| 路径 | TCPIP* |
APRS-IS 来源 |
| Payload 前缀 | FMO-V4 |
所有 V4 payload |
| CERT 前缀 | CERT: |
证书 blob |
| SIG 前缀 | SIG: |
Ed25519 签名 |
| SH 前缀 | SH: |
SHA-256 哈希(联合模式) |
12.4 CBOR 类型映射
| 语义类型 | CBOR 主类型 | 备注 |
|---|---|---|
| 协议字符串 (“FMO”) | text string (major 3) | 固定 |
| 消息类型字符串 | text string (major 3) | 如 “CQ”, “BEACON” |
| 大写呼号 | text string (major 3) | 编码前规范化 |
| 坐标 (lat/lon) | text string (major 3) | APRS 原始格式,如 “3037.98N” |
| 自由文本 (name, url) | text string (major 3) | UTF-8 |
| 整数 (uid, port 等) | unsigned integer (major 0) | 最小形式编码 |
| 公钥 (32 字节) | byte string (major 2) | 原始字节 |
| 签名 (64 字节) | byte string (major 2) | 原始字节 |
| 哈希 (32 字节) | byte string (major 2) | 原始字节 |
| 版本号 (4) | unsigned integer (major 0) | 最小形式 |
| 布尔值 | special (major 7, value 20/21) | 用于 CA 证书 (isCA) |
| 国家码 (2 字符) | text string (major 3) | 大写,2 字符 |
| freq (BEACON) | text string (major 3) | 格式化 “%.4f”,非 CBOR float |
13. 完整示例
13.1 CQ 广播解析
原始 APRS 帧:
BG5ESN-15>APFMO4,TCPIP*:=3037.98N/11932.20EI FMO-V4,CQ,CERT:g2FDhKdFmP...Lq0A,S123,SIG:R8tY3UjHw5...XpQz9
解析后的包头:
| 字段 | 值 |
|---|---|
| callsign | BG5ESN |
| ssid | 15 |
| latStr | 3037.98N |
| lonStr | 11932.20E |
解析后的 payload token:
| 索引 | Token | 含义 |
|---|---|---|
| 0 | FMO-V4 |
协议 |
| 1 | CQ |
消息类型 |
| 2 | CERT:g2FDhKdFmP...Lq0A |
证书 blob (base64url) |
| 3 | S123 |
服务器 UID = 123 |
| 4 | SIG:R8tY3UjHw5...XpQz9 |
Ed25519 签名 (base64url, 86 字符) |
CERT blob 解码(10 元素 CBOR 数组 → 逻辑字段):
| 字段 | 值 |
|---|---|
| protocol | "FMO" |
| version | 4 |
| type | "userCert" |
| issuerSn | 1001 |
| callsign | "BG5ESN" |
| uid | 1234 |
| publicKey | [32 字节] |
| iat | 1783291391 |
| exp | 1898999829 |
| caSignature | [64 字节] |
SIG TBS 数组(10 元素):
["FMO", 4, "CQ", "BG5ESN", 15, "3037.98N", "11932.20E", <certBlobHash 32B>, 123, <timeSalt>]
13.2 STATION 广播解析
原始 APRS 帧:
BG5ESN-15>APFMO4,TCPIP*:=3037.98N/11932.20EI FMO-V4,STATION,CERT:g2FDhKdFmP...,CN,安吉FMO中继,fmo.bg5esn.com,P1883,F500KM,U5/200,SIG:H1zQ6RfWx8...
解析后的 payload:
| 索引 | Token | 含义 |
|---|---|---|
| 0 | FMO-V4 |
协议 |
| 1 | STATION |
类型 |
| 2 | CERT:g2FDhKdFmP... |
证书 blob |
| 3 | CN |
国家码 |
| 4 | 安吉FMO中继 |
台站名称 |
| 5 | fmo.bg5esn.com |
MQTT 服务器 URL |
| 6 | P1883 |
MQTT 端口 = 1883 |
| 7 | F500KM |
覆盖范围 = 500 km |
| 8 | U5/200 |
在线=5, 峰值=200 |
| 9 | SIG:H1zQ6RfWx8... |
签名 |
SIG TBS 数组(16 元素):
["FMO", 4, "STATION",
"BG5ESN", 15, "3037.98N", "11932.20E",
<certBlobHash 32B>,
"CN", "安吉FMO中继", "fmo.bg5esn.com",
1883, 500, 5, 200, <timeSalt>]
13.3 EVENT 联合模式
POSITION 帧:
BG5ESN-15>APFMO4,TCPIP*:=3037.98N/11932.20EI FMO-V4,JOINT,CERT:g2FDhKdFmP...,SH:y8xV4KmP2...,SIG:Z3aB9CdEf1...
STATUS 帧:
BG5ESN-15>APFMO4,TCPIP*:>FMO-V4,EVENT,12345,WeatherAlert,QTH now heavy rain
接收端验证:
- 验证 POSITION SIG(涵盖 SH)。
- 计算
SHA-256("FMO-V4,EVENT,12345,WeatherAlert,QTH now heavy rain")= 原始 SH。 - 与 POSITION 中的
SH:比对。 - 匹配 → 信任 EVENT 内容。
附录:快速参考卡
所有 V4 消息类型一览
| Payload 类型 | 帧类型 | TBS 元素数 | 有 SIG | 描述 |
|---|---|---|---|---|
CQ |
POSITION | 10 | 是 | 通用呼叫 |
OMCQ |
POSITION | 10 | 是 | 老友定向呼叫 |
VOCAL |
POSITION | 10 | 是 | 语音活跃通知 |
ONLINE |
POSITION | 10 | 是 | 上线通知 |
BEACON |
POSITION | 10–13 | 是 | 设备信标 |
STATION |
POSITION | 16 | 是 | 服务器广播 |
JOINT |
POSITION | 10 | 是 | 联合模式锚点 |
EVENT |
STATUS | N/A | 否 | 联合模式内容 |
| (多种) | MESSAGE (APFM00) | N/A | 否 | 点对点消息 |
关键尺寸一览
| 项目 | 原始大小 | Base64url 大小 |
|---|---|---|
| Ed25519 公钥 | 32 字节 | 43 字符 |
| Ed25519 签名 | 64 字节 | 86 字符 |
| SHA-256 哈希 | 32 字节 | 43 字符 |
| CERT blob (CBOR) | ~125 字节 | ~164–168 字符 + 5 (CERT:) |
| 完整 CQ POSITION 帧 | — | ~339 字符 |
| 完整 STATION POSITION 帧 | — | ~411 字符 |
当前FMO国内所使用根证书和中间证书
//Root self sign CA Cert
{
"sn": 1,
"type": "rootCA",
"issuer": {
"name": "BG5ESN",
"email": "xifengzui@yeah.net"
},
"subject": {
"name": "BG5ESN",
"publicKey": "DCeeVS320f36ToVP2eOADVN-Q0LzpMYmiVkmNYzuysY"
},
"extensions": {
"isCA": true,
"pathLen": 1,
"crl": "https://bg5esn.com/share/ca/root_crl.json",
"license": "https://bg5esn.com/share/ca/license.md",
"keyId": "1"
},
"iat": 1779886213,
"exp": 2095505415,
"signatureAlgorithm": "Ed25519",
"signature": "9QTm906Da0Hf5z2HMdvT02tuYCHyYOcPTNi3Y1Bi-cqpos-Pms_ok7rScAwt2pUSQN34cuKydkyKwdus0N_6CA"
}
//Intermediate CA Cert
{
"sn": 1001,
"type": "intermediateCA",
"issuer": {
"sn": 1,
"name": "BG5ESN",
"publicKey": "DCeeVS320f36ToVP2eOADVN-Q0LzpMYmiVkmNYzuysY"
},
"subject": {
"name": "BG5ESN",
"email": "xifengzui@yeah.net",
"publicKey": "gYPN5agzrKZG2iyEztsVjGD1tVNLozHNm_km7n6OQyk"
},
"extensions": {
"isCA": true,
"pathLen": 0,
"keyId": "1",
"crl": "https://bg5esn.com/share/ca/intermediate_crl.json",
"license": "https://bg5esn.com/share/ca/intermediate_license.md",
"uidRange": {
"start": 1,
"end": 200000
},
"issuingCountries": [
"CN"
]
},
"iat": 1779886432,
"exp": 1937652834,
"signatureAlgorithm": "Ed25519",
"signature": "d-G7if4xAA5HTe39kT59tGhPY-WjLfXh1PgdpiR_T13zgNVuwHnWLiTORtWLhfW7kF1fN6YGpCpT9FFh0NKFDA"
}
文档版本:v1.0