use serde::{Deserialize, Serialize};
use crate::error::RvcsiError;
use crate::frame::CsiFrame;
use crate::ids::SessionId;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum AdapterKind {
File,
Replay,
Nexmon,
Esp32,
Intel,
Atheros,
Synthetic,
}
impl AdapterKind {
pub fn slug(self) -> &'static str {
match self {
AdapterKind::File => "file",
AdapterKind::Replay => "replay",
AdapterKind::Nexmon => "nexmon",
AdapterKind::Esp32 => "esp32",
AdapterKind::Intel => "intel",
AdapterKind::Atheros => "atheros",
AdapterKind::Synthetic => "synthetic",
}
}
}
impl core::fmt::Display for AdapterKind {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.write_str(self.slug())
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct AdapterProfile {
pub adapter_kind: AdapterKind,
pub chip: Option<String>,
pub firmware_version: Option<String>,
pub driver_version: Option<String>,
pub supported_channels: Vec<u16>,
pub supported_bandwidths_mhz: Vec<u16>,
pub expected_subcarrier_counts: Vec<u16>,
pub supports_live_capture: bool,
pub supports_injection: bool,
pub supports_monitor_mode: bool,
}
impl AdapterProfile {
pub fn offline(adapter_kind: AdapterKind) -> Self {
AdapterProfile {
adapter_kind,
chip: None,
firmware_version: None,
driver_version: None,
supported_channels: Vec::new(),
supported_bandwidths_mhz: Vec::new(),
expected_subcarrier_counts: Vec::new(),
supports_live_capture: false,
supports_injection: false,
supports_monitor_mode: false,
}
}
pub fn esp32_default() -> Self {
AdapterProfile {
adapter_kind: AdapterKind::Esp32,
chip: Some("ESP32-S3".to_string()),
firmware_version: None,
driver_version: None,
supported_channels: (1..=13).collect(),
supported_bandwidths_mhz: vec![20, 40],
expected_subcarrier_counts: vec![64, 128, 192],
supports_live_capture: true,
supports_injection: false,
supports_monitor_mode: false,
}
}
pub fn nexmon_default() -> Self {
AdapterProfile {
adapter_kind: AdapterKind::Nexmon,
chip: Some("BCM43455c0".to_string()),
firmware_version: None,
driver_version: None,
supported_channels: vec![1, 6, 11, 36, 40, 44, 48, 149, 153, 157, 161],
supported_bandwidths_mhz: vec![20, 40, 80],
expected_subcarrier_counts: vec![64, 128, 256],
supports_live_capture: true,
supports_injection: true,
supports_monitor_mode: true,
}
}
pub fn accepts_subcarrier_count(&self, count: u16) -> bool {
self.expected_subcarrier_counts.is_empty()
|| self.expected_subcarrier_counts.contains(&count)
}
pub fn accepts_channel(&self, channel: u16) -> bool {
self.supported_channels.is_empty() || self.supported_channels.contains(&channel)
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct SourceHealth {
pub connected: bool,
pub frames_delivered: u64,
pub frames_rejected: u64,
pub status: Option<String>,
}
impl SourceHealth {
pub fn fresh(connected: bool) -> Self {
SourceHealth {
connected,
frames_delivered: 0,
frames_rejected: 0,
status: None,
}
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct SourceConfig {
pub source: String,
#[serde(default)]
pub target: Option<String>,
#[serde(default)]
pub channel: Option<u16>,
#[serde(default)]
pub bandwidth_mhz: Option<u16>,
#[serde(default)]
pub replay_speed: Option<f32>,
#[serde(default)]
pub options_json: Option<String>,
}
impl SourceConfig {
pub fn new(source: impl Into<String>) -> Self {
SourceConfig {
source: source.into(),
target: None,
channel: None,
bandwidth_mhz: None,
replay_speed: None,
options_json: None,
}
}
pub fn target(mut self, t: impl Into<String>) -> Self {
self.target = Some(t.into());
self
}
pub fn channel(mut self, c: u16) -> Self {
self.channel = Some(c);
self
}
pub fn bandwidth_mhz(mut self, b: u16) -> Self {
self.bandwidth_mhz = Some(b);
self
}
}
pub trait CsiSource: Send {
fn profile(&self) -> &AdapterProfile;
fn session_id(&self) -> SessionId;
fn source_id(&self) -> &crate::ids::SourceId;
fn next_frame(&mut self) -> Result<Option<CsiFrame>, RvcsiError>;
fn health(&self) -> SourceHealth;
fn stop(&mut self) -> Result<(), RvcsiError> {
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn offline_profile_accepts_anything() {
let p = AdapterProfile::offline(AdapterKind::File);
assert!(p.accepts_subcarrier_count(57));
assert!(p.accepts_channel(999));
assert!(!p.supports_live_capture);
}
#[test]
fn esp32_profile_bounds() {
let p = AdapterProfile::esp32_default();
assert!(p.accepts_subcarrier_count(64));
assert!(!p.accepts_subcarrier_count(57));
assert!(p.accepts_channel(6));
assert!(!p.accepts_channel(36));
assert!(p.supports_live_capture);
}
#[test]
fn source_config_builder() {
let c = SourceConfig::new("nexmon").target("wlan0").channel(6).bandwidth_mhz(20);
assert_eq!(c.source, "nexmon");
assert_eq!(c.target.as_deref(), Some("wlan0"));
assert_eq!(c.channel, Some(6));
let json = serde_json::to_string(&c).unwrap();
assert_eq!(serde_json::from_str::<SourceConfig>(&json).unwrap(), c);
}
#[test]
fn adapter_kind_slug_display() {
assert_eq!(AdapterKind::Nexmon.slug(), "nexmon");
assert_eq!(AdapterKind::Esp32.to_string(), "esp32");
}
#[test]
fn health_fresh() {
let h = SourceHealth::fresh(true);
assert!(h.connected);
assert_eq!(h.frames_delivered, 0);
}
}