From ca585598d4beb6b540606321162fd05b8fa32607 Mon Sep 17 00:00:00 2001 From: Andrew McKenzie Date: Thu, 28 Sep 2023 14:29:24 +0100 Subject: [PATCH 1/5] verify asserted device type against wifi HB cell type --- Cargo.lock | 4 ++-- Cargo.toml | 4 ++-- mobile_config/src/gateway_info.rs | 17 ++++++++++++++--- mobile_verifier/src/cell_type.rs | 9 +++++++++ mobile_verifier/src/heartbeats/mod.rs | 18 +++++++++++++++++- 5 files changed, 44 insertions(+), 8 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 4fcba3b7f..106ebf3ab 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1130,7 +1130,7 @@ checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" [[package]] name = "beacon" version = "0.1.0" -source = "git+https://github.com/helium/proto?branch=master#9fc57133ed1e760c3f1b65dd22d55c09c84832da" +source = "git+https://github.com/helium/proto?branch=andymck/verify-hb-cell-type#1ac4ca2379ff85ae37c3ef3eb95d91222546e83c" dependencies = [ "base64 0.21.0", "byteorder", @@ -3003,7 +3003,7 @@ dependencies = [ [[package]] name = "helium-proto" version = "0.1.0" -source = "git+https://github.com/helium/proto?branch=master#9fc57133ed1e760c3f1b65dd22d55c09c84832da" +source = "git+https://github.com/helium/proto?branch=andymck/verify-hb-cell-type#1ac4ca2379ff85ae37c3ef3eb95d91222546e83c" dependencies = [ "bytes", "prost", diff --git a/Cargo.toml b/Cargo.toml index 5b0d23551..bf7842357 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -61,14 +61,14 @@ sqlx = {version = "0", features = [ ]} helium-crypto = {version = "0.8.1", features=["sqlx-postgres", "multisig"]} -helium-proto = {git = "https://github.com/helium/proto", branch = "master", features = ["services"]} +helium-proto = {git = "https://github.com/helium/proto", branch = "andymck/verify-hb-cell-type", features = ["services"]} hextree = "*" solana-client = "1.14" solana-sdk = "1.14" solana-program = "1.11" spl-token = "3.5.0" reqwest = {version = "0", default-features=false, features = ["gzip", "json", "rustls-tls"]} -beacon = { git = "https://github.com/helium/proto", branch = "master" } +beacon = { git = "https://github.com/helium/proto", branch = "andymck/verify-hb-cell-type" } humantime = "2" metrics = "0" metrics-exporter-prometheus = "0" diff --git a/mobile_config/src/gateway_info.rs b/mobile_config/src/gateway_info.rs index 2126d2efb..3c68fe532 100644 --- a/mobile_config/src/gateway_info.rs +++ b/mobile_config/src/gateway_info.rs @@ -3,12 +3,12 @@ use helium_crypto::PublicKeyBinary; use helium_proto::services::mobile_config::{ GatewayInfo as GatewayInfoProto, GatewayMetadata as GatewayMetadataProto, }; - pub type GatewayInfoStream = BoxStream<'static, GatewayInfo>; #[derive(Clone, Debug)] pub struct GatewayMetadata { pub location: u64, + pub device_type: Option, } #[derive(Clone, Debug)] @@ -32,12 +32,17 @@ pub trait GatewayInfoResolver { impl From for GatewayInfo { fn from(info: GatewayInfoProto) -> Self { let metadata = if let Some(metadata) = info.metadata { + let device_type = metadata.device_type.parse().ok(); u64::from_str_radix(&metadata.location, 16) - .map(|location| GatewayMetadata { location }) + .map(|location| GatewayMetadata { + location, + device_type, + }) .ok() } else { None }; + Self { address: info.address.into(), metadata, @@ -52,6 +57,7 @@ impl TryFrom for GatewayInfoProto { let metadata = if let Some(metadata) = info.metadata { Some(GatewayMetadataProto { location: hextree::Cell::from_raw(metadata.location)?.to_string(), + device_type: metadata.device_type.unwrap_or_default(), }) } else { None @@ -67,11 +73,12 @@ pub(crate) mod db { use super::{GatewayInfo, GatewayMetadata}; use futures::stream::{Stream, StreamExt}; use helium_crypto::PublicKeyBinary; + use sqlx::types::Json; use sqlx::{PgExecutor, Row}; use std::str::FromStr; const GET_METADATA_SQL: &str = r#" - select kta.entity_key, infos.location::bigint + select kta.entity_key, infos.location::bigint, infos.device_type from mobile_hotspot_infos infos join key_to_assets kta on infos.asset = kta.asset "#; @@ -102,10 +109,14 @@ pub(crate) mod db { impl sqlx::FromRow<'_, sqlx::postgres::PgRow> for GatewayInfo { fn from_row(row: &sqlx::postgres::PgRow) -> sqlx::Result { + let device_type = row + .get::>, &str>("device_type") + .map(|s| s.to_string()); let metadata = row .get::, &str>("location") .map(|loc| GatewayMetadata { location: loc as u64, + device_type, }); Ok(Self { address: PublicKeyBinary::from_str( diff --git a/mobile_verifier/src/cell_type.rs b/mobile_verifier/src/cell_type.rs index ad105754e..9cd03714b 100644 --- a/mobile_verifier/src/cell_type.rs +++ b/mobile_verifier/src/cell_type.rs @@ -87,6 +87,15 @@ impl CellType { _ => dec!(1.0), } } + + pub fn from_asserted(s: &Option) -> Option { + // TODO: currently only handling wifi indoor, handle other cell types + // when foundation device type values are in use + match s { + Some(s) if s.eq("wifiIndoor") => Some(CellType::NovaGenericWifiIndoor), + _ => None, + } + } } impl From for CellTypeProto { diff --git a/mobile_verifier/src/heartbeats/mod.rs b/mobile_verifier/src/heartbeats/mod.rs index b45e9c4e9..20af7d5d6 100644 --- a/mobile_verifier/src/heartbeats/mod.rs +++ b/mobile_verifier/src/heartbeats/mod.rs @@ -510,6 +510,23 @@ pub async fn validate_heartbeat( _ => None, }; + // verify the HB cell type matches that on chain + // TODO: currently only handling wifi indoor + // make this check generic and applicable to all cell types + // when device data is available in the db + match ( + heartbeat.hb_type.clone(), + CellType::from_asserted(&metadata.device_type), + ) { + (HbType::Wifi, Some(asserted_celltype)) if asserted_celltype != cell_type => { + return Ok((cell_type, distance_to_asserted, None, proto::HeartbeatValidity::BadCellType)); + } + (HbType::Wifi, None) => { + return Ok((cell_type, distance_to_asserted, None, proto::HeartbeatValidity::BadCellType)) + } + _ => (), + }; + /* let Some(coverage_object) = heartbeat.report.coverage_object() else { return Ok((cell_type, None, proto::HeartbeatValidity::BadCoverageObject)); @@ -529,7 +546,6 @@ pub async fn validate_heartbeat( let Ok(latlng) = LatLng::new(heartbeat.report.lat, heartbeat.report.lon) else { return Ok((cell_type, None, proto::HeartbeatValidity::InvalidLatLon)); - }; if coverage.max_distance_km(latlng) > max_distance { return Ok(( From 8c18857ae0cb48f0109c4130976b2fbdee46b0ec Mon Sep 17 00:00:00 2001 From: Andrew McKenzie Date: Thu, 28 Sep 2023 15:19:24 +0100 Subject: [PATCH 2/5] drop device type DB null support, simplify ev thing thereafter --- mobile_config/src/gateway_info.rs | 11 ++++------- mobile_verifier/src/cell_type.rs | 6 +++--- mobile_verifier/src/heartbeats/mod.rs | 19 +++---------------- 3 files changed, 10 insertions(+), 26 deletions(-) diff --git a/mobile_config/src/gateway_info.rs b/mobile_config/src/gateway_info.rs index 3c68fe532..f594d2fb0 100644 --- a/mobile_config/src/gateway_info.rs +++ b/mobile_config/src/gateway_info.rs @@ -8,7 +8,7 @@ pub type GatewayInfoStream = BoxStream<'static, GatewayInfo>; #[derive(Clone, Debug)] pub struct GatewayMetadata { pub location: u64, - pub device_type: Option, + pub device_type: String, } #[derive(Clone, Debug)] @@ -32,11 +32,10 @@ pub trait GatewayInfoResolver { impl From for GatewayInfo { fn from(info: GatewayInfoProto) -> Self { let metadata = if let Some(metadata) = info.metadata { - let device_type = metadata.device_type.parse().ok(); u64::from_str_radix(&metadata.location, 16) .map(|location| GatewayMetadata { location, - device_type, + device_type: metadata.device_type, }) .ok() } else { @@ -57,7 +56,7 @@ impl TryFrom for GatewayInfoProto { let metadata = if let Some(metadata) = info.metadata { Some(GatewayMetadataProto { location: hextree::Cell::from_raw(metadata.location)?.to_string(), - device_type: metadata.device_type.unwrap_or_default(), + device_type: metadata.device_type, }) } else { None @@ -109,9 +108,7 @@ pub(crate) mod db { impl sqlx::FromRow<'_, sqlx::postgres::PgRow> for GatewayInfo { fn from_row(row: &sqlx::postgres::PgRow) -> sqlx::Result { - let device_type = row - .get::>, &str>("device_type") - .map(|s| s.to_string()); + let device_type = row.get::, &str>("device_type").to_string(); let metadata = row .get::, &str>("location") .map(|loc| GatewayMetadata { diff --git a/mobile_verifier/src/cell_type.rs b/mobile_verifier/src/cell_type.rs index 9cd03714b..ec9ca74ec 100644 --- a/mobile_verifier/src/cell_type.rs +++ b/mobile_verifier/src/cell_type.rs @@ -88,11 +88,11 @@ impl CellType { } } - pub fn from_asserted(s: &Option) -> Option { + pub fn from_asserted(device_type: &String) -> Option { // TODO: currently only handling wifi indoor, handle other cell types // when foundation device type values are in use - match s { - Some(s) if s.eq("wifiIndoor") => Some(CellType::NovaGenericWifiIndoor), + match device_type { + device_type if device_type.eq("wifiIndoor") => Some(CellType::NovaGenericWifiIndoor), _ => None, } } diff --git a/mobile_verifier/src/heartbeats/mod.rs b/mobile_verifier/src/heartbeats/mod.rs index 20af7d5d6..b93d1a37f 100644 --- a/mobile_verifier/src/heartbeats/mod.rs +++ b/mobile_verifier/src/heartbeats/mod.rs @@ -510,22 +510,9 @@ pub async fn validate_heartbeat( _ => None, }; - // verify the HB cell type matches that on chain - // TODO: currently only handling wifi indoor - // make this check generic and applicable to all cell types - // when device data is available in the db - match ( - heartbeat.hb_type.clone(), - CellType::from_asserted(&metadata.device_type), - ) { - (HbType::Wifi, Some(asserted_celltype)) if asserted_celltype != cell_type => { - return Ok((cell_type, distance_to_asserted, None, proto::HeartbeatValidity::BadCellType)); - } - (HbType::Wifi, None) => { - return Ok((cell_type, distance_to_asserted, None, proto::HeartbeatValidity::BadCellType)) - } - _ => (), - }; + if CellType::from_asserted(&metadata.device_type) != Some(cell_type) { + return Ok((cell_type, distance_to_asserted, None, proto::HeartbeatValidity::BadCellType)) + } /* let Some(coverage_object) = heartbeat.report.coverage_object() else { From 990efd2bdeaf9c0e25123b62f834ae672bb719cf Mon Sep 17 00:00:00 2001 From: Andrew McKenzie Date: Fri, 29 Sep 2023 15:48:36 +0100 Subject: [PATCH 3/5] tweaks --- mobile_verifier/src/cell_type.rs | 32 +++++++++++++++++++-------- mobile_verifier/src/heartbeats/mod.rs | 17 ++++++++------ mobile_verifier/src/lib.rs | 7 +++--- 3 files changed, 37 insertions(+), 19 deletions(-) diff --git a/mobile_verifier/src/cell_type.rs b/mobile_verifier/src/cell_type.rs index ec9ca74ec..1ffe36b9c 100644 --- a/mobile_verifier/src/cell_type.rs +++ b/mobile_verifier/src/cell_type.rs @@ -3,6 +3,7 @@ use helium_proto::services::poc_mobile::CellType as CellTypeProto; use rust_decimal::Decimal; use rust_decimal_macros::dec; use serde::Serialize; +use std::str::FromStr; pub const CELLTYPE_NOVA_436H: &str = "2AG32MBS3100196N"; pub const CELLTYPE_NOVA_430I: &str = "2AG32PBS3101S"; @@ -87,15 +88,6 @@ impl CellType { _ => dec!(1.0), } } - - pub fn from_asserted(device_type: &String) -> Option { - // TODO: currently only handling wifi indoor, handle other cell types - // when foundation device type values are in use - match device_type { - device_type if device_type.eq("wifiIndoor") => Some(CellType::NovaGenericWifiIndoor), - _ => None, - } - } } impl From for CellTypeProto { @@ -111,3 +103,25 @@ impl From for CellTypeProto { } } } + +impl CellTypeLabel { + pub fn from_asserted(device_type: &String) -> Option { + match device_type { + device_type if device_type.eq("wifiIndoor") => Some(CellTypeLabel::Wifi), + device_type if device_type.eq("cbrs") => Some(CellTypeLabel::CBRS), + _ => None, + } + } +} + +impl FromStr for CellTypeLabel { + type Err = (); + fn from_str(input: &str) -> Result { + match input { + "wifiIndoor" => Ok(CellTypeLabel::Wifi), + "cbrs" => Ok(CellTypeLabel::CBRS), + _ => Err(()) + } + } +} + diff --git a/mobile_verifier/src/heartbeats/mod.rs b/mobile_verifier/src/heartbeats/mod.rs index b93d1a37f..79f4ce218 100644 --- a/mobile_verifier/src/heartbeats/mod.rs +++ b/mobile_verifier/src/heartbeats/mod.rs @@ -29,8 +29,8 @@ const MINIMUM_HEARTBEAT_COUNT: i64 = 12; #[sqlx(type_name = "radio_type")] #[sqlx(rename_all = "lowercase")] pub enum HbType { - Cbrs, - Wifi, + Cbrs = 0, + Wifi = 1, } #[derive(Copy, Clone)] @@ -504,16 +504,19 @@ pub async fn validate_heartbeat( proto::HeartbeatValidity::GatewayNotAsserted, )) } - GatewayResolution::AssertedLocation(location) if heartbeat.hb_type == HbType::Wifi => { + GatewayResolution::GatewayAsserted(metadata) if heartbeat.hb_type == HbType::Wifi => { + // for wifi HBs, check the asserted device type matches that defined in the HB + let asserted_celltype_label = CellTypeLabel::from_str(&metadata.device_type)?; + if asserted_celltype_label == CellTypeLabel::Wifi + && cell_type.to_label() != asserted_celltype_label + { + return Ok((cell_type, None, None, proto::HeartbeatValidity::BadCellType)); + }; Some(heartbeat.asserted_distance(location)?) } _ => None, }; - if CellType::from_asserted(&metadata.device_type) != Some(cell_type) { - return Ok((cell_type, distance_to_asserted, None, proto::HeartbeatValidity::BadCellType)) - } - /* let Some(coverage_object) = heartbeat.report.coverage_object() else { return Ok((cell_type, None, proto::HeartbeatValidity::BadCoverageObject)); diff --git a/mobile_verifier/src/lib.rs b/mobile_verifier/src/lib.rs index d9d9ff121..086aff260 100644 --- a/mobile_verifier/src/lib.rs +++ b/mobile_verifier/src/lib.rs @@ -14,11 +14,12 @@ mod telemetry; pub use settings::Settings; use async_trait::async_trait; - +// use helium_proto::services::mobile_config::GatewayMetadata; +use mobile_config::gateway_info::GatewayMetadata; pub enum GatewayResolution { GatewayNotFound, GatewayNotAsserted, - AssertedLocation(u64), + GatewayAsserted(GatewayMetadata), } #[async_trait] @@ -45,7 +46,7 @@ impl GatewayResolver for mobile_config::GatewayClient { Some(GatewayInfo { metadata: Some(metadata), .. - }) => Ok(GatewayResolution::AssertedLocation(metadata.location)), + }) => Ok(GatewayResolution::GatewayAsserted(metadata)), Some(_) => Ok(GatewayResolution::GatewayNotAsserted), } } From 1e5490927090a1090f7dce840bda0cc47090ba46 Mon Sep 17 00:00:00 2001 From: Andrew McKenzie Date: Fri, 17 Nov 2023 14:59:50 +0000 Subject: [PATCH 4/5] tweaks --- Cargo.lock | 8 ++++---- mobile_verifier/src/cell_type.rs | 27 ++++++++------------------- mobile_verifier/src/heartbeats/mod.rs | 7 ++++--- 3 files changed, 16 insertions(+), 26 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 106ebf3ab..71fbe17db 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1130,7 +1130,7 @@ checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" [[package]] name = "beacon" version = "0.1.0" -source = "git+https://github.com/helium/proto?branch=andymck/verify-hb-cell-type#1ac4ca2379ff85ae37c3ef3eb95d91222546e83c" +source = "git+https://github.com/helium/proto?branch=andymck/verify-hb-cell-type#16ada190a336eab3adaa5dc783c9d8117e26380c" dependencies = [ "base64 0.21.0", "byteorder", @@ -1140,7 +1140,7 @@ dependencies = [ "rand_chacha 0.3.0", "rust_decimal", "serde", - "sha2 0.9.9", + "sha2 0.10.6", "thiserror", ] @@ -3003,7 +3003,7 @@ dependencies = [ [[package]] name = "helium-proto" version = "0.1.0" -source = "git+https://github.com/helium/proto?branch=andymck/verify-hb-cell-type#1ac4ca2379ff85ae37c3ef3eb95d91222546e83c" +source = "git+https://github.com/helium/proto?branch=andymck/verify-hb-cell-type#16ada190a336eab3adaa5dc783c9d8117e26380c" dependencies = [ "bytes", "prost", @@ -8282,7 +8282,7 @@ dependencies = [ "rand 0.8.5", "serde", "serde_json", - "sha2 0.9.9", + "sha2 0.10.6", "thiserror", "twox-hash", "xorf", diff --git a/mobile_verifier/src/cell_type.rs b/mobile_verifier/src/cell_type.rs index 1ffe36b9c..864198e44 100644 --- a/mobile_verifier/src/cell_type.rs +++ b/mobile_verifier/src/cell_type.rs @@ -28,7 +28,7 @@ pub enum CellType { pub enum CellTypeLabel { CellTypeLabelNone = 0, CBRS = 1, - Wifi = 2, + WifiIndoor = 2, } impl CellType { @@ -51,7 +51,7 @@ impl CellType { Self::SercommIndoor => CellTypeLabel::CBRS, Self::SercommOutdoor => CellTypeLabel::CBRS, Self::CellTypeNone => CellTypeLabel::CellTypeLabelNone, - Self::NovaGenericWifiIndoor => CellTypeLabel::Wifi, + Self::NovaGenericWifiIndoor => CellTypeLabel::WifiIndoor, } } @@ -104,24 +104,13 @@ impl From for CellTypeProto { } } -impl CellTypeLabel { - pub fn from_asserted(device_type: &String) -> Option { - match device_type { - device_type if device_type.eq("wifiIndoor") => Some(CellTypeLabel::Wifi), - device_type if device_type.eq("cbrs") => Some(CellTypeLabel::CBRS), - _ => None, - } - } -} - impl FromStr for CellTypeLabel { - type Err = (); - fn from_str(input: &str) -> Result { - match input { - "wifiIndoor" => Ok(CellTypeLabel::Wifi), - "cbrs" => Ok(CellTypeLabel::CBRS), - _ => Err(()) + type Err = anyhow::Error; + fn from_str(s: &str) -> anyhow::Result { + match s { + "wifiIndoor" => Ok(CellTypeLabel::WifiIndoor), + "cbrs" => Ok(CellTypeLabel::CBRS), + _ => anyhow::bail!("unknown cell type"), } } } - diff --git a/mobile_verifier/src/heartbeats/mod.rs b/mobile_verifier/src/heartbeats/mod.rs index 79f4ce218..ead5b6527 100644 --- a/mobile_verifier/src/heartbeats/mod.rs +++ b/mobile_verifier/src/heartbeats/mod.rs @@ -19,6 +19,7 @@ use helium_proto::services::poc_mobile as proto; use retainer::Cache; use rust_decimal::{prelude::ToPrimitive, Decimal}; use sqlx::{postgres::PgTypeInfo, Decode, Encode, Postgres, Transaction, Type}; +use std::str::FromStr; use std::{ops::Range, pin::pin, time}; use uuid::Uuid; @@ -247,7 +248,7 @@ impl HeartbeatReward { .cbsd_id .clone() .ok_or_else(|| anyhow!("expected cbsd_id, found none"))?), - CellTypeLabel::Wifi => Ok(self.hotspot_key.to_string()), + CellTypeLabel::WifiIndoor => Ok(self.hotspot_key.to_string()), _ => Err(anyhow!("failed to derive label from cell type")), } } @@ -507,12 +508,12 @@ pub async fn validate_heartbeat( GatewayResolution::GatewayAsserted(metadata) if heartbeat.hb_type == HbType::Wifi => { // for wifi HBs, check the asserted device type matches that defined in the HB let asserted_celltype_label = CellTypeLabel::from_str(&metadata.device_type)?; - if asserted_celltype_label == CellTypeLabel::Wifi + if asserted_celltype_label == CellTypeLabel::WifiIndoor && cell_type.to_label() != asserted_celltype_label { return Ok((cell_type, None, None, proto::HeartbeatValidity::BadCellType)); }; - Some(heartbeat.asserted_distance(location)?) + Some(heartbeat.asserted_distance(metadata.location)?) } _ => None, }; From a24af614b43ab6f5b63593fb159a4e2cff20ff45 Mon Sep 17 00:00:00 2001 From: Andrew McKenzie Date: Fri, 17 Nov 2023 15:22:00 +0000 Subject: [PATCH 5/5] tweaks --- mobile_verifier/src/heartbeats/mod.rs | 7 +++---- mobile_verifier/src/lib.rs | 2 +- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/mobile_verifier/src/heartbeats/mod.rs b/mobile_verifier/src/heartbeats/mod.rs index ead5b6527..847d8c309 100644 --- a/mobile_verifier/src/heartbeats/mod.rs +++ b/mobile_verifier/src/heartbeats/mod.rs @@ -19,8 +19,7 @@ use helium_proto::services::poc_mobile as proto; use retainer::Cache; use rust_decimal::{prelude::ToPrimitive, Decimal}; use sqlx::{postgres::PgTypeInfo, Decode, Encode, Postgres, Transaction, Type}; -use std::str::FromStr; -use std::{ops::Range, pin::pin, time}; +use std::{ops::Range, pin::pin, str::FromStr, time}; use uuid::Uuid; /// Minimum number of heartbeats required to give a reward to the hotspot. @@ -30,8 +29,8 @@ const MINIMUM_HEARTBEAT_COUNT: i64 = 12; #[sqlx(type_name = "radio_type")] #[sqlx(rename_all = "lowercase")] pub enum HbType { - Cbrs = 0, - Wifi = 1, + Cbrs, + Wifi, } #[derive(Copy, Clone)] diff --git a/mobile_verifier/src/lib.rs b/mobile_verifier/src/lib.rs index 086aff260..e648f834c 100644 --- a/mobile_verifier/src/lib.rs +++ b/mobile_verifier/src/lib.rs @@ -14,7 +14,7 @@ mod telemetry; pub use settings::Settings; use async_trait::async_trait; -// use helium_proto::services::mobile_config::GatewayMetadata; + use mobile_config::gateway_info::GatewayMetadata; pub enum GatewayResolution { GatewayNotFound,