iOS 设备认证 SDK
SDK Version: 1.0.0
Product:XTalkDeviceAuthSdk
认证产品不依赖 CoreBluetooth。客户通过 DeviceAuthTransport 把 BLE、UART 或其他串行 AT 通道接入认证器。
1. Transport 必须满足的契约
label是客户可读的传输名称,用于区分通道。atEncoding必须反映当前真实编码。setAtEncoding必须同时修改底层通道编码。runAtCommand必须串行化请求,并在返回错误前恢复干净的命令边界。.collectFullWindow与.recoverableEncodingProbe不得因普通 OK 或早到错误提前结束。- Task 取消、断链和超时必须能映射成 transport error;Auth 会转换成公开认证错误。
BLE adapter
import XTalkBleSdk
import XTalkDeviceAuthSdk
@MainActor
final class XTalkBleAuthTransport: DeviceAuthTransport {
let label = "ble"
private(set) var atEncoding: DeviceAuthAtEncoding = .plain
private let ble: XTalkBleClient
init(ble: XTalkBleClient) {
self.ble = ble
}
func setAtEncoding(_ encoding: DeviceAuthAtEncoding) {
atEncoding = encoding
switch encoding {
case .plain: ble.setAtEncoding(.plain)
case .xor: ble.setAtEncoding(.xor)
}
}
func runAtCommand(
_ command: String,
timeoutMs: Int,
log: Bool,
responseMode: DeviceAuthResponseMode
) async throws -> [String] {
let bleMode: XTalkAtResponseMode
switch responseMode {
case .automatic: bleMode = .automatic
case .collectFullWindow, .recoverableEncodingProbe:
bleMode = .collectUntilTimeout
}
return try await ble.runAtCommand(
command,
timeoutMs: timeoutMs,
log: log,
responseMode: bleMode
)
}
}
BLE adapter 直接复用 XTalkBleClient 的事务隔离。UART 等自定义 transport 也必须保证迟到响应不会进入下一条事务,否则编码探测或 Challenge 重试可能读取到上一条命令的数据。
执行认证
try await model.waitUntilAtReady()
let transport = XTalkBleAuthTransport(ble: model.ble)
let authenticator = XTalkDeviceAuthenticator(transport: transport)
do {
let result = try await authenticator.authenticate()
let did = result.identity.did
let selectedEncoding = result.encoding
let challenged = result.didRequireChallenge
onAuthenticated(did, selectedEncoding, challenged)
} catch let error as XTalkDeviceAuthError {
handleAuthenticationError(error)
}
transport 和 authenticator 应与 BLE client 一样被 session 强引用。不要每次点击认证都新建 transport 后立即释放。
默认配置为 probe 800 ms、strict probe 1200 ms、Challenge 4000 ms、DID 4000 ms、最多 3 次 Challenge。所有值必须大于 0。
同一认证器同时只允许一个会话;并发调用返回 .busy。页面离开、断链或切换设备时,取消等待认证的 Task,并调用 authenticator.cancel()。
认证结果中的 encoding 应继续用于后续 AT 操作。只有成功返回的 identity 才能进入客户业务状态。
3. 状态与结果
认证状态依次可能为 .probing、.challenging(attempt:maxAttempts:)、.readingIdentity,最后进入 .authenticated(identity)、.failed(error) 或 .cancelled。客户可以通过 $state 驱动 UI。
authenticator.$state
.receive(on: RunLoop.main)
.sink { state in
// 更新进度、错误提示或认证成功界面。
}
.store(in: &cancellables)
成功结果:
identity.deviceID:规范化后的 32 位设备标识。identity.did:推荐存储和展示的十六进制 DID 字符串。identity.serial:规范化序列号文本。identity.source:来自 EFUSE SN 或普通 SN 回退。encoding:认证成功时选中的.plain或.xor。didRequireChallenge:本次是否执行 Challenge。
4. 取消、重试和安全边界
private var authTask: Task<Void, Never>?
func beginAuthentication() {
authTask?.cancel()
authTask = Task { @MainActor in
do {
let result = try await authenticator.authenticate()
guard !Task.isCancelled else { return }
saveAuthenticatedIdentity(result.identity)
} catch is CancellationError {
// 正常页面退出。
} catch {
presentAuthenticationFailure(error)
}
}
}
func stopAuthentication() {
authTask?.cancel()
authenticator.cancel()
authTask = nil
}
认证失败默认 fail closed:不得沿用旧 DID 进入当前设备业务。重试前先确认同一设备仍连接且 atReady == true;通道状态不确定时先断开重连。
5. DID 工具
XTalkDeviceIDCodec 用于客户已有持久化数据的规范化、解析和格式化:
let parsed: UInt32? = XTalkDeviceIDCodec.parse("0x12345678")
if let parsed {
let canonical = XTalkDeviceIDCodec.canonicalize(parsed)
let text = XTalkDeviceIDCodec.format(canonical)
print(text)
}
业务通常直接使用认证结果中的 identity.did,不需要自行从原始设备响应拼 DID。