alpine-protocol-sdk 0.2.4

High-level SDK on top of the ALPINE protocol layer.
Documentation
use std::io;
use std::net::SocketAddr;

use alpine::messages::{CapabilitySet, DiscoveryReply, MessageType};
use serde_json::json;
use tokio::net::UdpSocket;
use tokio::task::JoinHandle;

#[derive(Debug, Clone, Copy)]
pub enum BadDeviceScenario {
    DropPackets,
    InvalidCbor,
    VendorOnlyStatus,
}

#[derive(Debug)]
pub struct BadDeviceSimulator {
    local_addr: SocketAddr,
    handle: JoinHandle<()>,
}

impl BadDeviceSimulator {
    pub async fn start(
        bind_addr: SocketAddr,
        scenario: BadDeviceScenario,
    ) -> Result<Self, io::Error> {
        let socket = UdpSocket::bind(bind_addr).await?;
        let local_addr = socket.local_addr()?;
        let handle = tokio::spawn(async move {
            let mut buf = vec![0u8; 2048];
            loop {
                let (len, peer) = match socket.recv_from(&mut buf).await {
                    Ok(res) => res,
                    Err(_) => continue,
                };
                if len == 0 {
                    continue;
                }
                match scenario {
                    BadDeviceScenario::DropPackets => {}
                    BadDeviceScenario::InvalidCbor => {
                        let _ = socket.send_to(&[0xff, 0x00, 0x01], peer).await;
                    }
                    BadDeviceScenario::VendorOnlyStatus => {
                        let mut caps = CapabilitySet::default();
                        caps.vendor_extensions = Some(
                            json!({
                                "vendor_only_status": true
                            })
                            .as_object()
                            .unwrap()
                            .iter()
                            .map(|(k, v)| (k.clone(), v.clone()))
                            .collect(),
                        );
                        let reply = DiscoveryReply {
                            message_type: MessageType::AlpineDiscoverReply,
                            alpine_version: alpine::messages::ALPINE_VERSION.to_string(),
                            device_id: "bad-device".to_string(),
                            manufacturer_id: "vendor-only".to_string(),
                            model_id: "sim".to_string(),
                            hardware_rev: "0".to_string(),
                            firmware_rev: "0".to_string(),
                            mac: "00:00:00:00:00:00".to_string(),
                            server_nonce: vec![0u8; 32],
                            capabilities: caps,
                            signature: vec![0u8; 64],
                            device_identity_pubkey: Vec::new(),
                            device_identity_attestation: Vec::new(),
                            device_identity_trusted: false,
                        };
                        if let Ok(payload) = serde_cbor::to_vec(&reply) {
                            let _ = socket.send_to(&payload, peer).await;
                        }
                    }
                }
            }
        });
        Ok(Self { local_addr, handle })
    }

    pub fn local_addr(&self) -> SocketAddr {
        self.local_addr
    }

    pub async fn stop(self) {
        self.handle.abort();
    }
}