Skip to main content

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

  • label is a customer-readable channel name.
  • atEncoding must report the actual active encoding.
  • setAtEncoding must update the underlying channel encoding.
  • runAtCommand must serialize requests and restore a clean command boundary before returning an error.
  • .collectFullWindow and .recoverableEncodingProbe must 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 .plain or .xor encoding.
  • 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.

Open the API Reference