iOS Device Authentication SDK
SDK Version: 1.0.0
Product:XTalkDeviceAuthSdk
The authentication product does not depend on CoreBluetooth. The customer connects BLE, UART, or another serialized AT channel through DeviceAuthTransport.
1. Transport contract
labelis a customer-readable channel name.atEncodingmust report the actual active encoding.setAtEncodingmust update the underlying channel encoding.runAtCommandmust serialize requests and restore a clean command boundary before returning an error..collectFullWindowand.recoverableEncodingProbemust not stop early on an ordinary OK or an early error.- Task cancellation, link loss, and timeout must surface as transport errors; Auth maps them to public authentication errors.
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
)
}
}
The BLE adapter reuses XTalkBleClient transaction isolation. A UART or other custom transport must also prevent late responses from entering the next transaction, or an encoding probe or Challenge retry can consume data from the preceding command.
Authenticate
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)
}
Retain the transport and authenticator for the lifetime of the session. Do not construct and immediately release a transport for each button tap.
Defaults are an 800 ms probe, 1,200 ms strict probe, 4,000 ms challenge, 4,000 ms DID query, and three challenge attempts. Every value must be greater than zero.
One authenticator permits one active session. Concurrent calls fail with .busy. When leaving the screen, disconnecting, or switching devices, cancel the task awaiting authentication and call authenticator.cancel().
Keep using the encoding returned by the result for subsequent AT operations. Only an identity returned by successful authentication may enter customer business state.
3. State and result
State can progress through .probing, .challenging(attempt:maxAttempts:), and .readingIdentity, then finish as .authenticated(identity), .failed(error), or .cancelled. Drive UI from $state when needed.
authenticator.$state
.receive(on: RunLoop.main)
.sink { state in
// Update progress, failure, or authenticated UI.
}
.store(in: &cancellables)
Successful result fields:
identity.deviceID: canonical 32-bit device identifier.identity.did: recommended hexadecimal DID for display and persistence.identity.serial: canonical serial text.identity.source: EFUSE SN or the regular SN fallback.encoding: selected.plainor.xorencoding.didRequireChallenge: whether this session performed Challenge verification.
4. Cancellation, retry, and security boundary
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 {
// Normal page exit.
} catch {
presentAuthenticationFailure(error)
}
}
}
func stopAuthentication() {
authTask?.cancel()
authenticator.cancel()
authTask = nil
}
Authentication fails closed. Never reuse an old DID for the current device after failure. Before retrying, verify that the same device remains connected and atReady == true; reconnect first when channel state is uncertain.
5. DID utilities
Use XTalkDeviceIDCodec to normalize, parse, and format identifiers already stored by the customer:
let parsed: UInt32? = XTalkDeviceIDCodec.parse("0x12345678")
if let parsed {
let canonical = XTalkDeviceIDCodec.canonicalize(parsed)
let text = XTalkDeviceIDCodec.format(canonical)
print(text)
}
Most business code should use identity.did from the authentication result instead of rebuilding a DID from raw device responses.