diff --git a/Cargo.lock b/Cargo.lock index c589d5db..97059a17 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -876,6 +876,7 @@ dependencies = [ name = "chirpstack_api" version = "4.7.0-test.3" dependencies = [ + "diesel", "hex", "pbjson", "pbjson-build", diff --git a/api/proto/internal/internal.proto b/api/proto/internal/internal.proto index a01466f1..489eb97c 100644 --- a/api/proto/internal/internal.proto +++ b/api/proto/internal/internal.proto @@ -7,15 +7,9 @@ import "gw/gw.proto"; import "google/protobuf/timestamp.proto"; message DeviceSession { - // Device EUI. - bytes dev_eui = 1; - // Device address. bytes dev_addr = 2; - // Join EUI. - bytes join_eui = 3; - // LoRaWAN mac-version. common.MacVersion mac_version = 4; diff --git a/api/rust/Cargo.toml b/api/rust/Cargo.toml index 13f05f27..c5a35c41 100644 --- a/api/rust/Cargo.toml +++ b/api/rust/Cargo.toml @@ -12,6 +12,7 @@ edition = "2021" default = ["api", "json"] api = ["tonic/transport", "tonic-build/transport", "tokio"] json = ["pbjson", "pbjson-types", "serde"] +diesel = ["dep:diesel"] internal = [] [dependencies] @@ -25,6 +26,7 @@ tokio = { version = "1.32", features = ["macros"], optional = true } pbjson = { version = "0.6", optional = true } pbjson-types = { version = "0.6", optional = true } serde = { version = "1.0", optional = true } +diesel = { version = "2.1", features = ["postgres_backend"], optional = true } [build-dependencies] tonic-build = { version = "0.10", features = ["prost"], default-features = false } diff --git a/api/rust/build.rs b/api/rust/build.rs index ef4a69a9..b1148bfb 100644 --- a/api/rust/build.rs +++ b/api/rust/build.rs @@ -73,13 +73,20 @@ fn main() -> Result<(), Box> { } // internal - tonic_build::configure() - .out_dir(out_dir.join("internal")) - .file_descriptor_set_path(out_dir.join("internal").join("proto_descriptor.bin")) - .compile_well_known_types(true) - .extern_path(".google.protobuf", well_known_types_path) - .extern_path(".common", "crate::common") - .compile( + { + let mut builder = tonic_build::configure() + .out_dir(out_dir.join("internal")) + .file_descriptor_set_path(out_dir.join("internal").join("proto_descriptor.bin")) + .compile_well_known_types(true) + .extern_path(".google.protobuf", well_known_types_path) + .extern_path(".common", "crate::common"); + + #[cfg(feature = "diesel")] + { + builder = builder.message_attribute("internal.DeviceSession", "#[derive(diesel::expression::AsExpression, diesel::deserialize::FromSqlRow)] #[diesel(sql_type = diesel::sql_types::Binary)]"); + } + + builder.compile( &[cs_dir .join("internal") .join("internal.proto") @@ -90,6 +97,7 @@ fn main() -> Result<(), Box> { proto_dir.join("google").to_str().unwrap(), ], )?; + } #[cfg(feature = "json")] { diff --git a/api/rust/proto/chirpstack/internal/internal.proto b/api/rust/proto/chirpstack/internal/internal.proto index a01466f1..489eb97c 100644 --- a/api/rust/proto/chirpstack/internal/internal.proto +++ b/api/rust/proto/chirpstack/internal/internal.proto @@ -7,15 +7,9 @@ import "gw/gw.proto"; import "google/protobuf/timestamp.proto"; message DeviceSession { - // Device EUI. - bytes dev_eui = 1; - // Device address. bytes dev_addr = 2; - // Join EUI. - bytes join_eui = 3; - // LoRaWAN mac-version. common.MacVersion mac_version = 4; diff --git a/api/rust/src/internal.rs b/api/rust/src/internal.rs index d62ba6c1..114e5a65 100644 --- a/api/rust/src/internal.rs +++ b/api/rust/src/internal.rs @@ -2,6 +2,14 @@ include!(concat!(env!("OUT_DIR"), "/internal/internal.rs")); #[cfg(feature = "json")] include!(concat!(env!("OUT_DIR"), "/internal/internal.serde.rs")); +#[cfg(feature = "diesel")] +use std::io::Cursor; +#[cfg(feature = "diesel")] +use diesel::{backend::Backend, deserialize, serialize, sql_types::Binary}; +#[cfg(feature = "diesel")] +use prost::Message; + + impl DeviceSession { pub fn get_a_f_cnt_down(&self) -> u32 { if self.mac_version().to_string().starts_with("1.0") { @@ -23,3 +31,28 @@ impl DeviceSession { } } } + +#[cfg(feature = "diesel")] +impl deserialize::FromSql for DeviceSession +where + DB: Backend, + *const [u8]: deserialize::FromSql, +{ + fn from_sql(value: DB::RawValue<'_>) -> deserialize::Result { + let bytes = as deserialize::FromSql>::from_sql(value)?; + Ok(DeviceSession::decode(&mut Cursor::new(bytes))?) + } +} + +#[cfg(feature = "diesel")] +impl serialize::ToSql for DeviceSession +where + [u8]: serialize::ToSql, +{ + fn to_sql<'b>(&self, out: &mut serialize::Output<'b, '_, diesel::pg::Pg>) -> serialize::Result { + <[u8] as serialize::ToSql>::to_sql( + &self.encode_to_vec(), + &mut out.reborrow(), + ) + } +} \ No newline at end of file diff --git a/chirpstack/Cargo.toml b/chirpstack/Cargo.toml index 1318847b..b0f65cae 100644 --- a/chirpstack/Cargo.toml +++ b/chirpstack/Cargo.toml @@ -49,7 +49,7 @@ tracing-subscriber = { version = "0.3", features = [ ], default-features = true } # ChirpStack API definitions -chirpstack_api = { path = "../api/rust", features = ["default", "internal"] } +chirpstack_api = { path = "../api/rust", features = ["default", "internal", "diesel"] } lrwn = { path = "../lrwn", features = ["serde", "diesel", "regions", "crypto"] } backend = { path = "../backend" } diff --git a/chirpstack/migrations/2024-02-07-083424_add_device_session_to_device/down.sql b/chirpstack/migrations/2024-02-07-083424_add_device_session_to_device/down.sql new file mode 100644 index 00000000..baf833aa --- /dev/null +++ b/chirpstack/migrations/2024-02-07-083424_add_device_session_to_device/down.sql @@ -0,0 +1,6 @@ +drop index idx_device_dev_addr; +drop index idx_device_secondary_dev_addr; + +alter table device + drop column secondary_dev_addr, + drop column device_session; diff --git a/chirpstack/migrations/2024-02-07-083424_add_device_session_to_device/up.sql b/chirpstack/migrations/2024-02-07-083424_add_device_session_to_device/up.sql new file mode 100644 index 00000000..d3f381ca --- /dev/null +++ b/chirpstack/migrations/2024-02-07-083424_add_device_session_to_device/up.sql @@ -0,0 +1,6 @@ +alter table device + add column secondary_dev_addr bytea, + add column device_session bytea; + +create index idx_device_dev_addr on device (dev_addr); +create index idx_device_secondary_dev_addr on device (secondary_dev_addr); diff --git a/chirpstack/src/api/backend/mod.rs b/chirpstack/src/api/backend/mod.rs index f3f66c2b..04994579 100644 --- a/chirpstack/src/api/backend/mod.rs +++ b/chirpstack/src/api/backend/mod.rs @@ -18,7 +18,7 @@ use crate::backend::{joinserver, keywrap, roaming}; use crate::downlink::data_fns; use crate::helpers::errors::PrintFullError; use crate::storage::{ - device_session, error::Error as StorageError, get_async_redis_conn, passive_roaming, redis_key, + device, error::Error as StorageError, get_async_redis_conn, passive_roaming, redis_key, }; use crate::uplink::{ data_sns, error::Error as UplinkError, helpers, join_sns, RoamingMetaData, UplinkFrameSet, @@ -312,9 +312,10 @@ async fn _handle_pr_start_req_data( }; // get device-session - let ds = device_session::get_for_phypayload(&mut ufs.phy_payload, ufs.dr, ufs.ch as u8).await?; + let d = device::get_for_phypayload(&mut ufs.phy_payload, ufs.dr, ufs.ch as u8).await?; let pr_lifetime = roaming::get_passive_roaming_lifetime(sender_id)?; let kek_label = roaming::get_passive_roaming_kek_label(sender_id)?; + let ds = d.get_device_session()?; let nwk_s_key = if ds.mac_version().to_string().starts_with("1.0") { Some(keywrap::wrap( @@ -343,7 +344,7 @@ async fn _handle_pr_start_req_data( base: pl .base .to_base_payload_result(backend::ResultCode::Success, ""), - dev_eui: ds.dev_eui.clone(), + dev_eui: d.dev_eui.to_vec(), lifetime: if pr_lifetime.is_zero() { None } else { diff --git a/chirpstack/src/api/device.rs b/chirpstack/src/api/device.rs index 0ff37160..adccec6e 100644 --- a/chirpstack/src/api/device.rs +++ b/chirpstack/src/api/device.rs @@ -15,10 +15,11 @@ use lrwn::{AES128Key, DevAddr, EUI64}; use super::auth::validator; use super::error::ToStatus; use super::helpers::{self, FromProto, ToProto}; -use crate::storage::error::Error; use crate::storage::{ device::{self, DeviceClass}, - device_keys, device_profile, device_queue, device_session, fields, metrics, + device_keys, device_profile, device_queue, + error::Error as StorageError, + fields, metrics, }; use crate::{codec, devaddr::get_random_dev_addr}; @@ -514,7 +515,6 @@ impl DeviceService for Device { let mut ds = internal::DeviceSession { region_config_id: "".to_string(), - dev_eui: dev_eui.to_vec(), dev_addr: dev_addr.to_vec(), mac_version: dp.mac_version.to_proto().into(), s_nwk_s_int_key: s_nwk_s_int_key.to_vec(), @@ -532,12 +532,12 @@ impl DeviceService for Device { }; dp.reset_session_to_boot_params(&mut ds); - device_session::save(&ds).await.map_err(|e| e.status())?; - - // Set device DevAddr. - device::set_dev_addr(dev_eui, dev_addr) - .await - .map_err(|e| e.status())?; + let mut device_changeset = device::DeviceChangeset { + device_session: Some(Some(ds)), + dev_addr: Some(Some(dev_addr)), + secondary_dev_addr: Some(None), + ..Default::default() + }; // Flush queue (if configured). if dp.flush_queue_on_activate { @@ -548,15 +548,15 @@ impl DeviceService for Device { // LoRaWAN 1.1 devices send a mac-command when changing to Class-C. Change the class here for LoRaWAN 1.0 devices. if dp.supports_class_c && dp.mac_version.to_string().starts_with("1.0") { - let _ = device::set_enabled_class(&dev_eui, DeviceClass::C) - .await - .map_err(|e| e.status())?; + device_changeset.enabled_class = Some(DeviceClass::C); } else { - let _ = device::set_enabled_class(&dev_eui, DeviceClass::A) - .await - .map_err(|e| e.status())?; + device_changeset.enabled_class = Some(DeviceClass::A); } + device::partial_update(dev_eui, &device_changeset) + .await + .map_err(|e| e.status())?; + let mut resp = Response::new(()); resp.metadata_mut() .insert("x-log-dev_eui", req_da.dev_eui.parse().unwrap()); @@ -581,9 +581,18 @@ impl DeviceService for Device { device_queue::flush_for_dev_eui(&dev_eui) .await .map_err(|e| e.status())?; - device_session::delete(&dev_eui) - .await - .map_err(|e| e.status())?; + + device::partial_update( + dev_eui, + &device::DeviceChangeset { + dev_addr: Some(None), + secondary_dev_addr: Some(None), + device_session: Some(None), + ..Default::default() + }, + ) + .await + .map_err(|e| e.status())?; let mut resp = Response::new(()); resp.metadata_mut() @@ -606,25 +615,24 @@ impl DeviceService for Device { ) .await?; - let ds = match device_session::get(&dev_eui).await { + let d = device::get(&dev_eui).await.map_err(|e| e.status())?; + let ds = match d.get_device_session() { Ok(v) => v, - Err(e) => match e { - Error::NotFound(_) => { - return Ok(Response::new(api::GetDeviceActivationResponse { - device_activation: None, - join_server_context: None, - })); - } - _ => { - return Err(e.status()); - } - }, + Err(StorageError::NotFound(_)) => { + return Ok(Response::new(api::GetDeviceActivationResponse { + device_activation: None, + join_server_context: None, + })); + } + Err(e) => { + return Err(e.status()); + } }; let mut resp = Response::new(api::GetDeviceActivationResponse { device_activation: Some(api::DeviceActivation { - dev_eui: hex::encode(&ds.dev_eui), - dev_addr: hex::encode(&ds.dev_addr), + dev_eui: d.dev_eui.to_string(), + dev_addr: d.get_dev_addr().map_err(|e| e.status())?.to_string(), app_s_key: match &ds.app_s_key { Some(v) => hex::encode(&v.aes_key), None => "".to_string(), @@ -1188,7 +1196,14 @@ impl DeviceService for Device { ) .await?; - let ds = device_session::get(&dev_eui).await.unwrap_or_default(); + let d = device::get(&dev_eui).await.map_err(|e| e.status())?; + let ds = match d.get_device_session() { + Ok(v) => v.clone(), + Err(StorageError::NotFound(_)) => Default::default(), + Err(e) => { + return Err(e.status()); + } + }; let max_f_cnt_down_queue = device_queue::get_max_f_cnt_down(dev_eui) .await @@ -1210,7 +1225,7 @@ pub mod test { use super::*; use crate::api::auth::validator::RequestValidator; use crate::api::auth::AuthID; - use crate::storage::{application, tenant, user}; + use crate::storage::{application, device, tenant, user}; use crate::test; use lrwn::NetID; @@ -1526,12 +1541,18 @@ pub mod test { assert!(get_activation_resp.get_ref().device_activation.is_none()); // test get activation with JS session-key ID. - device_session::save(&internal::DeviceSession { - dev_eui: vec![1, 2, 3, 4, 5, 6, 7, 8], - dev_addr: vec![1, 2, 3, 4], - js_session_key_id: vec![8, 7, 6, 5, 4, 3, 2, 1], - ..Default::default() - }) + device::partial_update( + dev.dev_eui, + &device::DeviceChangeset { + dev_addr: Some(Some(DevAddr::from_be_bytes([1, 2, 3, 4]))), + device_session: Some(Some(internal::DeviceSession { + dev_addr: vec![1, 2, 3, 4], + js_session_key_id: vec![8, 7, 6, 5, 4, 3, 2, 1], + ..Default::default() + })), + ..Default::default() + }, + ) .await .unwrap(); let get_activation_req = get_request( @@ -1550,17 +1571,23 @@ pub mod test { ); // test activation with AppSKey key-envelope. - device_session::save(&internal::DeviceSession { - dev_eui: vec![1, 2, 3, 4, 5, 6, 7, 8], - dev_addr: vec![1, 2, 3, 4], - app_s_key: Some(common::KeyEnvelope { - kek_label: "test-key".into(), - aes_key: vec![8, 7, 6, 5, 4, 3, 2, 1, 8, 7, 6, 5, 4, 3, 2, 1], - }), - ..Default::default() - }) + device::partial_update( + dev.dev_eui, + &device::DeviceChangeset { + device_session: Some(Some(internal::DeviceSession { + dev_addr: vec![1, 2, 3, 4], + app_s_key: Some(common::KeyEnvelope { + kek_label: "test-key".into(), + aes_key: vec![8, 7, 6, 5, 4, 3, 2, 1, 8, 7, 6, 5, 4, 3, 2, 1], + }), + ..Default::default() + })), + ..Default::default() + }, + ) .await .unwrap(); + let get_activation_req = get_request( &u.id, api::GetDeviceActivationRequest { diff --git a/chirpstack/src/cmd/configfile.rs b/chirpstack/src/cmd/configfile.rs index 45cb4526..34f3f57a 100644 --- a/chirpstack/src/cmd/configfile.rs +++ b/chirpstack/src/cmd/configfile.rs @@ -185,12 +185,6 @@ pub fn run() { {{/each}} ] - # Device session expiration. - # - # The TTL value defines the time after which a device-session expires - # after no activity. - device_session_ttl="{{ network.device_session_ttl }}" - # Time to wait for uplink de-duplication. # # This is the time that ChirpStack will wait for other gateways to receive diff --git a/chirpstack/src/cmd/migrate_ds_to_pg.rs b/chirpstack/src/cmd/migrate_ds_to_pg.rs new file mode 100644 index 00000000..4b0c26cf --- /dev/null +++ b/chirpstack/src/cmd/migrate_ds_to_pg.rs @@ -0,0 +1,56 @@ +use anyhow::Result; +use diesel::prelude::*; +use diesel_async::RunQueryDsl; +use tracing::{debug, info}; + +use crate::storage::{self, device_session, error::Error, get_async_db_conn, schema::device}; +use lrwn::{DevAddr, EUI64}; + +pub async fn run() -> Result<()> { + storage::setup().await?; + + info!("Migrating device-sessions from Redis to PostgreSQL"); + info!("Getting DevEUIs from PostgreSQL without device-session"); + + let dev_euis: Vec = device::dsl::device + .select(device::dsl::dev_eui) + .filter(device::dsl::device_session.is_null()) + .load(&mut get_async_db_conn().await?) + .await?; + + info!( + "There are {} devices in PostgreSQL without device-session set", + dev_euis.len() + ); + + for dev_eui in &dev_euis { + debug!(dev_eui = %dev_eui, "Migrating device-session"); + + let ds = match device_session::get(dev_eui).await { + Ok(v) => v, + Err(e) => match e { + Error::NotFound(_) => { + debug!(dev_eui = %dev_eui, "Device does not have a device-session"); + continue; + } + _ => { + return Err(anyhow::Error::new(e)); + } + }, + }; + + storage::device::partial_update( + *dev_eui, + &storage::device::DeviceChangeset { + dev_addr: Some(Some(DevAddr::from_slice(&ds.dev_addr)?)), + device_session: Some(Some(ds)), + ..Default::default() + }, + ) + .await?; + + debug!(dev_eui = %dev_eui, "Device-session migrated"); + } + + Ok(()) +} diff --git a/chirpstack/src/cmd/mod.rs b/chirpstack/src/cmd/mod.rs index 08970f81..e85e3ed5 100644 --- a/chirpstack/src/cmd/mod.rs +++ b/chirpstack/src/cmd/mod.rs @@ -1,5 +1,6 @@ pub mod configfile; pub mod create_api_key; pub mod import_legacy_lorawan_devices_repository; +pub mod migrate_ds_to_pg; pub mod print_ds; pub mod root; diff --git a/chirpstack/src/cmd/print_ds.rs b/chirpstack/src/cmd/print_ds.rs index 948f2352..01ce8e87 100644 --- a/chirpstack/src/cmd/print_ds.rs +++ b/chirpstack/src/cmd/print_ds.rs @@ -1,14 +1,14 @@ use anyhow::{Context, Result}; use crate::storage; -use crate::storage::device_session; +use crate::storage::device; use lrwn::EUI64; pub async fn run(dev_eui: &EUI64) -> Result<()> { storage::setup().await.context("Setup storage")?; - let ds = device_session::get(dev_eui) - .await - .context("Get device-session")?; + + let d = device::get(dev_eui).await.context("Get device")?; + let ds = d.get_device_session()?; let json = serde_json::to_string_pretty(&ds)?; println!("{}", json); diff --git a/chirpstack/src/downlink/data.rs b/chirpstack/src/downlink/data.rs index ec3b1813..e7761122 100644 --- a/chirpstack/src/downlink/data.rs +++ b/chirpstack/src/downlink/data.rs @@ -16,14 +16,14 @@ use crate::storage; use crate::storage::{ application, device::{self, DeviceClass}, - device_gateway, device_profile, device_queue, device_session, downlink_frame, + device_gateway, device_profile, device_queue, downlink_frame, helpers::get_all_device_data, mac_command, relay, tenant, }; use crate::uplink::{RelayContext, UplinkFrameSet}; use crate::{adr, config, gateway, integration, maccommand, region, sensitivity}; use chirpstack_api::{gw, integration as integration_pb, internal}; -use lrwn::{keys, AES128Key, DevAddr, NetID}; +use lrwn::{keys, AES128Key, NetID}; struct DownlinkFrameItem { downlink_frame_item: gw::DownlinkFrameItem, @@ -37,7 +37,6 @@ pub struct Data { application: application::Application, device_profile: device_profile::DeviceProfile, device: device::Device, - device_session: internal::DeviceSession, network_conf: config::RegionNetwork, region_conf: Arc>, must_send: bool, @@ -61,7 +60,6 @@ impl Data { application: application::Application, device_profile: device_profile::DeviceProfile, device: device::Device, - device_session: internal::DeviceSession, must_send: bool, must_ack: bool, mac_commands: Vec, @@ -77,7 +75,6 @@ impl Data { application, device_profile, device, - device_session, must_send, must_ack, mac_commands, @@ -105,7 +102,6 @@ impl Data { application: application::Application, device_profile: device_profile::DeviceProfile, device: device::Device, - device_session: internal::DeviceSession, must_send: bool, must_ack: bool, mac_commands: Vec, @@ -122,7 +118,6 @@ impl Data { application, device_profile, device, - device_session, must_send, must_ack, mac_commands, @@ -170,17 +165,21 @@ impl Data { application: application::Application, device_profile: device_profile::DeviceProfile, device: device::Device, - device_session: internal::DeviceSession, must_send: bool, must_ack: bool, mac_commands: Vec, ) -> Result<()> { trace!("Downlink response flow"); - let network_conf = config::get_region_network(&device_session.region_config_id) - .context("Get network config for region")?; - let region_conf = region::get(&device_session.region_config_id) - .context("Get region config for region")?; + let (network_conf, region_conf) = { + let ds = device.get_device_session()?; + + ( + config::get_region_network(&ds.region_config_id) + .context("Get network config for region")?, + region::get(&ds.region_config_id).context("Get region config for region")?, + ) + }; let mut ctx = Data { relay_context: None, @@ -189,7 +188,6 @@ impl Data { application, device_profile, device, - device_session, network_conf, region_conf, must_send, @@ -216,13 +214,13 @@ impl Data { ctx.set_phy_payloads()?; ctx.update_device_queue_item().await?; ctx.save_downlink_frame().await?; + // Some mac-commands set their state (e.g. last requested) to the + // device-session. + ctx.update_device().await?; if ctx._is_roaming() { - ctx.save_device_session().await?; ctx.send_downlink_frame_passive_roaming().await?; ctx.handle_passive_roaming_tx_ack().await?; } else { - // Some mac-commands set their state (e.g. last requested) to the device-session. - ctx.save_device_session().await?; ctx.send_downlink_frame().await?; } } @@ -240,17 +238,20 @@ impl Data { application: application::Application, device_profile: device_profile::DeviceProfile, device: device::Device, - device_session: internal::DeviceSession, must_send: bool, must_ack: bool, mac_commands: Vec, ) -> Result<()> { trace!("Downlink relayed response flow"); - let network_conf = config::get_region_network(&device_session.region_config_id) - .context("Get network config for region")?; - let region_conf = region::get(&device_session.region_config_id) - .context("Get region config for region")?; + let (network_conf, region_conf) = { + let ds = device.get_device_session()?; + ( + config::get_region_network(&ds.region_config_id) + .context("Get network config for region")?, + region::get(&ds.region_config_id).context("Get region config for region")?, + ) + }; let mut ctx = Data { relay_context: Some(relay_ctx), @@ -259,7 +260,6 @@ impl Data { application, device_profile, device, - device_session, network_conf, region_conf, must_send, @@ -285,7 +285,7 @@ impl Data { ctx.set_phy_payloads()?; ctx.wrap_phy_payloads_in_forward_downlink_req()?; ctx.save_downlink_frame_relayed().await?; - ctx.save_device_session().await?; + ctx.update_device().await?; ctx.send_downlink_frame().await?; } else if ctx._must_respond_to_relay() { ctx.set_phy_payloads_relay()?; @@ -299,11 +299,15 @@ impl Data { async fn _handle_schedule_next_queue_item(downlink_id: u32, dev: device::Device) -> Result<()> { trace!("Handle schedule next-queue item flow"); - let (_, app, ten, dp) = get_all_device_data(dev.dev_eui).await?; - let ds = device_session::get(&dev.dev_eui).await?; - let rc = region::get(&ds.region_config_id)?; - let rn = config::get_region_network(&ds.region_config_id)?; + let (dev, app, ten, dp) = get_all_device_data(dev.dev_eui).await?; let dev_gw = device_gateway::get_rx_info(&dev.dev_eui).await?; + let (rc, rn) = { + let ds = dev.get_device_session()?; + ( + region::get(&ds.region_config_id)?, + config::get_region_network(&ds.region_config_id)?, + ) + }; let mut ctx = Data { relay_context: None, @@ -312,7 +316,6 @@ impl Data { application: app, device_profile: dp, device: dev, - device_session: ds, network_conf: rn, region_conf: rc, must_send: false, @@ -360,7 +363,7 @@ impl Data { let gw_down = helpers::select_downlink_gateway( Some(self.tenant.id), - &self.device_session.region_config_id, + &self.device.get_device_session()?.region_config_id, self.network_conf.gateway_prefer_min_margin, self.device_gateway_rx_info.as_mut().unwrap(), )?; @@ -430,6 +433,8 @@ impl Data { async fn get_next_device_queue_item(&mut self) -> Result<()> { trace!("Getting next device queue-item"); + let ds = self.device.get_device_session()?; + // sanity check if self.downlink_frame_items.is_empty() { return Err(anyhow!("downlink_frame_items is empty")); @@ -462,8 +467,7 @@ impl Data { if qi.data.len() <= max_payload_size && !qi.is_pending && !(qi.is_encrypted - && (qi.f_cnt_down.unwrap_or_default() as u32) - < self.device_session.get_a_f_cnt_down()) + && (qi.f_cnt_down.unwrap_or_default() as u32) < ds.get_a_f_cnt_down()) { trace!(id = %qi.id, more_in_queue = more_in_queue, "Found device queue-item for downlink"); self.device_queue_item = Some(qi); @@ -551,9 +555,7 @@ impl Data { } // Handling encrypted payload with invalid FCntDown - if qi.is_encrypted - && (qi.f_cnt_down.unwrap_or_default() as u32) - < self.device_session.get_a_f_cnt_down() + if qi.is_encrypted && (qi.f_cnt_down.unwrap_or_default() as u32) < ds.get_a_f_cnt_down() { device_queue::delete_item(&qi.id) .await @@ -569,7 +571,7 @@ impl Data { context: [ ( "device_f_cnt_down".to_string(), - self.device_session.get_a_f_cnt_down().to_string(), + ds.get_a_f_cnt_down().to_string(), ), ( "queue_item_f_cnt_down".to_string(), @@ -619,7 +621,9 @@ impl Data { self._update_end_device_conf().await?; } - self.mac_commands = filter_mac_commands(&self.device_session, &self.mac_commands); + let ds = self.device.get_device_session()?; + + self.mac_commands = filter_mac_commands(&ds, &self.mac_commands); Ok(()) } @@ -667,6 +671,7 @@ impl Data { fn set_phy_payloads(&mut self) -> Result<()> { trace!("Setting downlink PHYPayloads"); let mut f_pending = self.more_device_queue_items; + let ds = self.device.get_device_session()?; for item in self.downlink_frame_items.iter_mut() { let mut mac_size: usize = 0; @@ -699,8 +704,8 @@ impl Data { // LoRaWAN MAC payload let mut mac_pl = lrwn::MACPayload { fhdr: lrwn::FHDR { - devaddr: lrwn::DevAddr::from_slice(&self.device_session.dev_addr)?, - f_cnt: self.device_session.n_f_cnt_down, + devaddr: self.device.get_dev_addr()?, + f_cnt: ds.n_f_cnt_down, f_ctrl: lrwn::FCtrl { adr: !self.network_conf.adr_disabled, ack: self.must_ack, @@ -729,7 +734,7 @@ impl Data { mac_pl.f_port = Some(0); // Network-server FCnt. - mac_pl.fhdr.f_cnt = self.device_session.n_f_cnt_down; + mac_pl.fhdr.f_cnt = ds.n_f_cnt_down; } else { // In this case mac-commands are sent using the FOpts field. In case there // is a device-queue item, we will validate if it still fits within the @@ -745,7 +750,7 @@ impl Data { mac_pl.f_port = Some(qi.f_port as u8); mac_pl.fhdr.f_cnt = match qi.is_encrypted { true => qi.f_cnt_down.unwrap_or_default() as u32, - false => self.device_session.get_a_f_cnt_down(), + false => ds.get_a_f_cnt_down(), }; mac_pl.frm_payload = Some(lrwn::FRMPayload::Raw(qi.data.clone())); @@ -776,13 +781,11 @@ impl Data { if mac_size > 15 { // Encrypt mac-commands. - phy.encrypt_frm_payload(&lrwn::AES128Key::from_slice( - &self.device_session.nwk_s_enc_key, - )?) - .context("Encrypt frm_payload mac-commands")?; + phy.encrypt_frm_payload(&lrwn::AES128Key::from_slice(&ds.nwk_s_enc_key)?) + .context("Encrypt frm_payload mac-commands")?; } else if self.device_queue_item.is_some() && !qi_encrypted { // Encrypt application payload. - if let Some(key_env) = &self.device_session.app_s_key { + if let Some(key_env) = &ds.app_s_key { let app_s_key = lrwn::AES128Key::from_slice(&key_env.aes_key)?; phy.encrypt_frm_payload(&app_s_key) .context("Encrypt frm_payload application payload")?; @@ -790,25 +793,18 @@ impl Data { } // Encrypt f_opts mac-commands (LoRaWAN 1.1) - if !self - .device_session - .mac_version() - .to_string() - .starts_with("1.0") - { - phy.encrypt_f_opts(&lrwn::AES128Key::from_slice( - &self.device_session.nwk_s_enc_key, - )?) - .context("Encrypt f_opts")?; + if !ds.mac_version().to_string().starts_with("1.0") { + phy.encrypt_f_opts(&lrwn::AES128Key::from_slice(&ds.nwk_s_enc_key)?) + .context("Encrypt f_opts")?; } // Set MIC. // If this is an ACK, then FCntUp has already been incremented by one. If // this is not an ACK, then DownlinkDataMIC will zero out ConfFCnt. phy.set_downlink_data_mic( - self.device_session.mac_version().from_proto(), - self.device_session.f_cnt_up.overflowing_sub(1).0, - &lrwn::AES128Key::from_slice(&self.device_session.s_nwk_s_int_key)?, + ds.mac_version().from_proto(), + ds.f_cnt_up.overflowing_sub(1).0, + &lrwn::AES128Key::from_slice(&ds.s_nwk_s_int_key)?, ) .context("Set downlink data MIC")?; @@ -827,6 +823,7 @@ impl Data { trace!("Wrap PhyPayloads in ForwardDownlinkReq"); let relay_ctx = self.relay_context.as_ref().unwrap(); + let relay_ds = relay_ctx.device.get_device_session()?; for item in self.downlink_frame.items.iter_mut() { let mut relay_phy = lrwn::PhyPayload { @@ -836,8 +833,8 @@ impl Data { }, payload: lrwn::Payload::MACPayload(lrwn::MACPayload { fhdr: lrwn::FHDR { - devaddr: lrwn::DevAddr::from_slice(&relay_ctx.device_session.dev_addr)?, - f_cnt: relay_ctx.device_session.get_a_f_cnt_down(), + devaddr: relay_ctx.device.get_dev_addr()?, + f_cnt: relay_ds.get_a_f_cnt_down(), f_ctrl: lrwn::FCtrl { adr: !self.network_conf.adr_disabled, ack: relay_ctx.must_ack, @@ -851,17 +848,16 @@ impl Data { mic: None, }; - relay_phy.encrypt_frm_payload(&lrwn::AES128Key::from_slice( - &relay_ctx.device_session.nwk_s_enc_key, - )?)?; + relay_phy + .encrypt_frm_payload(&lrwn::AES128Key::from_slice(&relay_ds.nwk_s_enc_key)?)?; // Set MIC. // If this is an ACK, then FCntUp has already been incremented by one. If // this is not an ACK, then DownlinkDataMIC will zero out ConfFCnt. relay_phy.set_downlink_data_mic( - relay_ctx.device_session.mac_version().from_proto(), - relay_ctx.device_session.f_cnt_up - 1, - &lrwn::AES128Key::from_slice(&relay_ctx.device_session.s_nwk_s_int_key)?, + relay_ds.mac_version().from_proto(), + relay_ds.f_cnt_up - 1, + &lrwn::AES128Key::from_slice(&relay_ds.s_nwk_s_int_key)?, )?; let relay_phy_b = relay_phy.to_vec()?; @@ -875,6 +871,7 @@ impl Data { trace!("Set relay PhyPayloads"); let relay_ctx = self.relay_context.as_ref().unwrap(); + let relay_ds = relay_ctx.device.get_device_session()?; for item in self.downlink_frame_items.iter_mut() { let mut relay_phy = lrwn::PhyPayload { @@ -884,8 +881,8 @@ impl Data { }, payload: lrwn::Payload::MACPayload(lrwn::MACPayload { fhdr: lrwn::FHDR { - devaddr: lrwn::DevAddr::from_slice(&relay_ctx.device_session.dev_addr)?, - f_cnt: relay_ctx.device_session.get_a_f_cnt_down(), + devaddr: relay_ctx.device.get_dev_addr()?, + f_cnt: relay_ds.get_a_f_cnt_down(), f_ctrl: lrwn::FCtrl { adr: !self.network_conf.adr_disabled, ack: relay_ctx.must_ack, @@ -903,9 +900,9 @@ impl Data { // If this is an ACK, then FCntUp has already been incremented by one. If // this is not an ACK, then DownlinkDataMIC will zero out ConfFCnt. relay_phy.set_downlink_data_mic( - relay_ctx.device_session.mac_version().from_proto(), - relay_ctx.device_session.f_cnt_up - 1, - &lrwn::AES128Key::from_slice(&relay_ctx.device_session.s_nwk_s_int_key)?, + relay_ds.mac_version().from_proto(), + relay_ds.f_cnt_up - 1, + &lrwn::AES128Key::from_slice(&relay_ds.s_nwk_s_int_key)?, )?; let relay_phy_b = relay_phy.to_vec()?; @@ -920,6 +917,8 @@ impl Data { async fn update_device_queue_item(&mut self) -> Result<()> { trace!("Updating device queue-item"); + let ds = self.device.get_device_session()?; + if let Some(qi) = &mut self.device_queue_item { // Note that the is_pending is set to true after a tx acknowledgement. If it would be // set to true at this point, the queue-item would be removed in the following Class-A @@ -932,7 +931,7 @@ impl Data { // Do not update the frame-counter in case the queue-item is encrypted. if !qi.is_encrypted { - qi.f_cnt_down = Some(self.device_session.get_a_f_cnt_down() as i64); + qi.f_cnt_down = Some(ds.get_a_f_cnt_down() as i64); *qi = device_queue::update_item(qi.clone()).await?; } } @@ -942,6 +941,7 @@ impl Data { async fn save_downlink_frame(&self) -> Result<()> { trace!("Saving downlink frame"); + let ds = self.device.get_device_session()?; downlink_frame::save(&internal::DownlinkFrame { downlink_id: self.downlink_frame.downlink_id, @@ -950,20 +950,16 @@ impl Data { Some(qi) => qi.id.as_bytes().to_vec(), None => vec![], }, - encrypted_fopts: self - .device_session - .mac_version() - .to_string() - .starts_with("1.1"), - nwk_s_enc_key: self.device_session.nwk_s_enc_key.clone(), + encrypted_fopts: ds.mac_version().to_string().starts_with("1.1"), + nwk_s_enc_key: ds.nwk_s_enc_key.clone(), downlink_frame: Some(self.downlink_frame.clone()), - n_f_cnt_down: self.device_session.n_f_cnt_down, + n_f_cnt_down: ds.n_f_cnt_down, a_f_cnt_down: match &self.device_queue_item { Some(v) => match v.is_encrypted { true => v.f_cnt_down.unwrap_or_default() as u32, - false => self.device_session.get_a_f_cnt_down(), + false => ds.get_a_f_cnt_down(), }, - None => self.device_session.get_a_f_cnt_down(), + None => ds.get_a_f_cnt_down(), }, ..Default::default() }) @@ -977,6 +973,7 @@ impl Data { trace!("Saving ForwardDownlinkReq frame"); let relay_ctx = self.relay_context.as_ref().unwrap(); + let relay_ds = relay_ctx.device.get_device_session()?; downlink_frame::save(&internal::DownlinkFrame { downlink_id: self.downlink_frame.downlink_id, @@ -986,10 +983,10 @@ impl Data { Some(qi) => qi.id.as_bytes().to_vec(), None => vec![], }, - nwk_s_enc_key: relay_ctx.device_session.nwk_s_enc_key.clone(), + nwk_s_enc_key: relay_ds.nwk_s_enc_key.clone(), downlink_frame: Some(self.downlink_frame.clone()), - n_f_cnt_down: relay_ctx.device_session.n_f_cnt_down, - a_f_cnt_down: relay_ctx.device_session.get_a_f_cnt_down(), + n_f_cnt_down: relay_ds.n_f_cnt_down, + a_f_cnt_down: relay_ds.get_a_f_cnt_down(), ..Default::default() }) .await?; @@ -999,21 +996,20 @@ impl Data { async fn send_downlink_frame(&self) -> Result<()> { trace!("Sending downlink frame"); + let ds = self.device.get_device_session()?; - gateway::backend::send_downlink( - &self.device_session.region_config_id, - &self.downlink_frame, - ) - .await - .context("Send downlink frame")?; + gateway::backend::send_downlink(&ds.region_config_id, &self.downlink_frame) + .await + .context("Send downlink frame")?; Ok(()) } fn check_for_first_uplink(&self) -> Result<(), Error> { trace!("Checking if device has sent its first uplink already"); + let ds = self.device.get_device_session().map_err(|_| Error::Abort)?; - if self.device_session.f_cnt_up == 0 { + if ds.f_cnt_up == 0 { debug!("Device must send its first uplink first"); return Err(Error::Abort); } @@ -1027,12 +1023,18 @@ impl Data { Ok(()) } - async fn save_device_session(&self) -> Result<()> { - trace!("Saving device-session"); + async fn update_device(&self) -> Result<()> { + trace!("Updating device"); + + device::partial_update( + self.device.dev_eui, + &device::DeviceChangeset { + device_session: Some(self.device.device_session.clone()), + ..Default::default() + }, + ) + .await?; - device_session::save(&self.device_session) - .await - .context("Save device-session")?; Ok(()) } @@ -1040,7 +1042,7 @@ impl Data { trace!("Sending downlink-frame using passive-roaming"); let ufs = self.uplink_frame_set.as_ref().unwrap(); - + let ds = self.device.get_device_session()?; let roaming_meta = ufs.roaming_meta_data.as_ref().unwrap(); let net_id = NetID::from_slice(&roaming_meta.base_payload.sender_id)?; @@ -1050,7 +1052,7 @@ impl Data { phy_payload: self.downlink_frame.items[0].phy_payload.clone(), dl_meta_data: Some(backend::DLMetaData { class_mode: Some("A".to_string()), - dev_eui: self.device_session.dev_eui.clone(), + dev_eui: self.device.dev_eui.to_vec(), f_ns_ul_token: roaming_meta.ul_meta_data.f_ns_ul_token.clone(), dl_freq_1: { let rx1_freq = self @@ -1058,16 +1060,15 @@ impl Data { .get_rx1_frequency_for_uplink_frequency(ufs.tx_info.frequency)?; Some(rx1_freq as f64 / 1_000_000.0) }, - dl_freq_2: Some(self.device_session.rx2_frequency as f64 / 1_000_000.0), + dl_freq_2: Some(ds.rx2_frequency as f64 / 1_000_000.0), data_rate_1: { - let rx1_dr = self.region_conf.get_rx1_data_rate_index( - self.device_session.dr as u8, - self.device_session.rx1_dr_offset as usize, - )?; + let rx1_dr = self + .region_conf + .get_rx1_data_rate_index(ds.dr as u8, ds.rx1_dr_offset as usize)?; Some(rx1_dr) }, - data_rate_2: Some(self.device_session.rx2_dr as u8), - rx_delay_1: Some(self.device_session.rx1_delay as usize), + data_rate_2: Some(ds.rx2_dr as u8), + rx_delay_1: Some(ds.rx1_delay as usize), gw_info: roaming_meta .ul_meta_data .gw_info @@ -1119,6 +1120,7 @@ impl Data { async fn _request_custom_channel_reconfiguration(&mut self) -> Result<()> { trace!("Requesting custom channel re-configuration"); let mut wanted_channels: HashMap = HashMap::new(); + let ds = self.device.get_device_session_mut()?; for i in self.region_conf.get_user_defined_uplink_channel_indices() { let c = self.region_conf.get_uplink_channel(i)?; @@ -1127,8 +1129,7 @@ impl Data { // cleanup channels that do not exist anydmore // these will be disabled by the LinkADRReq channel-mask reconfiguration - let ds_keys: Vec = self - .device_session + let ds_keys: Vec = ds .extra_uplink_channels .keys() .map(|k| *k as usize) @@ -1136,14 +1137,11 @@ impl Data { for k in &ds_keys { if !wanted_channels.contains_key(k) { - self.device_session - .extra_uplink_channels - .remove(&(*k as u32)); + ds.extra_uplink_channels.remove(&(*k as u32)); } } - let current_channels: HashMap = self - .device_session + let current_channels: HashMap = ds .extra_uplink_channels .iter() .map(|(k, v)| { @@ -1173,9 +1171,9 @@ impl Data { // Note: this must come before ADR! async fn _request_channel_mask_reconfiguration(&mut self) -> Result<()> { trace!("Requesting channel-mask reconfiguration"); + let ds = self.device.get_device_session()?; - let enabled_uplink_channel_indices: Vec = self - .device_session + let enabled_uplink_channel_indices: Vec = ds .enabled_uplink_channel_indices .iter() .map(|i| *i as usize) @@ -1193,9 +1191,9 @@ impl Data { } let last = payloads.last_mut().unwrap(); - last.tx_power = self.device_session.tx_power_index as u8; - last.dr = self.device_session.dr as u8; - last.redundancy.nb_rep = self.device_session.nb_trans as u8; + last.tx_power = ds.tx_power_index as u8; + last.dr = ds.dr as u8; + last.redundancy.nb_rep = ds.nb_trans as u8; let set = lrwn::MACCommandSet::new( payloads @@ -1223,6 +1221,7 @@ impl Data { .get_data_rate(self.uplink_frame_set.as_ref().unwrap().dr)?; let ufs = self.uplink_frame_set.as_ref().unwrap(); + let ds = self.device.get_device_session()?; let req = adr::Request { region_config_id: ufs.region_config_id.clone(), @@ -1230,12 +1229,12 @@ impl Data { dev_eui: self.device.dev_eui, mac_version: self.device_profile.mac_version, reg_params_revision: self.device_profile.reg_params_revision, - adr: self.device_session.adr, + adr: ds.adr, dr: self.uplink_frame_set.as_ref().unwrap().dr, - tx_power_index: self.device_session.tx_power_index as u8, - nb_trans: self.device_session.nb_trans as u8, - max_tx_power_index: if self.device_session.max_supported_tx_power_index != 0 { - self.device_session.max_supported_tx_power_index as u8 + tx_power_index: ds.tx_power_index as u8, + nb_trans: ds.nb_trans as u8, + max_tx_power_index: if ds.max_supported_tx_power_index != 0 { + ds.max_supported_tx_power_index as u8 } else { let mut max_tx_power_index: u8 = 0; for n in 0..16 { @@ -1254,8 +1253,8 @@ impl Data { installation_margin: self.network_conf.installation_margin, min_dr: self.network_conf.min_dr, max_dr: self.network_conf.max_dr, - uplink_history: self.device_session.uplink_adr_history.clone(), - skip_f_cnt_check: self.device_session.skip_f_cnt_check, + uplink_history: ds.uplink_adr_history.clone(), + skip_f_cnt_check: ds.skip_f_cnt_check, device_variables: self.device.variables.into_hashmap(), }; @@ -1294,7 +1293,7 @@ impl Data { let mut ch_mask: [bool; 16] = [false; 16]; let mut ch_mask_cntl: Option = None; - for i in &self.device_session.enabled_uplink_channel_indices { + for i in &ds.enabled_uplink_channel_indices { match ch_mask_cntl { None => { // set the chMaskCntl @@ -1338,13 +1337,15 @@ impl Data { return Ok(()); } - match &self.device_session.last_device_status_request { + let ds = self.device.get_device_session_mut()?; + + match &ds.last_device_status_request { None => { self.mac_commands.push(lrwn::MACCommandSet::new(vec![ lrwn::MACCommand::DevStatusReq, ])); - self.device_session.last_device_status_request = Some(Utc::now().into()); + ds.last_device_status_request = Some(Utc::now().into()); } Some(ts) => { let ts: DateTime = ts.clone().try_into().map_err(anyhow::Error::msg)?; @@ -1358,7 +1359,7 @@ impl Data { lrwn::MACCommand::DevStatusReq, ])); - self.device_session.last_device_status_request = Some(Utc::now().into()); + ds.last_device_status_request = Some(Utc::now().into()); } } } @@ -1369,22 +1370,18 @@ impl Data { async fn _request_rejoin_param_setup(&mut self) -> Result<()> { trace!("Requesting rejoin param setup"); + let ds = self.device.get_device_session()?; + // Rejoin-request is disabled or device does not support LoRaWAN 1.1. if !self.network_conf.rejoin_request.enabled - || self - .device_session - .mac_version() - .to_string() - .starts_with("1.0") + || ds.mac_version().to_string().starts_with("1.0") { return Ok(()); } - if !self.device_session.rejoin_request_enabled - || self.device_session.rejoin_request_max_count_n as u8 - != self.network_conf.rejoin_request.max_count_n - || self.device_session.rejoin_request_max_time_n as u8 - != self.network_conf.rejoin_request.max_time_n + if !ds.rejoin_request_enabled + || ds.rejoin_request_max_count_n as u8 != self.network_conf.rejoin_request.max_count_n + || ds.rejoin_request_max_time_n as u8 != self.network_conf.rejoin_request.max_time_n { let set = maccommand::rejoin_param_setup::request( self.network_conf.rejoin_request.max_time_n, @@ -1401,13 +1398,14 @@ impl Data { async fn _set_ping_slot_parameters(&mut self) -> Result<()> { trace!("Setting ping-slot parameters"); + let ds = self.device.get_device_session()?; + if !self.device_profile.supports_class_b { return Ok(()); } - if self.device_session.class_b_ping_slot_dr as u8 != self.network_conf.class_b.ping_slot_dr - || self.device_session.class_b_ping_slot_freq - != self.network_conf.class_b.ping_slot_frequency + if ds.class_b_ping_slot_dr as u8 != self.network_conf.class_b.ping_slot_dr + || ds.class_b_ping_slot_freq != self.network_conf.class_b.ping_slot_frequency { let set = maccommand::ping_slot_channel::request( self.network_conf.class_b.ping_slot_dr, @@ -1423,10 +1421,11 @@ impl Data { async fn _set_rx_parameters(&mut self) -> Result<()> { trace!("Setting rx parameters"); + let ds = self.device.get_device_session()?; - if self.device_session.rx2_frequency != self.network_conf.rx2_frequency - || self.device_session.rx2_dr as u8 != self.network_conf.rx2_dr - || self.device_session.rx1_dr_offset as u8 != self.network_conf.rx1_dr_offset + if ds.rx2_frequency != self.network_conf.rx2_frequency + || ds.rx2_dr as u8 != self.network_conf.rx2_dr + || ds.rx1_dr_offset as u8 != self.network_conf.rx1_dr_offset { let set = maccommand::rx_param_setup::request( self.network_conf.rx1_dr_offset, @@ -1438,7 +1437,7 @@ impl Data { self.mac_commands.push(set); } - let rx1_delay = self.device_session.rx1_delay as u8; + let rx1_delay = ds.rx1_delay as u8; if rx1_delay != self.network_conf.rx1_delay { let set = maccommand::rx_timing_setup::request(self.network_conf.rx1_delay); mac_command::set_pending(&self.device.dev_eui, lrwn::CID::RxTimingSetupReq, &set) @@ -1451,10 +1450,11 @@ impl Data { async fn _set_tx_parameters(&mut self) -> Result<()> { trace!("Setting tx parameters"); + let ds = self.device.get_device_session()?; if !self .region_conf - .implements_tx_param_setup(self.device_session.mac_version().from_proto()) + .implements_tx_param_setup(ds.mac_version().from_proto()) { return Ok(()); } @@ -1462,10 +1462,9 @@ impl Data { let uplink_eirp_index = lrwn::get_tx_param_setup_eirp_index(self.network_conf.uplink_max_eirp); - if self.device_session.uplink_dwell_time_400ms != self.network_conf.uplink_dwell_time_400ms - || self.device_session.downlink_dwell_time_400ms - != self.network_conf.downlink_dwell_time_400ms - || self.device_session.uplink_max_eirp_index as u8 != uplink_eirp_index + if ds.uplink_dwell_time_400ms != self.network_conf.uplink_dwell_time_400ms + || ds.downlink_dwell_time_400ms != self.network_conf.downlink_dwell_time_400ms + || ds.uplink_max_eirp_index as u8 != uplink_eirp_index { let set = maccommand::tx_param_setup::request( self.network_conf.uplink_dwell_time_400ms, @@ -1483,19 +1482,22 @@ impl Data { async fn _update_uplink_list(&mut self) -> Result<()> { trace!("Updating Relay uplink list"); - if self.device_session.relay.is_none() { - self.device_session.relay = Some(internal::Relay::default()); + let dev_eui = self.device.dev_eui; + let ds = self.device.get_device_session_mut()?; + + if ds.relay.is_none() { + ds.relay = Some(internal::Relay::default()); } // Get a copy of the current relay state. - let relay = self.device_session.relay.as_ref().unwrap().clone(); + let relay = ds.relay.as_ref().unwrap().clone(); // Get devices that must be configured on the relay. let relay_devices = relay::list_devices( 15, 0, &relay::DeviceFilters { - relay_dev_eui: Some(self.device.dev_eui), + relay_dev_eui: Some(dev_eui), }, ) .await?; @@ -1511,7 +1513,7 @@ impl Data { if let Some(dev_addr) = device.dev_addr { let mut found = false; - for rd in &mut self.device_session.relay.as_mut().unwrap().devices { + for rd in &mut ds.relay.as_mut().unwrap().devices { if rd.dev_eui == device.dev_eui.to_vec() { found = true; @@ -1524,7 +1526,8 @@ impl Data { || rd.uplink_limit_reload_rate != device.relay_ed_uplink_limit_reload_rate as u32 { - let ds = match device_session::get(&device.dev_eui).await { + let d = device::get(&device.dev_eui).await?; + let ds = match d.get_device_session() { Ok(v) => v, Err(_) => { // It is valid that the device is no longer activated. @@ -1546,13 +1549,13 @@ impl Data { as u8, }, dev_addr, - w_fcnt: ds.relay.map(|v| v.w_f_cnt).unwrap_or(0), + w_fcnt: ds.relay.as_ref().map(|v| v.w_f_cnt).unwrap_or(0), root_wor_s_key, }, ), ]); mac_command::set_pending( - &self.device.dev_eui, + &dev_eui, lrwn::CID::UpdateUplinkListReq, &set, ) @@ -1579,11 +1582,12 @@ impl Data { // available slot). if !found { if free_slots.is_empty() { - warn!(relay_dev_eui = %self.device.dev_eui, "Relay does not have any free UpdateUplinkListReq slots"); + warn!(relay_dev_eui = %dev_eui, "Relay does not have any free UpdateUplinkListReq slots"); continue; } - let ds = match device_session::get(&device.dev_eui).await { + let d = device::get(&device.dev_eui).await?; + let dss = match d.get_device_session() { Ok(v) => v, Err(_) => { // It is valid that the device is no longer activated. @@ -1591,7 +1595,7 @@ impl Data { } }; let root_wor_s_key = - keys::get_root_wor_s_key(&AES128Key::from_slice(&ds.nwk_s_enc_key)?)?; + keys::get_root_wor_s_key(&AES128Key::from_slice(&dss.nwk_s_enc_key)?)?; let set = lrwn::MACCommandSet::new(vec![lrwn::MACCommand::UpdateUplinkListReq( @@ -1602,20 +1606,19 @@ impl Data { reload_rate: device.relay_ed_uplink_limit_reload_rate as u8, }, dev_addr, - w_fcnt: ds.relay.map(|v| v.w_f_cnt).unwrap_or(0), + w_fcnt: dss.relay.as_ref().map(|v| v.w_f_cnt).unwrap_or(0), root_wor_s_key, }, )]); - mac_command::set_pending( - &self.device.dev_eui, - lrwn::CID::UpdateUplinkListReq, - &set, - ) - .await?; + mac_command::set_pending(&dev_eui, lrwn::CID::UpdateUplinkListReq, &set) + .await?; self.mac_commands.push(set); - self.device_session.relay.as_mut().unwrap().devices.push( - internal::RelayDevice { + ds.relay + .as_mut() + .unwrap() + .devices + .push(internal::RelayDevice { index: free_slots[0], join_eui: vec![], dev_eui: device.dev_eui.to_vec(), @@ -1627,8 +1630,7 @@ impl Data { as u32, provisioned: false, w_f_cnt_last_request: Some(Utc::now().into()), - }, - ); + }); // Return because we can't add multiple sets and if we would combine // multiple commands as a single set, it might not fit in a single @@ -1644,19 +1646,22 @@ impl Data { async fn _request_ctrl_uplink_list(&mut self) -> Result<()> { trace!("Requesting CtrlUplinkList to sync WFCnt"); - if self.device_session.relay.is_none() { - self.device_session.relay = Some(internal::Relay::default()); + let dev_eui = self.device.dev_eui; + let ds = self.device.get_device_session_mut()?; + + if ds.relay.is_none() { + ds.relay = Some(internal::Relay::default()); } // Get a copy of the current relay state. - let mut relay = self.device_session.relay.as_ref().unwrap().clone(); + let mut relay = ds.relay.as_ref().unwrap().clone(); // Get devices that must be configured on the relay. let relay_devices = relay::list_devices( 15, 0, &relay::DeviceFilters { - relay_dev_eui: Some(self.device.dev_eui), + relay_dev_eui: Some(dev_eui), }, ) .await?; @@ -1739,7 +1744,7 @@ impl Data { } } - self.device_session.relay = Some(relay); + ds.relay = Some(relay); if !commands.is_empty() { let set = lrwn::MACCommandSet::new(commands); @@ -1754,8 +1759,11 @@ impl Data { async fn _configure_fwd_limit_req(&mut self) -> Result<()> { trace!("Configuring Relay Fwd Limit"); + let dev_eui = self.device.dev_eui; + let ds = self.device.get_device_session_mut()?; + // Get the current relay state. - let relay = if let Some(r) = &self.device_session.relay { + let relay = if let Some(r) = &ds.relay { r.clone() } else { internal::Relay::default() @@ -1806,12 +1814,11 @@ impl Data { }, }, )]); - mac_command::set_pending(&self.device.dev_eui, lrwn::CID::ConfigureFwdLimitReq, &set) - .await?; + mac_command::set_pending(&dev_eui, lrwn::CID::ConfigureFwdLimitReq, &set).await?; self.mac_commands.push(set); } - self.device_session.relay = Some(relay); + ds.relay = Some(relay); Ok(()) } @@ -1819,19 +1826,22 @@ impl Data { async fn _update_filter_list(&mut self) -> Result<()> { trace!("Updating Relay filter list"); - if self.device_session.relay.is_none() { - self.device_session.relay = Some(internal::Relay::default()); + let dev_eui = self.device.dev_eui; + let ds = self.device.get_device_session_mut()?; + + if ds.relay.is_none() { + ds.relay = Some(internal::Relay::default()); } // Get a copy of the current relay state. - let relay = self.device_session.relay.as_ref().unwrap().clone(); + let relay = ds.relay.as_ref().unwrap().clone(); // Get devices that must be configured on the relay. let relay_devices = relay::list_devices( 15, 0, &relay::DeviceFilters { - relay_dev_eui: Some(self.device.dev_eui), + relay_dev_eui: Some(dev_eui), }, ) .await?; @@ -1886,7 +1896,7 @@ impl Data { // Make sure the first item contains the "catch-all" filter. // This is needed to make sure that only the rest of the filter items are allowed to join // through the Relay. - if let Some(relay) = self.device_session.relay.as_mut() { + if let Some(relay) = ds.relay.as_mut() { if relay.filters.is_empty() { relay.filters.push(internal::RelayFilter { index: 0, @@ -1921,7 +1931,7 @@ impl Data { for device in &relay_devices { let mut found = false; - for f in &mut self.device_session.relay.as_mut().unwrap().filters { + for f in &mut ds.relay.as_mut().unwrap().filters { if f.dev_eui == device.dev_eui.to_vec() { found = true; @@ -1938,12 +1948,7 @@ impl Data { filter_list_eui: eui, }, )]); - mac_command::set_pending( - &self.device.dev_eui, - lrwn::CID::FilterListReq, - &set, - ) - .await?; + mac_command::set_pending(&dev_eui, lrwn::CID::FilterListReq, &set).await?; self.mac_commands.push(set); f.join_eui = device.join_eui.to_vec(); @@ -1961,7 +1966,7 @@ impl Data { // available slot). if !found { if free_slots.is_empty() { - warn!(relay_dev_eui = %self.device.dev_eui, "Relay does have have any free FilterListReq slots"); + warn!(relay_dev_eui = %dev_eui, "Relay does have have any free FilterListReq slots"); continue; } @@ -1975,12 +1980,10 @@ impl Data { filter_list_eui: eui, }, )]); - mac_command::set_pending(&self.device.dev_eui, lrwn::CID::FilterListReq, &set) - .await?; + mac_command::set_pending(&dev_eui, lrwn::CID::FilterListReq, &set).await?; self.mac_commands.push(set); - self.device_session - .relay + ds.relay .as_mut() .unwrap() .filters @@ -2005,8 +2008,11 @@ impl Data { async fn _update_relay_conf(&mut self) -> Result<()> { trace!("Updating Relay Conf"); + let dev_eui = self.device.dev_eui; + let ds = self.device.get_device_session_mut()?; + // Get the current relay state. - let relay = if let Some(r) = &self.device_session.relay { + let relay = if let Some(r) = &ds.relay { r.clone() } else { internal::Relay::default() @@ -2041,11 +2047,11 @@ impl Data { second_ch_freq: self.device_profile.relay_second_channel_freq as u32, }, )]); - mac_command::set_pending(&self.device.dev_eui, lrwn::CID::RelayConfReq, &set).await?; + mac_command::set_pending(&dev_eui, lrwn::CID::RelayConfReq, &set).await?; self.mac_commands.push(set); } - self.device_session.relay = Some(relay); + ds.relay = Some(relay); Ok(()) } @@ -2053,8 +2059,11 @@ impl Data { async fn _update_end_device_conf(&mut self) -> Result<()> { trace!("Updating End Device Conf"); + let dev_eui = self.device.dev_eui; + let ds = self.device.get_device_session_mut()?; + // Get the current relay state. - let relay = if let Some(r) = &self.device_session.relay { + let relay = if let Some(r) = &ds.relay { r.clone() } else { internal::Relay::default() @@ -2088,12 +2097,11 @@ impl Data { second_ch_freq: self.device_profile.relay_second_channel_freq as u32, }, )]); - mac_command::set_pending(&self.device.dev_eui, lrwn::CID::EndDeviceConfReq, &set) - .await?; + mac_command::set_pending(&dev_eui, lrwn::CID::EndDeviceConfReq, &set).await?; self.mac_commands.push(set); } - self.device_session.relay = Some(relay); + ds.relay = Some(relay); Ok(()) } @@ -2101,6 +2109,8 @@ impl Data { fn set_tx_info_for_rx1(&mut self) -> Result<()> { trace!("Setting tx-info for RX1"); + let ds = self.device.get_device_session()?; + let gw_down = self.downlink_gateway.as_ref().unwrap(); let mut tx_info = gw::DownlinkTxInfo { board: gw_down.board, @@ -2112,7 +2122,7 @@ impl Data { // get RX1 DR. let rx1_dr_index = self.region_conf.get_rx1_data_rate_index( self.uplink_frame_set.as_ref().unwrap().dr, - self.device_session.rx1_dr_offset as usize, + ds.rx1_dr_offset as usize, )?; let rx1_dr = self.region_conf.get_data_rate(rx1_dr_index)?; @@ -2134,8 +2144,8 @@ impl Data { } // set timestamp - let delay = if self.device_session.rx1_delay > 0 { - Duration::from_secs(self.device_session.rx1_delay as u64) + let delay = if ds.rx1_delay > 0 { + Duration::from_secs(ds.rx1_delay as u64) } else { self.region_conf.get_defaults().rx1_delay }; @@ -2147,7 +2157,7 @@ impl Data { // get remaining payload size let max_pl_size = self.region_conf.get_max_payload_size( - self.device_session.mac_version().from_proto(), + ds.mac_version().from_proto(), self.device_profile.reg_params_revision, rx1_dr_index, )?; @@ -2168,6 +2178,8 @@ impl Data { let gw_down = self.downlink_gateway.as_ref().unwrap(); let relay_ctx = self.relay_context.as_ref().unwrap(); + let relay_ds = relay_ctx.device.get_device_session()?; + let ds = self.device.get_device_session()?; let mut tx_info = gw::DownlinkTxInfo { board: gw_down.board, @@ -2179,7 +2191,7 @@ impl Data { // get RX1 DR. let rx1_dr_index_relay = self.region_conf.get_rx1_data_rate_index( self.uplink_frame_set.as_ref().unwrap().dr, - relay_ctx.device_session.rx1_dr_offset as usize, + relay_ds.rx1_dr_offset as usize, )?; let rx1_dr_relay = self.region_conf.get_data_rate(rx1_dr_index_relay)?; @@ -2201,8 +2213,8 @@ impl Data { } // set timestamp - let delay = if relay_ctx.device_session.rx1_delay > 0 { - Duration::from_secs(relay_ctx.device_session.rx1_delay as u64) + let delay = if relay_ds.rx1_delay > 0 { + Duration::from_secs(relay_ds.rx1_delay as u64) } else { self.region_conf.get_defaults().rx1_delay }; @@ -2214,18 +2226,17 @@ impl Data { // get remaining payload size (relay) let max_pl_size_relay = self.region_conf.get_max_payload_size( - relay_ctx.device_session.mac_version().from_proto(), + relay_ds.mac_version().from_proto(), relay_ctx.device_profile.reg_params_revision, rx1_dr_index_relay, )?; // Get remaining payload size (end-device) - let rx1_dr_index_ed = self.region_conf.get_rx1_data_rate_index( - relay_ctx.req.metadata.dr, - self.device_session.rx1_dr_offset as usize, - )?; + let rx1_dr_index_ed = self + .region_conf + .get_rx1_data_rate_index(relay_ctx.req.metadata.dr, ds.rx1_dr_offset as usize)?; let max_pl_size_ed = self.region_conf.get_max_payload_size( - self.device_session.mac_version().from_proto(), + ds.mac_version().from_proto(), self.device_profile.reg_params_revision, rx1_dr_index_ed, )?; @@ -2251,24 +2262,23 @@ impl Data { fn set_tx_info_for_rx2(&mut self) -> Result<()> { trace!("Setting tx-info for RX2"); + let ds = self.device.get_device_session()?; let gw_down = self.downlink_gateway.as_ref().unwrap(); let mut tx_info = gw::DownlinkTxInfo { board: gw_down.board, antenna: gw_down.antenna, - frequency: if self.device_session.rx2_frequency == 0 { + frequency: if ds.rx2_frequency == 0 { self.region_conf.get_defaults().rx2_frequency } else { - self.device_session.rx2_frequency + ds.rx2_frequency }, context: gw_down.context.clone(), ..Default::default() }; // Set DR to tx-info. - let rx2_dr = self - .region_conf - .get_data_rate(self.device_session.rx2_dr as u8)?; + let rx2_dr = self.region_conf.get_data_rate(ds.rx2_dr as u8)?; helpers::set_tx_info_data_rate(&mut tx_info, &rx2_dr)?; // set tx power @@ -2282,8 +2292,8 @@ impl Data { // set timestamp if !self.immediately { - let delay = if self.device_session.rx1_delay > 0 { - Duration::from_secs(self.device_session.rx1_delay as u64 + 1) + let delay = if ds.rx1_delay > 0 { + Duration::from_secs(ds.rx1_delay as u64 + 1) } else { self.region_conf.get_defaults().rx2_delay }; @@ -2305,9 +2315,9 @@ impl Data { // get remaining payload size let max_pl_size = self.region_conf.get_max_payload_size( - self.device_session.mac_version().from_proto(), + ds.mac_version().from_proto(), self.device_profile.reg_params_revision, - self.device_session.rx2_dr as u8, + ds.rx2_dr as u8, )?; self.downlink_frame_items.push(DownlinkFrameItem { @@ -2326,19 +2336,19 @@ impl Data { let gw_down = self.downlink_gateway.as_ref().unwrap(); let relay_ctx = self.relay_context.as_ref().unwrap(); + let relay_ds = relay_ctx.device.get_device_session()?; + let ds = self.device.get_device_session()?; let mut tx_info = gw::DownlinkTxInfo { board: gw_down.board, antenna: gw_down.antenna, - frequency: relay_ctx.device_session.rx2_frequency, + frequency: relay_ds.rx2_frequency, context: gw_down.context.clone(), ..Default::default() }; // Set DR to tx-info. - let rx2_dr_relay = self - .region_conf - .get_data_rate(relay_ctx.device_session.rx2_dr as u8)?; + let rx2_dr_relay = self.region_conf.get_data_rate(relay_ds.rx2_dr as u8)?; helpers::set_tx_info_data_rate(&mut tx_info, &rx2_dr_relay)?; // set tx power @@ -2352,8 +2362,8 @@ impl Data { // set timestamp if !self.immediately { - let delay = if relay_ctx.device_session.rx1_delay > 0 { - Duration::from_secs(relay_ctx.device_session.rx1_delay as u64 + 1) + let delay = if relay_ds.rx1_delay > 0 { + Duration::from_secs(relay_ds.rx1_delay as u64 + 1) } else { self.region_conf.get_defaults().rx2_delay }; @@ -2375,16 +2385,16 @@ impl Data { // get remaining payload size (relay). let max_pl_size_relay = self.region_conf.get_max_payload_size( - relay_ctx.device_session.mac_version().from_proto(), + relay_ds.mac_version().from_proto(), relay_ctx.device_profile.reg_params_revision, - relay_ctx.device_session.rx2_dr as u8, + relay_ds.rx2_dr as u8, )?; // get remaining payload size (end-device). let max_pl_size_ed = self.region_conf.get_max_payload_size( - self.device_session.mac_version().from_proto(), + ds.mac_version().from_proto(), self.device_profile.reg_params_revision, - self.device_session.rx2_dr as u8, + ds.rx2_dr as u8, )?; // Take the smallest payload size to make sure it can be sent using the relay downlink DR @@ -2412,9 +2422,14 @@ impl Data { let conf = config::get(); let scheduler_run_after_ts = Utc::now() + conf.network.scheduler.class_c_lock_duration; - self.device = - device::set_scheduler_run_after(&self.device.dev_eui, Some(scheduler_run_after_ts)) - .await?; + self.device = device::partial_update( + self.device.dev_eui, + &device::DeviceChangeset { + scheduler_run_after: Some(Some(scheduler_run_after_ts)), + ..Default::default() + }, + ) + .await?; Ok(()) } @@ -2423,12 +2438,13 @@ impl Data { // as we need to calculate the ping_slot_ts for the tx_info. async fn set_tx_info_for_class_b_and_update_scheduler_run_after(&mut self) -> Result<()> { trace!("Setting tx-info for Class-B"); + let ds = self.device.get_device_session()?; let gw_down = self.downlink_gateway.as_ref().unwrap(); let mut tx_info = gw::DownlinkTxInfo { board: gw_down.board, antenna: gw_down.antenna, - frequency: self.device_session.class_b_ping_slot_freq, + frequency: ds.class_b_ping_slot_freq, context: gw_down.context.clone(), ..Default::default() }; @@ -2436,7 +2452,7 @@ impl Data { // Set DR to tx-info. let ping_dr = self .region_conf - .get_data_rate(self.device_session.class_b_ping_slot_dr as u8)?; + .get_data_rate(ds.class_b_ping_slot_dr as u8)?; helpers::set_tx_info_data_rate(&mut tx_info, &ping_dr)?; // set tx power @@ -2452,8 +2468,8 @@ impl Data { let now_gps_ts = Utc::now().to_gps_time() + chrono::Duration::seconds(1); let ping_slot_ts = classb::get_next_ping_slot_after( now_gps_ts, - &DevAddr::from_slice(&self.device_session.dev_addr)?, - self.device_session.class_b_ping_slot_nb as usize, + &self.device.get_dev_addr()?, + ds.class_b_ping_slot_nb as usize, )?; trace!(gps_time_now_ts = %now_gps_ts, ping_slot_ts = %ping_slot_ts, "Calculated ping-slot timestamp"); tx_info.timing = Some(gw::Timing { @@ -2465,26 +2481,30 @@ impl Data { // Update the device next scheduler run. let scheduler_run_after_ts = ping_slot_ts.to_date_time(); trace!(scheduler_run_after = %scheduler_run_after_ts, "Setting scheduler_run_after for device"); - self.device = - device::set_scheduler_run_after(&self.device.dev_eui, Some(scheduler_run_after_ts)) - .await?; + let device = device::partial_update( + self.device.dev_eui, + &device::DeviceChangeset { + scheduler_run_after: Some(Some(scheduler_run_after_ts)), + ..Default::default() + }, + ) + .await?; // Use default frequency if not configured. Based on the configured region this will use // channel-hopping. if tx_info.frequency == 0 { let beacon_ts = classb::get_beacon_start(ping_slot_ts); - let freq = self.region_conf.get_ping_slot_frequency( - DevAddr::from_slice(&self.device_session.dev_addr)?, - beacon_ts.to_std()?, - )?; + let freq = self + .region_conf + .get_ping_slot_frequency(self.device.dev_addr.unwrap(), beacon_ts.to_std()?)?; tx_info.frequency = freq; } // get remaining payload size let max_pl_size = self.region_conf.get_max_payload_size( - self.device_session.mac_version().from_proto(), + ds.mac_version().from_proto(), self.device_profile.reg_params_revision, - self.device_session.class_b_ping_slot_dr as u8, + ds.class_b_ping_slot_dr as u8, )?; self.downlink_frame_items.push(DownlinkFrameItem { @@ -2495,16 +2515,20 @@ impl Data { remaining_payload_size: max_pl_size.n, }); + self.device = device; + Ok(()) } fn _prefer_rx2_dr(&self) -> Result { + let ds = self.device.get_device_session()?; + // The device has not yet been updated to the network-server RX2 parameters // (using mac-commands). Do not prefer RX2 over RX1 in this case. - if self.device_session.rx2_frequency != self.network_conf.rx2_frequency - || self.device_session.rx2_dr != self.network_conf.rx2_dr as u32 - || self.device_session.rx1_dr_offset != self.network_conf.rx1_dr_offset as u32 - || self.device_session.rx1_delay != self.network_conf.rx1_delay as u32 + if ds.rx2_frequency != self.network_conf.rx2_frequency + || ds.rx2_dr != self.network_conf.rx2_dr as u32 + || ds.rx1_dr_offset != self.network_conf.rx1_dr_offset as u32 + || ds.rx1_delay != self.network_conf.rx1_delay as u32 { return Ok(false); } @@ -2512,7 +2536,7 @@ impl Data { // get rx1 data-rate let dr_rx1 = self.region_conf.get_rx1_data_rate_index( self.uplink_frame_set.as_ref().unwrap().dr, - self.device_session.rx1_dr_offset as usize, + ds.rx1_dr_offset as usize, )?; if dr_rx1 < self.network_conf.rx2_prefer_on_rx1_dr_lt { @@ -2523,12 +2547,14 @@ impl Data { } fn _prefer_rx2_link_budget(&self) -> Result { + let ds = self.device.get_device_session()?; + // The device has not yet been updated to the network-server RX2 parameters // (using mac-commands). Do not prefer RX2 over RX1 in this case. - if self.device_session.rx2_frequency != self.network_conf.rx2_frequency - || self.device_session.rx2_dr != self.network_conf.rx2_dr as u32 - || self.device_session.rx1_dr_offset != self.network_conf.rx1_dr_offset as u32 - || self.device_session.rx1_delay != self.network_conf.rx1_delay as u32 + if ds.rx2_frequency != self.network_conf.rx2_frequency + || ds.rx2_dr != self.network_conf.rx2_dr as u32 + || ds.rx1_dr_offset != self.network_conf.rx1_dr_offset as u32 + || ds.rx1_delay != self.network_conf.rx1_delay as u32 { return Ok(false); } @@ -2536,13 +2562,11 @@ impl Data { // get rx1 data-rate let dr_rx1_index = self.region_conf.get_rx1_data_rate_index( self.uplink_frame_set.as_ref().unwrap().dr, - self.device_session.rx1_dr_offset as usize, + ds.rx1_dr_offset as usize, )?; let rx1_dr = self.region_conf.get_data_rate(dr_rx1_index)?; - let rx2_dr = self - .region_conf - .get_data_rate(self.device_session.rx2_dr as u8)?; + let rx2_dr = self.region_conf.get_data_rate(ds.rx2_dr as u8)?; // the calculation below only applies for LORA modulation if let lrwn::region::DataRateModulation::Lora(rx1_dr) = rx1_dr { @@ -2561,8 +2585,7 @@ impl Data { self.network_conf.downlink_tx_power } else { self.region_conf - .get_downlink_tx_power_eirp(self.device_session.rx2_frequency) - as i32 + .get_downlink_tx_power_eirp(ds.rx2_frequency) as i32 }; let link_budget_rx1 = sensitivity::calculate_link_budget( @@ -2636,7 +2659,7 @@ fn filter_mac_commands( mod test { use super::*; use crate::test; - use lrwn::EUI64; + use lrwn::{DevAddr, EUI64}; use tokio::time::sleep; use uuid::Uuid; @@ -2842,6 +2865,17 @@ mod test { device_queue::enqueue_item(qi.clone()).await.unwrap(); } + // update device device-session + let d = device::partial_update( + d.dev_eui, + &device::DeviceChangeset { + device_session: Some(Some(ds.clone())), + ..Default::default() + }, + ) + .await + .unwrap(); + let mut ctx = Data { relay_context: None, uplink_frame_set: None, @@ -2849,7 +2883,6 @@ mod test { application: app.clone(), device_profile: dp.clone(), device: d.clone(), - device_session: ds.clone(), network_conf: config::get_region_network("eu868").unwrap(), region_conf: region::get("eu868").unwrap(), must_send: false, @@ -3380,15 +3413,11 @@ mod test { dev_addr: Some(*dev_addr), application_id: app.id, device_profile_id: dp_ed.id, - ..Default::default() - }) - .await - .unwrap(); - - let _ = device_session::save(&internal::DeviceSession { - dev_addr: dev_addr.to_vec(), - dev_eui: dev_eui.to_vec(), - nwk_s_enc_key: vec![0; 16], + device_session: Some(internal::DeviceSession { + dev_addr: dev_addr.to_vec(), + nwk_s_enc_key: vec![0; 16], + ..Default::default() + }), ..Default::default() }) .await @@ -3397,6 +3426,17 @@ mod test { relay::add_device(d_relay.dev_eui, d.dev_eui).await.unwrap(); } + // update device with device-session + let d_relay = device::partial_update( + d_relay.dev_eui, + &device::DeviceChangeset { + device_session: Some(Some(test.device_session.clone())), + ..Default::default() + }, + ) + .await + .unwrap(); + let mut ctx = Data { relay_context: None, uplink_frame_set: None, @@ -3404,7 +3444,6 @@ mod test { application: app.clone(), device_profile: dp_relay.clone(), device: d_relay.clone(), - device_session: test.device_session.clone(), network_conf: config::get_region_network("eu868").unwrap(), region_conf: region::get("eu868").unwrap(), must_send: false, @@ -3427,14 +3466,17 @@ mod test { } // We can not predict the w_f_cnt_last_request timestamp. - if let Some(relay) = &mut ctx.device_session.relay { + if let Some(relay) = &mut ctx.device.get_device_session_mut().unwrap().relay { for rd in &mut relay.devices { rd.w_f_cnt_last_request = None; } } assert_eq!(test.expected_mac_commands, ctx.mac_commands); - assert_eq!(test.expected_device_session, ctx.device_session); + assert_eq!( + &test.expected_device_session, + ctx.device.get_device_session().unwrap() + ); } } @@ -3833,6 +3875,17 @@ mod test { relay::add_device(d_relay.dev_eui, d.dev_eui).await.unwrap(); } + // update relay device with device-session + let d_relay = device::partial_update( + d_relay.dev_eui, + &device::DeviceChangeset { + device_session: Some(Some(test.device_session.clone())), + ..Default::default() + }, + ) + .await + .unwrap(); + let mut ctx = Data { relay_context: None, uplink_frame_set: None, @@ -3840,7 +3893,6 @@ mod test { application: app.clone(), device_profile: dp_relay.clone(), device: d_relay.clone(), - device_session: test.device_session.clone(), network_conf: config::get_region_network("eu868").unwrap(), region_conf: region::get("eu868").unwrap(), must_send: false, @@ -3863,7 +3915,10 @@ mod test { } assert_eq!(test.expected_mac_commands, ctx.mac_commands); - assert_eq!(test.expected_device_session, ctx.device_session); + assert_eq!( + &test.expected_device_session, + ctx.device.get_device_session().unwrap() + ); } } @@ -3954,8 +4009,10 @@ mod test { tenant: tenant::Tenant::default(), application: application::Application::default(), device_profile: test.device_profile.clone(), - device: device::Device::default(), - device_session: test.device_session.clone(), + device: device::Device { + device_session: Some(test.device_session.clone()), + ..Default::default() + }, network_conf: config::get_region_network("eu868").unwrap(), region_conf: region::get("eu868").unwrap(), must_send: false, @@ -4063,8 +4120,10 @@ mod test { tenant: tenant::Tenant::default(), application: application::Application::default(), device_profile: test.device_profile.clone(), - device: device::Device::default(), - device_session: test.device_session.clone(), + device: device::Device { + device_session: Some(test.device_session.clone()), + ..Default::default() + }, network_conf: config::get_region_network("eu868").unwrap(), region_conf: region::get("eu868").unwrap(), must_send: false, @@ -4182,8 +4241,10 @@ mod test { tenant: tenant::Tenant::default(), application: application::Application::default(), device_profile: test.device_profile.clone(), - device: device::Device::default(), - device_session: test.device_session.clone(), + device: device::Device { + device_session: Some(test.device_session.clone()), + ..Default::default() + }, network_conf: config::get_region_network("eu868").unwrap(), region_conf: region::get("eu868").unwrap(), must_send: false, @@ -4218,7 +4279,6 @@ mod test { name: "w_f_cnt has been recently requested".into(), relay_devices: vec![EUI64::from_be_bytes([1, 1, 1, 1, 1, 1, 1, 2])], device_session: internal::DeviceSession { - dev_eui: vec![1, 1, 1, 1, 1, 1, 1, 1], relay: Some(internal::Relay { devices: vec![internal::RelayDevice { index: 1, @@ -4236,7 +4296,6 @@ mod test { name: "w_f_cnt has never been requested".into(), relay_devices: vec![EUI64::from_be_bytes([1, 1, 1, 1, 1, 1, 1, 2])], device_session: internal::DeviceSession { - dev_eui: vec![1, 1, 1, 1, 1, 1, 1, 1], relay: Some(internal::Relay { devices: vec![internal::RelayDevice { index: 1, @@ -4261,7 +4320,6 @@ mod test { name: "w_f_cnt has been requested two days ago".into(), relay_devices: vec![EUI64::from_be_bytes([1, 1, 1, 1, 1, 1, 1, 2])], device_session: internal::DeviceSession { - dev_eui: vec![1, 1, 1, 1, 1, 1, 1, 1], relay: Some(internal::Relay { devices: vec![internal::RelayDevice { index: 1, @@ -4296,7 +4354,6 @@ mod test { EUI64::from_be_bytes([1, 1, 1, 1, 1, 1, 1, 5]), ], device_session: internal::DeviceSession { - dev_eui: vec![1, 1, 1, 1, 1, 1, 1, 1], relay: Some(internal::Relay { devices: vec![ internal::RelayDevice { @@ -4354,7 +4411,6 @@ mod test { name: "device has been removed".into(), relay_devices: vec![], device_session: internal::DeviceSession { - dev_eui: vec![1, 1, 1, 1, 1, 1, 1, 1], relay: Some(internal::Relay { devices: vec![internal::RelayDevice { index: 1, @@ -4439,6 +4495,17 @@ mod test { relay::add_device(d_relay.dev_eui, d.dev_eui).await.unwrap(); } + // update relay device + let d_relay = device::partial_update( + d_relay.dev_eui, + &device::DeviceChangeset { + device_session: Some(Some(test.device_session.clone())), + ..Default::default() + }, + ) + .await + .unwrap(); + let mut ctx = Data { relay_context: None, uplink_frame_set: None, @@ -4446,7 +4513,6 @@ mod test { application: application::Application::default(), device_profile: device_profile::DeviceProfile::default(), device: d_relay.clone(), - device_session: test.device_session.clone(), network_conf: config::get_region_network("eu868").unwrap(), region_conf: region::get("eu868").unwrap(), must_send: false, diff --git a/chirpstack/src/downlink/join.rs b/chirpstack/src/downlink/join.rs index c2f39709..fe1e83a6 100644 --- a/chirpstack/src/downlink/join.rs +++ b/chirpstack/src/downlink/join.rs @@ -21,7 +21,6 @@ pub struct JoinAccept<'a> { relay_context: Option<&'a RelayContext>, tenant: &'a tenant::Tenant, device: &'a device::Device, - device_session: &'a internal::DeviceSession, join_accept: &'a PhyPayload, network_conf: config::RegionNetwork, region_conf: Arc>, @@ -36,20 +35,12 @@ impl JoinAccept<'_> { ufs: &UplinkFrameSet, tenant: &tenant::Tenant, device: &device::Device, - device_session: &internal::DeviceSession, join_accept: &PhyPayload, ) -> Result<()> { let downlink_id: u32 = rand::thread_rng().gen(); let span = span!(Level::INFO, "join_accept", downlink_id = downlink_id); - let fut = JoinAccept::_handle( - downlink_id, - ufs, - tenant, - device, - device_session, - join_accept, - ); + let fut = JoinAccept::_handle(downlink_id, ufs, tenant, device, join_accept); fut.instrument(span).await } @@ -58,7 +49,6 @@ impl JoinAccept<'_> { ufs: &UplinkFrameSet, tenant: &tenant::Tenant, device: &device::Device, - device_session: &internal::DeviceSession, join_accept: &PhyPayload, ) -> Result<()> { let downlink_id: u32 = rand::thread_rng().gen(); @@ -68,15 +58,8 @@ impl JoinAccept<'_> { downlink_id = downlink_id ); - let fut = JoinAccept::_handle_relayed( - downlink_id, - relay_ctx, - ufs, - tenant, - device, - device_session, - join_accept, - ); + let fut = + JoinAccept::_handle_relayed(downlink_id, relay_ctx, ufs, tenant, device, join_accept); fut.instrument(span).await } @@ -85,7 +68,6 @@ impl JoinAccept<'_> { ufs: &UplinkFrameSet, tenant: &tenant::Tenant, device: &device::Device, - device_session: &internal::DeviceSession, join_accept: &PhyPayload, ) -> Result<()> { let mut ctx = JoinAccept { @@ -93,7 +75,6 @@ impl JoinAccept<'_> { relay_context: None, tenant, device, - device_session, join_accept, network_conf: config::get_region_network(&ufs.region_config_id)?, region_conf: region::get(&ufs.region_config_id)?, @@ -122,7 +103,6 @@ impl JoinAccept<'_> { ufs: &UplinkFrameSet, tenant: &tenant::Tenant, device: &device::Device, - device_session: &internal::DeviceSession, join_accept: &PhyPayload, ) -> Result<()> { let mut ctx = JoinAccept { @@ -130,7 +110,6 @@ impl JoinAccept<'_> { relay_context: Some(relay_ctx), tenant, device, - device_session, join_accept, network_conf: config::get_region_network(&ufs.region_config_id)?, region_conf: region::get(&ufs.region_config_id)?, @@ -302,6 +281,7 @@ impl JoinAccept<'_> { let gw_down = self.downlink_gateway.as_ref().unwrap(); let relay_ctx = self.relay_context.unwrap(); + let relay_ds = relay_ctx.device.get_device_session()?; let mut tx_info = chirpstack_api::gw::DownlinkTxInfo { board: gw_down.board, @@ -311,7 +291,7 @@ impl JoinAccept<'_> { }; // Get RX1 DR offset. - let rx1_dr_offset = relay_ctx.device_session.rx1_dr_offset as usize; + let rx1_dr_offset = relay_ds.rx1_dr_offset as usize; // get RX1 DR. let rx1_dr_index = self @@ -337,8 +317,8 @@ impl JoinAccept<'_> { } // Set timestamp. - let delay = if relay_ctx.device_session.rx1_delay > 0 { - Duration::from_secs(relay_ctx.device_session.rx1_delay as u64) + let delay = if relay_ds.rx1_delay > 0 { + Duration::from_secs(relay_ds.rx1_delay as u64) } else { self.region_conf.get_defaults().rx1_delay }; @@ -415,9 +395,10 @@ impl JoinAccept<'_> { let gw_down = self.downlink_gateway.as_ref().unwrap(); let relay_ctx = self.relay_context.unwrap(); + let relay_ds = relay_ctx.device.get_device_session()?; // Get frequency. - let frequency = relay_ctx.device_session.rx2_frequency; + let frequency = relay_ds.rx2_frequency; let mut tx_info = chirpstack_api::gw::DownlinkTxInfo { board: gw_down.board, @@ -428,7 +409,7 @@ impl JoinAccept<'_> { }; // get RX2 DR - let rx2_dr_index = relay_ctx.device_session.rx2_dr as u8; + let rx2_dr_index = relay_ds.rx2_dr as u8; let rx2_dr = self.region_conf.get_data_rate(rx2_dr_index)?; // set DR to tx_info @@ -444,8 +425,8 @@ impl JoinAccept<'_> { } // Set timestamp. - let delay = if relay_ctx.device_session.rx1_delay > 0 { - Duration::from_secs(relay_ctx.device_session.rx1_delay as u64 + 1) + let delay = if relay_ds.rx1_delay > 0 { + Duration::from_secs(relay_ds.rx1_delay as u64 + 1) } else { self.region_conf.get_defaults().rx2_delay }; @@ -481,6 +462,7 @@ impl JoinAccept<'_> { trace!("Setting ForwardDownlinkReq frame"); let relay_ctx = self.relay_context.as_ref().unwrap(); + let relay_ds = relay_ctx.device.get_device_session()?; let mut relay_phy = lrwn::PhyPayload { mhdr: lrwn::MHDR { @@ -489,16 +471,11 @@ impl JoinAccept<'_> { }, payload: lrwn::Payload::MACPayload(lrwn::MACPayload { fhdr: lrwn::FHDR { - devaddr: lrwn::DevAddr::from_slice(&relay_ctx.device_session.dev_addr)?, - f_cnt: if relay_ctx - .device_session - .mac_version() - .to_string() - .starts_with("1.0") - { - relay_ctx.device_session.n_f_cnt_down + devaddr: relay_ctx.device.get_dev_addr()?, + f_cnt: if relay_ds.mac_version().to_string().starts_with("1.0") { + relay_ds.n_f_cnt_down } else { - relay_ctx.device_session.a_f_cnt_down + relay_ds.a_f_cnt_down }, f_ctrl: lrwn::FCtrl { adr: !self.network_conf.adr_disabled, @@ -517,17 +494,15 @@ impl JoinAccept<'_> { mic: None, }; - relay_phy.encrypt_frm_payload(&lrwn::AES128Key::from_slice( - &relay_ctx.device_session.nwk_s_enc_key, - )?)?; + relay_phy.encrypt_frm_payload(&lrwn::AES128Key::from_slice(&relay_ds.nwk_s_enc_key)?)?; // Set MIC. // If this is an ACK, then FCntUp has already been incremented by one. If // this is not an ACK, then DownlinkDataMIC will zero out ConfFCnt. relay_phy.set_downlink_data_mic( - relay_ctx.device_session.mac_version().from_proto(), - relay_ctx.device_session.f_cnt_up - 1, - &lrwn::AES128Key::from_slice(&relay_ctx.device_session.s_nwk_s_int_key)?, + relay_ds.mac_version().from_proto(), + relay_ds.f_cnt_up - 1, + &lrwn::AES128Key::from_slice(&relay_ds.s_nwk_s_int_key)?, )?; let relay_phy_b = relay_phy.to_vec()?; @@ -551,12 +526,13 @@ impl JoinAccept<'_> { async fn save_downlink_frame(&self) -> Result<()> { trace!("Saving downlink frame"); + let ds = self.device.get_device_session()?; let df = chirpstack_api::internal::DownlinkFrame { dev_eui: self.device.dev_eui.to_be_bytes().to_vec(), downlink_id: self.downlink_frame.downlink_id, downlink_frame: Some(self.downlink_frame.clone()), - nwk_s_enc_key: self.device_session.nwk_s_enc_key.clone(), + nwk_s_enc_key: ds.nwk_s_enc_key.clone(), ..Default::default() }; @@ -571,14 +547,15 @@ impl JoinAccept<'_> { trace!("Saving ForwardDownlinkReq frame"); let relay_ctx = self.relay_context.as_ref().unwrap(); + let relay_ds = relay_ctx.device.get_device_session()?; let df = chirpstack_api::internal::DownlinkFrame { dev_eui: relay_ctx.device.dev_eui.to_be_bytes().to_vec(), downlink_id: self.downlink_frame.downlink_id, downlink_frame: Some(self.downlink_frame.clone()), - nwk_s_enc_key: relay_ctx.device_session.nwk_s_enc_key.clone(), - a_f_cnt_down: relay_ctx.device_session.get_a_f_cnt_down(), - n_f_cnt_down: relay_ctx.device_session.n_f_cnt_down, + nwk_s_enc_key: relay_ds.nwk_s_enc_key.clone(), + a_f_cnt_down: relay_ds.get_a_f_cnt_down(), + n_f_cnt_down: relay_ds.n_f_cnt_down, ..Default::default() }; diff --git a/chirpstack/src/downlink/tx_ack.rs b/chirpstack/src/downlink/tx_ack.rs index 7333f378..3efe4d28 100644 --- a/chirpstack/src/downlink/tx_ack.rs +++ b/chirpstack/src/downlink/tx_ack.rs @@ -9,7 +9,9 @@ use crate::api::helpers::ToProto; use crate::storage::{ application, device::{self, DeviceClass}, - device_profile, device_queue, device_session, downlink_frame, multicast, tenant, + device_profile, device_queue, downlink_frame, + helpers::get_all_device_data, + multicast, tenant, }; use crate::{integration, stream}; use chirpstack_api::{common, gw, integration as integration_pb, internal, stream as stream_pb}; @@ -23,8 +25,6 @@ pub struct TxAck { downlink_frame_item: Option, phy_payload: Option, phy_payload_relayed: Option, - device_session: Option, - device_session_relayed: Option, tenant: Option, tenant_relayed: Option, application: Option, @@ -66,8 +66,6 @@ impl TxAck { downlink_frame_item: None, phy_payload: None, phy_payload_relayed: None, - device_session: None, - device_session_relayed: None, tenant: None, tenant_relayed: None, application: None, @@ -88,10 +86,7 @@ impl TxAck { if ctx.is_error() { if ctx.is_application_payload() || ctx.is_mac_only_downlink() { - ctx.get_device().await?; - ctx.get_device_profile().await?; - ctx.get_application().await?; - ctx.get_tenant().await?; + ctx.get_device_data().await?; ctx.log_tx_ack_error().await?; } @@ -99,30 +94,28 @@ impl TxAck { ctx.delete_multicast_group_queue_item().await?; } } else { - if ctx.is_application_payload() { - ctx.get_device().await?; - ctx.get_device_profile().await?; - ctx.get_application().await?; - ctx.get_tenant().await?; - ctx.get_device_session().await?; - ctx.get_device_queue_item().await?; - if ctx.is_unconfirmed_downlink() { - ctx.delete_device_queue_item().await?; + if ctx.is_application_payload() || ctx.is_mac_only_downlink() { + ctx.get_device_data().await?; + + if ctx.is_application_payload() { + ctx.get_device_queue_item().await?; + if ctx.is_unconfirmed_downlink() { + ctx.delete_device_queue_item().await?; + } + + if ctx.is_confirmed_downlink() { + ctx.set_device_queue_item_pending().await?; + ctx.set_device_session_conf_f_cnt()?; + } + + ctx.increment_a_f_cnt_down()?; + ctx.send_tx_ack_event().await?; } - if ctx.is_confirmed_downlink() { - ctx.set_device_queue_item_pending().await?; - ctx.set_device_session_conf_f_cnt()?; + if ctx.is_mac_only_downlink() { + ctx.increment_n_f_cnt_down()?; } - ctx.increment_a_f_cnt_down()?; - ctx.save_device_session().await?; - ctx.send_tx_ack_event().await?; - } - - if ctx.is_mac_only_downlink() { - ctx.get_device_session().await?; - ctx.increment_n_f_cnt_down()?; ctx.save_device_session().await?; } @@ -140,24 +133,22 @@ impl TxAck { async fn _handle_relayed(&mut self) -> Result<()> { self.get_phy_payload_relayed()?; + self.get_device_data().await?; // the device-data of the relay if self.is_error() { // We log the tx ack error under the relay as this is the device to which the downlink // is sent. - self.get_device().await?; - self.get_device_profile().await?; - self.get_application().await?; - self.get_tenant().await?; self.log_tx_ack_error().await?; } else { // First handle the relay frame-counter increment. - self.get_device_session().await?; self.increment_a_f_cnt_down()?; self.save_device_session().await?; + // Get data of relayed device. + self.get_device_data_relayed().await?; + // Handle end-device frame-counter increment + queue item. if self.is_application_payload_relayed() { - self.get_device_session_relayed().await?; self.get_device_queue_item().await?; if self.is_unconfirmed_downlink_relayed() { self.delete_device_queue_item().await?; @@ -172,13 +163,9 @@ impl TxAck { self.save_device_session_relayed().await?; // Log tx ack event. - self.get_device_relayed().await?; - self.get_device_profile_relayed().await?; - self.get_application_relayed().await?; - self.get_tenant_relayed().await?; + self.get_device_data_relayed().await?; self.send_tx_ack_event_relayed().await?; } else if self.is_mac_only_downlink_relayed() { - self.get_device_session_relayed().await?; self.increment_n_f_cnt_down_relayed()?; self.save_device_session_relayed().await?; } @@ -225,75 +212,29 @@ impl TxAck { Ok(()) } - async fn get_device_session(&mut self) -> Result<()> { - trace!("Getting device-session"); + async fn get_device_data(&mut self) -> Result<()> { + trace!("Getting device data"); let dev_eui = EUI64::from_slice(&self.downlink_frame.as_ref().unwrap().dev_eui)?; - self.device_session = Some(device_session::get(&dev_eui).await?); + let (dev, app, t, dp) = get_all_device_data(dev_eui).await?; + + self.tenant = Some(t); + self.application = Some(app); + self.device_profile = Some(dp); + self.device = Some(dev); Ok(()) } - async fn get_device_session_relayed(&mut self) -> Result<()> { - trace!("Getting relayed device-session"); + async fn get_device_data_relayed(&mut self) -> Result<()> { + trace!("Getting relayed device data"); let dev_eui = EUI64::from_slice(&self.downlink_frame.as_ref().unwrap().dev_eui_relayed)?; - self.device_session_relayed = Some(device_session::get(&dev_eui).await?); + let (dev, app, t, dp) = get_all_device_data(dev_eui).await?; - Ok(()) - } + self.tenant_relayed = Some(t); + self.application_relayed = Some(app); + self.device_profile_relayed = Some(dp); + self.device_relayed = Some(dev); - async fn get_device(&mut self) -> Result<()> { - trace!("Getting device"); - let dev_eui = EUI64::from_slice(&self.downlink_frame.as_ref().unwrap().dev_eui)?; - self.device = Some(device::get(&dev_eui).await?); - Ok(()) - } - - async fn get_device_relayed(&mut self) -> Result<()> { - trace!("Getting relayed device"); - let dev_eui = EUI64::from_slice(&self.downlink_frame.as_ref().unwrap().dev_eui_relayed)?; - self.device_relayed = Some(device::get(&dev_eui).await?); - Ok(()) - } - - async fn get_device_profile(&mut self) -> Result<()> { - trace!("Getting device-profile"); - self.device_profile = - Some(device_profile::get(&self.device.as_ref().unwrap().device_profile_id).await?); - Ok(()) - } - - async fn get_device_profile_relayed(&mut self) -> Result<()> { - trace!("Getting relayed device-profile"); - self.device_profile_relayed = Some( - device_profile::get(&self.device_relayed.as_ref().unwrap().device_profile_id).await?, - ); - Ok(()) - } - - async fn get_application(&mut self) -> Result<()> { - trace!("Getting application"); - self.application = - Some(application::get(&self.device.as_ref().unwrap().application_id).await?); - Ok(()) - } - - async fn get_application_relayed(&mut self) -> Result<()> { - trace!("Getting relayed application"); - self.application_relayed = - Some(application::get(&self.device_relayed.as_ref().unwrap().application_id).await?); - Ok(()) - } - - async fn get_tenant(&mut self) -> Result<()> { - trace!("Getting tenant"); - self.tenant = Some(tenant::get(&self.application.as_ref().unwrap().tenant_id).await?); - Ok(()) - } - - async fn get_tenant_relayed(&mut self) -> Result<()> { - trace!("Getting relayed tenant"); - self.tenant_relayed = - Some(tenant::get(&self.application_relayed.as_ref().unwrap().tenant_id).await?); Ok(()) } @@ -353,7 +294,8 @@ impl TxAck { fn set_device_session_conf_f_cnt(&mut self) -> Result<()> { trace!("Setting device-session conf_f_cnt"); - let ds = self.device_session.as_mut().unwrap(); + let d = self.device.as_mut().unwrap(); + let ds = d.get_device_session_mut()?; let qi = self.device_queue_item.as_ref().unwrap(); ds.conf_f_cnt = match qi.f_cnt_down { @@ -370,7 +312,8 @@ impl TxAck { fn set_device_session_conf_f_cnt_relayed(&mut self) -> Result<()> { trace!("Setting relayed device-session conf_f_cnt"); - let ds = self.device_session_relayed.as_mut().unwrap(); + let d = self.device_relayed.as_mut().unwrap(); + let ds = d.get_device_session_mut()?; let qi = self.device_queue_item.as_ref().unwrap(); ds.conf_f_cnt = match qi.f_cnt_down { @@ -387,7 +330,8 @@ impl TxAck { fn increment_a_f_cnt_down(&mut self) -> Result<()> { trace!("Incrementing a_f_cnt_down"); - let ds = self.device_session.as_mut().unwrap(); + let d = self.device.as_mut().unwrap(); + let ds = d.get_device_session_mut()?; ds.set_a_f_cnt_down(self.downlink_frame.as_ref().unwrap().a_f_cnt_down + 1); Ok(()) @@ -396,7 +340,8 @@ impl TxAck { fn increment_a_f_cnt_down_relayed(&mut self) -> Result<()> { trace!("Incrementing relayed a_f_cnt_down"); - let ds = self.device_session_relayed.as_mut().unwrap(); + let d = self.device_relayed.as_mut().unwrap(); + let ds = d.get_device_session_mut()?; ds.set_a_f_cnt_down(ds.get_a_f_cnt_down() + 1); Ok(()) @@ -405,7 +350,8 @@ impl TxAck { fn increment_n_f_cnt_down(&mut self) -> Result<()> { trace!("Incrementing n_f_cnt_down"); - let ds = self.device_session.as_mut().unwrap(); + let d = self.device.as_mut().unwrap(); + let ds = d.get_device_session_mut()?; ds.n_f_cnt_down += 1; Ok(()) @@ -414,7 +360,8 @@ impl TxAck { fn increment_n_f_cnt_down_relayed(&mut self) -> Result<()> { trace!("Incrementing relayed n_f_cnt_down"); - let ds = self.device_session_relayed.as_mut().unwrap(); + let d = self.device_relayed.as_mut().unwrap(); + let ds = d.get_device_session_mut()?; ds.n_f_cnt_down += 1; Ok(()) @@ -422,13 +369,35 @@ impl TxAck { async fn save_device_session(&self) -> Result<()> { trace!("Saving device-session"); - device_session::save(self.device_session.as_ref().unwrap()).await?; + + let d = self.device.as_ref().unwrap(); + + device::partial_update( + d.dev_eui, + &device::DeviceChangeset { + device_session: Some(d.device_session.clone()), + ..Default::default() + }, + ) + .await?; + Ok(()) } async fn save_device_session_relayed(&self) -> Result<()> { trace!("Saving relayed device-session"); - device_session::save(self.device_session_relayed.as_ref().unwrap()).await?; + + let d = self.device_relayed.as_ref().unwrap(); + + device::partial_update( + d.dev_eui, + &device::DeviceChangeset { + device_session: Some(d.device_session.clone()), + ..Default::default() + }, + ) + .await?; + Ok(()) } diff --git a/chirpstack/src/maccommand/configure_fwd_limit.rs b/chirpstack/src/maccommand/configure_fwd_limit.rs index 48b7bb06..2603ad57 100644 --- a/chirpstack/src/maccommand/configure_fwd_limit.rs +++ b/chirpstack/src/maccommand/configure_fwd_limit.rs @@ -5,11 +5,13 @@ use crate::storage::device; use chirpstack_api::internal; pub fn handle( - dev: &device::Device, - ds: &mut internal::DeviceSession, + dev: &mut device::Device, _block: &lrwn::MACCommandSet, pending: Option<&lrwn::MACCommandSet>, ) -> Result> { + let dev_eui = dev.dev_eui; + let ds = dev.get_device_session_mut()?; + if pending.is_none() { return Err(anyhow!("Expected pending ConfigureFwdLimitReq mac-command")); } @@ -27,7 +29,7 @@ pub fn handle( ds.relay = Some(internal::Relay::default()); } - info!(dev_eui = %dev.dev_eui, "ConfigureFwdLimitReq acknowledged"); + info!(dev_eui = %dev_eui, "ConfigureFwdLimitReq acknowledged"); if let Some(relay) = &mut ds.relay { relay.join_req_limit_reload_rate = req_pl.reload_rate.join_req_reload_rate as u32; @@ -115,10 +117,12 @@ mod test { ]; for tst in &tests { - let mut ds = tst.device_session.clone(); + let mut dev = device::Device { + device_session: Some(tst.device_session.clone()), + ..Default::default() + }; let resp = handle( - &device::Device::default(), - &mut ds, + &mut dev, &tst.configure_fwd_limit_ans, tst.configure_fwd_limit_req.as_ref(), ); @@ -130,7 +134,12 @@ mod test { assert_eq!(true, resp.unwrap().is_none()); } - assert_eq!(tst.expected_device_session, ds, "{}", tst.name); + assert_eq!( + &tst.expected_device_session, + dev.get_device_session().unwrap(), + "{}", + tst.name + ); } } } diff --git a/chirpstack/src/maccommand/ctrl_uplink_list.rs b/chirpstack/src/maccommand/ctrl_uplink_list.rs index 4cd7d70c..b9e9b8c3 100644 --- a/chirpstack/src/maccommand/ctrl_uplink_list.rs +++ b/chirpstack/src/maccommand/ctrl_uplink_list.rs @@ -3,16 +3,17 @@ use std::iter::zip; use anyhow::Result; use tracing::{info, warn}; -use crate::storage::{device, device_session}; -use chirpstack_api::internal; +use crate::storage::device; use lrwn::EUI64; pub async fn handle( - dev: &device::Device, - ds: &mut internal::DeviceSession, + dev: &mut device::Device, block: &lrwn::MACCommandSet, pending: Option<&lrwn::MACCommandSet>, ) -> Result> { + let dev_eui = dev.dev_eui; + let ds = dev.get_device_session_mut()?; + if pending.is_none() { return Err(anyhow!("Expected pending CtrlUplinkListReq mac-command")); } @@ -49,7 +50,7 @@ pub async fn handle( if ans_pl.uplink_list_idx_ack { if let Some(relay) = &mut ds.relay { info!( - dev_eui = %dev.dev_eui, + dev_eui = %dev_eui, uplink_list_idx = req_pl.ctrl_uplink_action.uplink_list_idx, ctrl_uplink_action = action, w_f_cnt = ans_pl.w_fcnt, @@ -59,12 +60,20 @@ pub async fn handle( if action == 0 { for rd in &relay.devices { if req_pl.ctrl_uplink_action.uplink_list_idx as u32 == rd.index { - let mut ds = - device_session::get(&EUI64::from_slice(&rd.dev_eui)?).await?; + let dev_eui = EUI64::from_slice(&rd.dev_eui)?; + let mut d = device::get(&dev_eui).await?; + let ds = d.get_device_session_mut()?; if let Some(relay) = &mut ds.relay { relay.w_f_cnt = ans_pl.w_fcnt; }; - device_session::save(&ds).await?; + device::partial_update( + dev_eui, + &device::DeviceChangeset { + device_session: Some(d.device_session.clone()), + ..Default::default() + }, + ) + .await?; } } } else if action == 1 { @@ -75,7 +84,7 @@ pub async fn handle( } } else { warn!( - dev_eui = %dev.dev_eui, + dev_eui = %dev_eui, uplink_list_idx = req_pl.ctrl_uplink_action.uplink_list_idx, "CtrlUplinkListReq not acknowledged", ); @@ -88,7 +97,9 @@ pub async fn handle( #[cfg(test)] mod test { use super::*; + use crate::storage; use crate::test; + use chirpstack_api::internal; struct Test { name: String, @@ -104,6 +115,39 @@ mod test { async fn test_response() { let _handle = test::prepare().await; + let t = storage::tenant::create(storage::tenant::Tenant { + name: "test-tenant".into(), + ..Default::default() + }) + .await + .unwrap(); + + let app = storage::application::create(storage::application::Application { + name: "test-app".into(), + tenant_id: t.id, + ..Default::default() + }) + .await + .unwrap(); + + let dp = storage::device_profile::create(storage::device_profile::DeviceProfile { + name: "test-dp".into(), + tenant_id: t.id, + ..Default::default() + }) + .await + .unwrap(); + + let dev = storage::device::create(storage::device::Device { + name: "test-dev".into(), + dev_eui: EUI64::from_be_bytes([1, 2, 3, 4, 5, 6, 7, 8]), + application_id: app.id, + device_profile_id: dp.id, + ..Default::default() + }) + .await + .unwrap(); + let tests = vec![ Test { name: "acked, nothing pending".into(), @@ -120,7 +164,6 @@ mod test { }, device_session_ed: internal::DeviceSession { dev_addr: vec![1, 2, 3, 4], - dev_eui: vec![1, 2, 3, 4, 5, 6, 7, 8], relay: Some(internal::Relay { w_f_cnt: 1, ..Default::default() @@ -136,7 +179,6 @@ mod test { ]), expected_device_session_ed: internal::DeviceSession { dev_addr: vec![1, 2, 3, 4], - dev_eui: vec![1, 2, 3, 4, 5, 6, 7, 8], relay: Some(internal::Relay { w_f_cnt: 1, ..Default::default() @@ -160,7 +202,6 @@ mod test { }, device_session_ed: internal::DeviceSession { dev_addr: vec![1, 2, 3, 4], - dev_eui: vec![1, 2, 3, 4, 5, 6, 7, 8], relay: Some(internal::Relay { w_f_cnt: 1, ..Default::default() @@ -183,7 +224,6 @@ mod test { ]), expected_device_session_ed: internal::DeviceSession { dev_addr: vec![1, 2, 3, 4], - dev_eui: vec![1, 2, 3, 4, 5, 6, 7, 8], relay: Some(internal::Relay { w_f_cnt: 10, ..Default::default() @@ -207,7 +247,6 @@ mod test { }, device_session_ed: internal::DeviceSession { dev_addr: vec![1, 2, 3, 4], - dev_eui: vec![1, 2, 3, 4, 5, 6, 7, 8], ..Default::default() }, ctrl_uplink_list_req: Some(lrwn::MACCommandSet::new(vec![ @@ -226,7 +265,6 @@ mod test { ]), expected_device_session_ed: internal::DeviceSession { dev_addr: vec![1, 2, 3, 4], - dev_eui: vec![1, 2, 3, 4, 5, 6, 7, 8], ..Default::default() }, expected_error: None, @@ -236,12 +274,23 @@ mod test { for tst in &tests { println!("> {}", tst.name); - device_session::save(&tst.device_session_ed).await.unwrap(); + device::partial_update( + dev.dev_eui, + &device::DeviceChangeset { + device_session: Some(Some(tst.device_session_ed.clone())), + ..Default::default() + }, + ) + .await + .unwrap(); + + let mut relay_dev = device::Device { + device_session: Some(tst.device_session.clone()), + ..Default::default() + }; - let mut ds = tst.device_session.clone(); let resp = handle( - &device::Device::default(), - &mut ds, + &mut relay_dev, &tst.ctrl_uplink_list_ans, tst.ctrl_uplink_list_req.as_ref(), ) @@ -254,11 +303,9 @@ mod test { assert_eq!(true, resp.unwrap().is_none()); } - let ds = - device_session::get(&EUI64::from_slice(&tst.device_session_ed.dev_eui).unwrap()) - .await - .unwrap(); - assert_eq!(tst.expected_device_session_ed, ds); + let d = device::get(&dev.dev_eui).await.unwrap(); + let ds = d.get_device_session().unwrap(); + assert_eq!(&tst.expected_device_session_ed, ds); } } } diff --git a/chirpstack/src/maccommand/dev_status.rs b/chirpstack/src/maccommand/dev_status.rs index 3ef5b1b9..505e0998 100644 --- a/chirpstack/src/maccommand/dev_status.rs +++ b/chirpstack/src/maccommand/dev_status.rs @@ -23,15 +23,18 @@ pub async fn handle( if let lrwn::MACCommand::DevStatusAns(pl) = mac { info!(dev_eui = %dev.dev_eui, battery = pl.battery, margin = pl.margin, "DevStatusAns received"); - device::set_status( - &dev.dev_eui, - pl.margin as i32, - pl.battery == 0, - if pl.battery > 0 && pl.battery < 255 { - let v: BigDecimal = ((pl.battery as f32) / 254.0 * 100.0).try_into()?; - Some(v.with_scale(2)) - } else { - None + device::partial_update( + dev.dev_eui, + &device::DeviceChangeset { + margin: Some(pl.margin as i32), + external_power_source: Some(pl.battery == 0), + battery_level: Some(if pl.battery > 0 && pl.battery < 255 { + let v: BigDecimal = ((pl.battery as f32) / 254.0 * 100.0).try_into()?; + Some(v.with_scale(2)) + } else { + None + }), + ..Default::default() }, ) .await?; diff --git a/chirpstack/src/maccommand/device_mode_ind.rs b/chirpstack/src/maccommand/device_mode_ind.rs index 9e0d6eb3..9a13ce6d 100644 --- a/chirpstack/src/maccommand/device_mode_ind.rs +++ b/chirpstack/src/maccommand/device_mode_ind.rs @@ -10,11 +10,14 @@ pub async fn handle( .first() .ok_or_else(|| anyhow!("Expected DeviceModeInd"))?; if let lrwn::MACCommand::DeviceModeInd(pl) = mac { - device::set_enabled_class( - &dev.dev_eui, - match pl.class { - lrwn::DeviceModeClass::ClassA => DeviceClass::A, - lrwn::DeviceModeClass::ClassC => DeviceClass::C, + device::partial_update( + dev.dev_eui, + &device::DeviceChangeset { + enabled_class: Some(match pl.class { + lrwn::DeviceModeClass::ClassA => DeviceClass::A, + lrwn::DeviceModeClass::ClassC => DeviceClass::C, + }), + ..Default::default() }, ) .await?; diff --git a/chirpstack/src/maccommand/end_device_conf.rs b/chirpstack/src/maccommand/end_device_conf.rs index 7f8ab551..58dca997 100644 --- a/chirpstack/src/maccommand/end_device_conf.rs +++ b/chirpstack/src/maccommand/end_device_conf.rs @@ -5,11 +5,13 @@ use crate::storage::device; use chirpstack_api::internal; pub fn handle( - dev: &device::Device, - ds: &mut internal::DeviceSession, + dev: &mut device::Device, block: &lrwn::MACCommandSet, pending: Option<&lrwn::MACCommandSet>, ) -> Result> { + let dev_eui = dev.dev_eui; + let ds = dev.get_device_session_mut()?; + if pending.is_none() { return Err(anyhow!("Expected pending EndDeviceConfReq mac-command")); } @@ -41,7 +43,7 @@ pub fn handle( && ans_pl.second_ch_idx_ack && ans_pl.backoff_ack { - info!(dev_eui = %dev.dev_eui, "EndDeviceConfReq acknowledged"); + info!(dev_eui = %dev_eui, "EndDeviceConfReq acknowledged"); if let Some(relay) = &mut ds.relay { relay.ed_activation_mode = @@ -57,7 +59,7 @@ pub fn handle( } } else { warn!( - dev_eui = %dev.dev_eui, + dev_eui = %dev_eui, second_ch_freq_ack = ans_pl.second_ch_freq_ack, second_ch_dr_ack = ans_pl.second_ch_dr_ack, second_ch_idx_ack = ans_pl.second_ch_idx_ack, @@ -175,10 +177,12 @@ mod test { ]; for tst in &tests { - let mut ds = tst.device_session.clone(); + let mut dev = device::Device { + device_session: Some(tst.device_session.clone()), + ..Default::default() + }; let resp = handle( - &device::Device::default(), - &mut ds, + &mut dev, &tst.end_device_conf_ans, tst.end_device_conf_req.as_ref(), ); @@ -190,7 +194,12 @@ mod test { assert_eq!(true, resp.unwrap().is_none()); } - assert_eq!(tst.expected_device_session, ds, "{}", tst.name); + assert_eq!( + &tst.expected_device_session, + dev.get_device_session().unwrap(), + "{}", + tst.name + ); } } } diff --git a/chirpstack/src/maccommand/filter_list.rs b/chirpstack/src/maccommand/filter_list.rs index 9a9473d6..5cc60470 100644 --- a/chirpstack/src/maccommand/filter_list.rs +++ b/chirpstack/src/maccommand/filter_list.rs @@ -2,14 +2,14 @@ use anyhow::Result; use tracing::{info, warn}; use crate::storage::device; -use chirpstack_api::internal; pub fn handle( - dev: &device::Device, - ds: &mut internal::DeviceSession, + dev: &mut device::Device, block: &lrwn::MACCommandSet, pending: Option<&lrwn::MACCommandSet>, ) -> Result> { + let ds = dev.get_device_session_mut()?; + if pending.is_none() { return Err(anyhow!("Expected pending FilterListReq mac-command")); } @@ -68,6 +68,7 @@ pub fn handle( #[cfg(test)] mod test { use super::*; + use chirpstack_api::internal; struct Test { name: String, @@ -214,13 +215,11 @@ mod test { ]; for tst in &tests { - let mut ds = tst.device_session.clone(); - let resp = handle( - &device::Device::default(), - &mut ds, - &tst.filter_list_ans, - tst.filter_list_req.as_ref(), - ); + let mut dev = device::Device { + device_session: Some(tst.device_session.clone()), + ..Default::default() + }; + let resp = handle(&mut dev, &tst.filter_list_ans, tst.filter_list_req.as_ref()); if let Some(e) = &tst.expected_error { assert_eq!(true, resp.is_err(), "{}", tst.name); @@ -229,7 +228,12 @@ mod test { assert_eq!(true, resp.unwrap().is_none()); } - assert_eq!(tst.expected_device_session, ds, "{}", tst.name); + assert_eq!( + &tst.expected_device_session, + dev.get_device_session().unwrap(), + "{}", + tst.name + ); } } } diff --git a/chirpstack/src/maccommand/link_adr.rs b/chirpstack/src/maccommand/link_adr.rs index cfacc499..3c1bf8fa 100644 --- a/chirpstack/src/maccommand/link_adr.rs +++ b/chirpstack/src/maccommand/link_adr.rs @@ -4,15 +4,16 @@ use tracing::{info, warn}; use crate::region; use crate::storage::device; use crate::uplink::UplinkFrameSet; -use chirpstack_api::internal; pub fn handle( uplink_frame_set: &UplinkFrameSet, - dev: &device::Device, - ds: &mut internal::DeviceSession, + dev: &mut device::Device, block: &lrwn::MACCommandSet, pending: Option<&lrwn::MACCommandSet>, ) -> Result> { + let dev_eui = dev.dev_eui; + let ds = dev.get_device_session_mut()?; + if pending.is_none() { return Err(anyhow!("Expected pending LinkADRReq mac-command")); } @@ -82,7 +83,7 @@ pub fn handle( ds.nb_trans = link_adr_req.redundancy.nb_rep as u32; ds.enabled_uplink_channel_indices = chans.iter().map(|i| *i as u32).collect::>(); - info!(dev_eui = %dev.dev_eui, tx_power_index = ds.tx_power_index, dr = ds.dr, nb_trans = ds.nb_trans, enabled_channels = ?ds.enabled_uplink_channel_indices, "LinkADRReq acknowledged"); + info!(dev_eui = %dev_eui, tx_power_index = ds.tx_power_index, dr = ds.dr, nb_trans = ds.nb_trans, enabled_channels = ?ds.enabled_uplink_channel_indices, "LinkADRReq acknowledged"); } else if !ds.adr && ch_mask_ack { // In case the device has ADR disabled, at least it must acknowledge the // channel-mask. It does not have to acknowledge the other parameters. @@ -113,7 +114,7 @@ pub fn handle( ds.tx_power_index = link_adr_req.tx_power as u32; } - info!(dev_eui = %dev.dev_eui, tx_power_index = ds.tx_power_index, dr = ds.dr, nb_trans = ds.nb_trans, enabled_channels = ?ds.enabled_uplink_channel_indices, "LinkADRReq acknowledged (device has ADR disabled)"); + info!(dev_eui = %dev_eui, tx_power_index = ds.tx_power_index, dr = ds.dr, nb_trans = ds.nb_trans, enabled_channels = ?ds.enabled_uplink_channel_indices, "LinkADRReq acknowledged (device has ADR disabled)"); } else { // increase the error counter let count = ds @@ -153,6 +154,7 @@ pub fn handle( pub mod test { use super::*; use crate::region; + use chirpstack_api::internal; use std::collections::HashMap; use std::str::FromStr; use uuid::Uuid; @@ -357,11 +359,11 @@ pub mod test { }; for tst in &tests { - let dev = device::Device { + let mut dev = device::Device { dev_eui: lrwn::EUI64::from_str("0102030405060708").unwrap(), + device_session: Some(tst.device_session.clone()), ..Default::default() }; - let mut ds = tst.device_session.clone(); let block = lrwn::MACCommandSet::new(vec![lrwn::MACCommand::LinkADRAns( tst.link_adr_ans.clone(), )]); @@ -372,7 +374,7 @@ pub mod test { None => None, }; - let res = handle(&ufs, &dev, &mut ds, &block, pending.as_ref()); + let res = handle(&ufs, &mut dev, &block, pending.as_ref()); if let Some(e) = &tst.expected_error { assert_eq!(true, res.is_err(), "{}", tst.name); assert_eq!(e, &format!("{}", res.err().unwrap()), "{}", tst.name); @@ -380,7 +382,12 @@ pub mod test { assert_eq!(true, res.unwrap().is_none(), "{}", tst.name); } - assert_eq!(tst.expected_device_session, ds, "{}", tst.name); + assert_eq!( + &tst.expected_device_session, + dev.get_device_session().unwrap(), + "{}", + tst.name + ); } } } diff --git a/chirpstack/src/maccommand/mod.rs b/chirpstack/src/maccommand/mod.rs index f38d06b1..aede19de 100644 --- a/chirpstack/src/maccommand/mod.rs +++ b/chirpstack/src/maccommand/mod.rs @@ -7,8 +7,6 @@ use crate::config; use crate::helpers::errors::PrintFullError; use crate::storage::{application, device, device_profile, mac_command, tenant}; use crate::uplink::UplinkFrameSet; -use chirpstack_api::internal; -use lrwn::EUI64; pub mod configure_fwd_limit; pub mod ctrl_uplink_list; @@ -41,15 +39,13 @@ pub async fn handle_uplink<'a>( tenant: &tenant::Tenant, app: &application::Application, dp: &device_profile::DeviceProfile, - dev: &device::Device, - ds: &mut internal::DeviceSession, + dev: &mut device::Device, ) -> Result<(Vec, bool)> { let conf = config::get(); if conf.network.mac_commands_disabled { return Ok((Vec::new(), false)); } - let dev_eui = EUI64::from_slice(&ds.dev_eui)?; let mut cids: Vec = Vec::new(); // to maintain the CID order let mut blocks: HashMap = HashMap::new(); @@ -81,18 +77,18 @@ pub async fn handle_uplink<'a>( ); // Get pending mac-command block, this could return None. - let pending = match mac_command::get_pending(&dev_eui, cid).await { + let pending = match mac_command::get_pending(&dev.dev_eui, cid).await { Ok(v) => v, Err(e) => { - error!(dev_eui = %dev_eui, cid = %cid, error = %e, "Get pending mac-command block error"); + error!(dev_eui = %dev.dev_eui, cid = %cid, error = %e, "Get pending mac-command block error"); continue; } }; // Delete the pending mac-command. if pending.is_some() { - if let Err(e) = mac_command::delete_pending(&dev_eui, cid).await { - error!(dev_eui = %dev_eui, cid = %cid, error = %e, "Delete pending mac-command error"); + if let Err(e) = mac_command::delete_pending(&dev.dev_eui, cid).await { + error!(dev_eui = %dev.dev_eui, cid = %cid, error = %e, "Delete pending mac-command error"); } } @@ -107,13 +103,12 @@ pub async fn handle_uplink<'a>( app, dp, dev, - ds, ) .await { Ok(v) => v, Err(e) => { - warn!(dev_eui = %dev_eui, cid = %cid, error = %e.full(), "Handle mac-command error"); + warn!(dev_eui = %dev.dev_eui, cid = %cid, error = %e.full(), "Handle mac-command error"); continue; } }; @@ -135,8 +130,7 @@ async fn handle( tenant: &tenant::Tenant, app: &application::Application, dp: &device_profile::DeviceProfile, - dev: &device::Device, - ds: &mut internal::DeviceSession, + dev: &mut device::Device, ) -> Result> { match cid { lrwn::CID::DevStatusAns => { @@ -144,30 +138,26 @@ async fn handle( } lrwn::CID::DeviceModeInd => device_mode_ind::handle(dev, block).await, lrwn::CID::DeviceTimeReq => device_time::handle(uplink_frame_set, dev, block), - lrwn::CID::LinkADRAns => link_adr::handle(uplink_frame_set, dev, ds, block, pending_block), + lrwn::CID::LinkADRAns => link_adr::handle(uplink_frame_set, dev, block, pending_block), lrwn::CID::LinkCheckReq => link_check::handle(uplink_frame_set, dev, block), - lrwn::CID::NewChannelAns => new_channel::handle(dev, ds, block, pending_block), - lrwn::CID::PingSlotChannelAns => ping_slot_channel::handle(dev, ds, block, pending_block), - lrwn::CID::PingSlotInfoReq => ping_slot_info::handle(dev, ds, block), - lrwn::CID::RejoinParamSetupAns => rejoin_param_setup::handle(dev, ds, block, pending_block), + lrwn::CID::NewChannelAns => new_channel::handle(dev, block, pending_block), + lrwn::CID::PingSlotChannelAns => ping_slot_channel::handle(dev, block, pending_block), + lrwn::CID::PingSlotInfoReq => ping_slot_info::handle(dev, block), + lrwn::CID::RejoinParamSetupAns => rejoin_param_setup::handle(dev, block, pending_block), lrwn::CID::RekeyInd => rekey::handle(dev, block), - lrwn::CID::ResetInd => reset::handle(dev, dp, ds, block), - lrwn::CID::RxParamSetupAns => rx_param_setup::handle(dev, ds, block, pending_block), - lrwn::CID::RxTimingSetupAns => rx_timing_setup::handle(dev, ds, block, pending_block), - lrwn::CID::TxParamSetupAns => tx_param_setup::handle(dev, ds, block, pending_block), - lrwn::CID::RelayConfAns => relay_conf::handle(dev, ds, block, pending_block), - lrwn::CID::EndDeviceConfAns => end_device_conf::handle(dev, ds, block, pending_block), - lrwn::CID::FilterListAns => filter_list::handle(dev, ds, block, pending_block), - lrwn::CID::UpdateUplinkListAns => update_uplink_list::handle(dev, ds, block, pending_block), - lrwn::CID::ConfigureFwdLimitAns => { - configure_fwd_limit::handle(dev, ds, block, pending_block) - } + lrwn::CID::ResetInd => reset::handle(dev, dp, block), + lrwn::CID::RxParamSetupAns => rx_param_setup::handle(dev, block, pending_block), + lrwn::CID::RxTimingSetupAns => rx_timing_setup::handle(dev, block, pending_block), + lrwn::CID::TxParamSetupAns => tx_param_setup::handle(dev, block, pending_block), + lrwn::CID::RelayConfAns => relay_conf::handle(dev, block, pending_block), + lrwn::CID::EndDeviceConfAns => end_device_conf::handle(dev, block, pending_block), + lrwn::CID::FilterListAns => filter_list::handle(dev, block, pending_block), + lrwn::CID::UpdateUplinkListAns => update_uplink_list::handle(dev, block, pending_block), + lrwn::CID::ConfigureFwdLimitAns => configure_fwd_limit::handle(dev, block, pending_block), lrwn::CID::NotifyNewEndDeviceReq => { notify_new_end_device::handle(tenant, dp, app, dev, block).await } - lrwn::CID::CtrlUplinkListAns => { - ctrl_uplink_list::handle(dev, ds, block, pending_block).await - } + lrwn::CID::CtrlUplinkListAns => ctrl_uplink_list::handle(dev, block, pending_block).await, _ => { warn!(cid = %cid, "Unexpected CID"); // Return OK, we don't want to break out of the uplink handling. @@ -180,6 +170,8 @@ async fn handle( pub mod test { use super::*; use crate::config; + use chirpstack_api::internal; + use lrwn::EUI64; use uuid::Uuid; #[tokio::test] @@ -213,16 +205,18 @@ pub mod test { let t: tenant::Tenant = Default::default(); let app: application::Application = Default::default(); let dp: device_profile::DeviceProfile = Default::default(); - let dev: device::Device = Default::default(); - let mut ds = internal::DeviceSession { - dev_eui: vec![1, 2, 3, 4, 5, 6, 7, 8], + let mut dev = device::Device { + dev_eui: EUI64::from_be_bytes([1, 2, 3, 4, 5, 6, 7, 8]), + device_session: Some(internal::DeviceSession { + ..Default::default() + }), ..Default::default() }; // must respond let cmds = lrwn::MACCommandSet::new(vec![lrwn::MACCommand::RxTimingSetupAns]); - let (resp, must_respond) = handle_uplink(&upfs, &cmds, &t, &app, &dp, &dev, &mut ds) + let (resp, must_respond) = handle_uplink(&upfs, &cmds, &t, &app, &dp, &mut dev) .await .unwrap(); assert_eq!(0, resp.len()); @@ -233,7 +227,7 @@ pub mod test { conf.network.mac_commands_disabled = true; config::set(conf); - let (resp, must_respond) = handle_uplink(&upfs, &cmds, &t, &app, &dp, &dev, &mut ds) + let (resp, must_respond) = handle_uplink(&upfs, &cmds, &t, &app, &dp, &mut dev) .await .unwrap(); assert_eq!(0, resp.len()); diff --git a/chirpstack/src/maccommand/new_channel.rs b/chirpstack/src/maccommand/new_channel.rs index 9cd010df..926e78ad 100644 --- a/chirpstack/src/maccommand/new_channel.rs +++ b/chirpstack/src/maccommand/new_channel.rs @@ -62,11 +62,13 @@ pub fn request( } pub fn handle( - dev: &device::Device, - ds: &mut internal::DeviceSession, + dev: &mut device::Device, block: &lrwn::MACCommandSet, pending: Option<&lrwn::MACCommandSet>, ) -> Result> { + let dev_eui = dev.dev_eui; + let ds = dev.get_device_session_mut()?; + if pending.is_none() { return Err(anyhow!("Expected pending NewChannelReq")); } @@ -115,7 +117,7 @@ pub fn handle( .push(req_pl.ch_index as u32); } - info!(dev_eui = %dev.dev_eui, freq = req_pl.freq, channel = req_pl.ch_index, min_dr = req_pl.min_dr, max_dr = req_pl.max_dr, "NewChannelReq acknowledged"); + info!(dev_eui = %dev_eui, freq = req_pl.freq, channel = req_pl.ch_index, min_dr = req_pl.min_dr, max_dr = req_pl.max_dr, "NewChannelReq acknowledged"); } else { let count = ds .mac_command_error_count @@ -124,7 +126,7 @@ pub fn handle( *count += 1; warn!( - dev_eui = %dev.dev_eui, + dev_eui = %dev_eui, freq = req_pl.freq, channel = req_pl.ch_index, min_dr = req_pl.min_dr, @@ -469,16 +471,12 @@ pub mod test { ]; for tst in &tests { - let mut ds = tst.device_session.clone(); + let mut dev = device::Device { + device_session: Some(tst.device_session.clone()), + ..Default::default() + }; - let res = handle( - &device::Device { - ..Default::default() - }, - &mut ds, - &tst.new_channel_ans, - tst.new_channel_req.as_ref(), - ); + let res = handle(&mut dev, &tst.new_channel_ans, tst.new_channel_req.as_ref()); if let Some(e) = &tst.expected_error { assert_eq!(true, res.is_err(), "{}", tst.name); @@ -487,7 +485,12 @@ pub mod test { assert_eq!(true, res.unwrap().is_none(), "{}", tst.name); } - assert_eq!(tst.expected_device_session, ds, "{}", tst.name); + assert_eq!( + &tst.expected_device_session, + dev.get_device_session().unwrap(), + "{}", + tst.name + ); } } } diff --git a/chirpstack/src/maccommand/ping_slot_channel.rs b/chirpstack/src/maccommand/ping_slot_channel.rs index dd516bbd..c47e817a 100644 --- a/chirpstack/src/maccommand/ping_slot_channel.rs +++ b/chirpstack/src/maccommand/ping_slot_channel.rs @@ -2,7 +2,6 @@ use anyhow::Result; use tracing::{info, warn}; use crate::storage::device; -use chirpstack_api::internal; pub fn request(dr: u8, freq: u32) -> lrwn::MACCommandSet { lrwn::MACCommandSet::new(vec![lrwn::MACCommand::PingSlotChannelReq( @@ -11,11 +10,12 @@ pub fn request(dr: u8, freq: u32) -> lrwn::MACCommandSet { } pub fn handle( - dev: &device::Device, - ds: &mut internal::DeviceSession, + dev: &mut device::Device, block: &lrwn::MACCommandSet, pending: Option<&lrwn::MACCommandSet>, ) -> Result> { + let ds = dev.get_device_session_mut()?; + if pending.is_none() { return Err(anyhow!("Pending PingSlotChannelReq expected")); } @@ -66,6 +66,7 @@ pub fn handle( #[cfg(test)] pub mod test { use super::*; + use chirpstack_api::internal; struct Test { name: String, @@ -181,12 +182,12 @@ pub mod test { ]; for tst in &tests { - let mut ds = tst.device_session.clone(); + let mut dev = device::Device { + device_session: Some(tst.device_session.clone()), + ..Default::default() + }; let resp = handle( - &device::Device { - ..Default::default() - }, - &mut ds, + &mut dev, &tst.ping_slot_channel_ans, tst.ping_slot_channel_req.as_ref(), ); @@ -198,7 +199,10 @@ pub mod test { assert_eq!(true, resp.unwrap().is_none()); } - assert_eq!(tst.expected_device_session, ds); + assert_eq!( + &tst.expected_device_session, + dev.get_device_session().unwrap() + ); } } } diff --git a/chirpstack/src/maccommand/ping_slot_info.rs b/chirpstack/src/maccommand/ping_slot_info.rs index c376f43a..45dbdc8e 100644 --- a/chirpstack/src/maccommand/ping_slot_info.rs +++ b/chirpstack/src/maccommand/ping_slot_info.rs @@ -2,13 +2,14 @@ use anyhow::Result; use tracing::info; use crate::storage::device; -use chirpstack_api::internal; pub fn handle( - dev: &device::Device, - ds: &mut internal::DeviceSession, + dev: &mut device::Device, block: &lrwn::MACCommandSet, ) -> Result> { + let dev_eui = dev.dev_eui; + let ds = dev.get_device_session_mut()?; + let mac = (**block) .first() .ok_or_else(|| anyhow!("MACCommandSet is empty"))?; @@ -21,7 +22,7 @@ pub fn handle( ds.class_b_ping_slot_nb = 1 << (7 - pl.periodicity); - info!(dev_eui = %dev.dev_eui, periodicity = pl.periodicity, ping_slot_nb = ds.class_b_ping_slot_nb, "PingSlotInfoReq received"); + info!(dev_eui = %dev_eui, periodicity = pl.periodicity, ping_slot_nb = ds.class_b_ping_slot_nb, "PingSlotInfoReq received"); Ok(Some(lrwn::MACCommandSet::new(vec![ lrwn::MACCommand::PingSlotInfoAns, @@ -31,22 +32,19 @@ pub fn handle( #[cfg(test)] pub mod test { use super::*; + use chirpstack_api::internal; #[test] fn test_handle() { - let mut ds: internal::DeviceSession = Default::default(); + let mut dev = device::Device { + device_session: Some(internal::DeviceSession::default()), + ..Default::default() + }; let block = lrwn::MACCommandSet::new(vec![lrwn::MACCommand::PingSlotInfoReq( lrwn::PingSlotInfoReqPayload { periodicity: 3 }, )]); - let res = handle( - &device::Device { - ..Default::default() - }, - &mut ds, - &block, - ) - .unwrap(); - assert_eq!(16, ds.class_b_ping_slot_nb); + let res = handle(&mut dev, &block).unwrap(); + assert_eq!(16, dev.get_device_session().unwrap().class_b_ping_slot_nb); assert_eq!( Some(lrwn::MACCommandSet::new(vec![ lrwn::MACCommand::PingSlotInfoAns, diff --git a/chirpstack/src/maccommand/rejoin_param_setup.rs b/chirpstack/src/maccommand/rejoin_param_setup.rs index 6eb29f91..fab43c74 100644 --- a/chirpstack/src/maccommand/rejoin_param_setup.rs +++ b/chirpstack/src/maccommand/rejoin_param_setup.rs @@ -2,7 +2,6 @@ use anyhow::Result; use tracing::{info, warn}; use crate::storage::device; -use chirpstack_api::internal; pub fn request(max_time_n: u8, max_count_n: u8) -> lrwn::MACCommandSet { lrwn::MACCommandSet::new(vec![lrwn::MACCommand::RejoinParamSetupReq( @@ -14,11 +13,12 @@ pub fn request(max_time_n: u8, max_count_n: u8) -> lrwn::MACCommandSet { } pub fn handle( - dev: &device::Device, - ds: &mut internal::DeviceSession, + dev: &mut device::Device, block: &lrwn::MACCommandSet, pending: Option<&lrwn::MACCommandSet>, ) -> Result> { + let ds = dev.get_device_session_mut()?; + if pending.is_none() { return Err(anyhow!("Pending RejoinParamSetupReq expected")); } @@ -57,6 +57,7 @@ pub fn handle( #[cfg(test)] pub mod test { use super::*; + use chirpstack_api::internal; struct Test { name: String, @@ -159,12 +160,12 @@ pub mod test { ]; for tst in &tests { - let mut ds = tst.device_session.clone(); + let mut dev = device::Device { + device_session: Some(tst.device_session.clone()), + ..Default::default() + }; let resp = handle( - &device::Device { - ..Default::default() - }, - &mut ds, + &mut dev, &tst.rejoin_param_setup_ans, tst.rejoin_param_setup_req.as_ref(), ); @@ -176,7 +177,12 @@ pub mod test { assert_eq!(true, resp.unwrap().is_none()); } - assert_eq!(tst.expected_device_session, ds, "{}", tst.name); + assert_eq!( + &tst.expected_device_session, + dev.get_device_session().unwrap(), + "{}", + tst.name + ); } } } diff --git a/chirpstack/src/maccommand/relay_conf.rs b/chirpstack/src/maccommand/relay_conf.rs index e79b07e3..b28a8267 100644 --- a/chirpstack/src/maccommand/relay_conf.rs +++ b/chirpstack/src/maccommand/relay_conf.rs @@ -5,11 +5,13 @@ use crate::storage::device; use chirpstack_api::internal; pub fn handle( - dev: &device::Device, - ds: &mut internal::DeviceSession, + dev: &mut device::Device, block: &lrwn::MACCommandSet, pending: Option<&lrwn::MACCommandSet>, ) -> Result> { + let dev_eui = dev.dev_eui; + let ds = dev.get_device_session_mut()?; + if pending.is_none() { return Err(anyhow!("Expected pending RelayConfReq mac-command")); } @@ -43,7 +45,7 @@ pub fn handle( && ans_pl.default_ch_idx_ack && ans_pl.cad_periodicity_ack { - info!(dev_eui = %dev.dev_eui, "RelayConfReq acknowledged"); + info!(dev_eui = %dev_eui, "RelayConfReq acknowledged"); if let Some(relay) = &mut ds.relay { relay.enabled = req_pl.channel_settings_relay.start_stop == 1; @@ -56,7 +58,7 @@ pub fn handle( } } else { warn!( - dev_eui = %dev.dev_eui, + dev_eui = %dev_eui, second_ch_ack_offset_ack = ans_pl.second_ch_ack_offset_ack, second_ch_dr_ack = ans_pl.second_ch_dr_ack, second_ch_idx_ack = ans_pl.second_ch_idx_ack, @@ -177,13 +179,11 @@ mod test { ]; for tst in &tests { - let mut ds = tst.device_session.clone(); - let resp = handle( - &device::Device::default(), - &mut ds, - &tst.relay_conf_ans, - tst.relay_conf_req.as_ref(), - ); + let mut dev = device::Device { + device_session: Some(tst.device_session.clone()), + ..Default::default() + }; + let resp = handle(&mut dev, &tst.relay_conf_ans, tst.relay_conf_req.as_ref()); if let Some(e) = &tst.expected_error { assert_eq!(true, resp.is_err(), "{}", tst.name); @@ -192,7 +192,12 @@ mod test { assert_eq!(true, resp.unwrap().is_none()); } - assert_eq!(tst.expected_device_session, ds, "{}", tst.name); + assert_eq!( + &tst.expected_device_session, + dev.get_device_session().unwrap(), + "{}", + tst.name + ); } } } diff --git a/chirpstack/src/maccommand/reset.rs b/chirpstack/src/maccommand/reset.rs index 51caaba1..baa54254 100644 --- a/chirpstack/src/maccommand/reset.rs +++ b/chirpstack/src/maccommand/reset.rs @@ -2,16 +2,17 @@ use anyhow::Result; use tracing::info; use crate::storage::{device, device_profile}; -use chirpstack_api::internal; const SERV_LORAWAN_VERSION: lrwn::Version = lrwn::Version::LoRaWAN1_1; pub fn handle( - dev: &device::Device, + dev: &mut device::Device, dp: &device_profile::DeviceProfile, - ds: &mut internal::DeviceSession, block: &lrwn::MACCommandSet, ) -> Result> { + let dev_eui = dev.dev_eui; + let ds = dev.get_device_session_mut()?; + let block_mac = (**block) .first() .ok_or_else(|| anyhow!("MACCommandSet is empty"))?; @@ -21,7 +22,7 @@ pub fn handle( return Err(anyhow!("ResetInd expected")); }; - info!(dev_eui = %dev.dev_eui, dev_lorawan_version = %block_pl.dev_lorawan_version, serv_lorawan_version = %SERV_LORAWAN_VERSION, "ResetInd received"); + info!(dev_eui = %dev_eui, dev_lorawan_version = %block_pl.dev_lorawan_version, serv_lorawan_version = %SERV_LORAWAN_VERSION, "ResetInd received"); dp.reset_session_to_boot_params(ds); @@ -41,11 +42,29 @@ pub fn handle( #[cfg(test)] pub mod test { use super::*; + use chirpstack_api::internal; use std::collections::HashMap; #[test] fn test_handle() { - let dev: device::Device = Default::default(); + let mut dev = device::Device { + device_session: Some(internal::DeviceSession { + tx_power_index: 3, + min_supported_tx_power_index: 1, + max_supported_tx_power_index: 5, + extra_uplink_channels: [(3, Default::default())].iter().cloned().collect(), + rx1_delay: 3, + rx1_dr_offset: 1, + rx2_dr: 5, + rx2_frequency: 868900000, + enabled_uplink_channel_indices: vec![0, 1], + class_b_ping_slot_dr: 3, + class_b_ping_slot_freq: 868100000, + nb_trans: 3, + ..Default::default() + }), + ..Default::default() + }; let dp = device_profile::DeviceProfile { supports_otaa: false, abp_rx1_delay: 1, @@ -57,26 +76,10 @@ pub mod test { class_b_ping_slot_nb_k: 1, ..Default::default() }; - let mut ds = internal::DeviceSession { - tx_power_index: 3, - min_supported_tx_power_index: 1, - max_supported_tx_power_index: 5, - extra_uplink_channels: [(3, Default::default())].iter().cloned().collect(), - rx1_delay: 3, - rx1_dr_offset: 1, - rx2_dr: 5, - rx2_frequency: 868900000, - enabled_uplink_channel_indices: vec![0, 1], - class_b_ping_slot_dr: 3, - class_b_ping_slot_freq: 868100000, - nb_trans: 3, - ..Default::default() - }; let resp = handle( - &dev, + &mut dev, &dp, - &mut ds, &lrwn::MACCommandSet::new(vec![lrwn::MACCommand::ResetInd(lrwn::ResetIndPayload { dev_lorawan_version: lrwn::Version::LoRaWAN1_1, })]), @@ -93,7 +96,7 @@ pub mod test { ); assert_eq!( - internal::DeviceSession { + &internal::DeviceSession { rx1_delay: 1, rx1_dr_offset: 0, rx2_dr: 0, @@ -110,7 +113,7 @@ pub mod test { extra_uplink_channels: HashMap::new(), ..Default::default() }, - ds + dev.get_device_session().unwrap() ); } } diff --git a/chirpstack/src/maccommand/rx_param_setup.rs b/chirpstack/src/maccommand/rx_param_setup.rs index 423a00f5..392371af 100644 --- a/chirpstack/src/maccommand/rx_param_setup.rs +++ b/chirpstack/src/maccommand/rx_param_setup.rs @@ -2,7 +2,6 @@ use anyhow::Result; use tracing::{info, warn}; use crate::storage::device; -use chirpstack_api::internal; pub fn request(rx1_dr_offset: u8, rx2_freq: u32, rx2_dr: u8) -> lrwn::MACCommandSet { lrwn::MACCommandSet::new(vec![lrwn::MACCommand::RxParamSetupReq( @@ -18,11 +17,12 @@ pub fn request(rx1_dr_offset: u8, rx2_freq: u32, rx2_dr: u8) -> lrwn::MACCommand } pub fn handle( - dev: &device::Device, - ds: &mut internal::DeviceSession, + dev: &mut device::Device, block: &lrwn::MACCommandSet, pending: Option<&lrwn::MACCommandSet>, ) -> Result> { + let ds = dev.get_device_session_mut()?; + if pending.is_none() { return Err(anyhow!("Expected pending RxParamSetupReq")); } @@ -70,6 +70,7 @@ pub fn handle( #[cfg(test)] pub mod test { use super::*; + use chirpstack_api::internal; struct Test { name: String, @@ -182,12 +183,12 @@ pub mod test { ]; for tst in &tests { - let mut ds = tst.device_session.clone(); + let mut dev = device::Device { + device_session: Some(tst.device_session.clone()), + ..Default::default() + }; let resp = handle( - &device::Device { - ..Default::default() - }, - &mut ds, + &mut dev, &tst.rx_param_setup_ans, tst.rx_param_setup_req.as_ref(), ); @@ -199,7 +200,12 @@ pub mod test { assert_eq!(true, resp.unwrap().is_none()); } - assert_eq!(tst.expected_device_session, ds, "{}", tst.name); + assert_eq!( + &tst.expected_device_session, + dev.get_device_session().unwrap(), + "{}", + tst.name + ); } } } diff --git a/chirpstack/src/maccommand/rx_timing_setup.rs b/chirpstack/src/maccommand/rx_timing_setup.rs index cab67814..596da149 100644 --- a/chirpstack/src/maccommand/rx_timing_setup.rs +++ b/chirpstack/src/maccommand/rx_timing_setup.rs @@ -2,7 +2,6 @@ use anyhow::Result; use tracing::info; use crate::storage::device; -use chirpstack_api::internal; pub fn request(rx1_delay: u8) -> lrwn::MACCommandSet { lrwn::MACCommandSet::new(vec![lrwn::MACCommand::RxTimingSetupReq( @@ -11,11 +10,13 @@ pub fn request(rx1_delay: u8) -> lrwn::MACCommandSet { } pub fn handle( - dev: &device::Device, - ds: &mut internal::DeviceSession, + dev: &mut device::Device, _block: &lrwn::MACCommandSet, pending: Option<&lrwn::MACCommandSet>, ) -> Result> { + let dev_eui = dev.dev_eui; + let ds = dev.get_device_session_mut()?; + if pending.is_none() { return Err(anyhow!("Pending RxTimingSetupReq expected")); } @@ -31,7 +32,7 @@ pub fn handle( }; ds.rx1_delay = req_pl.delay as u32; - info!(dev_eui = %dev.dev_eui, rx1_delay = req_pl.delay, "RxTimingSetupReq acknowledged"); + info!(dev_eui = %dev_eui, rx1_delay = req_pl.delay, "RxTimingSetupReq acknowledged"); Ok(None) } @@ -39,6 +40,7 @@ pub fn handle( #[cfg(test)] pub mod test { use super::*; + use chirpstack_api::internal; struct Test { name: String, @@ -100,12 +102,12 @@ pub mod test { ]; for tst in &tests { - let mut ds = tst.device_session.clone(); + let mut dev = device::Device { + device_session: Some(tst.device_session.clone()), + ..Default::default() + }; let resp = handle( - &device::Device { - ..Default::default() - }, - &mut ds, + &mut dev, &tst.rx_timing_setup_ans, tst.rx_timing_setup_req.as_ref(), ); @@ -117,7 +119,12 @@ pub mod test { assert_eq!(true, resp.unwrap().is_none()); } - assert_eq!(tst.expected_device_session, ds, "{}", tst.name); + assert_eq!( + &tst.expected_device_session, + dev.get_device_session().unwrap(), + "{}", + tst.name + ); } } } diff --git a/chirpstack/src/maccommand/tx_param_setup.rs b/chirpstack/src/maccommand/tx_param_setup.rs index 3c453b22..a709423a 100644 --- a/chirpstack/src/maccommand/tx_param_setup.rs +++ b/chirpstack/src/maccommand/tx_param_setup.rs @@ -2,7 +2,6 @@ use anyhow::Result; use tracing::info; use crate::storage::device; -use chirpstack_api::internal; pub fn request( uplink_dwell_time_400ms: bool, @@ -29,11 +28,13 @@ pub fn request( } pub fn handle( - dev: &device::Device, - ds: &mut internal::DeviceSession, + dev: &mut device::Device, _block: &lrwn::MACCommandSet, pending: Option<&lrwn::MACCommandSet>, ) -> Result> { + let dev_eui = dev.dev_eui; + let ds = dev.get_device_session_mut()?; + if pending.is_none() { return Err(anyhow!("Expected pending TxParamSetupReq")); } @@ -52,13 +53,14 @@ pub fn handle( ds.downlink_dwell_time_400ms = req_pl.downlink_dwell_time == lrwn::DwellTime::Limit400ms; ds.uplink_max_eirp_index = req_pl.max_eirp as u32; - info!(dev_eui = %dev.dev_eui, uplink_dwell_time_400ms = ds.uplink_dwell_time_400ms, downlink_dwell_time_400ms = ds.downlink_dwell_time_400ms, uplink_max_eirp_index = ds.uplink_max_eirp_index, "TxParamSetupReq acknowledged"); + info!(dev_eui = %dev_eui, uplink_dwell_time_400ms = ds.uplink_dwell_time_400ms, downlink_dwell_time_400ms = ds.downlink_dwell_time_400ms, uplink_max_eirp_index = ds.uplink_max_eirp_index, "TxParamSetupReq acknowledged"); Ok(None) } #[cfg(test)] pub mod test { use super::*; + use chirpstack_api::internal; struct Test { name: String, @@ -136,12 +138,12 @@ pub mod test { ]; for tst in &tests { - let mut ds = tst.device_session.clone(); + let mut dev = device::Device { + device_session: Some(tst.device_session.clone()), + ..Default::default() + }; let resp = handle( - &device::Device { - ..Default::default() - }, - &mut ds, + &mut dev, &tst.tx_param_setup_ans, tst.tx_param_setup_req.as_ref(), ); @@ -153,7 +155,12 @@ pub mod test { assert_eq!(true, resp.unwrap().is_none()); } - assert_eq!(tst.expected_device_session, ds, "{}", tst.name); + assert_eq!( + &tst.expected_device_session, + dev.get_device_session().unwrap(), + "{}", + tst.name + ); } } } diff --git a/chirpstack/src/maccommand/update_uplink_list.rs b/chirpstack/src/maccommand/update_uplink_list.rs index 8e65d2d8..f9e5fee4 100644 --- a/chirpstack/src/maccommand/update_uplink_list.rs +++ b/chirpstack/src/maccommand/update_uplink_list.rs @@ -2,14 +2,14 @@ use anyhow::Result; use tracing::info; use crate::storage::device; -use chirpstack_api::internal; pub fn handle( - _dev: &device::Device, - ds: &mut internal::DeviceSession, + dev: &mut device::Device, _block: &lrwn::MACCommandSet, pending: Option<&lrwn::MACCommandSet>, ) -> Result> { + let ds = dev.get_device_session_mut()?; + if pending.is_none() { return Err(anyhow!("Expected pending UpdateUplinkListReq mac-command")); } @@ -43,6 +43,7 @@ pub fn handle( #[cfg(test)] pub mod test { use super::*; + use chirpstack_api::internal; struct Test { name: String, @@ -124,12 +125,13 @@ pub mod test { ]; for tst in &tests { - let mut ds = tst.device_session.clone(); + let mut dev = device::Device { + device_session: Some(tst.device_session.clone()), + ..Default::default() + }; + let resp = handle( - &device::Device { - ..Default::default() - }, - &mut ds, + &mut dev, &tst.update_uplink_list_ans, tst.update_uplink_list_req.as_ref(), ); @@ -141,7 +143,12 @@ pub mod test { assert_eq!(true, resp.unwrap().is_none()); } - assert_eq!(tst.expected_device_session, ds, "{}", tst.name); + assert_eq!( + &tst.expected_device_session, + dev.get_device_session().unwrap(), + "{}", + tst.name + ); } } } diff --git a/chirpstack/src/main.rs b/chirpstack/src/main.rs index afcac15c..35f90f8d 100644 --- a/chirpstack/src/main.rs +++ b/chirpstack/src/main.rs @@ -78,6 +78,9 @@ enum Commands { #[arg(short, long, value_name = "NAME")] name: String, }, + + /// Migrate device-sessions from Redis to PostgreSQL. + MigrateDeviceSessionsToPostgres {}, } #[tokio::main] @@ -116,6 +119,7 @@ async fn main() -> Result<()> { .unwrap() } Some(Commands::CreateApiKey { name }) => cmd::create_api_key::run(name).await?, + Some(Commands::MigrateDeviceSessionsToPostgres {}) => cmd::migrate_ds_to_pg::run().await?, None => cmd::root::run().await?, } diff --git a/chirpstack/src/storage/device.rs b/chirpstack/src/storage/device.rs index 7a380efb..9093c14b 100644 --- a/chirpstack/src/storage/device.rs +++ b/chirpstack/src/storage/device.rs @@ -10,12 +10,20 @@ use diesel_async::RunQueryDsl; use tracing::info; use uuid::Uuid; +use chirpstack_api::internal; use lrwn::{DevAddr, EUI64}; use super::schema::{application, device, device_profile, multicast_group_device, tenant}; use super::{error::Error, fields, get_async_db_conn}; +use crate::api::helpers::FromProto; use crate::config; +pub enum ValidationStatus { + Ok(u32, Device), + Retransmission(u32, Device), + Reset(u32, Device), +} + #[derive(Debug, Clone, Copy, Eq, PartialEq, AsExpression, FromSqlRow)] #[diesel(sql_type = Text)] pub enum DeviceClass { @@ -95,6 +103,24 @@ pub struct Device { pub tags: fields::KeyValue, pub variables: fields::KeyValue, pub join_eui: EUI64, + pub secondary_dev_addr: Option, + pub device_session: Option, +} + +#[derive(AsChangeset, Debug, Clone, Default)] +#[diesel(table_name = device)] +pub struct DeviceChangeset { + pub last_seen_at: Option>>, + pub dr: Option>, + pub dev_addr: Option>, + pub enabled_class: Option, + pub join_eui: Option, + pub secondary_dev_addr: Option>, + pub device_session: Option>, + pub margin: Option, + pub external_power_source: Option, + pub battery_level: Option>, + pub scheduler_run_after: Option>>, } impl Device { @@ -104,6 +130,22 @@ impl Device { } Ok(()) } + + pub fn get_device_session(&self) -> Result<&internal::DeviceSession, Error> { + self.device_session + .as_ref() + .ok_or_else(|| Error::NotFound(self.dev_eui.to_string())) + } + + pub fn get_device_session_mut(&mut self) -> Result<&mut internal::DeviceSession, Error> { + self.device_session + .as_mut() + .ok_or_else(|| Error::NotFound(self.dev_eui.to_string())) + } + + pub fn get_dev_addr(&self) -> Result { + self.dev_addr.ok_or_else(|| anyhow!("DevAddr is not set")) + } } impl Default for Device { @@ -134,6 +176,8 @@ impl Default for Device { tags: fields::KeyValue::new(HashMap::new()), variables: fields::KeyValue::new(HashMap::new()), join_eui: EUI64::default(), + secondary_dev_addr: None, + device_session: None, } } } @@ -237,6 +281,230 @@ pub async fn get(dev_eui: &EUI64) -> Result { Ok(d) } +// Return the device-session matching the given PhyPayload. This will fetch all device-session +// associated with the used DevAddr and based on f_cont and mic, decides which one to use. +// This function will increment the uplink frame-counter and will immediately update the +// device-session in the database, to make sure that in case this function is called multiple +// times, at most one will be valid. +// On Ok response, the PhyPayload f_cnt will be set to the full 32bit frame-counter based on the +// device-session context. +pub async fn get_for_phypayload_and_incr_f_cnt_up( + relayed: bool, + phy: &mut lrwn::PhyPayload, + tx_dr: u8, + tx_ch: u8, +) -> Result { + // Get the dev_addr and original f_cnt. + let (dev_addr, f_cnt_orig) = if let lrwn::Payload::MACPayload(pl) = &phy.payload { + (pl.fhdr.devaddr, pl.fhdr.f_cnt) + } else { + return Err(Error::InvalidPayload("MacPayload".to_string())); + }; + + let mut c = get_async_db_conn().await?; + + c.build_transaction() + .run::(|c| { + Box::pin(async move { + let mut devices: Vec = device::dsl::device + .filter( + device::dsl::dev_addr + .eq(&dev_addr) + .or(device::dsl::secondary_dev_addr.eq(&dev_addr)), + ) + .filter(device::dsl::is_disabled.eq(false)) + .for_update() + .load(c) + .await?; + + if devices.is_empty() { + return Err(Error::NotFound(dev_addr.to_string())); + } + + for d in &mut devices { + let mut sessions = vec![]; + + if let Some(ds) = &d.device_session { + sessions.push(ds.clone()); + if let Some(ds) = &ds.pending_rejoin_device_session { + sessions.push(*ds.clone()); + } + } + + for ds in &mut sessions { + if ds.dev_addr != dev_addr.to_vec() { + continue; + } + + // Get the full 32bit frame-counter. + let full_f_cnt = get_full_f_cnt_up(ds.f_cnt_up, f_cnt_orig); + let f_nwk_s_int_key = lrwn::AES128Key::from_slice(&ds.f_nwk_s_int_key)?; + let s_nwk_s_int_key = lrwn::AES128Key::from_slice(&ds.s_nwk_s_int_key)?; + + // Check both the full frame-counter and the received frame-counter + // truncated to the 16LSB. + // The latter is needed in case of a frame-counter reset as the + // GetFullFCntUp will think the 16LSB has rolled over and will + // increment the 16MSB bit. + let mut mic_ok = false; + for f_cnt in [full_f_cnt, f_cnt_orig] { + // Set the full f_cnt. + if let lrwn::Payload::MACPayload(pl) = &mut phy.payload { + pl.fhdr.f_cnt = f_cnt; + } + + mic_ok = phy + .validate_uplink_data_mic( + ds.mac_version().from_proto(), + ds.conf_f_cnt, + tx_dr, + tx_ch, + &f_nwk_s_int_key, + &s_nwk_s_int_key, + ) + .context("Validate MIC")?; + + if mic_ok { + break; + } + } + + if mic_ok { + let full_f_cnt = if let lrwn::Payload::MACPayload(pl) = &phy.payload { + pl.fhdr.f_cnt + } else { + 0 + }; + + if let Some(relay) = &ds.relay { + if !relayed && relay.ed_relay_only { + info!( + dev_eui = %d.dev_eui, + "Only communication through relay is allowed" + ); + return Err(Error::NotFound(dev_addr.to_string())); + } + } + + if full_f_cnt >= ds.f_cnt_up { + // We immediately save the device-session to make sure that concurrent calls for + // the same uplink will fail on the frame-counter validation. + let ds_f_cnt_up = ds.f_cnt_up; + ds.f_cnt_up = full_f_cnt + 1; + + let _ = diesel::update(device::dsl::device.find(d.dev_eui)) + .set(device::device_session.eq(&ds.clone())) + .execute(c) + .await?; + + // We do return the device-session with original frame-counter + ds.f_cnt_up = ds_f_cnt_up; + d.device_session = Some(ds.clone()); + return Ok(ValidationStatus::Ok(full_f_cnt, d.clone())); + } else if ds.skip_f_cnt_check { + // re-transmission or frame-counter reset + ds.f_cnt_up = 0; + d.device_session = Some(ds.clone()); + return Ok(ValidationStatus::Ok(full_f_cnt, d.clone())); + } else if full_f_cnt == (ds.f_cnt_up - 1) { + // re-transmission, the frame-counter did not increment + d.device_session = Some(ds.clone()); + return Ok(ValidationStatus::Retransmission(full_f_cnt, d.clone())); + } else { + d.device_session = Some(ds.clone()); + return Ok(ValidationStatus::Reset(full_f_cnt, d.clone())); + } + } + + // Restore the original f_cnt. + if let lrwn::Payload::MACPayload(pl) = &mut phy.payload { + pl.fhdr.f_cnt = f_cnt_orig; + } + } + } + + Err(Error::InvalidMIC) + }) + }) + .await +} + +pub async fn get_for_phypayload( + phy: &mut lrwn::PhyPayload, + tx_dr: u8, + tx_ch: u8, +) -> Result { + // Get the dev_addr and original f_cnt. + let (dev_addr, f_cnt_orig) = if let lrwn::Payload::MACPayload(pl) = &phy.payload { + (pl.fhdr.devaddr, pl.fhdr.f_cnt) + } else { + return Err(Error::InvalidPayload("MacPayload".to_string())); + }; + + let devices: Vec = device::dsl::device + .filter( + device::dsl::dev_addr + .eq(&dev_addr) + .or(device::dsl::secondary_dev_addr.eq(&dev_addr)), + ) + .filter(device::dsl::is_disabled.eq(false)) + .load(&mut get_async_db_conn().await?) + .await?; + + if devices.is_empty() { + return Err(Error::NotFound(dev_addr.to_string())); + } + + for d in &devices { + let mut sessions = vec![]; + + if let Some(ds) = &d.device_session { + sessions.push(ds.clone()); + if let Some(ds) = &ds.pending_rejoin_device_session { + sessions.push(*ds.clone()); + } + } + + for ds in &mut sessions { + if ds.dev_addr != dev_addr.to_vec() { + continue; + } + + // Get the full 32bit frame-counter. + let full_f_cnt = get_full_f_cnt_up(ds.f_cnt_up, f_cnt_orig); + let f_nwk_s_int_key = lrwn::AES128Key::from_slice(&ds.f_nwk_s_int_key)?; + let s_nwk_s_int_key = lrwn::AES128Key::from_slice(&ds.s_nwk_s_int_key)?; + + // Set the full f_cnt + if let lrwn::Payload::MACPayload(pl) = &mut phy.payload { + pl.fhdr.f_cnt = full_f_cnt; + } + + let mic_ok = phy + .validate_uplink_data_mic( + ds.mac_version().from_proto(), + ds.conf_f_cnt, + tx_dr, + tx_ch, + &f_nwk_s_int_key, + &s_nwk_s_int_key, + ) + .context("Validate MIC")?; + + if mic_ok && full_f_cnt >= ds.f_cnt_up { + return Ok(d.clone()); + } + + // Restore the original f_cnt. + if let lrwn::Payload::MACPayload(pl) = &mut phy.payload { + pl.fhdr.f_cnt = f_cnt_orig; + } + } + } + + Err(Error::InvalidMIC) +} + pub async fn update(d: Device) -> Result { d.validate()?; @@ -260,80 +528,14 @@ pub async fn update(d: Device) -> Result { Ok(d) } -pub async fn set_enabled_class(dev_eui: &EUI64, mode: DeviceClass) -> Result { - let d: Device = diesel::update(device::dsl::device.find(&dev_eui)) - .set(device::enabled_class.eq(&mode)) - .get_result(&mut get_async_db_conn().await?) +pub async fn partial_update(dev_eui: EUI64, d: &DeviceChangeset) -> Result { + let d = diesel::update(device::dsl::device.find(&dev_eui)) + .set(d) + .get_result::(&mut get_async_db_conn().await?) .await .map_err(|e| Error::from_diesel(e, dev_eui.to_string()))?; - info!(dev_eui = %dev_eui, enabled_class = %mode, "Enabled class updated"); - Ok(d) -} -pub async fn set_join_eui(dev_eui: EUI64, join_eui: EUI64) -> Result { - let d: Device = diesel::update(device::dsl::device.find(&dev_eui)) - .set(device::join_eui.eq(&join_eui)) - .get_result(&mut get_async_db_conn().await?) - .await - .map_err(|e| Error::from_diesel(e, dev_eui.to_string()))?; - info!(dev_eui = %dev_eui, join_eui = %join_eui, "Updated JoinEUI"); - Ok(d) -} - -pub async fn set_dev_addr(dev_eui: EUI64, dev_addr: DevAddr) -> Result { - let d: Device = diesel::update(device::dsl::device.find(&dev_eui)) - .set(device::dev_addr.eq(&dev_addr)) - .get_result(&mut get_async_db_conn().await?) - .await - .map_err(|e| Error::from_diesel(e, dev_eui.to_string()))?; - info!(dev_eui = %dev_eui, dev_addr = %dev_addr, "Updated DevAddr"); - Ok(d) -} - -// In case the current_ts has been updated during the last device get and calling this update -// function, this will return a NotFound error. The purpose of this error is to catch concurrent -// scheduling, e.g. Class-A downlink and Class-B/C downlink. In such case we want to terminate one -// of the downlinks. -pub async fn set_scheduler_run_after( - dev_eui: &EUI64, - new_ts: Option>, -) -> Result { - diesel::update(device::dsl::device.find(&dev_eui)) - .set(device::scheduler_run_after.eq(&new_ts)) - .get_result(&mut get_async_db_conn().await?) - .await - .map_err(|e| Error::from_diesel(e, dev_eui.to_string())) -} - -pub async fn set_last_seen_dr(dev_eui: &EUI64, dr: u8) -> Result { - let d: Device = diesel::update(device::dsl::device.find(&dev_eui)) - .set(( - device::last_seen_at.eq(Utc::now()), - device::dr.eq(dr as i16), - )) - .get_result(&mut get_async_db_conn().await?) - .await - .map_err(|e| Error::from_diesel(e, dev_eui.to_string()))?; - info!(dev_eui = %dev_eui, dr = dr, "Data-rate updated"); - Ok(d) -} - -pub async fn set_status( - dev_eui: &EUI64, - margin: i32, - external_power_source: bool, - battery_level: Option, -) -> Result { - let d: Device = diesel::update(device::dsl::device.find(&dev_eui)) - .set(( - device::margin.eq(Some(margin)), - device::external_power_source.eq(external_power_source), - device::battery_level.eq(battery_level), - )) - .get_result(&mut get_async_db_conn().await?) - .await - .map_err(|e| Error::from_diesel(e, dev_eui.to_string()))?; - info!(dev_eui = %dev_eui, "Device status updated"); + info!(dev_eui = %dev_eui, "Device partially updated"); Ok(d) } @@ -530,12 +732,36 @@ pub async fn get_with_class_b_c_queue_items(limit: usize) -> Result> .context("Get with Class B/C queue-items transaction") } +// GetFullFCntUp returns the full 32bit frame-counter, given the fCntUp which +// has been truncated to the last 16 LSB. +// Notes: +// * After a succesful validation of the FCntUp and the MIC, don't forget +// to synchronize the device FCntUp with the packet FCnt. +// * In case of a frame-counter rollover, the returned values will be less +// than the given DeviceSession FCntUp. This must be validated outside this +// function! +// * In case of a re-transmission, the returned frame-counter equals +// DeviceSession.FCntUp - 1, as the FCntUp value holds the next expected +// frame-counter, not the FCntUp which was last seen. +fn get_full_f_cnt_up(next_expected_full_fcnt: u32, truncated_f_cnt: u32) -> u32 { + // Handle re-transmission. + if truncated_f_cnt == (((next_expected_full_fcnt % (1 << 16)) as u16).wrapping_sub(1)) as u32 { + return next_expected_full_fcnt - 1; + } + + let gap = ((truncated_f_cnt as u16).wrapping_sub((next_expected_full_fcnt % (1 << 16)) as u16)) + as u32; + + next_expected_full_fcnt.wrapping_add(gap) +} + #[cfg(test)] pub mod test { use super::*; use crate::storage; use crate::storage::device_queue; use crate::test; + use lrwn::AES128Key; struct FilterTest<'a> { filters: Filters, @@ -696,13 +922,37 @@ pub mod test { assert_eq!(0, res.len()); // device in Class-B. - let d = set_enabled_class(&d.dev_eui, DeviceClass::B).await.unwrap(); + let d = partial_update( + d.dev_eui, + &DeviceChangeset { + enabled_class: Some(DeviceClass::B), + ..Default::default() + }, + ) + .await + .unwrap(); let res = get_with_class_b_c_queue_items(10).await.unwrap(); - let d = set_scheduler_run_after(&d.dev_eui, None).await.unwrap(); + let d = partial_update( + d.dev_eui, + &DeviceChangeset { + scheduler_run_after: Some(None), + ..Default::default() + }, + ) + .await + .unwrap(); assert_eq!(1, res.len()); // device in Class-C - let d = set_enabled_class(&d.dev_eui, DeviceClass::C).await.unwrap(); + let d = partial_update( + d.dev_eui, + &DeviceChangeset { + enabled_class: Some(DeviceClass::C), + ..Default::default() + }, + ) + .await + .unwrap(); let res = get_with_class_b_c_queue_items(10).await.unwrap(); assert_eq!(1, res.len()); @@ -712,7 +962,15 @@ pub mod test { assert_eq!(0, res.len()); // device in class C / downlink is pending. - let _ = set_scheduler_run_after(&d.dev_eui, None).await.unwrap(); + let _ = partial_update( + d.dev_eui, + &DeviceChangeset { + scheduler_run_after: Some(None), + ..Default::default() + }, + ) + .await + .unwrap(); qi.is_pending = true; qi.timeout_after = Some(Utc::now() + Duration::seconds(10)); qi = device_queue::update_item(qi).await.unwrap(); @@ -726,4 +984,397 @@ pub mod test { let res = get_with_class_b_c_queue_items(10).await.unwrap(); assert_eq!(1, res.len()); } + + #[test] + fn test_get_full_f_cnt_up() { + // server, device, expected + let tests = vec![ + (1, 1, 1), // frame-counter is as expected + (1 << 16, 0, 1 << 16), // frame-counter is as expected + ((1 << 16) + 1, 1, (1 << 16) + 1), // frame-counter is as expected + (0, 1, 1), // one frame packet-loss + ((1 << 16) + 1, 2, (1 << 16) + 2), // one frame packet-loss + (2, 1, 1), // re-transmission of previous frame + ((1 << 16) + 1, 0, (1 << 16)), // re-transmission of previous frame + ((1 << 16), (1 << 16) - 1, (1 << 16) - 1), // re-transmission of previous frame + (u32::MAX, 0, 0), // 32bit frame-counter rollover + ]; + + for (i, tst) in tests.iter().enumerate() { + let out = get_full_f_cnt_up(tst.0, tst.1); + assert_eq!(tst.2, out, "Test: {}, expected: {}, got: {}", i, tst.2, out); + } + } + + #[tokio::test] + async fn test_device_session() { + let _guard = test::prepare().await; + + let t = storage::tenant::create(storage::tenant::Tenant { + name: "test-tenant".into(), + ..Default::default() + }) + .await + .unwrap(); + + let dp = storage::device_profile::create(storage::device_profile::DeviceProfile { + name: "test-dp".into(), + tenant_id: t.id, + ..Default::default() + }) + .await + .unwrap(); + + let app = storage::application::create(storage::application::Application { + name: "test-app".into(), + tenant_id: t.id, + ..Default::default() + }) + .await + .unwrap(); + + let mut devices = vec![ + Device { + application_id: app.id, + device_profile_id: dp.id, + name: "0101010101010101".into(), + dev_eui: EUI64::from_be_bytes([1, 1, 1, 1, 1, 1, 1, 1]), + dev_addr: Some(DevAddr::from_be_bytes([1, 2, 3, 4])), + device_session: Some(internal::DeviceSession { + dev_addr: vec![0x01, 0x02, 0x03, 0x04], + s_nwk_s_int_key: vec![ + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, + ], + f_nwk_s_int_key: vec![ + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, + ], + nwk_s_enc_key: vec![ + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, + ], + f_cnt_up: 100, + skip_f_cnt_check: true, + ..Default::default() + }), + ..Default::default() + }, + Device { + application_id: app.id, + device_profile_id: dp.id, + name: "0202020202020202".into(), + dev_eui: EUI64::from_be_bytes([2, 2, 2, 2, 2, 2, 2, 2]), + dev_addr: Some(DevAddr::from_be_bytes([1, 2, 3, 4])), + device_session: Some(internal::DeviceSession { + dev_addr: vec![0x01, 0x02, 0x03, 0x04], + s_nwk_s_int_key: vec![ + 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, + 0x02, 0x02, 0x02, 0x02, + ], + f_nwk_s_int_key: vec![ + 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, + 0x02, 0x02, 0x02, 0x02, + ], + nwk_s_enc_key: vec![ + 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, + 0x02, 0x02, 0x02, 0x02, + ], + f_cnt_up: 200, + ..Default::default() + }), + ..Default::default() + }, + Device { + application_id: app.id, + device_profile_id: dp.id, + name: "0303030303030303".into(), + dev_eui: EUI64::from_be_bytes([3, 3, 3, 3, 3, 3, 3, 3]), + dev_addr: Some(DevAddr::from_be_bytes([1, 2, 3, 4])), + secondary_dev_addr: Some(DevAddr::from_be_bytes([4, 3, 2, 1])), + device_session: Some(internal::DeviceSession { + dev_addr: vec![0x01, 0x02, 0x03, 0x04], + s_nwk_s_int_key: vec![ + 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, + 0x03, 0x03, 0x03, 0x03, + ], + f_nwk_s_int_key: vec![ + 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, + 0x03, 0x03, 0x03, 0x03, + ], + nwk_s_enc_key: vec![ + 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, + 0x03, 0x03, 0x03, 0x03, + ], + f_cnt_up: 300, + pending_rejoin_device_session: Some(Box::new(internal::DeviceSession { + dev_addr: vec![0x04, 0x03, 0x02, 0x01], + s_nwk_s_int_key: vec![ + 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, + 0x04, 0x04, 0x04, 0x04, + ], + f_nwk_s_int_key: vec![ + 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, + 0x04, 0x04, 0x04, 0x04, + ], + nwk_s_enc_key: vec![ + 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, + 0x04, 0x04, 0x04, 0x04, + ], + f_cnt_up: 0, + ..Default::default() + })), + ..Default::default() + }), + ..Default::default() + }, + Device { + application_id: app.id, + device_profile_id: dp.id, + name: "0505050505050505".into(), + dev_eui: EUI64::from_be_bytes([5, 5, 5, 5, 5, 5, 5, 5]), + dev_addr: Some(DevAddr::from_be_bytes([1, 2, 3, 4])), + device_session: Some(internal::DeviceSession { + dev_addr: vec![0x01, 0x02, 0x03, 0x04], + s_nwk_s_int_key: vec![ + 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, + 0x05, 0x05, 0x05, 0x05, + ], + f_nwk_s_int_key: vec![ + 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, + 0x05, 0x05, 0x05, 0x05, + ], + nwk_s_enc_key: vec![ + 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, + 0x05, 0x05, 0x05, 0x05, + ], + f_cnt_up: (1 << 16) + 1, + ..Default::default() + }), + ..Default::default() + }, + ]; + + for d in &mut devices { + *d = create(d.clone()).await.unwrap(); + } + + #[derive(Default)] + struct Test { + name: String, + dev_addr: DevAddr, + s_nwk_s_int_key: AES128Key, + f_nwk_s_int_key: AES128Key, + f_cnt: u32, + expected_retransmission: bool, + expected_reset: bool, + expected_dev_eui: EUI64, + expected_fcnt_up: u32, + expected_error: Option, + } + + let tests = vec![ + Test { + name: "matching dev_eui 0101010101010101".to_string(), + dev_addr: DevAddr::from_be_bytes([0x01, 0x02, 0x03, 0x04]), + f_nwk_s_int_key: AES128Key::from_slice( + &devices[0].get_device_session().unwrap().f_nwk_s_int_key, + ) + .unwrap(), + s_nwk_s_int_key: AES128Key::from_slice( + &devices[0].get_device_session().unwrap().s_nwk_s_int_key, + ) + .unwrap(), + f_cnt: devices[0].get_device_session().unwrap().f_cnt_up, + expected_retransmission: false, + expected_reset: false, + expected_fcnt_up: devices[0].get_device_session().unwrap().f_cnt_up, + expected_dev_eui: devices[0].dev_eui, + expected_error: None, + }, + Test { + name: "matching dev_eui 0202020202020202".to_string(), + dev_addr: DevAddr::from_be_bytes([0x01, 0x02, 0x03, 0x04]), + f_nwk_s_int_key: AES128Key::from_slice( + &devices[1].get_device_session().unwrap().f_nwk_s_int_key, + ) + .unwrap(), + s_nwk_s_int_key: AES128Key::from_slice( + &devices[1].get_device_session().unwrap().s_nwk_s_int_key, + ) + .unwrap(), + f_cnt: devices[1].get_device_session().unwrap().f_cnt_up, + expected_retransmission: false, + expected_reset: false, + expected_fcnt_up: devices[1].get_device_session().unwrap().f_cnt_up, + expected_dev_eui: devices[1].dev_eui, + expected_error: None, + }, + Test { + name: "matching dev_eui 0101010101010101 with frame-counter reset".to_string(), + dev_addr: DevAddr::from_be_bytes([0x01, 0x02, 0x03, 0x04]), + f_nwk_s_int_key: AES128Key::from_slice( + &devices[0].get_device_session().unwrap().f_nwk_s_int_key, + ) + .unwrap(), + s_nwk_s_int_key: AES128Key::from_slice( + &devices[0].get_device_session().unwrap().s_nwk_s_int_key, + ) + .unwrap(), + f_cnt: 0, + expected_retransmission: false, + expected_reset: false, + expected_fcnt_up: 0, + expected_dev_eui: devices[0].dev_eui, + expected_error: None, + }, + Test { + name: "matching dev_eui 0202020202020202 with invalid frame-counter".to_string(), + dev_addr: DevAddr::from_be_bytes([0x01, 0x02, 0x03, 0x04]), + f_nwk_s_int_key: AES128Key::from_slice( + &devices[1].get_device_session().unwrap().f_nwk_s_int_key, + ) + .unwrap(), + s_nwk_s_int_key: AES128Key::from_slice( + &devices[1].get_device_session().unwrap().s_nwk_s_int_key, + ) + .unwrap(), + f_cnt: 0, + expected_reset: true, + expected_dev_eui: devices[1].dev_eui, + ..Default::default() + }, + Test { + name: "invalid DevAddr".to_string(), + dev_addr: DevAddr::from_be_bytes([0x01, 0x01, 0x01, 0x01]), + f_nwk_s_int_key: AES128Key::from_slice( + &devices[0].get_device_session().unwrap().f_nwk_s_int_key, + ) + .unwrap(), + s_nwk_s_int_key: AES128Key::from_slice( + &devices[0].get_device_session().unwrap().s_nwk_s_int_key, + ) + .unwrap(), + f_cnt: devices[0].get_device_session().unwrap().f_cnt_up, + expected_error: Some("Object does not exist (id: 01010101)".to_string()), + ..Default::default() + }, + Test { + name: "invalid nwk_s_key".to_string(), + dev_addr: DevAddr::from_be_bytes([0x01, 0x02, 0x03, 0x04]), + f_nwk_s_int_key: AES128Key::from_bytes([ + 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, + ]), + s_nwk_s_int_key: AES128Key::from_bytes([ + 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, + ]), + f_cnt: devices[0].get_device_session().unwrap().f_cnt_up, + expected_error: Some("Invalid MIC".to_string()), + ..Default::default() + }, + Test { + name: "matching pending rejoin device-session".to_string(), + dev_addr: DevAddr::from_be_bytes([0x04, 0x03, 0x02, 0x01]), + f_nwk_s_int_key: AES128Key::from_bytes([ + 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, + 0x04, 0x04, 0x04, + ]), + s_nwk_s_int_key: AES128Key::from_bytes([ + 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, + 0x04, 0x04, 0x04, + ]), + f_cnt: 0, + expected_dev_eui: devices[2].dev_eui, + expected_fcnt_up: 0, + expected_retransmission: false, + expected_error: None, + expected_reset: false, + }, + Test { + name: "frame-counter rollover (16lsb)".to_string(), + dev_addr: DevAddr::from_be_bytes([0x01, 0x02, 0x03, 0x04]), + f_nwk_s_int_key: AES128Key::from_bytes([ + 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, + 0x05, 0x05, 0x05, + ]), + s_nwk_s_int_key: AES128Key::from_bytes([ + 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, + 0x05, 0x05, 0x05, + ]), + f_cnt: (1 << 16) + 11, + expected_dev_eui: devices[3].dev_eui, + expected_fcnt_up: (1 << 16) + 11, + expected_retransmission: false, + expected_error: None, + expected_reset: false, + }, + ]; + + for tst in &tests { + println!("> {}", tst.name); + let mut phy = lrwn::PhyPayload { + mhdr: lrwn::MHDR { + m_type: lrwn::MType::UnconfirmedDataUp, + major: lrwn::Major::LoRaWANR1, + }, + payload: lrwn::Payload::MACPayload(lrwn::MACPayload { + fhdr: lrwn::FHDR { + devaddr: tst.dev_addr, + f_ctrl: lrwn::FCtrl::default(), + f_cnt: tst.f_cnt, + ..Default::default() + }, + ..Default::default() + }), + mic: None, + }; + + phy.set_uplink_data_mic( + lrwn::MACVersion::LoRaWAN1_0, + 0, + 0, + 0, + &tst.f_nwk_s_int_key, + &tst.s_nwk_s_int_key, + ) + .unwrap(); + + // Truncate to 16LSB (as it would be transmitted over the air). + if let lrwn::Payload::MACPayload(pl) = &mut phy.payload { + pl.fhdr.f_cnt = tst.f_cnt % (1 << 16); + } + + let d = get_for_phypayload_and_incr_f_cnt_up(false, &mut phy, 0, 0).await; + if tst.expected_error.is_some() { + assert_eq!(true, d.is_err()); + assert_eq!( + tst.expected_error.as_ref().unwrap(), + &d.err().unwrap().to_string() + ); + if let lrwn::Payload::MACPayload(pl) = &phy.payload { + assert_eq!(tst.f_cnt, pl.fhdr.f_cnt); + } + } else { + let d = d.unwrap(); + + // Validate that the f_cnt of the PhyPayload was set to the full frame-counter. + if let lrwn::Payload::MACPayload(pl) = &phy.payload { + assert_eq!(tst.expected_fcnt_up, pl.fhdr.f_cnt); + } + + if let ValidationStatus::Ok(full_f_cnt, d) = d { + assert_eq!(false, tst.expected_retransmission); + assert_eq!(tst.expected_dev_eui, d.dev_eui,); + assert_eq!(tst.expected_fcnt_up, full_f_cnt); + } else if let ValidationStatus::Retransmission(full_f_cnt, d) = d { + assert_eq!(true, tst.expected_retransmission); + assert_eq!(tst.expected_dev_eui, d.dev_eui,); + assert_eq!(tst.expected_fcnt_up, full_f_cnt); + } else if let ValidationStatus::Reset(_, d) = d { + assert_eq!(true, tst.expected_reset); + assert_eq!(tst.expected_dev_eui, d.dev_eui,); + } + } + } + } } diff --git a/chirpstack/src/storage/device_session.rs b/chirpstack/src/storage/device_session.rs index 5a653dbc..5db643f5 100644 --- a/chirpstack/src/storage/device_session.rs +++ b/chirpstack/src/storage/device_session.rs @@ -1,80 +1,14 @@ -use std::collections::HashSet; use std::io::Cursor; use anyhow::{Context, Result}; use prost::Message; -use tracing::{error, info, trace}; use super::error::Error; use super::{get_async_redis_conn, redis_key}; -use crate::api::helpers::FromProto; -use crate::config; -use crate::helpers::errors::PrintFullError; use chirpstack_api::internal; -use lrwn::{AES128Key, DevAddr, Payload, PhyPayload, EUI64}; +use lrwn::EUI64; -pub enum ValidationStatus { - Ok(u32, internal::DeviceSession), - Retransmission(u32, internal::DeviceSession), - Reset(u32, internal::DeviceSession), -} - -pub async fn save(ds: &internal::DeviceSession) -> Result<()> { - let eui = EUI64::from_slice(&ds.dev_eui)?; - let addr = DevAddr::from_slice(&ds.dev_addr)?; - - let conf = config::get(); - let addr_key = redis_key(format!("devaddr:{{{}}}", addr)); - let ds_key = redis_key(format!("device:{{{}}}:ds", eui)); - let b = ds.encode_to_vec(); - let ttl = conf.network.device_session_ttl.as_millis() as usize; - - // Atomic add and pexpire. - redis::pipe() - .atomic() - .cmd("SADD") - .arg(&addr_key) - .arg(&eui.to_be_bytes()) - .ignore() - .cmd("PEXPIRE") - .arg(&addr_key) - .arg(ttl) - .ignore() - .query_async(&mut get_async_redis_conn().await?) - .await?; - - // In case there is a pending rejoin session, make sure that the new - // DevAddr also resolves to the device-session. - if let Some(pending_ds) = &ds.pending_rejoin_device_session { - let pending_addr = DevAddr::from_slice(&pending_ds.dev_addr)?; - let pending_addr_key = redis_key(format!("devaddr:{{{}}}", pending_addr)); - - redis::pipe() - .atomic() - .cmd("SADD") - .arg(&pending_addr_key) - .arg(&eui.to_be_bytes()) - .ignore() - .cmd("PEXPIRE") - .arg(&pending_addr_key) - .arg(ttl) - .ignore() - .query_async(&mut get_async_redis_conn().await?) - .await?; - } - - redis::cmd("PSETEX") - .arg(ds_key) - .arg(ttl) - .arg(b) - .query_async(&mut get_async_redis_conn().await?) - .await?; - - info!(dev_eui = %eui, dev_addr = %addr, "Device-session saved"); - Ok(()) -} - -pub async fn get(dev_eui: &EUI64) -> Result { +pub async fn get(dev_eui: &EUI64) -> Result { let key = redis_key(format!("device:{{{}}}:ds", dev_eui)); let v: Vec = redis::cmd("GET") @@ -85,687 +19,7 @@ pub async fn get(dev_eui: &EUI64) -> Result Result<()> { - let key = redis_key(format!("device:{{{}}}:ds", dev_eui)); - - redis::cmd("DEL") - .arg(&key) - .query_async(&mut get_async_redis_conn().await?) - .await?; - - info!(dev_eui = %dev_eui, "Device-session deleted"); - Ok(()) -} - -// Return the device-session matching the given PhyPayload. This will fetch all device-session -// associated with the used DevAddr and based on f_cont and mic, decides which one to use. -// This function will increment the uplink frame-counter and will immediately update the -// device-session in the database, to make sure that in case this function is called multiple -// times, at most one will be valid. -// On Ok response, the PhyPayload f_cnt will be set to the full 32bit frame-counter based on the -// device-session context. -pub async fn get_for_phypayload_and_incr_f_cnt_up( - relayed: bool, - phy: &mut PhyPayload, - tx_dr: u8, - tx_ch: u8, -) -> Result { - let mut _dev_addr = DevAddr::from_be_bytes([0x00, 0x00, 0x00, 0x00]); - let mut _f_cnt_orig = 0; - - // Get the dev_addr and original f_cnt. - if let Payload::MACPayload(pl) = &phy.payload { - _dev_addr = pl.fhdr.devaddr; - _f_cnt_orig = pl.fhdr.f_cnt; - } else { - return Err(Error::InvalidPayload("MacPayload".to_string())); - } - - let device_sessions = get_for_dev_addr(_dev_addr) - .await - .context("Get device-sessions for DevAddr")?; - if device_sessions.is_empty() { - return Err(Error::NotFound(_dev_addr.to_string())); - } - - for mut ds in device_sessions { - // Get the full 32bit frame-counter. - let full_f_cnt = get_full_f_cnt_up(ds.f_cnt_up, _f_cnt_orig); - let f_nwk_s_int_key = AES128Key::from_slice(&ds.f_nwk_s_int_key)?; - let s_nwk_s_int_key = AES128Key::from_slice(&ds.s_nwk_s_int_key)?; - - // Check both the full frame-counter and the received frame-counter - // truncated to the 16LSB. - // The latter is needed in case of a frame-counter reset as the - // GetFullFCntUp will think the 16LSB has rolled over and will - // increment the 16MSB bit. - let mut mic_ok = false; - for f_cnt in &[full_f_cnt, _f_cnt_orig] { - // Set the full f_cnt. - if let Payload::MACPayload(pl) = &mut phy.payload { - pl.fhdr.f_cnt = *f_cnt; - } - - mic_ok = phy - .validate_uplink_data_mic( - ds.mac_version().from_proto(), - ds.conf_f_cnt, - tx_dr, - tx_ch, - &f_nwk_s_int_key, - &s_nwk_s_int_key, - ) - .context("Validate MIC")?; - - if mic_ok { - break; - } - } - - if mic_ok { - let full_f_cnt = if let Payload::MACPayload(pl) = &phy.payload { - pl.fhdr.f_cnt - } else { - 0 - }; - - if let Some(relay) = &ds.relay { - if !relayed && relay.ed_relay_only { - info!( - dev_eui = hex::encode(ds.dev_eui), - "Only communication through relay is allowed" - ); - return Err(Error::NotFound(_dev_addr.to_string())); - } - } - - if full_f_cnt >= ds.f_cnt_up { - // Make sure that in case of concurrent calls for the same uplink only one will - // pass. Either the concurrent call would read the incremented uplink frame-counter - // or it is unable to aquire the lock. - - let lock_key = redis_key(format!( - "device:{{{}}}:ds:lock:{}", - hex::encode(&ds.dev_eui), - full_f_cnt, - )); - let set: bool = redis::cmd("SET") - .arg(&lock_key) - .arg("lock") - .arg("EX") - .arg(1_usize) - .arg("NX") - .query_async(&mut get_async_redis_conn().await?) - .await?; - - if !set { - return Ok(ValidationStatus::Retransmission(full_f_cnt, ds)); - } - - // We immediately save the device-session to make sure that concurrent calls for - // the same uplink will fail on the frame-counter validation. - let ds_f_cnt_up = ds.f_cnt_up; - ds.f_cnt_up = full_f_cnt + 1; - save(&ds).await?; - ds.f_cnt_up = ds_f_cnt_up; - - return Ok(ValidationStatus::Ok(full_f_cnt, ds)); - } else if ds.skip_f_cnt_check { - // re-transmission or frame-counter reset - ds.f_cnt_up = 0; - return Ok(ValidationStatus::Ok(full_f_cnt, ds)); - } else if full_f_cnt == (ds.f_cnt_up - 1) { - // re-transmission, the frame-counter did not increment - return Ok(ValidationStatus::Retransmission(full_f_cnt, ds)); - } else { - return Ok(ValidationStatus::Reset(full_f_cnt, ds)); - } - } - - // Restore the original f_cnt. - if let Payload::MACPayload(pl) = &mut phy.payload { - pl.fhdr.f_cnt = _f_cnt_orig; - } - } - - Err(Error::InvalidMIC) -} - -// Simmilar to get_for_phypayload_and_incr_f_cnt_up, but only retrieves the device-session for the -// given PhyPayload. As it does not return the ValidationStatus, it only returns the DeviceSession -// in case of a valid frame-counter. -// On Ok response, the PhyPayload f_cnt will be set to the full 32bit frame-counter based on the -// device-session context. -pub async fn get_for_phypayload( - phy: &mut PhyPayload, - tx_dr: u8, - tx_ch: u8, -) -> Result { - // Get the dev_addr and original f_cnt. - let (dev_addr, f_cnt_orig) = if let Payload::MACPayload(pl) = &phy.payload { - (pl.fhdr.devaddr, pl.fhdr.f_cnt) - } else { - return Err(Error::InvalidPayload("MacPayload".to_string())); - }; - - let device_sessions = get_for_dev_addr(dev_addr) - .await - .context("Get device-sessions for DevAddr")?; - if device_sessions.is_empty() { - return Err(Error::NotFound(dev_addr.to_string())); - } - - for ds in device_sessions { - // Get the full 32bit frame-counter. - let full_f_cnt = get_full_f_cnt_up(ds.f_cnt_up, f_cnt_orig); - let f_nwk_s_int_key = AES128Key::from_slice(&ds.f_nwk_s_int_key)?; - let s_nwk_s_int_key = AES128Key::from_slice(&ds.s_nwk_s_int_key)?; - - // Set the full f_cnt - if let Payload::MACPayload(pl) = &mut phy.payload { - pl.fhdr.f_cnt = full_f_cnt; - } - - let mic_ok = phy - .validate_uplink_data_mic( - ds.mac_version().from_proto(), - ds.conf_f_cnt, - tx_dr, - tx_ch, - &f_nwk_s_int_key, - &s_nwk_s_int_key, - ) - .context("Validate MIC")?; - - if mic_ok && full_f_cnt >= ds.f_cnt_up { - return Ok(ds); - } - - // Restore the original f_cnt. - if let Payload::MACPayload(pl) = &mut phy.payload { - pl.fhdr.f_cnt = f_cnt_orig; - } - } - - Err(Error::InvalidMIC) -} - -async fn get_dev_euis_for_dev_addr(dev_addr: DevAddr) -> Result> { - let key = redis_key(format!("devaddr:{{{}}}", dev_addr)); - - let dev_euis: HashSet> = redis::cmd("SMEMBERS") - .arg(key) - .query_async(&mut get_async_redis_conn().await?) - .await - .context("Get DevEUIs for DevAddr")?; - - let mut out = Vec::new(); - for dev_eui in &dev_euis { - out.push(EUI64::from_slice(dev_eui)?); - } - Ok(out) -} - -async fn remove_dev_eui_from_dev_addr_set(dev_addr: DevAddr, dev_eui: EUI64) -> Result<()> { - let key = redis_key(format!("devaddr:{{{}}}", dev_addr)); - - redis::cmd("SREM") - .arg(key) - .arg(&dev_eui.to_be_bytes()) - .query_async(&mut get_async_redis_conn().await?) - .await?; - - Ok(()) -} - -async fn get_for_dev_addr(dev_addr: DevAddr) -> Result> { - trace!(dev_addr = %dev_addr, "Getting device-session for DevAddr"); - let dev_euis = get_dev_euis_for_dev_addr(dev_addr).await?; - - let mut out = Vec::new(); - for dev_eui in dev_euis { - let ds = match get(&dev_eui).await { - Ok(v) => v, - Err(e) => { - if let Error::NotFound(_) = e { - if let Err(e) = remove_dev_eui_from_dev_addr_set(dev_addr, dev_eui).await { - error!(dev_addr = %dev_addr, dev_eui = %dev_eui, error = %e.full(), "Remove DevEUI from DevAddr->DevEUI set error"); - } - } else { - error!(dev_addr = %dev_addr, dev_eui = %dev_eui, error = %e.full(), "Get device-session for DevEUI error"); - } - continue; - } - }; - - let ds_dev_addr = DevAddr::from_slice(&ds.dev_addr)?; - - // When a pending rejoin device-session context is set and it has - // the given devAddr, add it to the items list. - if let Some(pending_ds) = &ds.pending_rejoin_device_session { - let pending_dev_addr = DevAddr::from_slice(&pending_ds.dev_addr)?; - if pending_dev_addr == dev_addr { - out.push(*pending_ds.clone()); - } - } - - // It is possible that the "main" device-session maps to a different - // devAddr as the PendingRejoinDeviceSession is set (using the devAddr - // that is used for the lookup). - if ds_dev_addr == dev_addr { - out.push(ds); - } - } - - Ok(out) -} - -// GetFullFCntUp returns the full 32bit frame-counter, given the fCntUp which -// has been truncated to the last 16 LSB. -// Notes: -// * After a succesful validation of the FCntUp and the MIC, don't forget -// to synchronize the device FCntUp with the packet FCnt. -// * In case of a frame-counter rollover, the returned values will be less -// than the given DeviceSession FCntUp. This must be validated outside this -// function! -// * In case of a re-transmission, the returned frame-counter equals -// DeviceSession.FCntUp - 1, as the FCntUp value holds the next expected -// frame-counter, not the FCntUp which was last seen. -fn get_full_f_cnt_up(next_expected_full_fcnt: u32, truncated_f_cnt: u32) -> u32 { - // Handle re-transmission. - if truncated_f_cnt == (((next_expected_full_fcnt % (1 << 16)) as u16).wrapping_sub(1)) as u32 { - return next_expected_full_fcnt - 1; - } - - let gap = ((truncated_f_cnt as u16).wrapping_sub((next_expected_full_fcnt % (1 << 16)) as u16)) - as u32; - - next_expected_full_fcnt.wrapping_add(gap) -} - -#[cfg(test)] -pub mod test { - use super::*; - use crate::test; - - #[test] - fn test_get_full_f_cnt_up() { - // server, device, expected - let tests = vec![ - (1, 1, 1), // frame-counter is as expected - (1 << 16, 0, 1 << 16), // frame-counter is as expected - ((1 << 16) + 1, 1, (1 << 16) + 1), // frame-counter is as expected - (0, 1, 1), // one frame packet-loss - ((1 << 16) + 1, 2, (1 << 16) + 2), // one frame packet-loss - (2, 1, 1), // re-transmission of previous frame - ((1 << 16) + 1, 0, (1 << 16)), // re-transmission of previous frame - ((1 << 16), (1 << 16) - 1, (1 << 16) - 1), // re-transmission of previous frame - (u32::MAX, 0, 0), // 32bit frame-counter rollover - ]; - - for (i, tst) in tests.iter().enumerate() { - let out = get_full_f_cnt_up(tst.0, tst.1); - assert_eq!(tst.2, out, "Test: {}, expected: {}, got: {}", i, tst.2, out); - } - } - - #[tokio::test] - async fn test_device_session() { - let _guard = test::prepare().await; - - let device_sessions = vec![ - internal::DeviceSession { - dev_addr: vec![0x01, 0x02, 0x03, 0x04], - dev_eui: vec![0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01], - s_nwk_s_int_key: vec![ - 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, - 0x01, 0x01, 0x01, - ], - f_nwk_s_int_key: vec![ - 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, - 0x01, 0x01, 0x01, - ], - nwk_s_enc_key: vec![ - 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, - 0x01, 0x01, 0x01, - ], - f_cnt_up: 100, - skip_f_cnt_check: true, - ..Default::default() - }, - internal::DeviceSession { - dev_addr: vec![0x01, 0x02, 0x03, 0x04], - dev_eui: vec![0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02], - s_nwk_s_int_key: vec![ - 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, - 0x02, 0x02, 0x02, - ], - f_nwk_s_int_key: vec![ - 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, - 0x02, 0x02, 0x02, - ], - nwk_s_enc_key: vec![ - 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, - 0x02, 0x02, 0x02, - ], - f_cnt_up: 200, - ..Default::default() - }, - internal::DeviceSession { - dev_addr: vec![0x01, 0x02, 0x03, 0x04], - dev_eui: vec![0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03], - s_nwk_s_int_key: vec![ - 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, - 0x03, 0x03, 0x03, - ], - f_nwk_s_int_key: vec![ - 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, - 0x03, 0x03, 0x03, - ], - nwk_s_enc_key: vec![ - 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, - 0x03, 0x03, 0x03, - ], - f_cnt_up: 300, - pending_rejoin_device_session: Some(Box::new(internal::DeviceSession { - dev_addr: vec![0x04, 0x03, 0x02, 0x01], - dev_eui: vec![0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03], - s_nwk_s_int_key: vec![ - 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, - 0x04, 0x04, 0x04, 0x04, - ], - f_nwk_s_int_key: vec![ - 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, - 0x04, 0x04, 0x04, 0x04, - ], - nwk_s_enc_key: vec![ - 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, - 0x04, 0x04, 0x04, 0x04, - ], - f_cnt_up: 0, - ..Default::default() - })), - ..Default::default() - }, - internal::DeviceSession { - dev_addr: vec![0x01, 0x02, 0x03, 0x04], - dev_eui: vec![0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05], - s_nwk_s_int_key: vec![ - 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, - 0x05, 0x05, 0x05, - ], - f_nwk_s_int_key: vec![ - 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, - 0x05, 0x05, 0x05, - ], - nwk_s_enc_key: vec![ - 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, - 0x05, 0x05, 0x05, - ], - f_cnt_up: (1 << 16) + 1, - ..Default::default() - }, - ]; - - for ds in &device_sessions { - save(ds).await.unwrap(); - } - - #[derive(Default)] - struct Test { - name: String, - dev_addr: DevAddr, - s_nwk_s_int_key: AES128Key, - f_nwk_s_int_key: AES128Key, - f_cnt: u32, - expected_retransmission: bool, - expected_reset: bool, - expected_dev_eui: EUI64, - expected_fcnt_up: u32, - expected_error: Option, - } - - let tests = vec![ - Test { - name: "matching dev_eui 0101010101010101".to_string(), - dev_addr: DevAddr::from_be_bytes([0x01, 0x02, 0x03, 0x04]), - f_nwk_s_int_key: AES128Key::from_slice(&device_sessions[0].f_nwk_s_int_key) - .unwrap(), - s_nwk_s_int_key: AES128Key::from_slice(&device_sessions[0].s_nwk_s_int_key) - .unwrap(), - f_cnt: device_sessions[0].f_cnt_up, - expected_retransmission: false, - expected_reset: false, - expected_fcnt_up: device_sessions[0].f_cnt_up, - expected_dev_eui: EUI64::from_slice(&device_sessions[0].dev_eui).unwrap(), - expected_error: None, - }, - Test { - name: "matching dev_eui 0202020202020202".to_string(), - dev_addr: DevAddr::from_be_bytes([0x01, 0x02, 0x03, 0x04]), - f_nwk_s_int_key: AES128Key::from_slice(&device_sessions[1].f_nwk_s_int_key) - .unwrap(), - s_nwk_s_int_key: AES128Key::from_slice(&device_sessions[1].s_nwk_s_int_key) - .unwrap(), - f_cnt: device_sessions[1].f_cnt_up, - expected_retransmission: false, - expected_reset: false, - expected_fcnt_up: device_sessions[1].f_cnt_up, - expected_dev_eui: EUI64::from_slice(&device_sessions[1].dev_eui).unwrap(), - expected_error: None, - }, - Test { - name: "matching dev_eui 0101010101010101 with frame-counter reset".to_string(), - dev_addr: DevAddr::from_be_bytes([0x01, 0x02, 0x03, 0x04]), - f_nwk_s_int_key: AES128Key::from_slice(&device_sessions[0].f_nwk_s_int_key) - .unwrap(), - s_nwk_s_int_key: AES128Key::from_slice(&device_sessions[0].s_nwk_s_int_key) - .unwrap(), - f_cnt: 0, - expected_retransmission: false, - expected_reset: false, - expected_fcnt_up: 0, - expected_dev_eui: EUI64::from_slice(&device_sessions[0].dev_eui).unwrap(), - expected_error: None, - }, - Test { - name: "matching dev_eui 0202020202020202 with invalid frame-counter".to_string(), - dev_addr: DevAddr::from_be_bytes([0x01, 0x02, 0x03, 0x04]), - f_nwk_s_int_key: AES128Key::from_slice(&device_sessions[1].f_nwk_s_int_key) - .unwrap(), - s_nwk_s_int_key: AES128Key::from_slice(&device_sessions[1].s_nwk_s_int_key) - .unwrap(), - f_cnt: 0, - expected_reset: true, - expected_dev_eui: EUI64::from_slice(&device_sessions[1].dev_eui).unwrap(), - ..Default::default() - }, - Test { - name: "invalid DevAddr".to_string(), - dev_addr: DevAddr::from_be_bytes([0x01, 0x01, 0x01, 0x01]), - f_nwk_s_int_key: AES128Key::from_slice(&device_sessions[0].f_nwk_s_int_key) - .unwrap(), - s_nwk_s_int_key: AES128Key::from_slice(&device_sessions[0].s_nwk_s_int_key) - .unwrap(), - f_cnt: device_sessions[0].f_cnt_up, - expected_error: Some("Object does not exist (id: 01010101)".to_string()), - ..Default::default() - }, - Test { - name: "invalid nwk_s_key".to_string(), - dev_addr: DevAddr::from_be_bytes([0x01, 0x02, 0x03, 0x04]), - f_nwk_s_int_key: AES128Key::from_bytes([ - 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, - ]), - s_nwk_s_int_key: AES128Key::from_bytes([ - 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, - ]), - f_cnt: device_sessions[0].f_cnt_up, - expected_error: Some("Invalid MIC".to_string()), - ..Default::default() - }, - Test { - name: "matching pending rejoin device-session".to_string(), - dev_addr: DevAddr::from_be_bytes([0x04, 0x03, 0x02, 0x01]), - f_nwk_s_int_key: AES128Key::from_bytes([ - 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, - 0x04, 0x04, 0x04, - ]), - s_nwk_s_int_key: AES128Key::from_bytes([ - 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, - 0x04, 0x04, 0x04, - ]), - f_cnt: 0, - expected_dev_eui: EUI64::from_slice(&device_sessions[2].dev_eui).unwrap(), - expected_fcnt_up: 0, - expected_retransmission: false, - expected_error: None, - expected_reset: false, - }, - Test { - name: "frame-counter rollover (16lsb)".to_string(), - dev_addr: DevAddr::from_be_bytes([0x01, 0x02, 0x03, 0x04]), - f_nwk_s_int_key: AES128Key::from_bytes([ - 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, - 0x05, 0x05, 0x05, - ]), - s_nwk_s_int_key: AES128Key::from_bytes([ - 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, - 0x05, 0x05, 0x05, - ]), - f_cnt: (1 << 16) + 11, - expected_dev_eui: EUI64::from_slice(&device_sessions[3].dev_eui).unwrap(), - expected_fcnt_up: (1 << 16) + 11, - expected_retransmission: false, - expected_error: None, - expected_reset: false, - }, - ]; - - for tst in &tests { - println!("> {}", tst.name); - let mut phy = lrwn::PhyPayload { - mhdr: lrwn::MHDR { - m_type: lrwn::MType::UnconfirmedDataUp, - major: lrwn::Major::LoRaWANR1, - }, - payload: lrwn::Payload::MACPayload(lrwn::MACPayload { - fhdr: lrwn::FHDR { - devaddr: tst.dev_addr, - f_ctrl: lrwn::FCtrl::default(), - f_cnt: tst.f_cnt, - ..Default::default() - }, - ..Default::default() - }), - mic: None, - }; - - phy.set_uplink_data_mic( - lrwn::MACVersion::LoRaWAN1_0, - 0, - 0, - 0, - &tst.f_nwk_s_int_key, - &tst.s_nwk_s_int_key, - ) - .unwrap(); - - // Truncate to 16LSB (as it would be transmitted over the air). - if let lrwn::Payload::MACPayload(pl) = &mut phy.payload { - pl.fhdr.f_cnt = tst.f_cnt % (1 << 16); - } - - let ds_res = get_for_phypayload_and_incr_f_cnt_up(false, &mut phy, 0, 0).await; - if tst.expected_error.is_some() { - assert_eq!(true, ds_res.is_err()); - assert_eq!( - tst.expected_error.as_ref().unwrap(), - &ds_res.err().unwrap().to_string() - ); - if let lrwn::Payload::MACPayload(pl) = &phy.payload { - assert_eq!(tst.f_cnt, pl.fhdr.f_cnt); - } - } else { - let ds = ds_res.unwrap(); - - // Validate that the f_cnt of the PhyPayload was set to the full frame-counter. - if let lrwn::Payload::MACPayload(pl) = &phy.payload { - assert_eq!(tst.expected_fcnt_up, pl.fhdr.f_cnt); - } - - if let ValidationStatus::Ok(full_f_cnt, ds) = ds { - assert_eq!(false, tst.expected_retransmission); - assert_eq!( - tst.expected_dev_eui, - EUI64::from_slice(&ds.dev_eui).unwrap() - ); - assert_eq!(tst.expected_fcnt_up, full_f_cnt); - } else if let ValidationStatus::Retransmission(full_f_cnt, ds) = ds { - assert_eq!(true, tst.expected_retransmission); - assert_eq!( - tst.expected_dev_eui, - EUI64::from_slice(&ds.dev_eui).unwrap() - ); - assert_eq!(tst.expected_fcnt_up, full_f_cnt); - } else if let ValidationStatus::Reset(_, ds) = ds { - assert_eq!(true, tst.expected_reset); - assert_eq!( - tst.expected_dev_eui, - EUI64::from_slice(&ds.dev_eui).unwrap() - ); - } - } - } - } - - #[tokio::test] - async fn test_get_for_dev_addr() { - let _guard = test::prepare().await; - - let dev_eui_1 = EUI64::from_be_bytes([1, 1, 1, 1, 1, 1, 1, 1]); - let dev_eui_2 = EUI64::from_be_bytes([2, 2, 2, 2, 2, 2, 2, 2]); - let dev_addr = DevAddr::from_be_bytes([1, 2, 3, 4]); - - let ds_1 = internal::DeviceSession { - dev_addr: dev_addr.to_vec(), - dev_eui: dev_eui_1.to_vec(), - ..Default::default() - }; - - let ds_2 = internal::DeviceSession { - dev_addr: dev_addr.to_vec(), - dev_eui: dev_eui_2.to_vec(), - ..Default::default() - }; - - save(&ds_1).await.unwrap(); - save(&ds_2).await.unwrap(); - - let dss = get_for_dev_addr(dev_addr).await.unwrap(); - assert_eq!(2, dss.len()); - - let dev_euis = get_dev_euis_for_dev_addr(dev_addr).await.unwrap(); - assert_eq!(2, dev_euis.len()); - - // At this point there is still a 'dangling' pointer from DevAddr->DevEUI. - delete(&dev_eui_2).await.unwrap(); - let dev_euis = get_dev_euis_for_dev_addr(dev_addr).await.unwrap(); - assert_eq!(2, dev_euis.len()); - - // This should only return one device-session. - let dss = get_for_dev_addr(dev_addr).await.unwrap(); - assert_eq!(1, dss.len()); - assert_eq!(dev_eui_1.to_vec(), dss[0].dev_eui); - - // 'dangling' DevAddr->DevEUI pointers have been cleaned up. - let dev_euis = get_dev_euis_for_dev_addr(dev_addr).await.unwrap(); - assert_eq!(1, dev_euis.len()); - assert_eq!(dev_eui_1, dev_euis[0]); - } -} diff --git a/chirpstack/src/storage/schema.rs b/chirpstack/src/storage/schema.rs index 30966b7e..b8ef82d8 100644 --- a/chirpstack/src/storage/schema.rs +++ b/chirpstack/src/storage/schema.rs @@ -63,6 +63,8 @@ diesel::table! { tags -> Jsonb, variables -> Jsonb, join_eui -> Bytea, + secondary_dev_addr -> Nullable, + device_session -> Nullable, } } diff --git a/chirpstack/src/test/assert.rs b/chirpstack/src/test/assert.rs index 06cb792b..a6b78f29 100644 --- a/chirpstack/src/test/assert.rs +++ b/chirpstack/src/test/assert.rs @@ -12,7 +12,7 @@ use crate::gateway::backend::mock as gateway_mock; use crate::integration::mock; use crate::storage::{ device::{self, DeviceClass}, - device_queue, device_session, downlink_frame, get_async_redis_conn, redis_key, + device_queue, downlink_frame, get_async_redis_conn, redis_key, }; use chirpstack_api::{gw, integration as integration_pb, internal, stream}; use lrwn::EUI64; @@ -27,7 +27,8 @@ pub fn f_cnt_up(dev_eui: EUI64, f_cnt: u32) -> Validator { Box::new(move || { let dev_eui = dev_eui.clone(); Box::pin(async move { - let ds = device_session::get(&dev_eui).await.unwrap(); + let d = device::get(&dev_eui).await.unwrap(); + let ds = d.get_device_session().unwrap(); assert_eq!(f_cnt, ds.f_cnt_up); }) }) @@ -37,7 +38,8 @@ pub fn n_f_cnt_down(dev_eui: EUI64, f_cnt: u32) -> Validator { Box::new(move || { let dev_eui = dev_eui.clone(); Box::pin(async move { - let ds = device_session::get(&dev_eui).await.unwrap(); + let d = device::get(&dev_eui).await.unwrap(); + let ds = d.get_device_session().unwrap(); assert_eq!(f_cnt, ds.n_f_cnt_down); }) }) @@ -47,7 +49,8 @@ pub fn a_f_cnt_down(dev_eui: EUI64, f_cnt: u32) -> Validator { Box::new(move || { let dev_eui = dev_eui.clone(); Box::pin(async move { - let ds = device_session::get(&dev_eui).await.unwrap(); + let d = device::get(&dev_eui).await.unwrap(); + let ds = d.get_device_session().unwrap(); assert_eq!(f_cnt, ds.a_f_cnt_down); }) }) @@ -57,7 +60,8 @@ pub fn tx_power_index(dev_eui: EUI64, tx_power: u32) -> Validator { Box::new(move || { let dev_eui = dev_eui.clone(); Box::pin(async move { - let ds = device_session::get(&dev_eui).await.unwrap(); + let d = device::get(&dev_eui).await.unwrap(); + let ds = d.get_device_session().unwrap(); assert_eq!(tx_power, ds.tx_power_index); }) }) @@ -67,7 +71,8 @@ pub fn nb_trans(dev_eui: EUI64, nb_trans: u32) -> Validator { Box::new(move || { let dev_eui = dev_eui.clone(); Box::pin(async move { - let ds = device_session::get(&dev_eui).await.unwrap(); + let d = device::get(&dev_eui).await.unwrap(); + let ds = d.get_device_session().unwrap(); assert_eq!(nb_trans, ds.nb_trans); }) }) @@ -78,7 +83,8 @@ pub fn enabled_uplink_channel_indices(dev_eui: EUI64, channels: Vec) -> Val let dev_eui = dev_eui.clone(); let channels = channels.clone(); Box::pin(async move { - let ds = device_session::get(&dev_eui).await.unwrap(); + let d = device::get(&dev_eui).await.unwrap(); + let ds = d.get_device_session().unwrap(); assert_eq!(channels, ds.enabled_uplink_channel_indices); }) }) @@ -88,7 +94,8 @@ pub fn dr(dev_eui: EUI64, dr: u32) -> Validator { Box::new(move || { let dev_eui = dev_eui.clone(); Box::pin(async move { - let ds = device_session::get(&dev_eui).await.unwrap(); + let d = device::get(&dev_eui).await.unwrap(); + let ds = d.get_device_session().unwrap(); assert_eq!(dr, ds.dr); }) }) @@ -98,7 +105,8 @@ pub fn mac_command_error_count(dev_eui: EUI64, cid: lrwn::CID, count: u32) -> Va Box::new(move || { let dev_eui = dev_eui.clone(); Box::pin(async move { - let ds = device_session::get(&dev_eui).await.unwrap(); + let d = device::get(&dev_eui).await.unwrap(); + let ds = d.get_device_session().unwrap(); assert_eq!( count, ds.mac_command_error_count @@ -115,7 +123,8 @@ pub fn uplink_adr_history(dev_eui: EUI64, uh: Vec) - let dev_eui = dev_eui.clone(); let uh = uh.clone(); Box::pin(async move { - let ds = device_session::get(&dev_eui).await.unwrap(); + let d = device::get(&dev_eui).await.unwrap(); + let ds = d.get_device_session().unwrap(); assert_eq!(uh, ds.uplink_adr_history); }) }) @@ -240,8 +249,9 @@ pub fn device_session(dev_eui: EUI64, ds: internal::DeviceSession) -> Validator Box::new(move || { let ds = ds.clone(); Box::pin(async move { - let ds_get = device_session::get(&dev_eui).await.unwrap(); - assert_eq!(ds, ds_get); + let d = device::get(&dev_eui).await.unwrap(); + let ds_get = d.get_device_session().unwrap(); + assert_eq!(&ds, ds_get); }) }) } @@ -249,8 +259,8 @@ pub fn device_session(dev_eui: EUI64, ds: internal::DeviceSession) -> Validator pub fn no_device_session(dev_eui: EUI64) -> Validator { Box::new(move || { Box::pin(async move { - let res = device_session::get(&dev_eui).await; - assert_eq!(true, res.is_err()); + let d = device::get(&dev_eui).await.unwrap(); + assert!(d.device_session.is_none()); }) }) } diff --git a/chirpstack/src/test/class_a_pr_test.rs b/chirpstack/src/test/class_a_pr_test.rs index 93c56fa6..f4dcdc50 100644 --- a/chirpstack/src/test/class_a_pr_test.rs +++ b/chirpstack/src/test/class_a_pr_test.rs @@ -12,7 +12,7 @@ use crate::gateway::backend as gateway_backend; use crate::storage::{ application, device::{self, DeviceClass}, - device_profile, device_queue, device_session, gateway, tenant, + device_profile, device_queue, gateway, tenant, }; use crate::{config, test, uplink}; use chirpstack_api::{common, gw, internal}; @@ -219,17 +219,41 @@ async fn test_sns_uplink() { .await .unwrap(); + let mut dev_addr = lrwn::DevAddr::from_be_bytes([0, 0, 0, 0]); + dev_addr.set_dev_addr_prefix(lrwn::NetID::from_str("000505").unwrap().dev_addr_prefix()); + let dev = device::create(device::Device { name: "device".into(), application_id: app.id.clone(), device_profile_id: dp.id.clone(), dev_eui: EUI64::from_be_bytes([2, 2, 3, 4, 5, 6, 7, 8]), enabled_class: DeviceClass::B, + dev_addr: Some(dev_addr), + device_session: Some(internal::DeviceSession { + mac_version: common::MacVersion::Lorawan104.into(), + dev_addr: dev_addr.to_vec(), + f_nwk_s_int_key: vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16], + s_nwk_s_int_key: vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16], + nwk_s_enc_key: vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16], + app_s_key: Some(common::KeyEnvelope { + kek_label: "".into(), + aes_key: vec![16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1], + }), + f_cnt_up: 8, + n_f_cnt_down: 5, + enabled_uplink_channel_indices: vec![0, 1, 2], + rx1_delay: 1, + rx2_frequency: 869525000, + region_config_id: "eu868".into(), + ..Default::default() + }), ..Default::default() }) .await .unwrap(); + let ds = dev.get_device_session().unwrap(); + device_queue::enqueue_item(device_queue::DeviceQueueItem { dev_eui: dev.dev_eui, f_port: 10, @@ -239,31 +263,6 @@ async fn test_sns_uplink() { .await .unwrap(); - let mut dev_addr = lrwn::DevAddr::from_be_bytes([0, 0, 0, 0]); - dev_addr.set_dev_addr_prefix(lrwn::NetID::from_str("000505").unwrap().dev_addr_prefix()); - - let ds = internal::DeviceSession { - dev_eui: vec![2, 2, 3, 4, 5, 6, 7, 8], - mac_version: common::MacVersion::Lorawan104.into(), - join_eui: vec![8, 7, 6, 5, 4, 3, 2, 1], - dev_addr: dev_addr.to_vec(), - f_nwk_s_int_key: vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16], - s_nwk_s_int_key: vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16], - nwk_s_enc_key: vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16], - app_s_key: Some(common::KeyEnvelope { - kek_label: "".into(), - aes_key: vec![16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1], - }), - f_cnt_up: 8, - n_f_cnt_down: 5, - enabled_uplink_channel_indices: vec![0, 1, 2], - rx1_delay: 1, - rx2_frequency: 869525000, - region_config_id: "eu868".into(), - ..Default::default() - }; - device_session::save(&ds).await.unwrap(); - let mut data_phy = lrwn::PhyPayload { mhdr: lrwn::MHDR { m_type: lrwn::MType::UnconfirmedDataUp, @@ -346,7 +345,7 @@ async fn test_sns_uplink() { }, phy_payload: hex::decode("600000000a8005000a54972baa8b983cd1").unwrap(), dl_meta_data: Some(backend::DLMetaData { - dev_eui: ds.dev_eui.clone(), + dev_eui: dev.dev_eui.to_vec(), dl_freq_1: Some(868.1), dl_freq_2: Some(869.525), rx_delay_1: Some(1), @@ -403,7 +402,7 @@ async fn test_sns_uplink() { }, ..Default::default() }, - dev_eui: ds.dev_eui.clone(), + dev_eui: dev.dev_eui.to_vec(), nwk_s_key: Some(backend::KeyEnvelope { kek_label: "".to_string(), aes_key: ds.nwk_s_enc_key.clone(), @@ -466,41 +465,40 @@ async fn test_sns_roaming_not_allowed() { .await .unwrap(); - let _dev = device::create(device::Device { + let mut dev_addr = lrwn::DevAddr::from_be_bytes([0, 0, 0, 0]); + dev_addr.set_dev_addr_prefix(lrwn::NetID::from_str("000505").unwrap().dev_addr_prefix()); + + let dev = device::create(device::Device { name: "device".into(), application_id: app.id.clone(), device_profile_id: dp.id.clone(), dev_eui: EUI64::from_be_bytes([2, 2, 3, 4, 5, 6, 7, 8]), enabled_class: DeviceClass::B, + dev_addr: Some(dev_addr), + device_session: Some(internal::DeviceSession { + mac_version: common::MacVersion::Lorawan104.into(), + dev_addr: dev_addr.to_vec(), + f_nwk_s_int_key: vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16], + s_nwk_s_int_key: vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16], + nwk_s_enc_key: vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16], + app_s_key: Some(common::KeyEnvelope { + kek_label: "".into(), + aes_key: vec![16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1], + }), + f_cnt_up: 8, + n_f_cnt_down: 5, + enabled_uplink_channel_indices: vec![0, 1, 2], + rx1_delay: 1, + rx2_frequency: 869525000, + region_config_id: "eu868".into(), + ..Default::default() + }), ..Default::default() }) .await .unwrap(); - let mut dev_addr = lrwn::DevAddr::from_be_bytes([0, 0, 0, 0]); - dev_addr.set_dev_addr_prefix(lrwn::NetID::from_str("000505").unwrap().dev_addr_prefix()); - - let ds = internal::DeviceSession { - dev_eui: vec![2, 2, 3, 4, 5, 6, 7, 8], - mac_version: common::MacVersion::Lorawan104.into(), - join_eui: vec![8, 7, 6, 5, 4, 3, 2, 1], - dev_addr: dev_addr.to_vec(), - f_nwk_s_int_key: vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16], - s_nwk_s_int_key: vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16], - nwk_s_enc_key: vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16], - app_s_key: Some(common::KeyEnvelope { - kek_label: "".into(), - aes_key: vec![16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1], - }), - f_cnt_up: 8, - n_f_cnt_down: 5, - enabled_uplink_channel_indices: vec![0, 1, 2], - rx1_delay: 1, - rx2_frequency: 869525000, - region_config_id: "eu868".into(), - ..Default::default() - }; - device_session::save(&ds).await.unwrap(); + let ds = dev.get_device_session().unwrap(); let mut data_phy = lrwn::PhyPayload { mhdr: lrwn::MHDR { diff --git a/chirpstack/src/test/class_a_test.rs b/chirpstack/src/test/class_a_test.rs index d5e01529..89abb87a 100644 --- a/chirpstack/src/test/class_a_test.rs +++ b/chirpstack/src/test/class_a_test.rs @@ -8,16 +8,17 @@ use super::assert; use crate::storage::{ application, device::{self, DeviceClass}, - device_profile, device_queue, device_session, gateway, mac_command, reset_redis, tenant, + device_profile, device_queue, gateway, mac_command, reset_redis, tenant, }; use crate::{config, gateway::backend as gateway_backend, integration, region, test, uplink}; use chirpstack_api::{common, gw, integration as integration_pb, internal, stream}; -use lrwn::{AES128Key, EUI64}; +use lrwn::{AES128Key, DevAddr, EUI64}; type Function = Box Pin>>>; struct Test { name: String, + dev_eui: EUI64, device_queue_items: Vec, before_func: Option, after_func: Option, @@ -93,11 +94,28 @@ async fn test_gateway_filtering() { device_profile_id: dp.id.clone(), dev_eui: EUI64::from_be_bytes([2, 2, 3, 4, 5, 6, 7, 8]), enabled_class: DeviceClass::B, + dev_addr: Some(DevAddr::from_be_bytes([1, 2, 3, 4])), + device_session: Some(internal::DeviceSession { + mac_version: common::MacVersion::Lorawan102.into(), + dev_addr: vec![1, 2, 3, 4], + f_nwk_s_int_key: vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16], + s_nwk_s_int_key: vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16], + nwk_s_enc_key: vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16], + f_cnt_up: 7, + n_f_cnt_down: 5, + enabled_uplink_channel_indices: vec![0, 1, 2], + rx1_delay: 1, + rx2_frequency: 869525000, + region_config_id: "eu868".into(), + ..Default::default() + }), ..Default::default() }) .await .unwrap(); + let ds = dev.get_device_session().unwrap(); + let mut rx_info_a = gw::UplinkRxInfo { gateway_id: gw_a.gateway_id.to_string(), location: Some(Default::default()), @@ -128,26 +146,10 @@ async fn test_gateway_filtering() { }; uplink::helpers::set_uplink_modulation(&"eu868", &mut tx_info, 0).unwrap(); - let ds = internal::DeviceSession { - dev_eui: vec![2, 2, 3, 4, 5, 6, 7, 8], - mac_version: common::MacVersion::Lorawan102.into(), - join_eui: vec![8, 7, 6, 5, 4, 3, 2, 1], - dev_addr: vec![1, 2, 3, 4], - f_nwk_s_int_key: vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16], - s_nwk_s_int_key: vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16], - nwk_s_enc_key: vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16], - f_cnt_up: 7, - n_f_cnt_down: 5, - enabled_uplink_channel_indices: vec![0, 1, 2], - rx1_delay: 1, - rx2_frequency: 869525000, - region_config_id: "eu868".into(), - ..Default::default() - }; - let tests = vec![ Test { name: "private gateway of same tenant".into(), + dev_eui: dev.dev_eui, device_queue_items: vec![], before_func: None, after_func: None, @@ -174,6 +176,7 @@ async fn test_gateway_filtering() { }, Test { name: "private gateway other tenant".into(), + dev_eui: dev.dev_eui, device_queue_items: vec![], before_func: None, after_func: None, @@ -196,7 +199,7 @@ async fn test_gateway_filtering() { }), mic: Some([48, 94, 26, 239]), }, - assert: vec![assert::f_cnt_up(dev.dev_eui.clone(), 7)], + assert: vec![assert::f_cnt_up(dev.dev_eui, 7)], }, ]; @@ -257,6 +260,7 @@ async fn test_region_config_id_filtering() { device_profile_id: dp.id.clone(), dev_eui: EUI64::from_be_bytes([2, 2, 3, 4, 5, 6, 7, 8]), enabled_class: DeviceClass::A, + dev_addr: Some(DevAddr::from_be_bytes([1, 2, 3, 4])), ..Default::default() }) .await @@ -293,9 +297,7 @@ async fn test_region_config_id_filtering() { uplink::helpers::set_uplink_modulation(&"eu868", &mut tx_info, 0).unwrap(); let ds = internal::DeviceSession { - dev_eui: vec![2, 2, 3, 4, 5, 6, 7, 8], mac_version: common::MacVersion::Lorawan102.into(), - join_eui: vec![8, 7, 6, 5, 4, 3, 2, 1], dev_addr: vec![1, 2, 3, 4], f_nwk_s_int_key: vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16], s_nwk_s_int_key: vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16], @@ -312,6 +314,7 @@ async fn test_region_config_id_filtering() { let tests = vec![ Test { name: "matching config id".into(), + dev_eui: dev.dev_eui, device_queue_items: vec![], before_func: None, after_func: None, @@ -338,6 +341,7 @@ async fn test_region_config_id_filtering() { }, Test { name: "non-matching configuration id".into(), + dev_eui: dev.dev_eui, device_queue_items: vec![], before_func: None, after_func: None, @@ -410,12 +414,13 @@ async fn test_lorawan_10_errors() { .await .unwrap(); - let _dev = device::create(device::Device { + let dev = device::create(device::Device { name: "device".into(), application_id: app.id.clone(), device_profile_id: dp.id.clone(), dev_eui: EUI64::from_be_bytes([2, 2, 3, 4, 5, 6, 7, 8]), enabled_class: DeviceClass::A, + dev_addr: Some(DevAddr::from_be_bytes([1, 2, 3, 4])), ..Default::default() }) .await @@ -440,9 +445,7 @@ async fn test_lorawan_10_errors() { uplink::helpers::set_uplink_modulation(&"eu868", &mut tx_info, 0).unwrap(); let ds = internal::DeviceSession { - dev_eui: vec![2, 2, 3, 4, 5, 6, 7, 8], mac_version: common::MacVersion::Lorawan102.into(), - join_eui: vec![8, 7, 6, 5, 4, 3, 2, 1], dev_addr: vec![1, 2, 3, 4], f_nwk_s_int_key: vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16], s_nwk_s_int_key: vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16], @@ -459,6 +462,7 @@ async fn test_lorawan_10_errors() { let tests = vec![ Test { name: "invalid frame-counter (did not increment)".into(), + dev_eui: dev.dev_eui, device_queue_items: vec![], before_func: None, after_func: None, @@ -491,6 +495,7 @@ async fn test_lorawan_10_errors() { }, Test { name: "invalid frame-counter (reset)".into(), + dev_eui: dev.dev_eui, device_queue_items: vec![], before_func: None, after_func: None, @@ -522,6 +527,7 @@ async fn test_lorawan_10_errors() { }, Test { name: "invalid mic".into(), + dev_eui: dev.dev_eui, device_queue_items: vec![], before_func: None, after_func: None, @@ -605,12 +611,13 @@ async fn test_lorawan_11_errors() { .await .unwrap(); - let _dev = device::create(device::Device { + let dev = device::create(device::Device { name: "device".into(), application_id: app.id.clone(), device_profile_id: dp.id.clone(), dev_eui: EUI64::from_be_bytes([2, 2, 3, 4, 5, 6, 7, 8]), enabled_class: DeviceClass::A, + dev_addr: Some(DevAddr::from_be_bytes([1, 2, 3, 4])), ..Default::default() }) .await @@ -641,9 +648,7 @@ async fn test_lorawan_11_errors() { uplink::helpers::set_uplink_modulation(&"eu868", &mut tx_info_dr, 3).unwrap(); let ds = internal::DeviceSession { - dev_eui: vec![2, 2, 3, 4, 5, 6, 7, 8], mac_version: common::MacVersion::Lorawan102.into(), - join_eui: vec![8, 7, 6, 5, 4, 3, 2, 1], dev_addr: vec![1, 2, 3, 4], f_nwk_s_int_key: vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16], s_nwk_s_int_key: vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16], @@ -660,6 +665,7 @@ async fn test_lorawan_11_errors() { let tests = vec![ Test { name: "invalid frequency (MIC)".into(), + dev_eui: dev.dev_eui, device_queue_items: vec![], before_func: None, after_func: None, @@ -686,6 +692,7 @@ async fn test_lorawan_11_errors() { }, Test { name: "invalid frequency (MIC)".into(), + dev_eui: dev.dev_eui, device_queue_items: vec![], before_func: None, after_func: None, @@ -765,6 +772,7 @@ async fn test_lorawan_10_skip_f_cnt() { dev_eui: EUI64::from_be_bytes([2, 2, 3, 4, 5, 6, 7, 8]), enabled_class: DeviceClass::A, skip_fcnt_check: true, + dev_addr: Some(DevAddr::from_be_bytes([1, 2, 3, 4])), ..Default::default() }) .await @@ -789,9 +797,7 @@ async fn test_lorawan_10_skip_f_cnt() { uplink::helpers::set_uplink_modulation(&"eu868", &mut tx_info, 0).unwrap(); let ds = internal::DeviceSession { - dev_eui: vec![2, 2, 3, 4, 5, 6, 7, 8], mac_version: common::MacVersion::Lorawan102.into(), - join_eui: vec![8, 7, 6, 5, 4, 3, 2, 1], dev_addr: vec![1, 2, 3, 4], f_nwk_s_int_key: vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16], s_nwk_s_int_key: vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16], @@ -809,6 +815,7 @@ async fn test_lorawan_10_skip_f_cnt() { let tests = vec![ Test { name: "frame-counter is invalid but not 0".into(), + dev_eui: dev.dev_eui, device_queue_items: vec![], before_func: None, after_func: None, @@ -857,6 +864,7 @@ async fn test_lorawan_10_skip_f_cnt() { }, Test { name: "frame-counter is invalid and 0".into(), + dev_eui: dev.dev_eui, device_queue_items: vec![], before_func: None, after_func: None, @@ -958,6 +966,7 @@ async fn test_lorawan_10_device_disabled() { dev_eui: EUI64::from_be_bytes([2, 2, 3, 4, 5, 6, 7, 8]), enabled_class: DeviceClass::A, is_disabled: true, + dev_addr: Some(DevAddr::from_be_bytes([1, 2, 3, 4])), ..Default::default() }) .await @@ -982,9 +991,7 @@ async fn test_lorawan_10_device_disabled() { uplink::helpers::set_uplink_modulation(&"eu868", &mut tx_info, 0).unwrap(); let ds = internal::DeviceSession { - dev_eui: vec![2, 2, 3, 4, 5, 6, 7, 8], mac_version: common::MacVersion::Lorawan102.into(), - join_eui: vec![8, 7, 6, 5, 4, 3, 2, 1], dev_addr: vec![1, 2, 3, 4], f_nwk_s_int_key: vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16], s_nwk_s_int_key: vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16], @@ -1000,6 +1007,7 @@ async fn test_lorawan_10_device_disabled() { let tests = vec![Test { name: "uplink ignored".into(), + dev_eui: dev.dev_eui, device_queue_items: vec![], before_func: None, after_func: None, @@ -1081,6 +1089,7 @@ async fn test_lorawan_10_uplink() { device_profile_id: dp.id.clone(), dev_eui: EUI64::from_be_bytes([2, 2, 3, 4, 5, 6, 7, 8]), enabled_class: DeviceClass::A, + dev_addr: Some(DevAddr::from_be_bytes([1, 2, 3, 4])), ..Default::default() }) .await @@ -1111,9 +1120,7 @@ async fn test_lorawan_10_uplink() { uplink::helpers::set_uplink_modulation(&"eu868", &mut tx_info_lr_fhss, 10).unwrap(); let ds = internal::DeviceSession { - dev_eui: vec![2, 2, 3, 4, 5, 6, 7, 8], mac_version: common::MacVersion::Lorawan104.into(), - join_eui: vec![8, 7, 6, 5, 4, 3, 2, 1], dev_addr: vec![1, 2, 3, 4], f_nwk_s_int_key: vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16], s_nwk_s_int_key: vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16], @@ -1134,6 +1141,7 @@ async fn test_lorawan_10_uplink() { let tests = vec![ Test { name: "unconfirmed uplink with payload".into(), + dev_eui: dev.dev_eui, device_queue_items: vec![], before_func: None, after_func: None, @@ -1184,6 +1192,7 @@ async fn test_lorawan_10_uplink() { }, Test { name: "unconfirmed uplink with payload using LR-FHSS dr".into(), + dev_eui: dev.dev_eui, device_queue_items: vec![], before_func: Some(Box::new(move || { Box::pin(async move { @@ -1255,6 +1264,7 @@ async fn test_lorawan_10_uplink() { }, Test { name: "unconfirmed uplink with payload + ACK".into(), + dev_eui: dev.dev_eui, device_queue_items: vec![device_queue::DeviceQueueItem { id: Uuid::nil(), dev_eui: dev.dev_eui.clone(), @@ -1333,6 +1343,7 @@ async fn test_lorawan_10_uplink() { }, Test { name: "unconfirmed uplink without payload (just FPort)".into(), + dev_eui: dev.dev_eui, device_queue_items: vec![], before_func: None, after_func: None, @@ -1383,6 +1394,7 @@ async fn test_lorawan_10_uplink() { }, Test { name: "confirmed uplink with payload".into(), + dev_eui: dev.dev_eui, device_queue_items: vec![], before_func: None, after_func: None, @@ -1494,6 +1506,7 @@ async fn test_lorawan_10_uplink() { }, Test { name: "confirmed uplink without payload".into(), + dev_eui: dev.dev_eui, device_queue_items: vec![], before_func: None, after_func: None, @@ -1604,21 +1617,34 @@ async fn test_lorawan_10_uplink() { }, Test { name: "uplink of class-c device updates scheduler_run_after".into(), + dev_eui: dev.dev_eui, device_queue_items: vec![], before_func: Some(Box::new(move || { let dev_eui = dev.dev_eui; Box::pin(async move { - device::set_enabled_class(&dev_eui, device::DeviceClass::C) - .await - .unwrap(); + device::partial_update( + dev_eui, + &device::DeviceChangeset { + enabled_class: Some(device::DeviceClass::C), + ..Default::default() + }, + ) + .await + .unwrap(); }) })), after_func: Some(Box::new(move || { let dev_eui = dev.dev_eui; Box::pin(async move { - device::set_enabled_class(&dev_eui, device::DeviceClass::A) - .await - .unwrap(); + device::partial_update( + dev_eui, + &device::DeviceChangeset { + enabled_class: Some(device::DeviceClass::A), + ..Default::default() + }, + ) + .await + .unwrap(); }) })), device_session: Some(ds.clone()), @@ -1700,6 +1726,7 @@ async fn test_lorawan_10_end_to_end_enc() { device_profile_id: dp.id.clone(), dev_eui: EUI64::from_be_bytes([2, 2, 3, 4, 5, 6, 7, 8]), enabled_class: DeviceClass::A, + dev_addr: Some(DevAddr::from_be_bytes([1, 2, 3, 4])), ..Default::default() }) .await @@ -1724,9 +1751,7 @@ async fn test_lorawan_10_end_to_end_enc() { uplink::helpers::set_uplink_modulation(&"eu868", &mut tx_info, 0).unwrap(); let ds_sess_key_id = internal::DeviceSession { - dev_eui: vec![2, 2, 3, 4, 5, 6, 7, 8], mac_version: common::MacVersion::Lorawan104.into(), - join_eui: vec![8, 7, 6, 5, 4, 3, 2, 1], dev_addr: vec![1, 2, 3, 4], f_nwk_s_int_key: vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16], s_nwk_s_int_key: vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16], @@ -1743,9 +1768,7 @@ async fn test_lorawan_10_end_to_end_enc() { }; let ds_app_s_key = internal::DeviceSession { - dev_eui: vec![2, 2, 3, 4, 5, 6, 7, 8], mac_version: common::MacVersion::Lorawan104.into(), - join_eui: vec![8, 7, 6, 5, 4, 3, 2, 1], dev_addr: vec![1, 2, 3, 4], f_nwk_s_int_key: vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16], s_nwk_s_int_key: vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16], @@ -1766,6 +1789,7 @@ async fn test_lorawan_10_end_to_end_enc() { let tests = vec![ Test { name: "end-to-end encryption with session key id".into(), + dev_eui: dev.dev_eui, device_queue_items: vec![], before_func: None, after_func: None, @@ -1816,6 +1840,7 @@ async fn test_lorawan_10_end_to_end_enc() { }, Test { name: "end-to-end encryption with AppSKey".into(), + dev_eui: dev.dev_eui, device_queue_items: vec![], before_func: None, after_func: None, @@ -1869,6 +1894,7 @@ async fn test_lorawan_10_end_to_end_enc() { }, Test { name: "end-to-end encryption using AppSkey + encrypted downlink".into(), + dev_eui: dev.dev_eui, device_queue_items: vec![device_queue::DeviceQueueItem { id: Uuid::nil(), dev_eui: dev.dev_eui.clone(), @@ -2028,6 +2054,7 @@ async fn test_lorawan_11_uplink() { device_profile_id: dp.id.clone(), dev_eui: EUI64::from_be_bytes([2, 2, 3, 4, 5, 6, 7, 8]), enabled_class: DeviceClass::A, + dev_addr: Some(DevAddr::from_be_bytes([1, 2, 3, 4])), ..Default::default() }) .await @@ -2058,9 +2085,7 @@ async fn test_lorawan_11_uplink() { uplink::helpers::set_uplink_modulation(&"eu868", &mut tx_info_lr_fhss, 8).unwrap(); let ds = internal::DeviceSession { - dev_eui: vec![2, 2, 3, 4, 5, 6, 7, 8], mac_version: common::MacVersion::Lorawan110.into(), - join_eui: vec![8, 7, 6, 5, 4, 3, 2, 1], dev_addr: vec![1, 2, 3, 4], f_nwk_s_int_key: vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16], s_nwk_s_int_key: vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16], @@ -2082,6 +2107,7 @@ async fn test_lorawan_11_uplink() { let tests = vec![ Test { name: "unconfirmed uplink with payload".into(), + dev_eui: dev.dev_eui, device_queue_items: vec![], before_func: None, after_func: None, @@ -2132,6 +2158,7 @@ async fn test_lorawan_11_uplink() { }, Test { name: "unconfirmed uplink with payload + ACK".into(), + dev_eui: dev.dev_eui, device_queue_items: vec![device_queue::DeviceQueueItem { id: Uuid::nil(), dev_eui: dev.dev_eui.clone(), @@ -2266,6 +2293,7 @@ async fn test_lorawan_10_rx_delay() { device_profile_id: dp.id.clone(), dev_eui: EUI64::from_be_bytes([2, 2, 3, 4, 5, 6, 7, 8]), enabled_class: DeviceClass::A, + dev_addr: Some(DevAddr::from_be_bytes([1, 2, 3, 4])), ..Default::default() }) .await @@ -2296,9 +2324,7 @@ async fn test_lorawan_10_rx_delay() { uplink::helpers::set_uplink_modulation(&"eu868", &mut tx_info_lr_fhss, 8).unwrap(); let ds = internal::DeviceSession { - dev_eui: vec![2, 2, 3, 4, 5, 6, 7, 8], mac_version: common::MacVersion::Lorawan104.into(), - join_eui: vec![8, 7, 6, 5, 4, 3, 2, 1], dev_addr: vec![1, 2, 3, 4], f_nwk_s_int_key: vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16], s_nwk_s_int_key: vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16], @@ -2318,6 +2344,7 @@ async fn test_lorawan_10_rx_delay() { let tests = vec![Test { name: "confirmed uplink without payload (rx_delay = 3)".into(), + dev_eui: dev.dev_eui, device_queue_items: vec![], before_func: None, after_func: None, @@ -2479,6 +2506,7 @@ async fn test_lorawan_10_mac_commands() { device_profile_id: dp.id.clone(), dev_eui: EUI64::from_be_bytes([2, 2, 3, 4, 5, 6, 7, 8]), enabled_class: DeviceClass::A, + dev_addr: Some(DevAddr::from_be_bytes([1, 2, 3, 4])), ..Default::default() }) .await @@ -2509,9 +2537,7 @@ async fn test_lorawan_10_mac_commands() { uplink::helpers::set_uplink_modulation(&"eu868", &mut tx_info_lr_fhss, 8).unwrap(); let ds = internal::DeviceSession { - dev_eui: vec![2, 2, 3, 4, 5, 6, 7, 8], mac_version: common::MacVersion::Lorawan104.into(), - join_eui: vec![8, 7, 6, 5, 4, 3, 2, 1], dev_addr: vec![1, 2, 3, 4], f_nwk_s_int_key: vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16], s_nwk_s_int_key: vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16], @@ -2532,6 +2558,7 @@ async fn test_lorawan_10_mac_commands() { let tests = vec![ Test { name: "unconfirmed uplink + device-status request downlink (FOpts)".into(), + dev_eui: dev.dev_eui, device_queue_items: vec![], before_func: Some(Box::new(move || { let dp_id = dp.id.clone(); @@ -2624,6 +2651,7 @@ async fn test_lorawan_10_mac_commands() { Test { name: "unconfirmed uplink + device-status request downlink (FOpts) + downlink payload" .into(), + dev_eui: dev.dev_eui, device_queue_items: vec![device_queue::DeviceQueueItem { id: Uuid::nil(), dev_eui: dev.dev_eui.clone(), @@ -2721,6 +2749,7 @@ async fn test_lorawan_10_mac_commands() { }, Test { name: "RxTimingSetupAns is answered with an empty downlink".into(), + dev_eui: dev.dev_eui, device_queue_items: vec![], before_func: None, after_func: None, @@ -2848,6 +2877,7 @@ async fn test_lorawan_11_mac_commands() { device_profile_id: dp.id.clone(), dev_eui: EUI64::from_be_bytes([2, 2, 3, 4, 5, 6, 7, 8]), enabled_class: DeviceClass::A, + dev_addr: Some(DevAddr::from_be_bytes([1, 2, 3, 4])), ..Default::default() }) .await @@ -2872,9 +2902,7 @@ async fn test_lorawan_11_mac_commands() { uplink::helpers::set_uplink_modulation(&"eu868", &mut tx_info, 0).unwrap(); let ds = internal::DeviceSession { - dev_eui: vec![2, 2, 3, 4, 5, 6, 7, 8], mac_version: common::MacVersion::Lorawan110.into(), - join_eui: vec![8, 7, 6, 5, 4, 3, 2, 1], dev_addr: vec![1, 2, 3, 4], f_nwk_s_int_key: vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16], s_nwk_s_int_key: vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16], @@ -2928,6 +2956,7 @@ async fn test_lorawan_11_mac_commands() { let tests = vec![Test { name: "uplink mac-command (encrypted fopts)".into(), + dev_eui: dev.dev_eui, device_queue_items: vec![], before_func: None, after_func: None, @@ -3042,6 +3071,7 @@ async fn test_lorawan_10_device_queue() { device_profile_id: dp.id.clone(), dev_eui: EUI64::from_be_bytes([2, 2, 3, 4, 5, 6, 7, 8]), enabled_class: DeviceClass::A, + dev_addr: Some(DevAddr::from_be_bytes([1, 2, 3, 4])), ..Default::default() }) .await @@ -3066,9 +3096,7 @@ async fn test_lorawan_10_device_queue() { uplink::helpers::set_uplink_modulation(&"eu868", &mut tx_info, 0).unwrap(); let ds = internal::DeviceSession { - dev_eui: vec![2, 2, 3, 4, 5, 6, 7, 8], mac_version: common::MacVersion::Lorawan104.into(), - join_eui: vec![8, 7, 6, 5, 4, 3, 2, 1], dev_addr: vec![1, 2, 3, 4], f_nwk_s_int_key: vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16], s_nwk_s_int_key: vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16], @@ -3089,6 +3117,7 @@ async fn test_lorawan_10_device_queue() { let tests = vec![ Test { name: "unconfirmed uplink + one unconfirmed downlink payload in queue".into(), + dev_eui: dev.dev_eui, device_queue_items: vec![device_queue::DeviceQueueItem { id: Uuid::nil(), dev_eui: dev.dev_eui.clone(), @@ -3166,6 +3195,7 @@ async fn test_lorawan_10_device_queue() { }, Test { name: "unconfirmed uplink + two unconfirmed downlinks payload in queue".into(), + dev_eui: dev.dev_eui, device_queue_items: vec![ device_queue::DeviceQueueItem { id: Uuid::new_v4(), @@ -3256,6 +3286,7 @@ async fn test_lorawan_10_device_queue() { }, Test { name: "unconfirmed uplink + one confirmed downlink payload in queue".into(), + dev_eui: dev.dev_eui, device_queue_items: vec![device_queue::DeviceQueueItem { id: Uuid::nil(), dev_eui: dev.dev_eui.clone(), @@ -3334,6 +3365,7 @@ async fn test_lorawan_10_device_queue() { }, Test { name: "unconfirmed uplink data + downlink payload which exceeds the max payload size (for dr 0)".into(), + dev_eui: dev.dev_eui, device_queue_items: vec![device_queue::DeviceQueueItem { id: Uuid::nil(), dev_eui: dev.dev_eui.clone(), @@ -3370,6 +3402,7 @@ async fn test_lorawan_10_device_queue() { }, Test { name: "unconfirmed uplink data + one unconfirmed downlink payload in queue (exactly max size for dr 0) + one mac command".into(), + dev_eui: dev.dev_eui, device_queue_items: vec![device_queue::DeviceQueueItem { id: Uuid::nil(), dev_eui: dev.dev_eui.clone(), @@ -3515,6 +3548,7 @@ async fn test_lorawan_11_device_queue() { device_profile_id: dp.id.clone(), dev_eui: EUI64::from_be_bytes([2, 2, 3, 4, 5, 6, 7, 8]), enabled_class: DeviceClass::A, + dev_addr: Some(DevAddr::from_be_bytes([1, 2, 3, 4])), ..Default::default() }) .await @@ -3539,9 +3573,7 @@ async fn test_lorawan_11_device_queue() { uplink::helpers::set_uplink_modulation(&"eu868", &mut tx_info, 0).unwrap(); let ds = internal::DeviceSession { - dev_eui: vec![2, 2, 3, 4, 5, 6, 7, 8], mac_version: common::MacVersion::Lorawan110.into(), - join_eui: vec![8, 7, 6, 5, 4, 3, 2, 1], dev_addr: vec![1, 2, 3, 4], f_nwk_s_int_key: vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16], s_nwk_s_int_key: vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16], @@ -3563,6 +3595,7 @@ async fn test_lorawan_11_device_queue() { let tests = vec![ Test { name: "unconfirmed uplink + one unconfirmed downlink payload in queue".into(), + dev_eui: dev.dev_eui, device_queue_items: vec![device_queue::DeviceQueueItem { id: Uuid::nil(), dev_eui: dev.dev_eui.clone(), @@ -3640,6 +3673,7 @@ async fn test_lorawan_11_device_queue() { }, Test { name: "unconfirmed uplink + two unconfirmed downlinks payload in queue".into(), + dev_eui: dev.dev_eui, device_queue_items: vec![ device_queue::DeviceQueueItem { id: Uuid::new_v4(), @@ -3730,6 +3764,7 @@ async fn test_lorawan_11_device_queue() { }, Test { name: "unconfirmed uplink + one confirmed downlink payload in queue".into(), + dev_eui: dev.dev_eui, device_queue_items: vec![device_queue::DeviceQueueItem { id: Uuid::nil(), dev_eui: dev.dev_eui.clone(), @@ -3808,6 +3843,7 @@ async fn test_lorawan_11_device_queue() { }, Test { name: "unconfirmed uplink data + downlink payload which exceeds the max payload size (for dr 0)".into(), + dev_eui: dev.dev_eui, device_queue_items: vec![device_queue::DeviceQueueItem { id: Uuid::nil(), dev_eui: dev.dev_eui.clone(), @@ -3844,6 +3880,7 @@ async fn test_lorawan_11_device_queue() { }, Test { name: "unconfirmed uplink data + one unconfirmed downlink payload in queue (exactly max size for dr 0) + one mac command".into(), + dev_eui: dev.dev_eui, device_queue_items: vec![device_queue::DeviceQueueItem { id: Uuid::nil(), dev_eui: dev.dev_eui.clone(), @@ -3992,6 +4029,7 @@ async fn test_lorawan_10_adr() { device_profile_id: dp.id.clone(), dev_eui: EUI64::from_be_bytes([2, 2, 3, 4, 5, 6, 7, 8]), enabled_class: DeviceClass::A, + dev_addr: Some(DevAddr::from_be_bytes([1, 2, 3, 4])), ..Default::default() }) .await @@ -4016,9 +4054,7 @@ async fn test_lorawan_10_adr() { uplink::helpers::set_uplink_modulation(&"eu868", &mut tx_info, 0).unwrap(); let ds = internal::DeviceSession { - dev_eui: vec![2, 2, 3, 4, 5, 6, 7, 8], mac_version: common::MacVersion::Lorawan104.into(), - join_eui: vec![8, 7, 6, 5, 4, 3, 2, 1], dev_addr: vec![1, 2, 3, 4], f_nwk_s_int_key: vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16], s_nwk_s_int_key: vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16], @@ -4057,6 +4093,7 @@ async fn test_lorawan_10_adr() { let tests = vec![ Test { name: "adr triggered".into(), + dev_eui: dev.dev_eui, device_queue_items: vec![], before_func: None, after_func: None, @@ -4160,6 +4197,7 @@ async fn test_lorawan_10_adr() { }, Test { name: "device has adr disabled".into(), + dev_eui: dev.dev_eui, device_queue_items: vec![], before_func: None, after_func: None, @@ -4193,6 +4231,7 @@ async fn test_lorawan_10_adr() { }, Test { name: "acknowledgement of pending adr request".into(), + dev_eui: dev.dev_eui, device_queue_items: vec![], before_func: Some(Box::new(move || { let dev_eui = dev.dev_eui.clone(); @@ -4260,6 +4299,7 @@ async fn test_lorawan_10_adr() { }, Test { name: "negative acknowledgement of pending adr request".into(), + dev_eui: dev.dev_eui, device_queue_items: vec![], before_func: Some(Box::new(move || { let dev_eui = dev.dev_eui.clone(); @@ -4327,6 +4367,7 @@ async fn test_lorawan_10_adr() { }, Test { name: "adr ack requested".into(), + dev_eui: dev.dev_eui, device_queue_items: vec![], before_func: None, after_func: None, @@ -4403,6 +4444,7 @@ async fn test_lorawan_10_adr() { }, Test { name: "channel re-configuration triggered".into(), + dev_eui: dev.dev_eui, device_queue_items: vec![], before_func: None, after_func: None, @@ -4502,6 +4544,7 @@ async fn test_lorawan_10_adr() { }, Test { name: "new channel re-configuration ack-ed".into(), + dev_eui: dev.dev_eui, device_queue_items: vec![], before_func: Some(Box::new(move || { let dev_eui = dev.dev_eui.clone(); @@ -4567,6 +4610,7 @@ async fn test_lorawan_10_adr() { }, Test { name: "new channel re-configuration not ack-ed".into(), + dev_eui: dev.dev_eui, device_queue_items: vec![], before_func: Some(Box::new(move || { let dev_eui = dev.dev_eui.clone(); @@ -4634,6 +4678,7 @@ async fn test_lorawan_10_adr() { }, Test { name: "channel re-configuration and adr triggered".into(), + dev_eui: dev.dev_eui, device_queue_items: vec![], before_func: None, after_func: None, @@ -4738,6 +4783,7 @@ async fn test_lorawan_10_adr() { // adr backoff triggered Test { name: "adr backoff triggered".into(), + dev_eui: dev.dev_eui, device_queue_items: vec![], before_func: None, after_func: None, @@ -4835,6 +4881,7 @@ async fn test_lorawan_10_device_status_request() { device_profile_id: dp.id.clone(), dev_eui: EUI64::from_be_bytes([2, 2, 3, 4, 5, 6, 7, 8]), enabled_class: DeviceClass::A, + dev_addr: Some(DevAddr::from_be_bytes([1, 2, 3, 4])), ..Default::default() }) .await @@ -4859,9 +4906,7 @@ async fn test_lorawan_10_device_status_request() { uplink::helpers::set_uplink_modulation(&"eu868", &mut tx_info, 0).unwrap(); let ds = internal::DeviceSession { - dev_eui: vec![2, 2, 3, 4, 5, 6, 7, 8], mac_version: common::MacVersion::Lorawan104.into(), - join_eui: vec![8, 7, 6, 5, 4, 3, 2, 1], dev_addr: vec![1, 2, 3, 4], f_nwk_s_int_key: vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16], s_nwk_s_int_key: vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16], @@ -4887,6 +4932,7 @@ async fn test_lorawan_10_device_status_request() { let tests = vec![ Test { name: "must request device-status".into(), + dev_eui: dev.dev_eui, device_queue_items: vec![], before_func: None, after_func: None, @@ -4963,6 +5009,7 @@ async fn test_lorawan_10_device_status_request() { }, Test { name: "interval has not yet expired".into(), + dev_eui: dev.dev_eui, device_queue_items: vec![], before_func: None, after_func: None, @@ -4993,6 +5040,7 @@ async fn test_lorawan_10_device_status_request() { // reporting device-status Test { name: "reporting device-status".into(), + dev_eui: dev.dev_eui, device_queue_items: vec![], before_func: None, after_func: None, @@ -5098,6 +5146,7 @@ async fn test_lorawan_11_receive_window_selection() { device_profile_id: dp.id.clone(), dev_eui: EUI64::from_be_bytes([2, 2, 3, 4, 5, 6, 7, 8]), enabled_class: DeviceClass::A, + dev_addr: Some(DevAddr::from_be_bytes([1, 2, 3, 4])), ..Default::default() }) .await @@ -5128,9 +5177,7 @@ async fn test_lorawan_11_receive_window_selection() { uplink::helpers::set_uplink_modulation(&"eu868", &mut tx_info_lr_fhss, 8).unwrap(); let ds = internal::DeviceSession { - dev_eui: vec![2, 2, 3, 4, 5, 6, 7, 8], mac_version: common::MacVersion::Lorawan110.into(), - join_eui: vec![8, 7, 6, 5, 4, 3, 2, 1], dev_addr: vec![1, 2, 3, 4], f_nwk_s_int_key: vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16], s_nwk_s_int_key: vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16], @@ -5155,6 +5202,7 @@ async fn test_lorawan_11_receive_window_selection() { run_test(&Test { name: "unconfirmed uplink with payload (rx1)".into(), + dev_eui: dev.dev_eui, device_queue_items: vec![device_queue::DeviceQueueItem { id: Uuid::nil(), dev_eui: dev.dev_eui.clone(), @@ -5224,6 +5272,7 @@ async fn test_lorawan_11_receive_window_selection() { run_test(&Test { name: "unconfirmed uplink with payload (rx2)".into(), + dev_eui: dev.dev_eui, device_queue_items: vec![device_queue::DeviceQueueItem { id: Uuid::nil(), dev_eui: dev.dev_eui.clone(), @@ -5293,6 +5342,7 @@ async fn test_lorawan_11_receive_window_selection() { run_test(&Test { name: "unconfirmed uplink with payload (rx1 + rx2)".into(), + dev_eui: dev.dev_eui, device_queue_items: vec![device_queue::DeviceQueueItem { id: Uuid::nil(), dev_eui: dev.dev_eui.clone(), @@ -5391,6 +5441,7 @@ async fn test_lorawan_11_receive_window_selection() { run_test(&Test { name: "unconfirmed uplink with payload (rx1, payload exceeds rx2 limit)".into(), + dev_eui: dev.dev_eui, device_queue_items: vec![device_queue::DeviceQueueItem { id: Uuid::nil(), dev_eui: dev.dev_eui.clone(), @@ -5497,7 +5548,6 @@ async fn test_lorawan_11_receive_window_selection() { async fn run_test(t: &Test) { println!("> {}", t.name); - reset_redis().await.unwrap(); integration::set_mock().await; @@ -5506,12 +5556,16 @@ async fn run_test(t: &Test) { integration::mock::reset().await; gateway_backend::mock::reset().await; - if let Some(ds) = &t.device_session { - let _ = device_session::save(&ds).await.unwrap(); - - let dev_eui = EUI64::from_slice(&ds.dev_eui).unwrap(); - device_queue::flush_for_dev_eui(&dev_eui).await.unwrap(); - } + device_queue::flush_for_dev_eui(&t.dev_eui).await.unwrap(); + device::partial_update( + t.dev_eui, + &device::DeviceChangeset { + device_session: Some(t.device_session.clone()), + ..Default::default() + }, + ) + .await + .unwrap(); if let Some(f) = &t.before_func { f().await; diff --git a/chirpstack/src/test/class_b_test.rs b/chirpstack/src/test/class_b_test.rs index a7ade2dc..d3eb38b0 100644 --- a/chirpstack/src/test/class_b_test.rs +++ b/chirpstack/src/test/class_b_test.rs @@ -5,7 +5,7 @@ use crate::gpstime::ToGpsTime; use crate::storage::{ application, device::{self, DeviceClass}, - device_gateway, device_profile, device_queue, device_session, gateway, reset_redis, tenant, + device_gateway, device_profile, device_queue, gateway, reset_redis, tenant, }; use crate::{ config, downlink, downlink::classb, gateway::backend as gateway_backend, integration, test, @@ -16,6 +16,7 @@ use lrwn::{DevAddr, EUI64}; struct UplinkTest { name: String, + dev_eui: EUI64, device_queue_items: Vec, device_session: Option, tx_info: gw::UplinkTxInfo, @@ -26,6 +27,7 @@ struct UplinkTest { struct DownlinkTest { name: String, + dev_eui: EUI64, device_queue_items: Vec, device_session: Option, device_gateway_rx_info: Option, @@ -80,6 +82,7 @@ async fn test_uplink() { device_profile_id: dp.id.clone(), dev_eui: EUI64::from_be_bytes([2, 2, 3, 4, 5, 6, 7, 8]), enabled_class: DeviceClass::A, + dev_addr: Some(DevAddr::from_be_bytes([1, 2, 3, 4])), ..Default::default() }) .await @@ -103,9 +106,7 @@ async fn test_uplink() { uplink::helpers::set_uplink_modulation(&"eu868", &mut tx_info, 0).unwrap(); let ds = internal::DeviceSession { - dev_eui: vec![2, 2, 3, 4, 5, 6, 7, 8], mac_version: common::MacVersion::Lorawan104.into(), - join_eui: vec![8, 7, 6, 5, 4, 3, 2, 1], dev_addr: vec![1, 2, 3, 4], f_nwk_s_int_key: vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16], s_nwk_s_int_key: vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16], @@ -130,6 +131,7 @@ async fn test_uplink() { // trigger beacon locked run_uplink_test(&UplinkTest { name: "trigger beacon locked".into(), + dev_eui: dev.dev_eui, device_queue_items: vec![], device_session: Some(ds.clone()), tx_info: tx_info.clone(), @@ -164,6 +166,7 @@ async fn test_uplink() { // trigger beacon unlocked run_uplink_test(&UplinkTest { name: "trigger beacon locked".into(), + dev_eui: dev.dev_eui, device_queue_items: vec![], device_session: Some(ds.clone()), tx_info: tx_info.clone(), @@ -244,15 +247,14 @@ async fn test_downlink_scheduler() { device_profile_id: dp.id.clone(), dev_eui: EUI64::from_be_bytes([2, 2, 3, 4, 5, 6, 7, 8]), enabled_class: DeviceClass::B, + dev_addr: Some(DevAddr::from_be_bytes([1, 2, 3, 4])), ..Default::default() }) .await .unwrap(); let ds = internal::DeviceSession { - dev_eui: vec![2, 2, 3, 4, 5, 6, 7, 8], mac_version: common::MacVersion::Lorawan104.into(), - join_eui: vec![8, 7, 6, 5, 4, 3, 2, 1], dev_addr: vec![1, 2, 3, 4], f_nwk_s_int_key: vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16], s_nwk_s_int_key: vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16], @@ -291,6 +293,7 @@ async fn test_downlink_scheduler() { run_scheduler_test(&DownlinkTest { name: "class-b downlink".into(), + dev_eui: dev.dev_eui, device_queue_items: vec![device_queue::DeviceQueueItem { id: Uuid::nil(), dev_eui: dev.dev_eui.clone(), @@ -342,6 +345,7 @@ async fn test_downlink_scheduler() { run_scheduler_test(&DownlinkTest { name: "scheduler_run_after has not yet expired".into(), + dev_eui: dev.dev_eui, device_queue_items: vec![device_queue::DeviceQueueItem { id: Uuid::nil(), dev_eui: dev.dev_eui.clone(), @@ -356,12 +360,19 @@ async fn test_downlink_scheduler() { .await; // remove the schedule run after - device::set_scheduler_run_after(&dev.dev_eui.clone(), None) - .await - .unwrap(); + device::partial_update( + dev.dev_eui, + &device::DeviceChangeset { + scheduler_run_after: Some(None), + ..Default::default() + }, + ) + .await + .unwrap(); run_scheduler_test(&DownlinkTest { name: "class-b downlink with more data".into(), + dev_eui: dev.dev_eui, device_queue_items: vec![ device_queue::DeviceQueueItem { id: Uuid::nil(), @@ -434,12 +445,16 @@ async fn run_uplink_test(t: &UplinkTest) { integration::mock::reset().await; gateway_backend::mock::reset().await; - if let Some(ds) = &t.device_session { - let _ = device_session::save(&ds).await.unwrap(); - - let dev_eui = EUI64::from_slice(&ds.dev_eui).unwrap(); - device_queue::flush_for_dev_eui(&dev_eui).await.unwrap(); - } + device_queue::flush_for_dev_eui(&t.dev_eui).await.unwrap(); + device::partial_update( + t.dev_eui, + &device::DeviceChangeset { + device_session: Some(t.device_session.clone()), + ..Default::default() + }, + ) + .await + .unwrap(); for qi in &t.device_queue_items { let _ = device_queue::enqueue_item(qi.clone()).await.unwrap(); @@ -471,13 +486,16 @@ async fn run_scheduler_test(t: &DownlinkTest) { integration::mock::reset().await; gateway_backend::mock::reset().await; - - if let Some(ds) = &t.device_session { - let _ = device_session::save(&ds).await.unwrap(); - - let dev_eui = EUI64::from_slice(&ds.dev_eui).unwrap(); - device_queue::flush_for_dev_eui(&dev_eui).await.unwrap(); - } + device_queue::flush_for_dev_eui(&t.dev_eui).await.unwrap(); + device::partial_update( + t.dev_eui, + &device::DeviceChangeset { + device_session: Some(t.device_session.clone()), + ..Default::default() + }, + ) + .await + .unwrap(); if let Some(rx_info) = &t.device_gateway_rx_info { let _ = device_gateway::save_rx_info(rx_info).await.unwrap(); diff --git a/chirpstack/src/test/class_c_test.rs b/chirpstack/src/test/class_c_test.rs index 878b2dba..89335a1d 100644 --- a/chirpstack/src/test/class_c_test.rs +++ b/chirpstack/src/test/class_c_test.rs @@ -4,14 +4,15 @@ use super::assert; use crate::storage::{ application, device::{self, DeviceClass}, - device_gateway, device_profile, device_queue, device_session, gateway, reset_redis, tenant, + device_gateway, device_profile, device_queue, gateway, reset_redis, tenant, }; use crate::{downlink, gateway::backend as gateway_backend, integration, test}; use chirpstack_api::{common, gw, internal}; -use lrwn::EUI64; +use lrwn::{DevAddr, EUI64}; struct DownlinkTest { name: String, + dev_eui: EUI64, device_queue_items: Vec, device_session: Option, device_gateway_rx_info: Option, @@ -71,6 +72,7 @@ async fn test_downlink_scheduler() { device_profile_id: dp.id.clone(), dev_eui: EUI64::from_be_bytes([2, 2, 3, 4, 5, 6, 7, 8]), enabled_class: DeviceClass::C, + dev_addr: Some(DevAddr::from_be_bytes([1, 2, 3, 4])), ..Default::default() }) .await @@ -86,10 +88,8 @@ async fn test_downlink_scheduler() { }; let ds = internal::DeviceSession { - dev_eui: vec![2, 2, 3, 4, 5, 6, 7, 8], - mac_version: common::MacVersion::Lorawan104.into(), - join_eui: vec![8, 7, 6, 5, 4, 3, 2, 1], dev_addr: vec![1, 2, 3, 4], + mac_version: common::MacVersion::Lorawan104.into(), f_nwk_s_int_key: vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16], s_nwk_s_int_key: vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16], nwk_s_enc_key: vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16], @@ -112,6 +112,7 @@ async fn test_downlink_scheduler() { run_scheduler_test(&DownlinkTest { name: "device has not yet sent an uplink".into(), + dev_eui: dev.dev_eui, device_queue_items: vec![device_queue::DeviceQueueItem { id: Uuid::nil(), dev_eui: dev.dev_eui.clone(), @@ -126,12 +127,19 @@ async fn test_downlink_scheduler() { .await; // remove the schedule run after - device::set_scheduler_run_after(&dev.dev_eui.clone(), None) - .await - .unwrap(); + device::partial_update( + dev.dev_eui, + &device::DeviceChangeset { + scheduler_run_after: Some(None), + ..Default::default() + }, + ) + .await + .unwrap(); run_scheduler_test(&DownlinkTest { name: "unconfirmed data".into(), + dev_eui: dev.dev_eui, device_queue_items: vec![device_queue::DeviceQueueItem { id: Uuid::nil(), dev_eui: dev.dev_eui.clone(), @@ -178,6 +186,7 @@ async fn test_downlink_scheduler() { run_scheduler_test(&DownlinkTest { name: "scheduler_run_after has not yet expired".into(), + dev_eui: dev.dev_eui, device_queue_items: vec![device_queue::DeviceQueueItem { id: Uuid::nil(), dev_eui: dev.dev_eui.clone(), @@ -192,12 +201,19 @@ async fn test_downlink_scheduler() { .await; // remove the schedule run after - device::set_scheduler_run_after(&dev.dev_eui.clone(), None) - .await - .unwrap(); + device::partial_update( + dev.dev_eui, + &device::DeviceChangeset { + scheduler_run_after: Some(None), + ..Default::default() + }, + ) + .await + .unwrap(); run_scheduler_test(&DownlinkTest { name: "unconfirmed data".into(), + dev_eui: dev.dev_eui, device_queue_items: vec![device_queue::DeviceQueueItem { id: Uuid::nil(), dev_eui: dev.dev_eui.clone(), @@ -246,12 +262,19 @@ async fn test_downlink_scheduler() { .await; // remove the schedule run after - device::set_scheduler_run_after(&dev.dev_eui.clone(), None) - .await - .unwrap(); + device::partial_update( + dev.dev_eui, + &device::DeviceChangeset { + scheduler_run_after: Some(None), + ..Default::default() + }, + ) + .await + .unwrap(); run_scheduler_test(&DownlinkTest { name: "unconfirmed data".into(), + dev_eui: dev.dev_eui, device_queue_items: vec![device_queue::DeviceQueueItem { id: Uuid::nil(), dev_eui: dev.dev_eui.clone(), @@ -276,13 +299,16 @@ async fn run_scheduler_test(t: &DownlinkTest) { integration::mock::reset().await; gateway_backend::mock::reset().await; - - if let Some(ds) = &t.device_session { - let _ = device_session::save(&ds).await.unwrap(); - - let dev_eui = EUI64::from_slice(&ds.dev_eui).unwrap(); - device_queue::flush_for_dev_eui(&dev_eui).await.unwrap(); - } + device_queue::flush_for_dev_eui(&t.dev_eui).await.unwrap(); + device::partial_update( + t.dev_eui, + &device::DeviceChangeset { + device_session: Some(t.device_session.clone()), + ..Default::default() + }, + ) + .await + .unwrap(); if let Some(rx_info) = &t.device_gateway_rx_info { let _ = device_gateway::save_rx_info(rx_info).await.unwrap(); diff --git a/chirpstack/src/test/otaa_js_test.rs b/chirpstack/src/test/otaa_js_test.rs index d9886786..533053d9 100644 --- a/chirpstack/src/test/otaa_js_test.rs +++ b/chirpstack/src/test/otaa_js_test.rs @@ -156,8 +156,6 @@ async fn test_js() { dev.dev_eui.clone(), internal::DeviceSession { dev_addr: vec![1, 2, 3, 4], - dev_eui: vec![1, 2, 3, 4, 5, 6, 7, 8], - join_eui: vec![1, 2, 3, 4, 5, 6, 7, 8], mac_version: common::MacVersion::Lorawan103.into(), f_nwk_s_int_key: vec![1, 2, 3, 4, 5, 6, 7, 8, 1, 2, 3, 4, 5, 6, 7, 8], s_nwk_s_int_key: vec![1, 2, 3, 4, 5, 6, 7, 8, 1, 2, 3, 4, 5, 6, 7, 8], @@ -225,8 +223,6 @@ async fn test_js() { dev.dev_eui.clone(), internal::DeviceSession { dev_addr: vec![1, 2, 3, 4], - dev_eui: vec![1, 2, 3, 4, 5, 6, 7, 8], - join_eui: vec![1, 2, 3, 4, 5, 6, 7, 8], mac_version: common::MacVersion::Lorawan103.into(), f_nwk_s_int_key: vec![1, 2, 3, 4, 5, 6, 7, 8, 1, 2, 3, 4, 5, 6, 7, 8], s_nwk_s_int_key: vec![1, 2, 3, 4, 5, 6, 7, 8, 1, 2, 3, 4, 5, 6, 7, 8], @@ -297,8 +293,6 @@ async fn test_js() { dev.dev_eui.clone(), internal::DeviceSession { dev_addr: vec![1, 2, 3, 4], - dev_eui: vec![1, 2, 3, 4, 5, 6, 7, 8], - join_eui: vec![1, 2, 3, 4, 5, 6, 7, 8], mac_version: common::MacVersion::Lorawan103.into(), f_nwk_s_int_key: vec![1, 2, 3, 4, 5, 6, 7, 8, 1, 2, 3, 4, 5, 6, 7, 8], s_nwk_s_int_key: vec![1, 2, 3, 4, 5, 6, 7, 8, 1, 2, 3, 4, 5, 6, 7, 8], diff --git a/chirpstack/src/test/otaa_test.rs b/chirpstack/src/test/otaa_test.rs index 55f2d5fd..058df919 100644 --- a/chirpstack/src/test/otaa_test.rs +++ b/chirpstack/src/test/otaa_test.rs @@ -8,7 +8,7 @@ use super::assert; use crate::storage::{ application, device::{self, DeviceClass}, - device_keys, device_profile, gateway, reset_redis, tenant, + device_keys, device_profile, gateway, tenant, }; use crate::{config, gateway::backend as gateway_backend, integration, region, test, uplink}; use chirpstack_api::{common, gw, internal, stream}; @@ -19,6 +19,7 @@ type Function = Box Pin>>>; struct Test { name: String, + dev_eui: EUI64, before_func: Option, after_func: Option, tx_info: gw::UplinkTxInfo, @@ -153,6 +154,7 @@ async fn test_gateway_filtering() { let tests = vec![ Test { name: "private gateway of same tenant".into(), + dev_eui: dev.dev_eui, before_func: Some(Box::new(move || { let dev_eui = dev.dev_eui.clone(); Box::pin(async move { @@ -168,8 +170,6 @@ async fn test_gateway_filtering() { dev.dev_eui.clone(), internal::DeviceSession { dev_addr: vec![1, 2, 3, 4], - dev_eui: vec![2, 2, 3, 4, 5, 6, 7, 8], - join_eui: vec![1, 2, 3, 4, 5, 6, 7, 8], mac_version: common::MacVersion::Lorawan102.into(), f_nwk_s_int_key: vec![ 128, 47, 168, 41, 62, 215, 212, 79, 19, 83, 183, 201, 43, 169, 125, 200, @@ -198,6 +198,7 @@ async fn test_gateway_filtering() { }, Test { name: "private gateway other tenant".into(), + dev_eui: dev.dev_eui, before_func: Some(Box::new(move || { let dev_eui = dev.dev_eui.clone(); Box::pin(async move { @@ -375,6 +376,7 @@ async fn test_lorawan_10() { let tests = vec![ Test { name: "dev-nonce already used".into(), + dev_eui: dev.dev_eui, before_func: None, after_func: None, rx_info: rx_info.clone(), @@ -387,6 +389,7 @@ async fn test_lorawan_10() { }, Test { name: "join-request accepted".into(), + dev_eui: dev.dev_eui, before_func: Some(Box::new(move || { let dev_eui = dev.dev_eui.clone(); Box::pin(async move { @@ -407,8 +410,6 @@ async fn test_lorawan_10() { dev.dev_eui.clone(), internal::DeviceSession { dev_addr: vec![1, 2, 3, 4], - dev_eui: vec![2, 2, 3, 4, 5, 6, 7, 8], - join_eui: vec![1, 2, 3, 4, 5, 6, 7, 8], mac_version: common::MacVersion::Lorawan102.into(), f_nwk_s_int_key: vec![ 128, 47, 168, 41, 62, 215, 212, 79, 19, 83, 183, 201, 43, 169, 125, 200, @@ -576,6 +577,7 @@ async fn test_lorawan_10() { }, Test { name: "join-request accepted + skip fcnt check".into(), + dev_eui: dev.dev_eui, before_func: Some(Box::new(move || { let dev_eui = dev.dev_eui.clone(); Box::pin(async move { @@ -602,8 +604,6 @@ async fn test_lorawan_10() { dev.dev_eui.clone(), internal::DeviceSession { dev_addr: vec![1, 2, 3, 4], - dev_eui: vec![2, 2, 3, 4, 5, 6, 7, 8], - join_eui: vec![1, 2, 3, 4, 5, 6, 7, 8], mac_version: common::MacVersion::Lorawan102.into(), f_nwk_s_int_key: vec![ 128, 47, 168, 41, 62, 215, 212, 79, 19, 83, 183, 201, 43, 169, 125, 200, @@ -633,6 +633,7 @@ async fn test_lorawan_10() { }, Test { name: "join-request accepted + cflist".into(), + dev_eui: dev.dev_eui, before_func: Some(Box::new(move || { let dev_eui = dev.dev_eui.clone(); Box::pin(async move { @@ -649,8 +650,6 @@ async fn test_lorawan_10() { dev.dev_eui.clone(), internal::DeviceSession { dev_addr: vec![1, 2, 3, 4], - dev_eui: vec![2, 2, 3, 4, 5, 6, 7, 8], - join_eui: vec![1, 2, 3, 4, 5, 6, 7, 8], mac_version: common::MacVersion::Lorawan102.into(), f_nwk_s_int_key: vec![ 128, 47, 168, 41, 62, 215, 212, 79, 19, 83, 183, 201, 43, 169, 125, 200, @@ -786,6 +785,7 @@ async fn test_lorawan_10() { }, Test { name: "join-request accepted + class-b supported".into(), + dev_eui: dev.dev_eui, before_func: Some(Box::new(move || { let dev_eui = dev.dev_eui.clone(); let dp_id = dp.id.clone(); @@ -813,6 +813,7 @@ async fn test_lorawan_10() { }, Test { name: "join-request accepted + class-c supported".into(), + dev_eui: dev.dev_eui, before_func: Some(Box::new(move || { let dev_eui = dev.dev_eui.clone(); let dp_id = dp.id.clone(); @@ -840,6 +841,7 @@ async fn test_lorawan_10() { }, Test { name: "device disabled".into(), + dev_eui: dev.dev_eui, before_func: Some(Box::new(move || { let dev_eui = dev.dev_eui.clone(); Box::pin(async move { @@ -997,6 +999,7 @@ async fn test_lorawan_11() { let tests = vec![ Test { name: "dev-nonce already used".into(), + dev_eui: dev.dev_eui, before_func: None, after_func: None, rx_info: rx_info.clone(), @@ -1009,6 +1012,7 @@ async fn test_lorawan_11() { }, Test { name: "join-request accepted".into(), + dev_eui: dev.dev_eui, before_func: Some(Box::new(move || { let dev_eui = dev.dev_eui.clone(); Box::pin(async move { @@ -1025,8 +1029,6 @@ async fn test_lorawan_11() { dev.dev_eui.clone(), internal::DeviceSession { dev_addr: vec![1, 2, 3, 4], - dev_eui: vec![2, 2, 3, 4, 5, 6, 7, 8], - join_eui: vec![1, 2, 3, 4, 5, 6, 7, 8], mac_version: common::MacVersion::Lorawan110.into(), f_nwk_s_int_key: vec![ 98, 222, 198, 158, 98, 155, 205, 235, 143, 171, 203, 19, 221, 9, 1, 231, @@ -1189,6 +1191,7 @@ async fn test_lorawan_11() { }, Test { name: "join-request accepted + class-c supported".into(), + dev_eui: dev.dev_eui, before_func: Some(Box::new(move || { let dev_eui = dev.dev_eui.clone(); let dp_id = dp.id.clone(); @@ -1224,8 +1227,6 @@ async fn test_lorawan_11() { async fn run_test(t: &Test) { println!("> {}", t.name); - reset_redis().await.unwrap(); - let mut conf: config::Configuration = (*config::get()).clone(); for f in &t.extra_uplink_channels { conf.regions[0] @@ -1246,6 +1247,17 @@ async fn run_test(t: &Test) { integration::mock::reset().await; gateway_backend::mock::reset().await; + device::partial_update( + t.dev_eui, + &device::DeviceChangeset { + dev_addr: Some(None), + device_session: Some(None), + ..Default::default() + }, + ) + .await + .unwrap(); + if let Some(f) = &t.before_func { f().await; } diff --git a/chirpstack/src/test/relay_class_a_test.rs b/chirpstack/src/test/relay_class_a_test.rs index 27d5590d..29908003 100644 --- a/chirpstack/src/test/relay_class_a_test.rs +++ b/chirpstack/src/test/relay_class_a_test.rs @@ -6,14 +6,16 @@ use super::assert; use crate::storage::{ application, device::{self, DeviceClass}, - device_profile, device_queue, device_session, gateway, reset_redis, tenant, + device_profile, device_queue, gateway, reset_redis, tenant, }; use crate::{gateway::backend as gateway_backend, integration, test, uplink}; use chirpstack_api::{common, gw, integration as integration_pb, internal}; -use lrwn::{AES128Key, EUI64}; +use lrwn::{AES128Key, DevAddr, EUI64}; struct Test { name: String, + dev_eui_relay: EUI64, + dev_eui_relay_ed: EUI64, device_queue_items_relay_ed: Vec, device_session_relay: Option, device_session_relay_ed: Option, @@ -85,6 +87,7 @@ async fn test_lorawan_10() { device_profile_id: dp_relay.id, dev_eui: EUI64::from_be_bytes([1, 1, 1, 1, 1, 1, 1, 1]), enabled_class: DeviceClass::A, + dev_addr: Some(DevAddr::from_be_bytes([1, 1, 1, 1])), ..Default::default() }) .await @@ -96,6 +99,7 @@ async fn test_lorawan_10() { device_profile_id: dp_relay_ed.id, dev_eui: EUI64::from_be_bytes([2, 2, 2, 2, 2, 2, 2, 2]), enabled_class: DeviceClass::A, + dev_addr: Some(DevAddr::from_be_bytes([2, 2, 2, 2])), ..Default::default() }) .await @@ -120,7 +124,6 @@ async fn test_lorawan_10() { uplink::helpers::set_uplink_modulation(&"eu868", &mut tx_info, 5).unwrap(); let ds_relay = internal::DeviceSession { - dev_eui: dev_relay.dev_eui.to_vec(), mac_version: common::MacVersion::Lorawan104.into(), dev_addr: vec![1, 1, 1, 1], f_nwk_s_int_key: vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16], @@ -140,7 +143,6 @@ async fn test_lorawan_10() { }; let ds_relay_ed = internal::DeviceSession { - dev_eui: dev_relay_ed.dev_eui.to_vec(), mac_version: common::MacVersion::Lorawan104.into(), dev_addr: vec![2, 2, 2, 2], f_nwk_s_int_key: vec![2, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16], @@ -439,6 +441,8 @@ async fn test_lorawan_10() { let tests = vec![ Test { name: "relayed unconfirmed uplink".into(), + dev_eui_relay: dev_relay.dev_eui, + dev_eui_relay_ed: dev_relay_ed.dev_eui, device_queue_items_relay_ed: vec![], device_session_relay: Some(ds_relay.clone()), device_session_relay_ed: Some(ds_relay_ed.clone()), @@ -503,6 +507,8 @@ async fn test_lorawan_10() { }, Test { name: "relayed confirmed uplink".into(), + dev_eui_relay: dev_relay.dev_eui, + dev_eui_relay_ed: dev_relay_ed.dev_eui, device_queue_items_relay_ed: vec![], device_session_relay: Some(ds_relay.clone()), device_session_relay_ed: Some(ds_relay_ed.clone()), @@ -627,6 +633,8 @@ async fn test_lorawan_10() { }, Test { name: "relayed unconfirmed uplink + adr_ack_req".into(), + dev_eui_relay: dev_relay.dev_eui, + dev_eui_relay_ed: dev_relay_ed.dev_eui, device_queue_items_relay_ed: vec![], device_session_relay: Some(ds_relay.clone()), device_session_relay_ed: Some(ds_relay_ed.clone()), @@ -765,20 +773,30 @@ async fn run_test(t: &Test) { integration::mock::reset().await; gateway_backend::mock::reset().await; - - if let Some(ds) = &t.device_session_relay { - let _ = device_session::save(&ds).await.unwrap(); - - let dev_eui = EUI64::from_slice(&ds.dev_eui).unwrap(); - device_queue::flush_for_dev_eui(&dev_eui).await.unwrap(); - } - - if let Some(ds) = &t.device_session_relay_ed { - let _ = device_session::save(&ds).await.unwrap(); - - let dev_eui = EUI64::from_slice(&ds.dev_eui).unwrap(); - device_queue::flush_for_dev_eui(&dev_eui).await.unwrap(); - } + device_queue::flush_for_dev_eui(&t.dev_eui_relay) + .await + .unwrap(); + device_queue::flush_for_dev_eui(&t.dev_eui_relay_ed) + .await + .unwrap(); + device::partial_update( + t.dev_eui_relay, + &device::DeviceChangeset { + device_session: Some(t.device_session_relay.clone()), + ..Default::default() + }, + ) + .await + .unwrap(); + device::partial_update( + t.dev_eui_relay_ed, + &device::DeviceChangeset { + device_session: Some(t.device_session_relay_ed.clone()), + ..Default::default() + }, + ) + .await + .unwrap(); for qi in &t.device_queue_items_relay_ed { let _ = device_queue::enqueue_item(qi.clone()).await.unwrap(); diff --git a/chirpstack/src/test/relay_otaa_test.rs b/chirpstack/src/test/relay_otaa_test.rs index 419b1945..aade0962 100644 --- a/chirpstack/src/test/relay_otaa_test.rs +++ b/chirpstack/src/test/relay_otaa_test.rs @@ -6,11 +6,11 @@ use super::assert; use crate::storage::{ application, device::{self, DeviceClass}, - device_keys, device_profile, device_session, gateway, tenant, + device_keys, device_profile, gateway, tenant, }; use crate::{gateway::backend as gateway_backend, integration, test, uplink}; use chirpstack_api::{common, gw, internal}; -use lrwn::{AES128Key, EUI64}; +use lrwn::{AES128Key, DevAddr, EUI64}; #[tokio::test] async fn test_lorawan_10() { @@ -96,26 +96,26 @@ async fn test_lorawan_10() { device_profile_id: dp_relay.id.clone(), dev_eui: EUI64::from_be_bytes([1, 1, 1, 1, 1, 1, 1, 2]), enabled_class: DeviceClass::A, + dev_addr: Some(DevAddr::from_be_bytes([4, 3, 2, 1])), + device_session: Some(internal::DeviceSession { + mac_version: common::MacVersion::Lorawan102.into(), + dev_addr: vec![4, 3, 2, 1], + f_nwk_s_int_key: vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16], + s_nwk_s_int_key: vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16], + nwk_s_enc_key: vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16], + f_cnt_up: 10, + n_f_cnt_down: 5, + rx1_delay: 1, + rx2_frequency: 869525000, + region_config_id: "eu868".into(), + ..Default::default() + }), ..Default::default() }) .await .unwrap(); - let ds_relay = internal::DeviceSession { - dev_eui: dev_relay.dev_eui.to_vec(), - mac_version: common::MacVersion::Lorawan102.into(), - dev_addr: vec![4, 3, 2, 1], - f_nwk_s_int_key: vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16], - s_nwk_s_int_key: vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16], - nwk_s_enc_key: vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16], - f_cnt_up: 10, - n_f_cnt_down: 5, - rx1_delay: 1, - rx2_frequency: 869525000, - region_config_id: "eu868".into(), - ..Default::default() - }; - device_session::save(&ds_relay).await.unwrap(); + let ds_relay = dev_relay.get_device_session().unwrap(); let mut rx_info = gw::UplinkRxInfo { gateway_id: gw.gateway_id.to_string(), @@ -268,8 +268,6 @@ async fn test_lorawan_10() { dev.dev_eui.clone(), internal::DeviceSession { dev_addr: vec![1, 2, 3, 4], - dev_eui: vec![1, 1, 1, 1, 1, 1, 1, 1], - join_eui: vec![1, 2, 3, 4, 5, 6, 7, 8], mac_version: common::MacVersion::Lorawan102.into(), f_nwk_s_int_key: vec![ 146, 184, 94, 251, 180, 89, 48, 96, 236, 112, 106, 181, 94, 25, 215, 162, diff --git a/chirpstack/src/uplink/data.rs b/chirpstack/src/uplink/data.rs index 0986b217..c220925c 100644 --- a/chirpstack/src/uplink/data.rs +++ b/chirpstack/src/uplink/data.rs @@ -17,7 +17,7 @@ use crate::storage::error::Error as StorageError; use crate::storage::{ application, device::{self, DeviceClass}, - device_gateway, device_profile, device_queue, device_session, fields, + device_gateway, device_profile, device_queue, fields, helpers::get_all_device_data, metrics, tenant, }; @@ -40,7 +40,6 @@ pub struct Data { retransmission: bool, f_cnt_up_full: u32, tenant: Option, - device_session: Option, device: Option, device_profile: Option, application: Option, @@ -50,6 +49,7 @@ pub struct Data { must_send_downlink: bool, downlink_mac_commands: Vec, device_gateway_rx_info: Option, + device_changeset: device::DeviceChangeset, } impl Data { @@ -99,7 +99,6 @@ impl Data { reset: false, retransmission: false, tenant: None, - device_session: None, device: None, device_profile: None, application: None, @@ -109,10 +108,11 @@ impl Data { must_send_downlink: false, downlink_mac_commands: Vec::new(), device_gateway_rx_info: None, + device_changeset: Default::default(), }; ctx.handle_passive_roaming_device().await?; - ctx.get_device_session().await?; + ctx.get_device_for_phy_payload().await?; ctx.get_device_data().await?; ctx.check_roaming_allowed()?; @@ -120,17 +120,16 @@ impl Data { let span = tracing::Span::current(); span.record("dev_eui", ctx.device.as_ref().unwrap().dev_eui.to_string()); - ctx.abort_on_device_is_disabled().await?; - ctx.set_device_info()?; - ctx.set_device_gateway_rx_info()?; - ctx.handle_retransmission_reset().await?; - ctx.set_scheduler_run_after().await?; if !ctx._is_roaming() { // In case of roaming we do not know the gateways and therefore it must not be // filtered. ctx.filter_rx_info_by_tenant().await?; ctx.filter_rx_info_by_region_config_id()?; } + ctx.set_device_info()?; + ctx.set_device_gateway_rx_info()?; + ctx.handle_retransmission_reset().await?; + ctx.set_scheduler_run_after().await?; ctx.decrypt_f_opts_mac_commands()?; ctx.decrypt_frm_payload()?; ctx.log_uplink_frame_set().await?; @@ -148,7 +147,7 @@ impl Data { ctx.detect_and_save_measurements().await?; ctx.sync_uplink_f_cnt()?; ctx.set_region_config_id()?; - ctx.save_device_session().await?; + ctx.update_device().await?; ctx.handle_uplink_ack().await?; ctx.save_metrics().await?; @@ -175,7 +174,6 @@ impl Data { reset: false, retransmission: false, tenant: None, - device_session: None, device: None, device_profile: None, application: None, @@ -184,11 +182,11 @@ impl Data { uplink_event: None, must_send_downlink: false, downlink_mac_commands: Vec::new(), + device_changeset: Default::default(), }; - ctx.get_device_session_relayed().await?; + ctx.get_device_for_phy_payload_relayed().await?; ctx.get_device_data().await?; - ctx.abort_on_device_is_disabled().await?; ctx.set_device_info()?; ctx.set_relay_rx_info()?; ctx.handle_retransmission_reset().await?; @@ -204,7 +202,7 @@ impl Data { ctx.detect_and_save_measurements().await?; ctx.sync_uplink_f_cnt()?; ctx.set_region_config_id()?; - ctx.save_device_session().await?; + ctx.update_device().await?; ctx.handle_uplink_ack().await?; ctx.save_metrics_relayed().await?; ctx.start_downlink_data_flow_relayed().await?; @@ -230,8 +228,8 @@ impl Data { Ok(()) } - async fn get_device_session(&mut self) -> Result<(), Error> { - trace!("Getting device-session for dev_addr"); + async fn get_device_for_phy_payload(&mut self) -> Result<(), Error> { + trace!("Getting device for PhyPayload"); let dev_addr = if let lrwn::Payload::MACPayload(pl) = &self.phy_payload.payload { pl.fhdr.devaddr @@ -239,7 +237,7 @@ impl Data { return Err(Error::AnyhowError(anyhow!("No MacPayload in PhyPayload"))); }; - match device_session::get_for_phypayload_and_incr_f_cnt_up( + match device::get_for_phypayload_and_incr_f_cnt_up( false, &mut self.phy_payload, self.uplink_frame_set.dr, @@ -248,18 +246,18 @@ impl Data { .await { Ok(v) => match v { - device_session::ValidationStatus::Ok(f_cnt, ds) => { - self.device_session = Some(ds); + device::ValidationStatus::Ok(f_cnt, d) => { + self.device = Some(d); self.f_cnt_up_full = f_cnt; } - device_session::ValidationStatus::Retransmission(f_cnt, ds) => { + device::ValidationStatus::Retransmission(f_cnt, d) => { self.retransmission = true; - self.device_session = Some(ds); + self.device = Some(d); self.f_cnt_up_full = f_cnt; } - device_session::ValidationStatus::Reset(f_cnt, ds) => { + device::ValidationStatus::Reset(f_cnt, d) => { self.reset = true; - self.device_session = Some(ds); + self.device = Some(d); self.f_cnt_up_full = f_cnt; } }, @@ -289,8 +287,8 @@ impl Data { Ok(()) } - async fn get_device_session_relayed(&mut self) -> Result<(), Error> { - trace!("Getting device-session for dev_addr (relayed)"); + async fn get_device_for_phy_payload_relayed(&mut self) -> Result<(), Error> { + trace!("Getting device for PhyPayload (relayed)"); let relay_ctx = self.relay_context.as_ref().unwrap(); @@ -307,27 +305,22 @@ impl Data { dr, )? as u8; - match device_session::get_for_phypayload_and_incr_f_cnt_up( - true, - &mut self.phy_payload, - dr, - ch, - ) - .await + match device::get_for_phypayload_and_incr_f_cnt_up(true, &mut self.phy_payload, dr, ch) + .await { Ok(v) => match v { - device_session::ValidationStatus::Ok(f_cnt, ds) => { - self.device_session = Some(ds); + device::ValidationStatus::Ok(f_cnt, d) => { + self.device = Some(d); self.f_cnt_up_full = f_cnt; } - device_session::ValidationStatus::Retransmission(f_cnt, ds) => { + device::ValidationStatus::Retransmission(f_cnt, d) => { self.retransmission = true; - self.device_session = Some(ds); + self.device = Some(d); self.f_cnt_up_full = f_cnt; } - device_session::ValidationStatus::Reset(f_cnt, ds) => { + device::ValidationStatus::Reset(f_cnt, d) => { self.reset = true; - self.device_session = Some(ds); + self.device = Some(d); self.f_cnt_up_full = f_cnt; } }, @@ -353,8 +346,9 @@ impl Data { async fn get_device_data(&mut self) -> Result<()> { trace!("Getting device data"); - let dev_eui = lrwn::EUI64::from_slice(&self.device_session.as_ref().unwrap().dev_eui)?; - let (dev, app, t, dp) = get_all_device_data(dev_eui).await?; + + let dev_eui = self.device.as_ref().unwrap().dev_eui; + let (_, app, t, dp) = get_all_device_data(dev_eui).await?; if dp.region != self.uplink_frame_set.region_common_name { return Err(anyhow!("Invalid device-profile region")); @@ -363,7 +357,6 @@ impl Data { self.tenant = Some(t); self.application = Some(app); self.device_profile = Some(dp); - self.device = Some(dev); Ok(()) } @@ -425,9 +418,10 @@ impl Data { fn set_device_gateway_rx_info(&mut self) -> Result<()> { trace!("Setting gateway rx-info for device"); + let d = self.device.as_ref().unwrap(); self.device_gateway_rx_info = Some(internal::DeviceGatewayRxInfo { - dev_eui: self.device_session.as_ref().unwrap().dev_eui.clone(), + dev_eui: d.dev_eui.to_vec(), dr: self.uplink_frame_set.dr as u32, items: self .uplink_frame_set @@ -469,24 +463,6 @@ impl Data { Ok(()) } - async fn abort_on_device_is_disabled(&self) -> Result<(), Error> { - let device = self.device.as_ref().unwrap(); - - if device.is_disabled { - // Restore the device-session in case the device is disabled. - // This is because during the fcnt validation, we immediately store the - // device-session with incremented fcnt to avoid race conditions. - device_session::save(self.device_session.as_ref().unwrap()) - .await - .context("Savel device-session")?; - - info!(dev_eui = %device.dev_eui, "Device is disabled, aborting flow"); - return Err(Error::Abort); - } - - Ok(()) - } - async fn handle_retransmission_reset(&self) -> Result<(), Error> { trace!("Handle retransmission and reset"); let dev = self.device.as_ref().unwrap(); @@ -555,8 +531,14 @@ impl Data { if dev.scheduler_run_after.is_none() || scheduler_run_after > dev.scheduler_run_after.unwrap() { - *dev = device::set_scheduler_run_after(&dev.dev_eui, Some(scheduler_run_after)) - .await?; + *dev = device::partial_update( + dev.dev_eui, + &device::DeviceChangeset { + scheduler_run_after: Some(Some(scheduler_run_after)), + ..Default::default() + }, + ) + .await?; } } @@ -575,9 +557,15 @@ impl Data { // Restore the device-session in case of an error (no gateways available). // This is because during the fcnt validation, we immediately store the // device-session with incremented fcnt to avoid race conditions. - device_session::save(self.device_session.as_ref().unwrap()) - .await - .context("Save device-session")?; + let d = self.device.as_ref().unwrap(); + device::partial_update( + d.dev_eui, + &device::DeviceChangeset { + device_session: Some(d.device_session.clone()), + ..Default::default() + }, + ) + .await?; Err(v) } @@ -597,7 +585,7 @@ impl Data { fn decrypt_f_opts_mac_commands(&mut self) -> Result<()> { trace!("Decrypting mac-commands"); - let ds = self.device_session.as_ref().unwrap(); + let ds = self.device.as_ref().unwrap().get_device_session()?; if ds.mac_version().to_string().starts_with("1.0") { if let Err(e) = self.phy_payload.decode_f_opts_to_mac_commands() { // This avoids failing in case of a corrupted mac-command in the frm_payload. @@ -616,7 +604,7 @@ impl Data { fn decrypt_frm_payload(&mut self) -> Result<()> { trace!("Decrypting FRMPayload"); - let ds = self.device_session.as_ref().unwrap(); + let ds = self.device.as_ref().unwrap().get_device_session()?; let mut f_port = 0; if let lrwn::Payload::MACPayload(pl) = &self.phy_payload.payload { @@ -656,7 +644,7 @@ impl Data { fn set_adr(&mut self) -> Result<()> { trace!("Set ADR flag in device-session"); - let ds = self.device_session.as_mut().unwrap(); + let ds = self.device.as_mut().unwrap().get_device_session_mut()?; if let lrwn::Payload::MACPayload(pl) = &self.phy_payload.payload { ds.adr = pl.fhdr.f_ctrl.adr; } @@ -666,9 +654,13 @@ impl Data { async fn set_uplink_data_rate(&mut self) -> Result<()> { trace!("Set uplink data-rate and reset tx-power on change"); let device = self.device.as_mut().unwrap(); - *device = device::set_last_seen_dr(&device.dev_eui, self.uplink_frame_set.dr).await?; - let ds = self.device_session.as_mut().unwrap(); + self.device_changeset.last_seen_at = Some(Some(Utc::now())); + if device.dr.is_none() || self.uplink_frame_set.dr as i16 != device.dr.unwrap_or_default() { + self.device_changeset.dr = Some(Some(self.uplink_frame_set.dr.into())); + } + + let ds = device.get_device_session_mut()?; // The node changed its data-rate. Possibly the node did also reset its // tx-power to max power. Because of this, we need to reset the tx-power // and the uplink history at the network-server side too. @@ -677,6 +669,7 @@ impl Data { ds.uplink_adr_history = Vec::new(); } ds.dr = self.uplink_frame_set.dr as u32; + Ok(()) } @@ -684,9 +677,13 @@ impl Data { trace!("Set relayed uplink data-rate and reset tx-power on change"); let device = self.device.as_mut().unwrap(); let relay_ctx = self.relay_context.as_ref().unwrap(); - *device = device::set_last_seen_dr(&device.dev_eui, self.uplink_frame_set.dr).await?; - let ds = self.device_session.as_mut().unwrap(); + self.device_changeset.last_seen_at = Some(Some(Utc::now())); + if device.dr.is_none() || self.uplink_frame_set.dr as i16 != device.dr.unwrap_or_default() { + self.device_changeset.dr = Some(Some(self.uplink_frame_set.dr.into())); + } + + let ds = device.get_device_session_mut()?; // The node changed its data-rate. Possibly the node did also reset its // tx-power to max power. Because of this, we need to reset the tx-power // and the uplink history at the network-server side too. @@ -717,7 +714,7 @@ impl Data { // Update if the enabled class has changed. if dev.enabled_class != enabled_class { - *dev = device::set_enabled_class(&dev.dev_eui, enabled_class).await?; + self.device_changeset.enabled_class = Some(enabled_class); } Ok(()) @@ -770,11 +767,12 @@ impl Data { // device did not reset these). fn reset_channels_on_adr_ack_req(&mut self) -> Result<()> { trace!("Reset channels on adr ack req"); + let d = self.device.as_mut().unwrap(); if let lrwn::Payload::MACPayload(pl) = &self.phy_payload.payload { if pl.fhdr.f_ctrl.adr_ack_req { let region_conf = region::get(&self.uplink_frame_set.region_config_id)?; - let ds = self.device_session.as_mut().unwrap(); + let ds = d.get_device_session_mut()?; // We reset the device-session enabled_uplink_channel_indices and // extra_uplink_channels. On the downlink path, the mac-command handling will @@ -804,8 +802,7 @@ impl Data { self.tenant.as_ref().unwrap(), self.application.as_ref().unwrap(), self.device_profile.as_ref().unwrap(), - self.device.as_ref().unwrap(), - self.device_session.as_mut().unwrap(), + self.device.as_mut().unwrap(), ) .await .context("Handle uplink mac-commands")?; @@ -822,8 +819,7 @@ impl Data { self.tenant.as_ref().unwrap(), self.application.as_ref().unwrap(), self.device_profile.as_ref().unwrap(), - self.device.as_ref().unwrap(), - self.device_session.as_mut().unwrap(), + self.device.as_mut().unwrap(), ) .await .context("Handle uplink mac-commands")?; @@ -847,7 +843,7 @@ impl Data { } fn append_meta_data_to_uplink_history(&mut self) -> Result<()> { - let ds = self.device_session.as_mut().unwrap(); + let ds = self.device.as_mut().unwrap().get_device_session_mut()?; // ignore re-transmissions we don't know the source of the // re-transmission (it might be a replay-attack) @@ -892,7 +888,7 @@ impl Data { fn append_meta_data_to_uplink_history_relayed(&mut self) -> Result<()> { trace!("Apping meta-data of relayed uplink to upink history"); - let ds = self.device_session.as_mut().unwrap(); + let ds = self.device.as_mut().unwrap().get_device_session_mut()?; let relay_ctx = self.relay_context.as_ref().unwrap(); // ignore re-transmissions we don't know the source of the @@ -929,7 +925,7 @@ impl Data { let app = self.application.as_ref().unwrap(); let dp = self.device_profile.as_ref().unwrap(); let dev = self.device.as_ref().unwrap(); - let ds = self.device_session.as_ref().unwrap(); + let ds = dev.get_device_session()?; let mac = if let lrwn::Payload::MACPayload(pl) = &self.phy_payload.payload { pl } else { @@ -1092,7 +1088,8 @@ impl Data { // required. fn sync_uplink_f_cnt(&mut self) -> Result<()> { trace!("Syncing uplink frame-counter"); - let ds = self.device_session.as_mut().unwrap(); + let d = self.device.as_mut().unwrap(); + let ds = d.get_device_session_mut()?; ds.f_cnt_up = self.f_cnt_up_full + 1; Ok(()) } @@ -1102,16 +1099,19 @@ impl Data { // value is not set initially. fn set_region_config_id(&mut self) -> Result<()> { trace!("Setting region_config_id to device-session"); - let ds = self.device_session.as_mut().unwrap(); + let d = self.device.as_mut().unwrap(); + let ds = d.get_device_session_mut()?; ds.region_config_id = self.uplink_frame_set.region_config_id.clone(); Ok(()) } - async fn save_device_session(&self) -> Result<()> { - trace!("Saving device-session"); - device_session::save(self.device_session.as_ref().unwrap()) - .await - .context("Save device-session")?; + async fn update_device(&mut self) -> Result<()> { + trace!("Updating device"); + + let d = self.device.as_mut().unwrap(); + self.device_changeset.device_session = Some(d.device_session.clone()); + + *d = device::partial_update(d.dev_eui, &self.device_changeset).await?; Ok(()) } @@ -1265,7 +1265,6 @@ impl Data { self.application.as_ref().cloned().unwrap(), self.device_profile.as_ref().cloned().unwrap(), self.device.as_ref().cloned().unwrap(), - self.device_session.as_ref().cloned().unwrap(), pl.fhdr.f_ctrl.adr_ack_req || self.must_send_downlink, self.phy_payload.mhdr.m_type == lrwn::MType::ConfirmedDataUp, self.downlink_mac_commands.clone(), @@ -1291,7 +1290,6 @@ impl Data { self.application.as_ref().cloned().unwrap(), self.device_profile.as_ref().cloned().unwrap(), self.device.as_ref().cloned().unwrap(), - self.device_session.as_ref().cloned().unwrap(), pl.fhdr.f_ctrl.adr_ack_req || self.must_send_downlink, self.phy_payload.mhdr.m_type == lrwn::MType::ConfirmedDataUp, self.downlink_mac_commands.clone(), @@ -1314,7 +1312,6 @@ impl Data { req: pl.clone(), device: self.device.as_ref().unwrap().clone(), device_profile: self.device_profile.as_ref().unwrap().clone(), - device_session: self.device_session.as_ref().unwrap().clone(), must_ack: self.phy_payload.mhdr.m_type == lrwn::MType::ConfirmedDataUp, must_send_downlink: relay_pl.fhdr.f_ctrl.adr_ack_req, @@ -1329,7 +1326,6 @@ impl Data { req: pl.clone(), device: self.device.as_ref().unwrap().clone(), device_profile: self.device_profile.as_ref().unwrap().clone(), - device_session: self.device_session.as_ref().unwrap().clone(), must_ack: self.phy_payload.mhdr.m_type == lrwn::MType::ConfirmedDataUp, must_send_downlink: relay_pl.fhdr.f_ctrl.adr_ack_req, @@ -1369,7 +1365,10 @@ impl Data { } fn _is_end_to_end_encrypted(&self) -> bool { - let ds = self.device_session.as_ref().unwrap(); + let ds = match self.device.as_ref().unwrap().get_device_session() { + Ok(v) => v, + Err(_) => return false, + }; if !ds.js_session_key_id.is_empty() { return true; diff --git a/chirpstack/src/uplink/join.rs b/chirpstack/src/uplink/join.rs index a34a9a18..31066950 100644 --- a/chirpstack/src/uplink/join.rs +++ b/chirpstack/src/uplink/join.rs @@ -6,8 +6,8 @@ use chrono::{DateTime, Local, Utc}; use tracing::{error, info, span, trace, warn, Instrument, Level}; use lrwn::{ - keys, AES128Key, CFList, DLSettings, DevAddr, JoinAcceptPayload, JoinRequestPayload, JoinType, - MType, Major, Payload, PhyPayload, MHDR, + keys, AES128Key, CFList, DLSettings, JoinAcceptPayload, JoinRequestPayload, JoinType, MType, + Major, Payload, PhyPayload, MHDR, }; use super::error::Error; @@ -20,7 +20,6 @@ use super::{ use crate::api::{backend::get_async_receiver, helpers::ToProto}; use crate::backend::{joinserver, keywrap, roaming}; use crate::helpers::errors::PrintFullError; -use crate::storage::device_session; use crate::storage::{ application, device::{self, DeviceClass}, @@ -40,12 +39,10 @@ pub struct JoinRequest { join_request: Option, join_accept: Option, device: Option, - device_session: Option, application: Option, tenant: Option, device_profile: Option, device_keys: Option, - dev_addr: Option, device_info: Option, relay_rx_info: Option, f_nwk_s_int_key: Option, @@ -96,12 +93,10 @@ impl JoinRequest { js_client: None, join_request: None, device: None, - device_session: None, application: None, tenant: None, device_profile: None, device_keys: None, - dev_addr: None, join_accept: None, device_info: None, relay_rx_info: None, @@ -130,7 +125,7 @@ impl JoinRequest { ctx.abort_on_relay_only_comm()?; ctx.log_uplink_frame_set().await?; ctx.abort_on_otaa_is_disabled()?; - ctx.get_random_dev_addr()?; + ctx.set_random_dev_addr()?; if ctx.js_client.is_some() { // Using join-server ctx.get_join_accept_from_js().await?; @@ -141,11 +136,10 @@ impl JoinRequest { ctx.construct_join_accept_and_set_keys()?; } ctx.log_uplink_meta().await?; - ctx.create_device_session().await?; + ctx.set_device_session().await?; ctx.flush_device_queue().await?; ctx.set_device_mode().await?; - ctx.set_dev_addr().await?; - ctx.set_join_eui().await?; + ctx.update_device().await?; ctx.start_downlink_join_accept_flow().await?; ctx.send_join_event().await?; @@ -159,12 +153,10 @@ impl JoinRequest { js_client: None, join_request: None, device: None, - device_session: None, application: None, tenant: None, device_profile: None, device_keys: None, - dev_addr: None, join_accept: None, device_info: None, relay_rx_info: None, @@ -183,7 +175,7 @@ impl JoinRequest { ctx.abort_on_device_is_disabled()?; ctx.abort_on_otaa_is_disabled()?; ctx.abort_on_relay_only_comm()?; - ctx.get_random_dev_addr()?; + ctx.set_random_dev_addr()?; if ctx.js_client.is_some() { // Using join-server ctx.get_join_accept_from_js().await?; @@ -193,11 +185,10 @@ impl JoinRequest { ctx.validate_dev_nonce_and_get_device_keys().await?; ctx.construct_join_accept_and_set_keys()?; } - ctx.create_device_session().await?; + ctx.set_device_session().await?; ctx.flush_device_queue().await?; ctx.set_device_mode().await?; - ctx.set_dev_addr().await?; - ctx.set_join_eui().await?; + ctx.update_device().await?; ctx.start_downlink_join_accept_flow_relayed().await?; ctx.send_join_event().await?; @@ -514,8 +505,10 @@ impl JoinRequest { Ok(()) } - fn get_random_dev_addr(&mut self) -> Result<()> { - self.dev_addr = Some(get_random_dev_addr()); + fn set_random_dev_addr(&mut self) -> Result<()> { + trace!("Setting random DevAddr"); + let d = self.device.as_mut().unwrap(); + d.dev_addr = Some(get_random_dev_addr()); Ok(()) } @@ -550,7 +543,7 @@ impl JoinRequest { mac_version: dp.mac_version.to_string(), phy_payload: phy_b, dev_eui: dev.dev_eui.to_vec(), - dev_addr: self.dev_addr.unwrap().to_vec(), + dev_addr: dev.dev_addr.unwrap().to_vec(), dl_settings: dl_settings.to_le_bytes()?.to_vec(), rx_delay: region_network.rx1_delay, cf_list: match region_conf.get_cf_list(dp.mac_version) { @@ -619,6 +612,7 @@ impl JoinRequest { let region_conf = region::get(&self.uplink_frame_set.region_config_id)?; let join_request = self.join_request.as_ref().unwrap(); + let d = self.device.as_ref().unwrap(); let dk = self.device_keys.as_mut().unwrap(); let join_nonce = dk.join_nonce - 1; // this was incremented on validation @@ -643,7 +637,7 @@ impl JoinRequest { payload: Payload::JoinAccept(JoinAcceptPayload { join_nonce: join_nonce as u32, home_netid: conf.network.net_id, - devaddr: self.dev_addr.unwrap(), + devaddr: d.dev_addr.unwrap(), dl_settings: DLSettings { opt_neg, rx2_dr: region_network.rx2_dr, @@ -768,21 +762,18 @@ impl JoinRequest { Ok(()) } - async fn create_device_session(&mut self) -> Result<()> { - trace!("Creating device-session"); + async fn set_device_session(&mut self) -> Result<()> { + trace!("Setting device-session"); let region_conf = region::get(&self.uplink_frame_set.region_config_id)?; let region_network = config::get_region_network(&self.uplink_frame_set.region_config_id)?; - let device = self.device.as_ref().unwrap(); + let device = self.device.as_mut().unwrap(); let device_profile = self.device_profile.as_ref().unwrap(); - let join_request = self.join_request.as_ref().unwrap(); let mut ds = internal::DeviceSession { region_config_id: self.uplink_frame_set.region_config_id.clone(), - dev_eui: device.dev_eui.to_be_bytes().to_vec(), - dev_addr: self.dev_addr.unwrap().to_be_bytes().to_vec(), - join_eui: join_request.join_eui.to_be_bytes().to_vec(), + dev_addr: device.dev_addr.unwrap().to_be_bytes().to_vec(), f_nwk_s_int_key: self.f_nwk_s_int_key.as_ref().unwrap().to_vec(), s_nwk_s_int_key: self.s_nwk_s_int_key.as_ref().unwrap().to_vec(), nwk_s_enc_key: self.nwk_s_enc_key.as_ref().unwrap().to_vec(), @@ -847,11 +838,7 @@ impl JoinRequest { None => {} } - device_session::save(&ds) - .await - .context("Saving device-session failed")?; - - self.device_session = Some(ds); + device.device_session = Some(ds); Ok(()) } @@ -870,30 +857,36 @@ impl JoinRequest { async fn set_device_mode(&mut self) -> Result<()> { let dp = self.device_profile.as_ref().unwrap(); - let device = self.device.as_mut().unwrap(); + let d = self.device.as_mut().unwrap(); // LoRaWAN 1.1 devices send a mac-command when changing to Class-C. if dp.supports_class_c && dp.mac_version.to_string().starts_with("1.0") { - *device = device::set_enabled_class(&device.dev_eui, DeviceClass::C).await?; + d.enabled_class = DeviceClass::C; } else { - *device = device::set_enabled_class(&device.dev_eui, DeviceClass::A).await?; + d.enabled_class = DeviceClass::A; } + Ok(()) } - async fn set_dev_addr(&mut self) -> Result<()> { - trace!("Setting DevAddr"); - let dev = self.device.as_mut().unwrap(); - *dev = device::set_dev_addr(dev.dev_eui, self.dev_addr.unwrap()).await?; - Ok(()) - } + async fn update_device(&mut self) -> Result<()> { + trace!("Updating device"); - async fn set_join_eui(&mut self) -> Result<()> { - trace!("Setting JoinEUI"); - let dev = self.device.as_mut().unwrap(); let req = self.join_request.as_ref().unwrap(); + let d = self.device.as_mut().unwrap(); - *dev = device::set_join_eui(dev.dev_eui, req.join_eui).await?; + *d = device::partial_update( + d.dev_eui, + &device::DeviceChangeset { + enabled_class: Some(d.enabled_class), + dev_addr: Some(d.dev_addr), + secondary_dev_addr: Some(None), + join_eui: Some(req.join_eui), + device_session: Some(d.device_session.clone()), + ..Default::default() + }, + ) + .await?; Ok(()) } @@ -904,7 +897,6 @@ impl JoinRequest { &self.uplink_frame_set, self.tenant.as_ref().unwrap(), self.device.as_ref().unwrap(), - self.device_session.as_ref().unwrap(), self.join_accept.as_ref().unwrap(), ) .await?; @@ -918,7 +910,6 @@ impl JoinRequest { &self.uplink_frame_set, self.tenant.as_ref().unwrap(), self.device.as_ref().unwrap(), - self.device_session.as_ref().unwrap(), self.join_accept.as_ref().unwrap(), ) .await?; @@ -939,7 +930,7 @@ impl JoinRequest { time: Some(ts.into()), device_info: self.device_info.clone(), relay_rx_info: self.relay_rx_info.clone(), - dev_addr: self.dev_addr.as_ref().unwrap().to_string(), + dev_addr: dev.dev_addr.unwrap().to_string(), join_server_context: if !self.js_session_key_id.is_empty() { Some(common::JoinServerContext { app_s_key: None, diff --git a/chirpstack/src/uplink/join_sns.rs b/chirpstack/src/uplink/join_sns.rs index 4ccc6ef5..84c61b3d 100644 --- a/chirpstack/src/uplink/join_sns.rs +++ b/chirpstack/src/uplink/join_sns.rs @@ -10,7 +10,7 @@ use crate::backend::{joinserver, keywrap, roaming}; use crate::storage::{ application, device::{self, DeviceClass}, - device_keys, device_profile, device_queue, device_session, + device_keys, device_profile, device_queue, error::Error as StorageError, helpers::get_all_device_data, metrics, tenant, @@ -28,7 +28,6 @@ pub struct JoinRequest { join_request: Option, join_accept: Option, device: Option, - device_session: Option, js_client: Option>, application: Option, tenant: Option, @@ -66,7 +65,6 @@ impl JoinRequest { join_request: None, join_accept: None, device: None, - device_session: None, js_client: None, application: None, tenant: None, @@ -99,10 +97,9 @@ impl JoinRequest { ctx.construct_join_accept_and_set_keys()?; } ctx.log_uplink_meta().await?; - ctx.create_device_session().await?; + ctx.set_device_session().await?; ctx.flush_device_queue().await?; - ctx.set_device_mode().await?; - ctx.set_join_eui().await?; + ctx.update_device().await?; ctx.send_join_event().await?; ctx.set_pr_start_ans_payload()?; @@ -562,21 +559,18 @@ impl JoinRequest { Ok(()) } - async fn create_device_session(&mut self) -> Result<()> { - trace!("Creating device-session"); + async fn set_device_session(&mut self) -> Result<()> { + trace!("Setting device-session"); let region_conf = region::get(&self.uplink_frame_set.region_config_id)?; let region_network = config::get_region_network(&self.uplink_frame_set.region_config_id)?; - let device = self.device.as_ref().unwrap(); + let device = self.device.as_mut().unwrap(); let device_profile = self.device_profile.as_ref().unwrap(); - let join_request = self.join_request.as_ref().unwrap(); let mut ds = internal::DeviceSession { region_config_id: self.uplink_frame_set.region_config_id.clone(), - dev_eui: device.dev_eui.to_be_bytes().to_vec(), dev_addr: self.dev_addr.unwrap().to_be_bytes().to_vec(), - join_eui: join_request.join_eui.to_be_bytes().to_vec(), f_nwk_s_int_key: self.f_nwk_s_int_key.as_ref().unwrap().to_vec(), s_nwk_s_int_key: self.s_nwk_s_int_key.as_ref().unwrap().to_vec(), nwk_s_enc_key: self.nwk_s_enc_key.as_ref().unwrap().to_vec(), @@ -627,11 +621,7 @@ impl JoinRequest { } } - device_session::save(&ds) - .await - .context("Saving device-session failed")?; - - self.device_session = Some(ds); + device.device_session = Some(ds); Ok(()) } @@ -648,25 +638,31 @@ impl JoinRequest { Ok(()) } - async fn set_device_mode(&mut self) -> Result<()> { + async fn update_device(&mut self) -> Result<()> { + trace!("Updating device"); let dp = self.device_profile.as_ref().unwrap(); - let device = self.device.as_mut().unwrap(); + let ds = self.device.as_ref().unwrap().get_device_session()?; - // LoRaWAN 1.1 devices send a mac-command when changing to Class-C. - if dp.supports_class_c && dp.mac_version.to_string().starts_with("1.0") { - *device = device::set_enabled_class(&device.dev_eui, DeviceClass::C).await?; - } else { - *device = device::set_enabled_class(&device.dev_eui, DeviceClass::A).await?; - } - Ok(()) - } - - async fn set_join_eui(&mut self) -> Result<()> { - trace!("Setting JoinEUI"); - let dev = self.device.as_mut().unwrap(); - let req = self.join_request.as_ref().unwrap(); - - *dev = device::set_join_eui(dev.dev_eui, req.join_eui).await?; + self.device = Some( + device::partial_update( + self.device.as_ref().unwrap().dev_eui, + &device::DeviceChangeset { + device_session: Some(Some(ds.clone())), + join_eui: Some(self.join_request.as_ref().unwrap().join_eui), + dev_addr: Some(Some(self.dev_addr.unwrap())), + secondary_dev_addr: Some(None), + enabled_class: Some( + if dp.supports_class_c && dp.mac_version.to_string().starts_with("1.0") { + DeviceClass::C + } else { + DeviceClass::A + }, + ), + ..Default::default() + }, + ) + .await?, + ); Ok(()) } @@ -714,7 +710,9 @@ impl JoinRequest { fn set_pr_start_ans_payload(&mut self) -> Result<()> { trace!("Setting PRStartAnsPayload"); - let ds = self.device_session.as_ref().unwrap(); + let d = self.device.as_ref().unwrap(); + let ds = d.get_device_session()?; + let region_conf = region::get(&self.uplink_frame_set.region_config_id)?; let sender_id = NetID::from_slice(&self.pr_start_req.base.sender_id)?; @@ -753,8 +751,8 @@ impl JoinRequest { .base .to_base_payload_result(backend::ResultCode::Success, ""), phy_payload: self.join_accept.as_ref().unwrap().to_vec()?, - dev_eui: ds.dev_eui.clone(), - dev_addr: ds.dev_addr.clone(), + dev_eui: d.dev_eui.to_vec(), + dev_addr: d.get_dev_addr()?.to_vec(), lifetime: if pr_lifetime.is_zero() { None } else { @@ -764,7 +762,7 @@ impl JoinRequest { nwk_s_key, f_cnt_up: Some(0), dl_meta_data: Some(backend::DLMetaData { - dev_eui: ds.dev_eui.clone(), + dev_eui: d.dev_eui.to_vec(), dl_freq_1: Some(rx1_freq as f64 / 1_000_000.0), dl_freq_2: Some(rx2_freq as f64 / 1_000_000.0), rx_delay_1: Some(rx1_delay.as_secs() as usize), diff --git a/chirpstack/src/uplink/mod.rs b/chirpstack/src/uplink/mod.rs index bd84f834..6a642058 100644 --- a/chirpstack/src/uplink/mod.rs +++ b/chirpstack/src/uplink/mod.rs @@ -21,7 +21,7 @@ use crate::storage::{ device, device_profile, error::Error as StorageError, gateway, get_async_redis_conn, redis_key, }; use crate::stream; -use chirpstack_api::{common, gw, internal, stream as stream_pb}; +use chirpstack_api::{common, gw, stream as stream_pb}; use lrwn::region::CommonName; use lrwn::{ForwardUplinkReq, MType, PhyPayload, EUI64}; @@ -75,7 +75,6 @@ pub struct RelayContext { pub req: ForwardUplinkReq, pub device: device::Device, pub device_profile: device_profile::DeviceProfile, - pub device_session: internal::DeviceSession, pub must_ack: bool, pub must_send_downlink: bool, } diff --git a/shell.nix b/shell.nix index 2f68ed0d..a6e4983b 100644 --- a/shell.nix +++ b/shell.nix @@ -11,6 +11,7 @@ pkgs.mkShell { pkgs.perl pkgs.cmake pkgs.clang + pkgs.postgresql # needed to build the diesel cli utility ]; LIBCLANG_PATH = "${pkgs.llvmPackages.libclang.lib}/lib"; BINDGEN_EXTRA_CLANG_ARGS = "-I${pkgs.llvmPackages.libclang.lib}/lib/clang/${pkgs.llvmPackages.libclang.version}/include";