// Sensors on a LoRaWAN network: what a gateway forwards here is checked and decrypted with the
// device's session keys, a replay is refused, a Cayenne LPP payload becomes readings, and a device
// joins by OTAA, both ends deriving the same keys. No radio needed: the first frame is the one the
// lora-packet project publishes, and the others are made here.
//
//   quillo run lorawan.qwe      then open http://localhost:8080/uplink, /tampered, /cayenne and /join
//
// For your own devices, POST what the gateway's bridge gives you to /uplink as { "phy": "..." }:
// hex or base64. Keep keys out of the program: LORAWAN_NWK_SKEY and LORAWAN_APP_SKEY.

use "lorawan.qwer"

let keys = {
    nwk_skey: env("LORAWAN_NWK_SKEY", "44024241ed4ce9a68c6a8bc055233fd3"),
    app_skey: env("LORAWAN_APP_SKEY", "ec925802ae430ca77fd3dd73cb2cc588"),
}
let frame = "40F17DBE4900020001954378762B11FF0D"

route GET /uplink {
    let up = lorawan_open(frame, keys)?
    { device: up.dev_addr, count: up.fcnt, port: up.fport, text: up.text, mic: up.mic, parsed: lorawan_parse(frame)?.type }
}

route POST /uplink { json: { phy: str } } {
    let up = lorawan_open(phy, keys)
    if is_err(up) { reply 401 up.error }
    up
}

// One bit changed on the way, the wrong key, and an old frame sent again.
route GET /tampered = {
    changed: lorawan_open("40F17DBE4900020001954378762B11FF0E", keys).error,
    wrong_key: lorawan_verify(frame, { nwk_skey: "00000000000000000000000000000000" })?,
    right_key: lorawan_verify(frame, keys)?,
    replay: lorawan_open(frame, { ...keys, last_fcnt: 2 }).error,
    cut_short: lorawan_parse("40F17DBE49").error,
}

// A counter past 65535: only its low half is sent, and the MIC needs all of it.
route GET /rollover {
    let sent = lorawan_build({ dev_addr: "49BE7DF1", fcnt: 70000, fport: 2, text: "late", adr: true }, keys)?
    let up = lorawan_open(sent, { ...keys, last_fcnt: 69999 })?
    { on_the_wire: up.fcnt, widened: up.fcnt32, text: up.text, adr: up.adr, without_the_hint: lorawan_verify(sent, keys)? }
}

// MAC commands: in the clear in FOpts, or encrypted with the network key on port 0.
route GET /mac {
    let plain = lorawan_open(lorawan_build({ type: "confirmed-up", dev_addr: "26011F22", fcnt: 9, fopts: "06FE0A" }, keys)?, keys)?
    let hidden = lorawan_open(lorawan_build({ dev_addr: "26011F22", fcnt: 10, fport: 0, payload: "02050106FE0A" }, keys)?, keys)?
    {
        type: plain.type,
        in_fopts: plain.mac_commands,
        on_port_0: hidden.mac_commands | map((c) => c.command),
        both: lorawan_build({ dev_addr: "26011F22", fcnt: 11, fport: 0, fopts: "02" }, keys).error,
    }
}

route GET /cayenne = {
    two_temperatures: lorawan_cayenne_decode("03670110056700FF")?.values,
    negative: lorawan_cayenne_decode("0167FFD7")?.readings,
    moving: lorawan_cayenne_decode("067104D2FB2E0000")?.values,
    gps: lorawan_cayenne_decode("018806765ff2960a0003e8")?.values,
    written: lorawan_cayenne_encode([{ channel: 3, type: "temperature", value: 27.2 }, { channel: 5, type: "temperature", value: 25.5 }])?,
    cut_off: lorawan_cayenne_decode("036701").error,
    too_big: lorawan_cayenne_encode([{ channel: 1, type: "humidity", value: 500 }]).error,
}

// Over-the-air activation: the device asks, the network answers, both hold the same session keys,
// and the first uplink under those keys is read by the network.
route GET /join {
    let app_key = "2b7e151628aed2a6abf7158809cf4f3c"
    let request = lorawan_join_request({ join_eui: "70B3D57ED0000000", dev_eui: "0004A30B001C0530", dev_nonce: "2C8A" }, app_key)?
    let asked = lorawan_parse(request)?
    let answer = lorawan_join_accept(request, { dev_addr: "260B1234", net_id: "000013", app_nonce: "C4A1F0", rx_delay: 1 }, app_key)?
    let device = lorawan_join_accept_read(answer.packet, app_key, asked.dev_nonce)?
    let session = { nwk_skey: device.nwk_skey, app_skey: device.app_skey }
    let first = lorawan_build({ dev_addr: device.dev_addr, fcnt: 1, fport: 10, text: "hello" }, session)?
    {
        device_eui: asked.dev_eui,
        request_ok: lorawan_join_request_verify(request, app_key)?,
        wrong_app_key: lorawan_join_accept(request, { dev_addr: "260B1234", net_id: "000013" }, "00000000000000000000000000000000").error,
        same_keys: device.nwk_skey == answer.nwk_skey && device.app_skey == answer.app_skey,
        keys_differ: device.nwk_skey != device.app_skey,
        addr: device.dev_addr,
        net: device.net_id,
        first: lorawan_open(first, { nwk_skey: answer.nwk_skey, app_skey: answer.app_skey })?.text,
    }
}
