package com.sptmobile.pairing import kotlinx.serialization.Serializable import kotlinx.serialization.encodeToString import kotlinx.serialization.json.Json // [impl->REQ-GATEWAY-PERIPHERAL] /** * The pairing payload the host prints as a QR code (`spt-mobile-host * pair-code`): version, iroh node key to dial, the Mobile Gateway endpoint id * being paired with, and the bearer token. Wire shape is owned by * `rust/link-proto` (`QrPayload`) — field names here must match it exactly, * because [toWireJson] re-encodes for `LinkNative.connect`. * * W2 entry path is manual paste (APP-PLAN Q1), so [parse] is defensive about * user-mangled input: surrounding whitespace is tolerated, every failure mode * returns a message fit for the pairing screen, and nothing throws. */ @Serializable data class QrPayload( val v: Int, val node: String, val endpoint: String, val token: String, ) { /** Canonical JSON for the JNI seam (`LinkNative.connect`). */ fun toWireJson(): String = json.encodeToString(this) companion object { /** The only payload version this app understands. */ const val SUPPORTED_VERSION = 1 private val json = Json { ignoreUnknownKeys = true } sealed interface ParseResult { data class Ok(val payload: QrPayload) : ParseResult data class Err(val message: String) : ParseResult } fun parse(text: String): ParseResult { val trimmed = text.trim() if (trimmed.isEmpty()) return ParseResult.Err("paste the host's pair-code JSON") val payload = try { json.decodeFromString(trimmed) } catch (e: IllegalArgumentException) { return ParseResult.Err("not a pair-code payload: ${e.message}") } if (payload.v != SUPPORTED_VERSION) { return ParseResult.Err( "unsupported pair-code version ${payload.v} (this app speaks v$SUPPORTED_VERSION)" ) } if (payload.node.isBlank()) return ParseResult.Err("pair-code is missing the node key") if (payload.endpoint.isBlank()) return ParseResult.Err("pair-code is missing the endpoint id") if (payload.token.isBlank()) return ParseResult.Err("pair-code is missing the token") return ParseResult.Ok(payload) } } }