use std::{collections::HashSet, fmt::Debug, ops::Deref};
use chrono::{DateTime, Utc};
use deku::DekuContainerRead;
use neli::{attr::Attribute, genl::Genlmsghdr};
use serde::{Deserialize, Serialize};
use super::{Error, Flags};
use crate::{
Bss,
ies::{self, Ie},
nl80211::{Attr, Cmd, ParseError},
};
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct Scan {
bss_list: Vec<Bss>,
wiphy: u32,
ifindex: u32,
freqs_mhz: Vec<u32>,
ies: Vec<Ie>,
flags: Option<Flags>,
start_time: DateTime<Utc>,
end_time: DateTime<Utc>,
}
impl Scan {
pub(super) fn new(scans: Vec<ScanInternal>) -> Result<Self, Error> {
if scans.is_empty() {
return Err(Error::EmptyScan);
}
Ok(Self {
bss_list: scans
.iter()
.flat_map(|scan| scan.bss_list.iter().cloned())
.collect(),
wiphy: scans.first().map(|scan| scan.wiphy()).unwrap_or_default(),
ifindex: scans.first().map(|scan| scan.ifindex()).unwrap_or_default(),
freqs_mhz: scans
.iter()
.flat_map(|scan| scan.freqs_mhz().iter().cloned())
.collect(),
ies: HashSet::<Ie>::from_iter(scans.iter().flat_map(|scan| scan.ies().iter().cloned()))
.into_iter()
.collect(),
flags: scans.first().and_then(|scan| scan.flags()),
start_time: scans
.first()
.map(|first_scan| first_scan.start_time())
.unwrap_or_default(),
end_time: scans
.last()
.map(|last_scan| last_scan.end_time())
.unwrap_or_default(),
})
}
pub fn bss_list(&self) -> &[Bss] {
&self.bss_list
}
pub fn wiphy(&self) -> u32 {
self.wiphy
}
pub fn ifindex(&self) -> u32 {
self.ifindex
}
pub fn freqs_mhz(&self) -> &[u32] {
&self.freqs_mhz
}
pub fn ies(&self) -> &[Ie] {
&self.ies
}
pub fn flags(&self) -> Option<crate::ScanFlags> {
self.flags
}
pub fn start_time(&self) -> DateTime<Utc> {
self.start_time
}
pub fn end_time(&self) -> DateTime<Utc> {
self.end_time
}
pub fn duration(&self) -> std::time::Duration {
(self.end_time() - self.start_time())
.to_std()
.unwrap_or_default()
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub(super) struct ScanInternal {
pub(super) bss_list: Vec<Bss>,
pub(super) scan_triggered: ScanTriggered,
pub(super) scan_completed: ScanCompleted,
}
impl ScanInternal {
fn wiphy(&self) -> u32 {
self.scan_completed.wiphy
}
fn ifindex(&self) -> u32 {
self.scan_completed.ifindex
}
fn freqs_mhz(&self) -> &[u32] {
&self.scan_completed.freqs_mhz
}
fn ies(&self) -> &[Ie] {
&self.scan_completed.ies
}
fn flags(&self) -> Option<Flags> {
self.scan_completed.flags
}
fn start_time(&self) -> DateTime<Utc> {
self.scan_triggered.timestamp
}
fn end_time(&self) -> DateTime<Utc> {
self.scan_completed.timestamp
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub(super) struct ScanTriggered {
params: ScanParams,
timestamp: DateTime<Utc>,
}
impl TryFrom<&Genlmsghdr<Cmd, Attr>> for ScanTriggered {
type Error = ParseError;
fn try_from(msghdr: &Genlmsghdr<Cmd, Attr>) -> Result<Self, Self::Error> {
if *msghdr.cmd() != Cmd::TriggerScan {
return Err(ParseError::UnexpectedCommand {
expected: Cmd::TriggerScan,
got: *msghdr.cmd(),
});
}
let params = ScanParams::try_from(msghdr)?;
Ok(Self {
params,
timestamp: Utc::now(),
})
}
}
impl Deref for ScanTriggered {
type Target = ScanParams;
fn deref(&self) -> &Self::Target {
&self.params
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub(super) struct ScanCompleted {
params: ScanParams,
timestamp: DateTime<Utc>,
}
impl TryFrom<&Genlmsghdr<Cmd, Attr>> for ScanCompleted {
type Error = ParseError;
fn try_from(msghdr: &Genlmsghdr<Cmd, Attr>) -> Result<Self, Self::Error> {
if *msghdr.cmd() != Cmd::NewScanResults {
return Err(ParseError::UnexpectedCommand {
expected: Cmd::NewScanResults,
got: *msghdr.cmd(),
});
}
let params = ScanParams::try_from(msghdr)?;
Ok(Self {
params,
timestamp: Utc::now(),
})
}
}
impl Deref for ScanCompleted {
type Target = ScanParams;
fn deref(&self) -> &Self::Target {
&self.params
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub(super) struct ScanParams {
pub(super) wiphy: u32,
pub(super) ifindex: u32,
pub(super) freqs_mhz: Vec<u32>,
pub(super) ies: Vec<Ie>,
pub(super) flags: Option<Flags>,
}
impl TryFrom<&Genlmsghdr<Cmd, Attr>> for ScanParams {
type Error = ParseError;
fn try_from(msghdr: &Genlmsghdr<Cmd, Attr>) -> Result<Self, Self::Error> {
let attr_handle = msghdr.attrs().get_attr_handle();
let wiphy = attr_handle.get_attr_payload_as::<u32>(Attr::Wiphy)?;
let ifindex = attr_handle.get_attr_payload_as::<u32>(Attr::Ifindex)?;
let scan_freqs_handle = attr_handle.get_nested_attributes::<u16>(Attr::ScanFrequencies)?;
let freqs_mhz = scan_freqs_handle
.iter()
.map(|attr| attr.get_payload_as::<u32>().unwrap_or_default())
.collect();
let ies = attr_handle
.get_attribute(Attr::Ie)
.map(|attr| ies::from_bytes(attr.payload().as_ref()))
.unwrap_or_default();
let flags = {
if let Ok(flags_payload) = attr_handle.get_attr_payload_as::<u32>(Attr::ScanFlags) {
let flags_bytes = flags_payload.to_le_bytes();
if let Ok((_, flags)) = Flags::from_bytes((&flags_bytes, 0)) {
Some(flags)
} else {
None
}
} else {
None
}
};
Ok(Self {
wiphy,
ifindex,
freqs_mhz,
ies,
flags,
})
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn scan_new_rejects_empty_sub_scan_list() {
assert!(matches!(Scan::new(Vec::new()), Err(Error::EmptyScan)));
}
}