From 098f8db4c6ef8c75a5a2306511760cb17fe902c2 Mon Sep 17 00:00:00 2001 From: Orne Brocaar Date: Tue, 27 Feb 2024 16:17:15 +0000 Subject: [PATCH] Migrate device-sessions from Redis to PostgreSQL. This migrates the device-sessions from Redis into PostgreSQL. This fixes a performance issue in case the same DevAddr is reused many times (e.g. devices rejoining very often or a NetID with small DevAddr space). There were two issues: The Redis key containing the DevAddr -> DevEUIs mapping could contain DevEUIs that no longer used the DevAddr. This mapping would only expire from the Redis database after none of the devices would use the DevAddr for more than the configured device_session_ttl. The other issue with the previous approach was that on for example a Type 7 NetID, a single DevAddr could be re-used multiple times. As each device-session could be stored on a different Redis Cluster instance, there was no option to retrieve all device-sessions at once. Thus a high re-usage of a single DevAddr would cause an increase in Redis queries. Both issues are solved by moving the device-session into PostgreSQL as the DevAddr is a column of the device record and thus filtering on this DevAddr would always result in the devices using that DevAddr, as well all device-sessions for a DevAddr can be retrieved by a single query. Note that to migrate the device-sessions, you must run: chirpstack -c path/to/config migrate-device-sessions-to-postgres A nice side-effect is that a PostgreSQL backup / restore will also restore the device connectivity. Closes #362 and #74. --- Cargo.lock | 1 + api/proto/internal/internal.proto | 6 - api/rust/Cargo.toml | 2 + api/rust/build.rs | 22 +- .../proto/chirpstack/internal/internal.proto | 6 - api/rust/src/internal.rs | 33 + chirpstack/Cargo.toml | 2 +- .../down.sql | 6 + .../up.sql | 6 + chirpstack/src/api/backend/mod.rs | 7 +- chirpstack/src/api/device.rs | 125 +-- chirpstack/src/cmd/configfile.rs | 6 - chirpstack/src/cmd/migrate_ds_to_pg.rs | 56 ++ chirpstack/src/cmd/mod.rs | 1 + chirpstack/src/cmd/print_ds.rs | 8 +- chirpstack/src/downlink/data.rs | 658 +++++++------- chirpstack/src/downlink/join.rs | 77 +- chirpstack/src/downlink/tx_ack.rs | 187 ++-- .../src/maccommand/configure_fwd_limit.rs | 23 +- chirpstack/src/maccommand/ctrl_uplink_list.rs | 95 ++- chirpstack/src/maccommand/dev_status.rs | 21 +- chirpstack/src/maccommand/device_mode_ind.rs | 13 +- chirpstack/src/maccommand/end_device_conf.rs | 25 +- chirpstack/src/maccommand/filter_list.rs | 26 +- chirpstack/src/maccommand/link_adr.rs | 25 +- chirpstack/src/maccommand/mod.rs | 68 +- chirpstack/src/maccommand/new_channel.rs | 31 +- .../src/maccommand/ping_slot_channel.rs | 22 +- chirpstack/src/maccommand/ping_slot_info.rs | 26 +- .../src/maccommand/rejoin_param_setup.rs | 24 +- chirpstack/src/maccommand/relay_conf.rs | 29 +- chirpstack/src/maccommand/reset.rs | 51 +- chirpstack/src/maccommand/rx_param_setup.rs | 24 +- chirpstack/src/maccommand/rx_timing_setup.rs | 27 +- chirpstack/src/maccommand/tx_param_setup.rs | 27 +- .../src/maccommand/update_uplink_list.rs | 25 +- chirpstack/src/main.rs | 4 + chirpstack/src/storage/device.rs | 801 ++++++++++++++++-- chirpstack/src/storage/device_session.rs | 754 +---------------- chirpstack/src/storage/schema.rs | 2 + chirpstack/src/test/assert.rs | 38 +- chirpstack/src/test/class_a_pr_test.rs | 104 ++- chirpstack/src/test/class_a_test.rs | 192 +++-- chirpstack/src/test/class_b_test.rs | 60 +- chirpstack/src/test/class_c_test.rs | 68 +- chirpstack/src/test/otaa_js_test.rs | 6 - chirpstack/src/test/otaa_test.rs | 38 +- chirpstack/src/test/relay_class_a_test.rs | 54 +- chirpstack/src/test/relay_otaa_test.rs | 36 +- chirpstack/src/uplink/data.rs | 185 ++-- chirpstack/src/uplink/join.rs | 89 +- chirpstack/src/uplink/join_sns.rs | 74 +- chirpstack/src/uplink/mod.rs | 3 +- shell.nix | 1 + 54 files changed, 2292 insertions(+), 2008 deletions(-) create mode 100644 chirpstack/migrations/2024-02-07-083424_add_device_session_to_device/down.sql create mode 100644 chirpstack/migrations/2024-02-07-083424_add_device_session_to_device/up.sql create mode 100644 chirpstack/src/cmd/migrate_ds_to_pg.rs 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";