跳到主要内容

Android 设备认证 SDK

SDK Version: 1.0.0
Package: com.xtalk.mesh.sdk.auth

产品用途

能力概览

设备认证 SDK 在 BLE 或 UART 的 AT 通道上完成设备身份门槛:选择 BLE 可用的 plain/XOR AT 编码;需要 Challenge 验签时,只有 Challenge payload 可解析、签名验证通过,并且该 payload 后收到独立的 AT_OK,当前连接才进入已认证状态。随后 SDK 再独立读取结构化 DID,并尝试把 DID 应用到 xTalk 核心身份状态。

认证只证明当前连接上的设备身份。DID 获取、频率、速率、附加地址、厂商默认参数、RF 默认参数和工作模式共同决定业务 readiness,不能视为认证门槛的一部分。

职责与边界

SDK 负责

  • 生成随机 Challenge、解析设备回应、验证签名,并要求 Challenge payload 后出现独立终态 AT_OK
  • BLE 下先等待连接/GATT 就绪,再探测 plain/XOR AT 路径。
  • 优先用 AT+EFUSESN? 读取结构化 DID,识别 +EFUSESN 和真机固件返回的 +SN 别名;只有该响应无效时才调用 AT+SN?
  • 在 SDK 内校验 SN 字段、调用统一 codec 构造 DID,并通过 Result.deviceLongId 返回。
  • 将常见链路、命令、验签和 DID 失败转换为稳定的 Result.reason
  • 为 BLE 提供进程级并发合并入口,避免同时发起多次 Challenge。

宿主负责

  • 先建立 BLE 或 UART 链路。BLE 使用 SDK 托管入口,不自行构造 BLE RadioTransport;UART 提供可串行执行 AT 命令的 RadioTransportAtCommandTransport
  • 在后台线程调用认证方法;用 Result.ok/Result.authenticated 判断身份门槛,取得 DID 后才能执行 Device Init。
  • 不记录 Challenge 原始材料、签名全文或完整认证回应,不允许绕过验签失败。
  • 在设备重启、OTA 完成、链路身份变化或认证失效时清除旧状态并重新认证。
  • 不得仅因 DID 读取/DID 应用、默认参数、厂商初始化或 RF 初始化失败,就撤销当前连接的认证或再次发送 Challenge。
  • 根据 reason 提示、重连或停止流程;不得只因读到一个数字就视为认证成功。
  • 直接使用 auth.deviceLongId;不得从原始 +SN/+EFUSESN 行拼接或计算 DID。

适用范围与前提

支持链路

  • BLE:使用 XTalkBleClient 当前已连接且 GATT 就绪的链路;认证入口在 License 校验通过后由 SDK 内部取得受保护 transport。
  • UART:使用客户实现的 DeviceAuthenticator.AtCommandTransport,或由 UART SDK/RadioTransport 适配。
  • Android minSdk 26,SDK 以 compileSdk 36 和 JVM 17 构建。

调用前提

  • BLE 调用前应已收到 CONNECTED。认证方法仍会等待连接和 GATT,但它不能代替扫描、连接或权限处理。
  • UART 必须已打开,并能返回按 CR/LF 拆分的完整 AT 行。
  • 同一物理通道不得同时执行 Auth、Init、OTA 或其他 AT 会话。
  • Result.authenticatedResult.ok 完全相等;两者都只报告设备身份门槛。DID/readiness 必须另行检查 deviceLongIdreason

产物与依赖

Release AAR

项目当前交付事实
主产物device-auth-sdk-release.aar
直接依赖ble-sdk-release.aarradio-transport-sdk-release.aar、xTalk SDK 1.0.0 核心依赖包;BLE 的实际依赖还包括 androidx.core:core-ktx:1.18.0
本页完整示例额外依赖device-init-sdk-release.aarorg.jetbrains.kotlinx:kotlinx-coroutines-core:1.8.1
独立 Maven 坐标本认证适配产物当前没有独立远程 Maven 坐标
聚合依赖供应商授权的 Maven 端点可使用 com.xtalk:xtalk-sdk:1.0.0;仓库地址以交付通知为准

纯 AAR 交付时,应按交付清单同时引入上述 Release AAR 和完整核心依赖。flat AAR 没有 POM 传递依赖信息,必须显式加入 BLE 所需 AndroidX 及本页完整示例使用的 Init/Coroutines。

Gradle 示例

dependencies {
implementation("com.xtalk:xtalk-sdk:1.0.0")
implementation(files("libs/radio-transport-sdk-release.aar"))
implementation(files("libs/ble-sdk-release.aar"))
implementation(files("libs/device-auth-sdk-release.aar"))
implementation(files("libs/device-init-sdk-release.aar"))
implementation("androidx.core:core-ktx:1.18.0")
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.8.1")
}

权限与项目配置

Android 权限

认证 AAR 自身不声明新的 Android 运行时权限。BLE 认证继承 BLE 连接 SDK 的扫描/连接权限;UART 认证继承宿主设备节点访问要求。认证算法本身不需要麦克风、通知、存储或网络权限。

安全配置

  • 使用 JDK 17 和 JVM target 17。
  • 禁止把 Challenge 请求、签名回应或可还原认证材料写入客户分析平台。
  • AtCommandTransport.label 只填写稳定、非敏感的通道标签,例如 uart,不要包含密钥、DID 或用户信息。
  • 如需记录结果,只记录成功/失败、reason 和脱敏设备标识。

公开类型

DeviceAuthenticator

DeviceAuthenticator 是 Kotlin object,不需要构造。其进程级 BLE exclusive 状态会被所有调用者共享。

类型公开契约
AtCommandTransport客户 AT 通道接口:label: StringrunAtCommand(command: String, timeoutMs: Long, log: Boolean = true): List<String>
Result(ok: Boolean, deviceLongId: Long?, reason: String)认证结果及可选 DID。authenticatedok 的只读别名。reason == "did_read_failed" 时为 ok == trueauthenticated == truedeviceLongId == null:身份认证已通过,但业务尚未 ready。

AtCommandTransport 实现契约

runAtCommand 必须串行发送一条命令,并返回按 CR/LF 分隔后的完整行。关闭、设备错误、发送失败和超时应抛异常;Auth 会把认证阶段的常见异常转换为失败结果。timeoutMs 是该次命令等待期限,log=false 时实现方应关闭普通 AT 内容日志。

初始化与生命周期

无独立初始化

认证模块没有 initclosedeinit。它不拥有 BLE/UART 链路;链路创建与销毁由对应 transport 的生命周期所有者完成。

BLE 认证生命周期

连接 → GATT ready → authenticateBleAndReadDid... → 要求 Result.ok/Result.authenticated 为真 → 要求 DID 非空 → Device Init。DID 或 Init 失败只会让业务保持不可发送,不会撤销当前连接认证。只有断链、设备重启、OTA、身份变化、显式作废或认证门槛失败后才重新认证。

UART 认证生命周期

打开 UART → 捕获当前连接 token → 调用 token-bound authenticateUart → 只把认证提交给同一连接 → 使用同一 token 读取 DID → UART 初始化。DID/默认参数/厂商/RF 初始化失败时,同一连接仍保持已认证,不得再次发送 Challenge。关闭 UART 由宿主负责。

完整 API 参考

AT 通道适配

接口参数、返回与错误
AtCommandTransport.label: String人类可读通道标签,只用于日志。不得包含秘密。
AtCommandTransport.runAtCommand(command, timeoutMs, log = true): List<String>返回完整回应行;实现方负责超时、错误异常和串行化。
SDK 的 RadioTransport 重载会在内部创建 AT 适配器,客户不需要、也不能构造公开的 Auth RadioAtCommandTransport

认证状态

接口参数、返回与副作用
markAuthenticationRequired(reason: String, resetAtMode: Boolean = true)清 BLE wireless-ready、取消核心初始化序列和进程级 exclusive 结果;默认重置当前 BLE AT 编码。无返回值。
applyDeviceLongId(deviceLongId: Long, source: String): Boolean将 DID 格式化后应用到核心身份管理器;应用成功为 truesource 只用于日志且不得敏感。

认证方法

接口线程/前提默认值和当前行为返回与失败
authenticateBleAndReadDid(timeoutMs: Long = 30000): Result后台线程;XTalkBleClient 已连接且 GATT ready推荐客户入口。License 校验通过后由 SDK 内部创建受保护 BLE transport。当前 leader 流程采用固定阶段期限:连接 8 s、GATT 8 s、每种 AT 模式探测 1.5 s、Challenge 6 s、DID 4 s;timeoutMs 当前保留在签名中,不改变这些阶段期限选择 AT 模式,必要时执行 Challenge,再尝试读取 DID;okauthenticated 相等,进入 Init 前需另行要求 deviceLongId 非空
authenticateBleAndReadDidExclusive(timeoutMs: Long = 30000, forceReauth: Boolean = false): Boolean后台线程;同一进程只允许一个 leader推荐并发合并入口。并发 caller 加入当前任务,最多等待 timeoutMs + 8000forceReauth 先清状态。顺序调用仍会重新认证,不使用成功缓存跳过流程认证 Boolean,等于 leader 的 Result.ok;需要业务 ready 的 caller 仍须另行取得 DID。joiner 超时或 leader 失败为 false,不启动第二个 Challenge
authenticateBleAndReadDid(radioTransport, timeoutMs: Long = 30000): Result后台线程;已由其他公开产品入口提供 BLE RadioTransport高级组合重载;基础 BLE SDK 1.0.0 不向客户提供该 transport 的构造器或工厂返回语义与托管入口相同
authenticateBleAndReadDidExclusive(radioTransport, timeoutMs: Long = 30000, forceReauth: Boolean = false): Boolean后台线程;已由其他公开产品入口提供 BLE RadioTransport高级组合重载;不用于基础 BLE 客户接入返回语义与托管 exclusive 入口相同
authenticateUart(radioTransport, expectedConnectionToken, challengeTimeoutMs: Long = 10000): Result后台线程;使用支持连接 token 的 UART RadioTransport 当前 token完整 Challenge 事务绑定 expectedConnectionToken;token 过期或 transport 缺少该能力时,不会降级执行普通 AT 写入Challenge 签名及其独立终态 AT_OK 被接受后立即返回:ok == authenticated == truedeviceLongId == nullreason == "ok"
authenticateUartAndReadDid(transport, challengeTimeoutMs: Long = 10000, didTimeoutMs: Long = 6000): Result后台线程;UART 已打开且由 caller 独占兼容自定义 AT 通道;两个期限分别作用于 Challenge 和每次 DID 查询Challenge 失败为 ok == authenticated == false;Challenge 已通过但 DID 失败时为 ok == authenticated == truedeviceLongId == nullreason == "did_read_failed"
authenticateUartAndReadDid(radioTransport, challengeTimeoutMs: Long = 10000, didTimeoutMs: Long = 6000): Result后台线程;caller 持有稳定 UART 连接不带 expected token 的兼容重载返回语义同自定义 transport 重载;存在重连替换物理连接的可能时,优先使用 token-bound authenticateUart 再执行 token-bound DID 读取
readDeviceLongId(transport, timeoutMs: Long, log: Boolean): Long?后台线程;AT 通道可用依次执行 AT+EFUSESN?AT+SN?timeoutMs 分别用于每条命令返回第一个结构合法的 DID;异常、超时或无合法结构返回 null
readDeviceLongId(radioTransport, expectedConnectionToken, timeoutMs: Long, log: Boolean): Long?后台线程;使用 UART 认证时的同一连接 token两个 DID 查询都绑定 expectedConnectionToken,不使用无绑定 AT 降级路径返回该连接上的合法 DID,否则为 null

SN 响应字段与 DID 映射

AT+EFUSESN? 可以返回 +EFUSESN:+EFUSESN +SN: 或真机固件使用的 +SN 。结构化响应必须有 7 段或 8 段;前 6 段是 ASCII 十进制,第 7 段 是 ASCII 十六进制序列号,第 8 段是可选的 ASCII 十六进制 EFUSE/追溯字段。

段号真机值格式SDK 含义
160ASCII 十进制 UInt32制造元数据 1;校验但不参与 DID
2002ASCII 十进制 UInt32制造元数据 2;校验但不参与 DID
324ASCII 十进制 UInt32year,取低 5 bit 写入 DID
424ASCII 十进制 UInt32week,取低 7 bit 写入 DID
511ASCII 十进制 UInt32制造元数据 5;校验但不参与 DID
64ASCII 十进制 UInt32制造元数据 6;校验但不参与 DID
71EE70ASCII 十六进制 UInt32serial,取低 20 bit 写入 DID
800025018ASCII 十六进制 UInt32可选 EFUSE/追溯字段;校验但不参与 DID

真机响应:

AT+EFUSESN?
+SN 60:002:24:24:11:4:1EE70:00025018
AT_OK

Android 和 iOS 使用相同字段映射:

(24 & 0x1F) << 27 | (24 & 0x7F) << 20 | (0x1EE70 & 0xFFFFF)
= 0xC181EE70

因此 Android 返回 auth.deviceLongId == 0xC181EE70L,且不会继续调用 AT+SN?。第 8 段 00025018 不进入 DID;任一字段格式不合法时,整条响应 无效。业务端只使用 auth.deviceLongId,不得从原始 AT 回应手算 DID。

Challenge payload 可解析、签名验证通过,并收到该 payload 后的独立 AT_OK 时,当前连接立即进入已认证状态;Result.authenticatedResult.ok 报告同一个身份状态。DID 是否可用由 deviceLongId 独立报告;UART 组合方法还会用 reason == "did_read_failed" 表示 DID 阶段失败。

token-bound UART 认证前先捕获 radioTransport.currentConnectionToken,并原样传入。该事务不会向替换后的连接写入,也不会消费替换连接的回应。完整 Challenge 终态已被消费后,之后发生的 token 变化不会追溯改写返回结果;宿主把认证写入自身状态前,仍必须确认捕获的 token 当前有效。

DID 读取/DID 应用、默认参数、厂商初始化和 RF 初始化都属于 readiness 阶段。它们失败时不得清除认证,也不得在同一连接上重复 Challenge。只有断链、设备重启/OTA、连接身份替换、显式调用 markAuthenticationRequired 或认证门槛失败,才需要新的 Challenge。applyDeviceLongId 的独立 Boolean 结果不包含在 Result 中;如需确认核心身份已发布,应在后续连接层查询当前 DID。

Result.reason

reason含义建议处理
ok认证门槛成功;challenge-only UART 尚未读取 DID,组合方法也必须检查 deviceLongId只有其他 readiness 条件都满足后才能进入业务
not_connected8 秒内未进入 BLE CONNECTED检查状态并重连
ble_not_ready8 秒内 GATT/AT 路径未就绪断开重连,不要循环空转 Auth
at_path_unavailableplain/XOR AT+FREQ? 均未形成可接受回应检查固件兼容和链路
challenge_command_failedChallenge 命令异常、设备错误或超时保持未认证;确认链路后最多重试一次
challenge_verify_failedChallenge payload/签名/终态 AT_OK 校验失败;authenticated == false不得绕过;断开并联系技术支持
did_read_failedChallenge 认证已成功,但两个 DID 查询都没有合法结构化 ID;ok == authenticated == truedeviceLongId == null保持当前连接已认证、保持业务不可用,只恢复 DID/readiness 阶段,不得再次 Challenge

分功能示例

BLE 认证

val result = withContext(Dispatchers.IO) {
DeviceAuthenticator.authenticateBleAndReadDid(
timeoutMs = 30_000L,
)
}
if (!result.ok) {
handleAuthenticationFailure(result.reason)
} else if (result.deviceLongId == null) {
handleDidReadinessFailure(result.reason)
} else {
onAuthenticated(requireNotNull(result.deviceLongId))
}

合并并发 BLE 认证

val authenticated = withContext(Dispatchers.IO) {
DeviceAuthenticator.authenticateBleAndReadDidExclusive(
timeoutMs = 30_000L,
forceReauth = false,
)
}

绑定连接的 UART 认证与 DID 读取

val connectionToken = requireNotNull(uartRadioTransport.currentConnectionToken)
val auth = DeviceAuthenticator.authenticateUart(
radioTransport = uartRadioTransport,
expectedConnectionToken = connectionToken,
challengeTimeoutMs = 10_000L,
)
check(auth.ok)
check(uartRadioTransport.isConnectionTokenCurrent(connectionToken))

val deviceLongId = DeviceAuthenticator.readDeviceLongId(
radioTransport = uartRadioTransport,
expectedConnectionToken = connectionToken,
timeoutMs = 6_000L,
log = false,
)
if (deviceLongId == null) {
keepAuthenticatedButNotReady()
} else {
continueInitialization(deviceLongId, connectionToken)
}

主动作废认证

DeviceAuthenticator.markAuthenticationRequired(
reason = "device_reboot_or_ota",
resetAtMode = true,
)

完整接入示例

BLE 连接和认证

suspend fun authenticateBleDevice(device: XTalkDevice): Long? =
withContext(Dispatchers.IO) {
if (XTalkBleClient.connect(device, timeoutMs = 8_000) != ConnectResult.CONNECT_OK) return@withContext null
if (!XTalkBleClient.awaitConnected(device.macAddress, 8_000L)) return@withContext null
if (!XTalkBleClient.awaitBleReady(8_000L)) return@withContext null

val auth = DeviceAuthenticator.authenticateBleAndReadDid()
if (!auth.ok) {
XTalkBleClient.disconnect(sendRemoteCommand = false)
return@withContext null
}
auth.deviceLongId
}

基础 BLE SDK 1.0.0 已提供可直接复制的托管 Auth 入口,但尚未提供客户可取得 通用 BLE RadioTransport 的公开工厂。需要 Device Init 或通用 RF transport 时,必须使用交付产品明确提供的公开工厂/实例;不要自行调用非公开构造器。

状态/并发/资源与恢复

线程

所有认证、DID 读取和客户 runAtCommand 都是阻塞式 API,必须在 Dispatchers.IO 或专用工作线程调用。不要用“放入协程”误认为底层可取消;协程取消不保证中断正在执行的 AT 调用。

并发

BLE 推荐使用 authenticateBleAndReadDidExclusive 合并进程内并发请求。第一个 caller 是 leader,其他 caller 只等待同一结果。UART 没有内建全局锁,客户的 AtCommandTransport 必须与 Init、OTA 和业务 AT 串行。

资源与恢复

认证模块不持有 Activity、BLE 连接或 UART 文件描述符,因此没有独立释放接口。取消业务时停止后续步骤,等待认证调用返回,再由链路所有者断开。DID/默认参数/厂商/RF readiness 失败时,应保留同一连接的认证,只恢复失败的 readiness 阶段。只有连接或认证状态失效后才重连并重新 Challenge;验签失败不得自动降级或跳过。

常见问题

BLE 返回 not_connected

connect() 的成功枚举只表示请求已接受。先等待 CONNECTED,确认没有其他逻辑主动断开,再调用认证。

BLE 返回 at_path_unavailable

plain 和 XOR 两条 AT 路径都未通过频率回应门槛。检查 GATT ready、固件版本和是否存在并发 AT/OTA,不要手工固定一种编码绕过探测。

返回 challenge_verify_failed

这是身份门槛失败,不是普通网络抖动。立即保持设备不可用并断开,不记录或上传完整响应,联系供应商分析。

返回 did_read_failed

Challenge 已通过,所以当前连接的 okauthenticated 都保持 true,但 deviceLongIdnull。保持业务不可用,只恢复 DID/readiness,不要再次发送 Challenge。若连接已替换、设备已重启或认证被显式作废,则按新连接正常重新认证。

Exclusive 调用等待很久

caller 可能加入了已经执行中的认证;joiner 最多等待自身 timeoutMs + 8000。超时返回 false,不会取消 leader,也不会并行发第二次 Challenge。

关联 SDK 与下一步

接入顺序

  1. BLE 链路先完成 BLE 连接 SDK 的连接和 GATT ready。
  2. UART 链路先完成 UART 传输 SDK 的打开和 AT 通道适配。
  3. 认证成功后调用 设备初始化 SDK
  4. 统一 BLE/UART 调用时参考 RadioTransport