From fae182aa3d28e86a5e1be84e6818106058d93ff7 Mon Sep 17 00:00:00 2001 From: Orne Brocaar Date: Mon, 19 Feb 2024 15:27:25 +0000 Subject: [PATCH] Migrate code to store device-session in PG (WIP). --- chirpstack/src/api/backend/mod.rs | 4 +- chirpstack/src/api/device.rs | 128 +-- chirpstack/src/cmd/print_ds.rs | 17 +- chirpstack/src/downlink/data.rs | 71 +- chirpstack/src/downlink/tx_ack.rs | 169 ++-- chirpstack/src/maccommand/ctrl_uplink_list.rs | 70 +- chirpstack/src/maccommand/dev_status.rs | 21 +- chirpstack/src/maccommand/device_mode_ind.rs | 13 +- chirpstack/src/storage/device.rs | 611 +++++++++++--- chirpstack/src/storage/device_session.rs | 754 +----------------- chirpstack/src/test/assert.rs | 56 +- chirpstack/src/test/class_a_pr_test.rs | 110 +-- chirpstack/src/test/class_a_test.rs | 162 +++- chirpstack/src/test/class_b_test.rs | 57 +- chirpstack/src/test/class_c_test.rs | 62 +- chirpstack/src/test/otaa_test.rs | 26 +- chirpstack/src/test/relay_class_a_test.rs | 57 +- chirpstack/src/test/relay_otaa_test.rs | 39 +- chirpstack/src/uplink/data.rs | 78 +- chirpstack/src/uplink/join_sns.rs | 51 +- 20 files changed, 1283 insertions(+), 1273 deletions(-) diff --git a/chirpstack/src/api/backend/mod.rs b/chirpstack/src/api/backend/mod.rs index f3f66c2b..00b45bb5 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,7 +312,7 @@ 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 ds = 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)?; diff --git a/chirpstack/src/api/device.rs b/chirpstack/src/api/device.rs index 0ff37160..2e6121a8 100644 --- a/chirpstack/src/api/device.rs +++ b/chirpstack/src/api/device.rs @@ -5,6 +5,7 @@ use std::time::SystemTime; use bigdecimal::ToPrimitive; use chrono::{DateTime, Local, Utc}; +use prost::Message; use tonic::{Request, Response, Status}; use uuid::Uuid; @@ -15,10 +16,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}; @@ -532,12 +534,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.encode_to_vec())), + 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 +550,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 +583,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,19 +617,18 @@ 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 { @@ -1188,7 +1198,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, + 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 +1227,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 +1543,21 @@ 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 { + device_session: Some(Some( + 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() + } + .encode_to_vec(), + )), + ..Default::default() + }, + ) .await .unwrap(); let get_activation_req = get_request( @@ -1550,17 +1576,27 @@ 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_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() + } + .encode_to_vec(), + )), + ..Default::default() + }, + ) .await .unwrap(); + let get_activation_req = get_request( &u.id, api::GetDeviceActivationRequest { diff --git a/chirpstack/src/cmd/print_ds.rs b/chirpstack/src/cmd/print_ds.rs index 948f2352..0073837c 100644 --- a/chirpstack/src/cmd/print_ds.rs +++ b/chirpstack/src/cmd/print_ds.rs @@ -1,14 +1,23 @@ +use std::io::Cursor; + use anyhow::{Context, Result}; +use prost::Message; use crate::storage; -use crate::storage::device_session; +use crate::storage::device; +use chirpstack_api::internal; 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 = match d.device_session { + Some(v) => internal::DeviceSession::decode(&mut Cursor::new(&v)) + .context("Decode device-session")?, + None => return Err(anyhow!("No 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..30642f1a 100644 --- a/chirpstack/src/downlink/data.rs +++ b/chirpstack/src/downlink/data.rs @@ -4,6 +4,7 @@ use std::time::Duration; use anyhow::{Context, Result}; use chrono::{DateTime, Utc}; +use prost::Message; use rand::Rng; use tracing::{debug, span, trace, warn, Instrument, Level}; @@ -16,7 +17,7 @@ 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, }; @@ -216,13 +217,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.save_device_session().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?; } } @@ -299,8 +300,8 @@ 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 (dev, app, ten, dp) = get_all_device_data(dev.dev_eui).await?; + let ds = dev.get_device_session()?; let rc = region::get(&ds.region_config_id)?; let rn = config::get_region_network(&ds.region_config_id)?; let dev_gw = device_gateway::get_rx_info(&dev.dev_eui).await?; @@ -1030,9 +1031,15 @@ impl Data { async fn save_device_session(&self) -> Result<()> { trace!("Saving device-session"); - device_session::save(&self.device_session) - .await - .context("Save device-session")?; + device::partial_update( + self.device.dev_eui, + &device::DeviceChangeset { + device_session: Some(Some(self.device_session.encode_to_vec())), + ..Default::default() + }, + ) + .await?; + Ok(()) } @@ -1524,7 +1531,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. @@ -1583,7 +1591,8 @@ impl Data { continue; } - 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. @@ -2412,9 +2421,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(()) } @@ -2465,9 +2479,14 @@ 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?; + self.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. @@ -3380,15 +3399,15 @@ 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(), + dev_eui: dev_eui.to_vec(), + nwk_s_enc_key: vec![0; 16], + ..Default::default() + } + .encode_to_vec(), + ), ..Default::default() }) .await diff --git a/chirpstack/src/downlink/tx_ack.rs b/chirpstack/src/downlink/tx_ack.rs index 7333f378..ab2c289e 100644 --- a/chirpstack/src/downlink/tx_ack.rs +++ b/chirpstack/src/downlink/tx_ack.rs @@ -1,5 +1,6 @@ use anyhow::Result; use chrono::{Duration, Utc}; +use prost::Message; use tracing::{error, info, span, trace, Instrument, Level}; use uuid::Uuid; @@ -9,7 +10,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}; @@ -88,10 +91,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 +99,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 +138,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 +168,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 +217,31 @@ 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.device_session = Some(dev.get_device_session()?); + 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.device_session_relayed = Some(dev.get_device_session()?); + 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(()) } @@ -422,13 +370,36 @@ impl TxAck { async fn save_device_session(&self) -> Result<()> { trace!("Saving device-session"); - device_session::save(self.device_session.as_ref().unwrap()).await?; + + device::partial_update( + self.device.as_ref().unwrap().dev_eui, + &device::DeviceChangeset { + device_session: Some(Some(self.device_session.as_ref().unwrap().encode_to_vec())), + ..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?; + + device::partial_update( + self.device.as_ref().unwrap().dev_eui, + &device::DeviceChangeset { + device_session: Some(Some( + self.device_session_relayed + .as_ref() + .unwrap() + .encode_to_vec(), + )), + ..Default::default() + }, + ) + .await?; + Ok(()) } diff --git a/chirpstack/src/maccommand/ctrl_uplink_list.rs b/chirpstack/src/maccommand/ctrl_uplink_list.rs index 4cd7d70c..26d5ef85 100644 --- a/chirpstack/src/maccommand/ctrl_uplink_list.rs +++ b/chirpstack/src/maccommand/ctrl_uplink_list.rs @@ -1,9 +1,10 @@ use std::iter::zip; use anyhow::Result; +use prost::Message; use tracing::{info, warn}; -use crate::storage::{device, device_session}; +use crate::storage::device; use chirpstack_api::internal; use lrwn::EUI64; @@ -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 d = device::get(&dev_eui).await?; + let mut ds = d.get_device_session()?; 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(Some(ds.encode_to_vec())), + ..Default::default() + }, + ) + .await?; } } } else if action == 1 { @@ -88,6 +97,7 @@ pub async fn handle( #[cfg(test)] mod test { use super::*; + use crate::storage; use crate::test; struct Test { @@ -104,6 +114,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(), @@ -236,7 +279,15 @@ 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.encode_to_vec())), + ..Default::default() + }, + ) + .await + .unwrap(); let mut ds = tst.device_session.clone(); let resp = handle( @@ -254,10 +305,11 @@ 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(); + let ds = device::get(&dev.dev_eui) + .await + .unwrap() + .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/storage/device.rs b/chirpstack/src/storage/device.rs index 4b3c8fa7..102a6b0c 100644 --- a/chirpstack/src/storage/device.rs +++ b/chirpstack/src/storage/device.rs @@ -119,6 +119,10 @@ pub struct DeviceChangeset { 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 { @@ -128,6 +132,17 @@ impl Device { } Ok(()) } + + pub fn set_device_session(&mut self, ds: &internal::DeviceSession) { + self.device_session = Some(ds.encode_to_vec()) + } + + pub fn get_device_session(&self) -> Result { + match &self.device_session { + None => Err(Error::NotFound(self.dev_eui.to_string())), + Some(v) => Ok(internal::DeviceSession::decode(&mut Cursor::new(&v))?), + } + } } impl Default for Device { @@ -276,16 +291,12 @@ pub async fn get_for_phypayload_and_incr_f_cnt_up( tx_dr: u8, tx_ch: u8, ) -> Result { - let mut dev_addr = lrwn::DevAddr::from_be_bytes([0x00, 0x00, 0x00, 0x00]); - let mut f_cnt_orig = 0; - // Get the dev_addr and original f_cnt. - if let lrwn::Payload::MACPayload(pl) = &phy.payload { - dev_addr = pl.fhdr.devaddr; - f_cnt_orig = pl.fhdr.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?; @@ -299,18 +310,36 @@ pub async fn get_for_phypayload_and_incr_f_cnt_up( .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())); + } + + let mut sessions: Vec<(EUI64, internal::DeviceSession)> = Vec::new(); + for d in &devices { if d.1.is_none() { continue; } - let mut ds = + let ds = internal::DeviceSession::decode(&mut Cursor::new(d.1.as_ref().unwrap()))?; + if let Some(pending_ds) = &ds.pending_rejoin_device_session { + sessions.push((d.0, *pending_ds.clone())); + } + sessions.push((d.0, ds)); + } + + for (dev_eui, 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)?; @@ -354,7 +383,7 @@ pub async fn get_for_phypayload_and_incr_f_cnt_up( if let Some(relay) = &ds.relay { if !relayed && relay.ed_relay_only { info!( - dev_eui = %d.0, + dev_eui = %dev_eui, "Only communication through relay is allowed" ); return Err(Error::NotFound(dev_addr.to_string())); @@ -367,7 +396,7 @@ pub async fn get_for_phypayload_and_incr_f_cnt_up( 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.0)) + let _ = diesel::update(device::dsl::device.find(*dev_eui)) .set(device::device_session.eq(&ds.encode_to_vec())) .execute(c) .await?; @@ -375,16 +404,16 @@ pub async fn get_for_phypayload_and_incr_f_cnt_up( // We do return the device-session with original frame-counter ds.f_cnt_up = ds_f_cnt_up; - return Ok(ValidationStatus::Ok(full_f_cnt, ds)); + return Ok(ValidationStatus::Ok(full_f_cnt, ds.clone())); } 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)); + return Ok(ValidationStatus::Ok(full_f_cnt, ds.clone())); } 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)); + return Ok(ValidationStatus::Retransmission(full_f_cnt, ds.clone())); } else { - return Ok(ValidationStatus::Reset(full_f_cnt, ds)); + return Ok(ValidationStatus::Reset(full_f_cnt, ds.clone())); } } @@ -400,6 +429,74 @@ pub async fn get_for_phypayload_and_incr_f_cnt_up( .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<(EUI64, Option>)> = device::dsl::device + .select((device::dev_eui, device::device_session)) + .filter( + device::dsl::dev_addr + .eq(&dev_addr) + .or(device::dsl::secondary_dev_addr.eq(&dev_addr)), + ) + .for_update() + .load(&mut get_async_db_conn().await?) + .await?; + + if devices.is_empty() { + return Err(Error::NotFound(dev_addr.to_string())); + } + + for d in &devices { + if d.1.is_none() { + continue; + } + + let ds = internal::DeviceSession::decode(&mut Cursor::new(&d.1.as_ref().unwrap()))?; + + // 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(ds); + } + + // 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()?; @@ -434,83 +531,6 @@ pub async fn partial_update(dev_eui: EUI64, d: &DeviceChangeset) -> Result 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?) - .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"); - Ok(d) -} - pub async fn delete(dev_eui: &EUI64) -> Result<(), Error> { let ra = diesel::delete(device::dsl::device.find(&dev_eui)) .execute(&mut get_async_db_conn().await?) @@ -733,6 +753,7 @@ pub mod test { use crate::storage; use crate::storage::device_queue; use crate::test; + use lrwn::AES128Key; struct FilterTest<'a> { filters: Filters, @@ -893,13 +914,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()); @@ -909,7 +954,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(); @@ -923,4 +976,372 @@ 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 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() + }, + ]; + + 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(); + + for ds in &device_sessions { + create(Device { + dev_eui: EUI64::from_slice(&ds.dev_eui).unwrap(), + dev_addr: Some(DevAddr::from_slice(&ds.dev_addr).unwrap()), + secondary_dev_addr: ds + .pending_rejoin_device_session + .as_ref() + .map(|v| DevAddr::from_slice(&v.dev_addr).unwrap()), + name: hex::encode(&ds.dev_eui), + application_id: app.id, + device_profile_id: dp.id, + device_session: Some(ds.encode_to_vec()), + ..Default::default() + }) + .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() + ); + } + } + } + } } diff --git a/chirpstack/src/storage/device_session.rs b/chirpstack/src/storage/device_session.rs index 5a653dbc..96393202 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]); - } -} +} \ No newline at end of file diff --git a/chirpstack/src/test/assert.rs b/chirpstack/src/test/assert.rs index c0c5aeb9..42f1d491 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,11 @@ 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 ds = device::get(&dev_eui) + .await + .unwrap() + .get_device_session() + .unwrap(); assert_eq!(f_cnt, ds.f_cnt_up); }) }) @@ -37,7 +41,11 @@ 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 ds = device::get(&dev_eui) + .await + .unwrap() + .get_device_session() + .unwrap(); assert_eq!(f_cnt, ds.n_f_cnt_down); }) }) @@ -47,7 +55,11 @@ 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 ds = device::get(&dev_eui) + .await + .unwrap() + .get_device_session() + .unwrap(); assert_eq!(f_cnt, ds.a_f_cnt_down); }) }) @@ -57,7 +69,11 @@ 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 ds = device::get(&dev_eui) + .await + .unwrap() + .get_device_session() + .unwrap(); assert_eq!(tx_power, ds.tx_power_index); }) }) @@ -67,7 +83,11 @@ 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 ds = device::get(&dev_eui) + .await + .unwrap() + .get_device_session() + .unwrap(); assert_eq!(nb_trans, ds.nb_trans); }) }) @@ -78,7 +98,11 @@ 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 ds = device::get(&dev_eui) + .await + .unwrap() + .get_device_session() + .unwrap(); assert_eq!(channels, ds.enabled_uplink_channel_indices); }) }) @@ -88,7 +112,11 @@ 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 ds = device::get(&dev_eui) + .await + .unwrap() + .get_device_session() + .unwrap(); assert_eq!(dr, ds.dr); }) }) @@ -98,7 +126,11 @@ 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 ds = device::get(&dev_eui) + .await + .unwrap() + .get_device_session() + .unwrap(); assert_eq!( count, ds.mac_command_error_count @@ -115,7 +147,11 @@ 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 ds = device::get(&dev_eui) + .await + .unwrap() + .get_device_session() + .unwrap(); assert_eq!(uh, ds.uplink_adr_history); }) }) diff --git a/chirpstack/src/test/class_a_pr_test.rs b/chirpstack/src/test/class_a_pr_test.rs index 93c56fa6..6ad54c83 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,46 @@ 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 { + 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() + } + .encode_to_vec(), + ), ..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 +268,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, @@ -466,41 +470,45 @@ 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 { + 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() + } + .encode_to_vec(), + ), ..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..5d4112c6 100644 --- a/chirpstack/src/test/class_a_test.rs +++ b/chirpstack/src/test/class_a_test.rs @@ -2,22 +2,24 @@ use std::future::Future; use std::pin::Pin; use std::time::Duration; +use prost::Message; use uuid::Uuid; 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 +95,33 @@ 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 { + 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() + } + .encode_to_vec(), + ), ..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 +152,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 +182,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, @@ -257,6 +266,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 @@ -312,6 +322,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 +349,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 +422,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 @@ -459,6 +472,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 +505,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 +537,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 +621,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 @@ -660,6 +677,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 +704,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 +784,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 @@ -809,6 +829,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 +878,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 +980,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 @@ -1000,6 +1023,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 +1105,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 @@ -1134,6 +1159,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 +1210,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 +1282,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 +1361,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 +1412,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 +1524,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 +1635,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 +1744,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 @@ -1766,6 +1811,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 +1862,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 +1916,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 +2076,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 @@ -2082,6 +2131,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 +2182,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 +2317,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 @@ -2318,6 +2370,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 +2532,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 @@ -2532,6 +2586,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 +2679,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 +2777,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 +2905,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 @@ -2928,6 +2986,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 +3101,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 @@ -3089,6 +3149,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 +3227,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 +3318,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 +3397,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 +3434,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 +3580,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 @@ -3563,6 +3629,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 +3707,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 +3798,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 +3877,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 +3914,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 +4063,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 @@ -4057,6 +4129,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 +4233,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 +4267,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 +4335,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 +4403,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 +4480,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 +4580,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 +4646,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 +4714,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 +4819,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 +4917,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 @@ -4887,6 +4970,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 +5047,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 +5078,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 +5184,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 @@ -5155,6 +5242,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 +5312,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 +5382,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 +5481,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 +5588,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 +5596,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.as_ref().map(|v| v.encode_to_vec())), + ..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..6c4ccecc 100644 --- a/chirpstack/src/test/class_b_test.rs +++ b/chirpstack/src/test/class_b_test.rs @@ -1,3 +1,4 @@ +use prost::Message; use uuid::Uuid; use super::assert; @@ -5,7 +6,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 +17,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 +28,7 @@ struct UplinkTest { struct DownlinkTest { name: String, + dev_eui: EUI64, device_queue_items: Vec, device_session: Option, device_gateway_rx_info: Option, @@ -80,6 +83,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 @@ -130,6 +134,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 +169,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,6 +250,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::B, + dev_addr: Some(DevAddr::from_be_bytes([1, 2, 3, 4])), ..Default::default() }) .await @@ -291,6 +298,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 +350,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 +365,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 +450,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.as_ref().map(|v| v.encode_to_vec())), + ..Default::default() + }, + ) + .await + .unwrap(); for qi in &t.device_queue_items { let _ = device_queue::enqueue_item(qi.clone()).await.unwrap(); @@ -471,13 +491,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.as_ref().map(|v| v.encode_to_vec())), + ..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..ba3c60b5 100644 --- a/chirpstack/src/test/class_c_test.rs +++ b/chirpstack/src/test/class_c_test.rs @@ -1,10 +1,11 @@ +use prost::Message; use uuid::Uuid; 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}; @@ -12,6 +13,7 @@ use lrwn::EUI64; struct DownlinkTest { name: String, + dev_eui: EUI64, device_queue_items: Vec, device_session: Option, device_gateway_rx_info: Option, @@ -112,6 +114,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 +129,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 +188,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 +203,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 +264,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 +301,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.as_ref().map(|v| v.encode_to_vec())), + ..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_test.rs b/chirpstack/src/test/otaa_test.rs index 6760b0dc..115edf30 100644 --- a/chirpstack/src/test/otaa_test.rs +++ b/chirpstack/src/test/otaa_test.rs @@ -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 { @@ -198,6 +200,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 +378,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 +391,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 { @@ -576,6 +581,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 { @@ -633,6 +639,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 { @@ -786,6 +793,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 +821,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 +849,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 +1007,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 +1020,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 { @@ -1189,6 +1201,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 +1237,6 @@ async fn test_lorawan_11() { async fn run_test(t: &Test) { println!("> {}", t.name); - reset_db().await.unwrap(); - let mut conf: config::Configuration = (*config::get()).clone(); for f in &t.extra_uplink_channels { conf.regions[0] @@ -1246,6 +1257,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..c23a9938 100644 --- a/chirpstack/src/test/relay_class_a_test.rs +++ b/chirpstack/src/test/relay_class_a_test.rs @@ -1,19 +1,22 @@ use std::time::Duration; +use prost::Message; use uuid::Uuid; 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 +88,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 +100,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 @@ -439,6 +444,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 +510,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 +636,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 +776,34 @@ 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.as_ref().map(|v| v.encode_to_vec())), + ..Default::default() + }, + ) + .await + .unwrap(); + device::partial_update( + t.dev_eui_relay_ed, + &device::DeviceChangeset { + device_session: Some( + t.device_session_relay_ed + .as_ref() + .map(|v| v.encode_to_vec()), + ), + ..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..369fb2be 100644 --- a/chirpstack/src/test/relay_otaa_test.rs +++ b/chirpstack/src/test/relay_otaa_test.rs @@ -1,16 +1,17 @@ use std::time::Duration; +use prost::Message; use uuid::Uuid; 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 +97,30 @@ 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 { + dev_eui: vec![1, 1, 1, 1, 1, 1, 1, 2], + 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() + } + .encode_to_vec(), + ), ..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(), diff --git a/chirpstack/src/uplink/data.rs b/chirpstack/src/uplink/data.rs index 2c1b5081..e753d374 100644 --- a/chirpstack/src/uplink/data.rs +++ b/chirpstack/src/uplink/data.rs @@ -18,7 +18,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, }; @@ -123,7 +123,6 @@ 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?; @@ -193,7 +192,6 @@ impl Data { ctx.get_device_session_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?; @@ -210,6 +208,7 @@ impl Data { 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?; @@ -312,25 +311,20 @@ 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) => { + device::ValidationStatus::Ok(f_cnt, ds) => { self.device_session = Some(ds); self.f_cnt_up_full = f_cnt; } - device_session::ValidationStatus::Retransmission(f_cnt, ds) => { + device::ValidationStatus::Retransmission(f_cnt, ds) => { self.retransmission = true; self.device_session = Some(ds); self.f_cnt_up_full = f_cnt; } - device_session::ValidationStatus::Reset(f_cnt, ds) => { + device::ValidationStatus::Reset(f_cnt, ds) => { self.reset = true; self.device_session = Some(ds); self.f_cnt_up_full = f_cnt; @@ -474,24 +468,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(); @@ -560,8 +536,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?; } } @@ -580,9 +562,16 @@ 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")?; + device::partial_update( + self.device.as_ref().unwrap().dev_eui, + &device::DeviceChangeset { + device_session: Some(Some( + self.device_session.as_ref().unwrap().encode_to_vec(), + )), + ..Default::default() + }, + ) + .await?; Err(v) } @@ -670,8 +659,12 @@ 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 device = self.device.as_ref().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 = self.device_session.as_mut().unwrap(); // The node changed its data-rate. Possibly the node did also reset its @@ -682,6 +675,7 @@ impl Data { ds.uplink_adr_history = Vec::new(); } ds.dr = self.uplink_frame_set.dr as u32; + Ok(()) } @@ -689,7 +683,11 @@ 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?; + + 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 = self.device_session.as_mut().unwrap(); // The node changed its data-rate. Possibly the node did also reset its @@ -722,7 +720,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(()) diff --git a/chirpstack/src/uplink/join_sns.rs b/chirpstack/src/uplink/join_sns.rs index 4ccc6ef5..2ee46c0b 100644 --- a/chirpstack/src/uplink/join_sns.rs +++ b/chirpstack/src/uplink/join_sns.rs @@ -2,6 +2,7 @@ use std::sync::Arc; use anyhow::{Context, Result}; use chrono::{DateTime, Local, Utc}; +use prost::Message; use tracing::{span, trace, Instrument, Level}; use super::{error::Error, helpers, UplinkFrameSet}; @@ -10,7 +11,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, @@ -101,8 +102,7 @@ impl JoinRequest { ctx.log_uplink_meta().await?; ctx.create_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()?; @@ -627,10 +627,6 @@ impl JoinRequest { } } - device_session::save(&ds) - .await - .context("Saving device-session failed")?; - self.device_session = Some(ds); Ok(()) @@ -648,25 +644,32 @@ 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(); - // 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( + self.device_session.as_ref().unwrap().encode_to_vec(), + )), + 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(()) }