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();
}
}