Skip to main content

Android Device Authentication SDK

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

Authentication selects a working AT encoding, verifies the challenge response, and reads the DID. Frequency, rate, and work-mode values belong to the Init SDK and are not authentication parameters.

Create the BLE RadioTransport

BleRadioTransport requires an event bridge and realtime-send marker callbacks. A no-op implementation is sufficient when the transport is used only for Auth/Init:

object NoOpRadioTransportEventBridge : RadioTransportEventBridge {
override fun addPacketListener(listener: RadioPacketListener) = Unit
override fun removePacketListener(listener: RadioPacketListener) = Unit
override fun addRealtimePacketListener(listener: RadioPacketListener) = Unit
override fun removeRealtimePacketListener(listener: RadioPacketListener) = Unit
override fun addCrcErrorListener(listener: RadioCrcErrorListener) = Unit
override fun removeCrcErrorListener(listener: RadioCrcErrorListener) = Unit
}

val radioTransport: RadioTransport = BleRadioTransport(
eventBridge = NoOpRadioTransportEventBridge,
clearRealtimeNoWaitMark = {},
markRealtimeNoWaitSend = { _ -> },
)

If the application consumes RF packets or CRC events, replace the no-op bridge with a customer implementation of RadioTransportEventBridge.

BLE authentication

After the connection and GATT path are ready, authenticate on a background dispatcher:

val auth = withContext(Dispatchers.IO) {
DeviceAuthenticator.authenticateBleAndReadDid(
radioTransport = radioTransport,
timeoutMs = 30_000L,
)
}

if (!auth.ok) {
handleAuthenticationFailure(auth.reason)
} else {
val did = auth.deviceLongId
onAuthenticated(did)
}

Use the exclusive API to coalesce concurrent authentication requests:

val ok = DeviceAuthenticator.authenticateBleAndReadDidExclusive(
radioTransport = radioTransport,
timeoutMs = 30_000L,
forceReauth = false,
)

After a device restart, OTA completion, or invalidated authentication state, set forceReauth = true or call markAuthenticationRequired(reason, resetAtMode) first.

UART authentication

The customer implements AtCommandTransport; the SDK does not own the customer's UART driver:

val uartTransport = object : DeviceAuthenticator.AtCommandTransport {
override val label: String = "uart"
override fun runAtCommand(
command: String,
timeoutMs: Long,
log: Boolean,
): List<String> = myUart.runAtCommand(command, timeoutMs, log)
}

val auth = DeviceAuthenticator.authenticateUartAndReadDid(
transport = uartTransport,
challengeTimeoutMs = 10_000L,
didTimeoutMs = 6_000L,
)

Do not log challenge secrets or publish complete authentication responses. Initialize the device only after authentication succeeds.

Complete interface contract

AtCommandTransport

interface AtCommandTransport {
val label: String
fun runAtCommand(command: String, timeoutMs: Long, log: Boolean = true): List<String>
}

label is a readable non-secret log tag. runAtCommand serializes one transaction, returns complete CR/LF-delimited lines, and throws for a closed link, device error, or timeout. Auth converts exceptions to Result.reason. RadioAtCommandTransport(radioTransport) is recommended: it collects a complete Challenge response and fails fast for other commands.

Authentication methods

APIPrecondition/threadParametersReturn/failure
authenticateBleAndReadDid(radioTransport, timeoutMs = 30000)BLE CONNECTED; backgroundOverall budgetSelect AT mode, optional Challenge, read/apply DID; full Result
authenticateBleAndReadDidExclusive(radioTransport, timeoutMs = 30000, forceReauth = false)SameforceReauth clears stateCoalesces calls; joiners wait at most timeoutMs + 8000; Boolean only
authenticateUartAndReadDid(transport, challengeTimeoutMs = 10000, didTimeoutMs = 6000)UART open; backgroundSeparate deadlinesChallenge + DID, applies successful DID; Result
markAuthenticationRequired(reason, resetAtMode = true)Before reboot/OTA/recoveryReason and BLE AT resetClears Auth/wireless-ready state
readDeviceLongId(transport, timeoutMs, log)Authenticated/readable deviceTransport/deadline/logTries AT+EFUSESN? then AT+SN?; structured DID or null
applyDeviceLongId(deviceLongId, source)Verified DID32-bit ID/source tagtrue when stored in core identity state

Result.reason

reasonMeaningSafe recovery
okAuth and DID succeededContinue to Init
not_connectedNot CONNECTEDInspect state and reconnect
ble_not_readyGATT/AT not readyDisconnect/reconnect; do not spin Auth
at_path_unavailableNormal/XOR probes both failedCheck firmware/link compatibility
challenge_command_failedSend, device error, or timeoutStay unauthenticated; retry once after link validation
challenge_verify_failedSignature verification failedNever bypass; disconnect/contact support
did_read_failedNo valid structured DIDDo not Init; reconnect/check firmware

Only ok == true authorizes Init. A DID in a failed intermediate result is not authenticated identity. Android Auth has no independent cancel API; coroutine cancellation does not guarantee interruption of a blocking call. Stop later steps, wait for return, then disconnect. An exclusive joiner timeout returns false and does not start a parallel Challenge.

Next: Device Initialization