mirror of
https://github.com/rzuasti/oott.git
synced 2026-07-08 19:21:54 +02:00
Record originating scanner on device events
So that consumers of the events API can tell whether a sighting came from the ARP scanner or the mDNS listener. Adds a DeviceEventScanner enum (Arp / Mdns) plumbed from each scanner's call site through to a new scanner column on device_events, exposed via the existing events endpoint and OpenAPI schema. Pre-existing rows are backfilled with 'ARP' since that was the only scanner before mDNS. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.7
parent
0e43194492
commit
d6cf9be412
@@ -0,0 +1 @@
|
||||
ALTER TABLE device_events ADD COLUMN scanner TEXT NOT NULL DEFAULT 'ARP' COLLATE NOCASE;
|
||||
@@ -12,13 +12,14 @@ pub fn insert(event: DeviceEvent) -> Result<i64, DbError> {
|
||||
let mac_address = normalize_mac(&event.mac_address);
|
||||
|
||||
match conn.execute(
|
||||
"INSERT INTO device_events (mac_address, created_on, event_type, ipv4_address, vendor) VALUES (?1, ?2, ?3, ?4, ?5)",
|
||||
"INSERT INTO device_events (mac_address, created_on, event_type, ipv4_address, vendor, scanner) VALUES (?1, ?2, ?3, ?4, ?5, ?6)",
|
||||
params![
|
||||
mac_address,
|
||||
event.created_on.to_rfc3339_opts(chrono::SecondsFormat::Nanos, false),
|
||||
event.event_type,
|
||||
event.ipv4_address,
|
||||
event.vendor
|
||||
event.vendor,
|
||||
event.scanner
|
||||
],
|
||||
) {
|
||||
Ok(_) => {
|
||||
@@ -42,7 +43,7 @@ pub fn list(
|
||||
let conn = db::get_db_connection();
|
||||
|
||||
let mut sql_statement =
|
||||
"SELECT id, mac_address, created_on, event_type, ipv4_address, vendor FROM device_events WHERE 1=1"
|
||||
"SELECT id, mac_address, created_on, event_type, ipv4_address, vendor, scanner FROM device_events WHERE 1=1"
|
||||
.to_string();
|
||||
|
||||
let mut params: Vec<rusqlite::types::Value> = Vec::new();
|
||||
@@ -83,6 +84,7 @@ pub fn list(
|
||||
event_type: row.get(3)?,
|
||||
ipv4_address: row.get(4)?,
|
||||
vendor: row.get(5)?,
|
||||
scanner: row.get(6)?,
|
||||
})
|
||||
})?
|
||||
.collect::<Result<_, _>>()?;
|
||||
@@ -113,7 +115,7 @@ fn read(id: i64) -> Option<DeviceEvent> {
|
||||
let conn = db::get_db_connection();
|
||||
|
||||
let result: Result<DeviceEvent, rusqlite::Error> = conn.query_one(
|
||||
"SELECT id, mac_address, created_on, event_type, ipv4_address, vendor FROM device_events WHERE id=?1",
|
||||
"SELECT id, mac_address, created_on, event_type, ipv4_address, vendor, scanner FROM device_events WHERE id=?1",
|
||||
params![id],
|
||||
|row| {
|
||||
Ok(DeviceEvent {
|
||||
@@ -123,6 +125,7 @@ fn read(id: i64) -> Option<DeviceEvent> {
|
||||
event_type: row.get(3)?,
|
||||
ipv4_address: row.get(4)?,
|
||||
vendor: row.get(5)?,
|
||||
scanner: row.get(6)?,
|
||||
})
|
||||
},
|
||||
);
|
||||
@@ -146,7 +149,10 @@ mod tests {
|
||||
use chrono::Utc;
|
||||
|
||||
use super::*;
|
||||
use crate::{model::device_events::DeviceEventType, tests_common};
|
||||
use crate::{
|
||||
model::device_events::{DeviceEventScanner, DeviceEventType},
|
||||
tests_common,
|
||||
};
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_insert() {
|
||||
@@ -159,6 +165,7 @@ mod tests {
|
||||
DeviceEventType::NewDevice,
|
||||
"192.168.0.1".to_string(),
|
||||
"Vendor 1".to_string(),
|
||||
DeviceEventScanner::Arp,
|
||||
))
|
||||
.unwrap();
|
||||
|
||||
@@ -173,6 +180,7 @@ mod tests {
|
||||
assert_eq!(event.event_type, DeviceEventType::NewDevice);
|
||||
assert_eq!(event.ipv4_address, "192.168.0.1");
|
||||
assert_eq!(event.vendor, "Vendor 1");
|
||||
assert_eq!(event.scanner, DeviceEventScanner::Arp);
|
||||
|
||||
let inserted_id = insert(DeviceEvent::new(
|
||||
"bb:bb:bb:bb:bb:bb".to_string(),
|
||||
@@ -180,11 +188,13 @@ mod tests {
|
||||
DeviceEventType::DeviceSeen,
|
||||
"192.168.0.2".to_string(),
|
||||
"Vendor 2".to_string(),
|
||||
DeviceEventScanner::Mdns,
|
||||
))
|
||||
.unwrap();
|
||||
|
||||
let event = read(inserted_id).unwrap();
|
||||
assert_eq!(event.event_type, DeviceEventType::DeviceSeen);
|
||||
assert_eq!(event.scanner, DeviceEventScanner::Mdns);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
@@ -243,6 +253,7 @@ mod tests {
|
||||
DeviceEventType::NewDevice,
|
||||
"10.0.0.200".to_string(),
|
||||
"Old Vendor".to_string(),
|
||||
DeviceEventScanner::Arp,
|
||||
))
|
||||
.unwrap();
|
||||
|
||||
@@ -252,6 +263,7 @@ mod tests {
|
||||
DeviceEventType::DeviceSeen,
|
||||
"10.0.0.200".to_string(),
|
||||
"Old Vendor".to_string(),
|
||||
DeviceEventScanner::Arp,
|
||||
))
|
||||
.unwrap();
|
||||
|
||||
@@ -285,6 +297,7 @@ mod tests {
|
||||
DeviceEventType::NewDevice,
|
||||
"10.0.0.1".to_string(),
|
||||
"Vendor C".to_string(),
|
||||
DeviceEventScanner::Arp,
|
||||
))
|
||||
.unwrap();
|
||||
insert(DeviceEvent::new(
|
||||
@@ -295,6 +308,7 @@ mod tests {
|
||||
DeviceEventType::DeviceSeen,
|
||||
"10.0.0.1".to_string(),
|
||||
"Vendor C".to_string(),
|
||||
DeviceEventScanner::Arp,
|
||||
))
|
||||
.unwrap();
|
||||
insert(DeviceEvent::new(
|
||||
@@ -305,6 +319,7 @@ mod tests {
|
||||
DeviceEventType::DeviceSeen,
|
||||
"10.0.0.1".to_string(),
|
||||
"Vendor C".to_string(),
|
||||
DeviceEventScanner::Arp,
|
||||
))
|
||||
.unwrap();
|
||||
|
||||
@@ -337,6 +352,7 @@ mod tests {
|
||||
DeviceEventType::NewDevice,
|
||||
"10.0.0.1".to_string(),
|
||||
"Vendor F".to_string(),
|
||||
DeviceEventScanner::Arp,
|
||||
))
|
||||
.unwrap();
|
||||
insert(DeviceEvent::new(
|
||||
@@ -347,6 +363,7 @@ mod tests {
|
||||
DeviceEventType::DeviceSeen,
|
||||
"10.0.0.1".to_string(),
|
||||
"Vendor F".to_string(),
|
||||
DeviceEventScanner::Arp,
|
||||
))
|
||||
.unwrap();
|
||||
|
||||
@@ -379,6 +396,7 @@ mod tests {
|
||||
DeviceEventType::NewDevice,
|
||||
"10.0.0.1".to_string(),
|
||||
"Vendor D".to_string(),
|
||||
DeviceEventScanner::Arp,
|
||||
))
|
||||
.unwrap();
|
||||
insert(DeviceEvent::new(
|
||||
@@ -387,6 +405,7 @@ mod tests {
|
||||
DeviceEventType::DeviceSeen,
|
||||
"10.0.0.1".to_string(),
|
||||
"Vendor D".to_string(),
|
||||
DeviceEventScanner::Arp,
|
||||
))
|
||||
.unwrap();
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ use std::error::Error;
|
||||
use std::time::Duration;
|
||||
|
||||
use crate::db;
|
||||
use crate::model::device_events::{DeviceEvent, DeviceEventType};
|
||||
use crate::model::device_events::{DeviceEvent, DeviceEventScanner, DeviceEventType};
|
||||
use crate::model::devices::Device;
|
||||
use crate::model::notifications::Notification;
|
||||
use crate::model::notifications::NotificationType;
|
||||
@@ -52,13 +52,17 @@ fn send_notification(notification: Notification) -> Result<(), Box<dyn Error>> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn trigger_new_device(device: Device) -> Result<(), Box<dyn Error>> {
|
||||
pub fn trigger_new_device(
|
||||
device: Device,
|
||||
scanner: DeviceEventScanner,
|
||||
) -> Result<(), Box<dyn Error>> {
|
||||
let event = DeviceEvent::new(
|
||||
device.mac_address.clone(),
|
||||
Utc::now(),
|
||||
DeviceEventType::NewDevice,
|
||||
device.ipv4_address.clone(),
|
||||
device.vendor.clone(),
|
||||
scanner,
|
||||
);
|
||||
if let Err(err) = db::device_events::insert(event) {
|
||||
error!(
|
||||
@@ -89,6 +93,7 @@ pub fn trigger_new_device(device: Device) -> Result<(), Box<dyn Error>> {
|
||||
pub fn trigger_existing_device(
|
||||
existing_device: Device,
|
||||
new_device: Device,
|
||||
scanner: DeviceEventScanner,
|
||||
) -> Result<(), Box<dyn Error>> {
|
||||
let event = DeviceEvent::new(
|
||||
new_device.mac_address.clone(),
|
||||
@@ -96,6 +101,7 @@ pub fn trigger_existing_device(
|
||||
DeviceEventType::DeviceSeen,
|
||||
new_device.ipv4_address.clone(),
|
||||
new_device.vendor.clone(),
|
||||
scanner,
|
||||
);
|
||||
if let Err(err) = db::device_events::insert(event) {
|
||||
error!(
|
||||
|
||||
@@ -15,6 +15,7 @@ pub struct DeviceEvent {
|
||||
pub event_type: DeviceEventType,
|
||||
pub ipv4_address: String,
|
||||
pub vendor: String,
|
||||
pub scanner: DeviceEventScanner,
|
||||
}
|
||||
|
||||
impl DeviceEvent {
|
||||
@@ -24,6 +25,7 @@ impl DeviceEvent {
|
||||
event_type: DeviceEventType,
|
||||
ipv4_address: String,
|
||||
vendor: String,
|
||||
scanner: DeviceEventScanner,
|
||||
) -> Self {
|
||||
Self {
|
||||
id: -1,
|
||||
@@ -32,6 +34,7 @@ impl DeviceEvent {
|
||||
event_type,
|
||||
ipv4_address,
|
||||
vendor,
|
||||
scanner,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -40,13 +43,14 @@ impl fmt::Display for DeviceEvent {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(
|
||||
f,
|
||||
"id={}, mac_address={}, created_on={}, event_type={}, ipv4_address={}, vendor={}",
|
||||
"id={}, mac_address={}, created_on={}, event_type={}, ipv4_address={}, vendor={}, scanner={}",
|
||||
self.id,
|
||||
self.mac_address,
|
||||
self.created_on,
|
||||
self.event_type,
|
||||
self.ipv4_address,
|
||||
self.vendor,
|
||||
self.scanner,
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -109,3 +113,56 @@ impl FromSql for DeviceEventType {
|
||||
.map_err(|e| FromSqlError::Other(Box::new(e)))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, ToSchema)]
|
||||
pub enum DeviceEventScanner {
|
||||
Arp,
|
||||
Mdns,
|
||||
}
|
||||
|
||||
impl fmt::Display for DeviceEventScanner {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
match self {
|
||||
Self::Arp => write!(f, "ARP"),
|
||||
Self::Mdns => write!(f, "mDNS"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct DeviceEventScannerParseError;
|
||||
|
||||
impl fmt::Display for DeviceEventScannerParseError {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(f, "Error parsing device event scanner")
|
||||
}
|
||||
}
|
||||
|
||||
impl Error for DeviceEventScannerParseError {}
|
||||
|
||||
impl FromStr for DeviceEventScanner {
|
||||
type Err = DeviceEventScannerParseError;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
match s {
|
||||
"ARP" => Ok(DeviceEventScanner::Arp),
|
||||
"mDNS" => Ok(DeviceEventScanner::Mdns),
|
||||
_ => Err(DeviceEventScannerParseError),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ToSql for DeviceEventScanner {
|
||||
fn to_sql(&self) -> rusqlite::Result<ToSqlOutput<'_>> {
|
||||
Ok(self.to_string().into())
|
||||
}
|
||||
}
|
||||
|
||||
impl FromSql for DeviceEventScanner {
|
||||
fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
|
||||
value
|
||||
.as_str()?
|
||||
.parse()
|
||||
.map_err(|e| FromSqlError::Other(Box::new(e)))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ use super::finder;
|
||||
use super::status;
|
||||
use crate::db;
|
||||
use crate::events;
|
||||
use crate::model::device_events::DeviceEventScanner;
|
||||
use crate::settings::get_settings;
|
||||
use chrono::Utc;
|
||||
use log::{debug, info};
|
||||
@@ -38,7 +39,12 @@ pub async fn scan() -> Result<(), Box<dyn std::error::Error>> {
|
||||
device.device_type.clone(),
|
||||
device.name.clone(),
|
||||
)?;
|
||||
events::trigger_existing_device(recorded_device, device.clone()).ok(); // Ignoring errors here, do not stop loop if notification delivery fails
|
||||
events::trigger_existing_device(
|
||||
recorded_device,
|
||||
device.clone(),
|
||||
DeviceEventScanner::Arp,
|
||||
)
|
||||
.ok(); // Ignoring errors here, do not stop loop if notification delivery fails
|
||||
}
|
||||
None => {
|
||||
// If it doesn't exist insert it
|
||||
@@ -48,7 +54,7 @@ pub async fn scan() -> Result<(), Box<dyn std::error::Error>> {
|
||||
);
|
||||
|
||||
db::devices::insert(device.clone())?;
|
||||
events::trigger_new_device(device.clone()).ok(); // Ignoring errors here, do not stop loop if notification delivery fails
|
||||
events::trigger_new_device(device.clone(), DeviceEventScanner::Arp).ok(); // Ignoring errors here, do not stop loop if notification delivery fails
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ use crate::data::mac_vendor_finder;
|
||||
use crate::data::vendor_device_type_finder;
|
||||
use crate::db;
|
||||
use crate::events;
|
||||
use crate::model::device_events::DeviceEventScanner;
|
||||
use crate::model::devices::Device;
|
||||
use crate::settings::get_settings;
|
||||
|
||||
@@ -105,7 +106,7 @@ async fn process_announcement(
|
||||
return;
|
||||
}
|
||||
// Ignoring errors: do not stop the listener if notification delivery fails
|
||||
events::trigger_existing_device(recorded, device).ok();
|
||||
events::trigger_existing_device(recorded, device, DeviceEventScanner::Mdns).ok();
|
||||
}
|
||||
None => {
|
||||
debug!("New device {mac} discovered via mDNS; inserting");
|
||||
@@ -113,7 +114,7 @@ async fn process_announcement(
|
||||
error!("Failed to insert mDNS device {mac}: {err}");
|
||||
return;
|
||||
}
|
||||
events::trigger_new_device(device).ok();
|
||||
events::trigger_new_device(device, DeviceEventScanner::Mdns).ok();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use std::error::Error;
|
||||
|
||||
use crate::model::device_events::{DeviceEvent, DeviceEventType};
|
||||
use crate::model::device_events::{DeviceEvent, DeviceEventScanner, DeviceEventType};
|
||||
use crate::model::devices::{Device, DeviceSummary};
|
||||
use crate::model::notifications::{Notification, NotificationType};
|
||||
use crate::settings::get_settings;
|
||||
@@ -63,6 +63,7 @@ pub mod utils;
|
||||
UpdateDevicePayload,
|
||||
DeviceEvent,
|
||||
DeviceEventType,
|
||||
DeviceEventScanner,
|
||||
ArpScannerStatusResponse,
|
||||
MdnsScannerStatusResponse,
|
||||
)),
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
INSERT INTO device_events (id, mac_address, created_on, event_type, ipv4_address, vendor)
|
||||
VALUES (1, 'aa:aa:aa:aa:aa:aa', '2026-01-01T11:11:11+00:00', 'NewDevice', '192.168.0.1', 'Vendor 1');
|
||||
INSERT INTO device_events (id, mac_address, created_on, event_type, ipv4_address, vendor)
|
||||
VALUES (2, 'bb:bb:bb:bb:bb:bb', '2026-02-03T13:14:15+00:00', 'DeviceSeen', '192.168.0.2', 'Vendor 2');
|
||||
INSERT INTO device_events (id, mac_address, created_on, event_type, ipv4_address, vendor)
|
||||
VALUES (3, 'aa:aa:aa:aa:aa:aa', '2026-03-10T09:00:00+00:00', 'DeviceSeen', '192.168.0.1', 'Vendor 1');
|
||||
INSERT INTO device_events (id, mac_address, created_on, event_type, ipv4_address, vendor, scanner)
|
||||
VALUES (1, 'aa:aa:aa:aa:aa:aa', '2026-01-01T11:11:11+00:00', 'NewDevice', '192.168.0.1', 'Vendor 1', 'ARP');
|
||||
INSERT INTO device_events (id, mac_address, created_on, event_type, ipv4_address, vendor, scanner)
|
||||
VALUES (2, 'bb:bb:bb:bb:bb:bb', '2026-02-03T13:14:15+00:00', 'DeviceSeen', '192.168.0.2', 'Vendor 2', 'mDNS');
|
||||
INSERT INTO device_events (id, mac_address, created_on, event_type, ipv4_address, vendor, scanner)
|
||||
VALUES (3, 'aa:aa:aa:aa:aa:aa', '2026-03-10T09:00:00+00:00', 'DeviceSeen', '192.168.0.1', 'Vendor 1', 'ARP');
|
||||
|
||||
Reference in New Issue
Block a user