Device pairing from XML
Extract a pairing payload from an XML tree, with proc collecting the required elements.
module devicePairing
import nanyx/encoding
-- Extracting a device-pairing payload from an XML element. The two kinds of
-- registration are told apart by a tag, and `proc` collects the required
-- sub-elements, failing as a whole if any is missing.
type Element = (
name: string
attrs: map(string, string)
children: list(Element)
text: string
)
def Element.subtag: (Element, string) -> #some(Element) | #notFound = { el, name ->
el.children \find { .name == name }
}
def Element.subtagText: (Element, string) -> #some(string) | #notFound = { el, name ->
el \subtag(name) \Option.map { .text }
}
def Element.subtagBinary: (Element, string) -> #some(Binary) | #notFound = { el, name ->
el \subtagText(name) \Option.map(Base64.decode)
}
def Element.attr: (Element, string) -> #some(string) | #notFound = { el, name -> el.attrs(name) }
-- A missing required element is an extraction failure
def required: (#some(a) | #notFound) -> #some(a) | #error(#extractionFailed) = {
| #some(value) -> #some(value)
| #notFound -> #error(#extractionFailed)
}
type CompanionRegistration = (
pubKey: string
ref: Binary
deviceIdentity?: string
keyIndexList?: Binary
keyIndexListTs?: int
refCert?: Binary
)
type TrustCircleOnboarding = (
appId: int
authKey: string
deviceIdentity: string
famDeviceId: Binary
keyIndexList: Binary
keyIndexListTs: int
ref: Binary
)
export type Payload =
| #companion(CompanionRegistration)
| #trustCircle(TrustCircleOnboarding)
export type Context = #companionRegistration | #trustCircleOnboarding
def keyIndexListTs: Element -> #some(int) | #notFound = { el ->
el \subtag("key_index_list") \Option.then { .attr("ts") } \Option.then(Int.parse)
}
export def extractPairDevicePayload: (Element, Context) -> #some(Payload) | #error(#extractionFailed) = {
| el, #companionRegistration -> proc {
def pubKey = try required(el \subtagText("pub_key"))
def ref = try required(el \subtagBinary("ref"))
-- The remaining elements are optional: a missing one becomes `#none`
def deviceIdentity = el \subtagText("device_identity") except #notFound -> #none
def keyIndexList = el \subtagBinary("key_index_list") except #notFound -> #none
def ts = keyIndexListTs(el) except #notFound -> #none
def refCert = el \subtagBinary("ref_cert") except #notFound -> #none
return #companion(CompanionRegistration(
pubKey = pubKey
ref = ref
deviceIdentity = deviceIdentity
keyIndexList = keyIndexList
keyIndexListTs = ts
refCert = refCert
))
}
| el, #trustCircleOnboarding -> proc {
return #trustCircle(TrustCircleOnboarding(
appId = try required(el \subtagText("app_id") \Option.then(Int.parse))
authKey = try required(el \subtagText("auth_key"))
deviceIdentity = try required(el \subtagText("device_identity"))
famDeviceId = try required(el \subtagBinary("fam_device_id"))
keyIndexList = try required(el \subtagBinary("key_index_list"))
keyIndexListTs = try required(keyIndexListTs(el))
ref = try required(el \subtagBinary("ref"))
))
}
}
-- Building the reply --------------------------------------------------------
def element: (string, list(Element)) -> Element = { name, children ->
Element(name = name, attrs = [], children = children, text = "")
}
def textElement: (string, string) -> Element = { name, text ->
Element(name = name, attrs = [], children = [], text = text)
}
export def pairSuccessReply: (deviceId: string, Payload) -> Element = { deviceId, payload ->
def device = Element(name = "device", attrs = ["id" => deviceId], children = [], text = "")
def identity =
match payload
| #companion(c) -> c.deviceIdentity \Option.map { id -> textElement("device_identity", id) }
| #trustCircle(t) -> #some(textElement("device_identity", t.deviceIdentity))
element("pair_success", [device, ...(identity \Option.toList)])
}
This program lives in the repository as examples/pairing.nyx.