mirror of
https://github.com/chirpstack/chirpstack.git
synced 2025-03-15 00:36:33 +00:00
Update fuota + device-keys structs / storage.
This add the gen_app_key to the device keys which is needed for FUOTA and adds a random multicast address + key to the fuota deployment. To the FUOTA job structure, this adds a return msg such that errors can be captured in the database.
This commit is contained in:
parent
aa11db15de
commit
8e47ea1483
5
api/proto/api/device.proto
vendored
5
api/proto/api/device.proto
vendored
@ -278,6 +278,11 @@ message DeviceKeys {
|
||||
// Application root key (128 bit).
|
||||
// Note: This field only needs to be set for LoRaWAN 1.1.x devices!
|
||||
string app_key = 3;
|
||||
|
||||
// Gen App Key (128 bit).
|
||||
// Note: This field only needs to be set for LoRaWAN 1.0.x devices that
|
||||
// implement TS005 (remote multicast setup).
|
||||
string gen_app_key = 4;
|
||||
}
|
||||
|
||||
message CreateDeviceRequest {
|
||||
|
5
api/rust/proto/chirpstack/api/device.proto
vendored
5
api/rust/proto/chirpstack/api/device.proto
vendored
@ -278,6 +278,11 @@ message DeviceKeys {
|
||||
// Application root key (128 bit).
|
||||
// Note: This field only needs to be set for LoRaWAN 1.1.x devices!
|
||||
string app_key = 3;
|
||||
|
||||
// Gen App Key (128 bit).
|
||||
// Note: This field only needs to be set for LoRaWAN 1.0.x devices that
|
||||
// implement TS005 (remote multicast setup).
|
||||
string gen_app_key = 4;
|
||||
}
|
||||
|
||||
message CreateDeviceRequest {
|
||||
|
@ -1,3 +1,6 @@
|
||||
alter table device_keys
|
||||
drop column gen_app_key;
|
||||
|
||||
drop table fuota_deployment_job;
|
||||
drop table fuota_deployment_gateway;
|
||||
drop table fuota_deployment_device;
|
||||
|
@ -7,6 +7,8 @@ create table fuota_deployment (
|
||||
name varchar(100) not null,
|
||||
application_id uuid not null references application on delete cascade,
|
||||
device_profile_id uuid not null references device_profile on delete cascade,
|
||||
multicast_addr bytea not null,
|
||||
multicast_key bytea not null,
|
||||
multicast_group_type char(1) not null,
|
||||
multicast_class_c_scheduling_type varchar(20) not null,
|
||||
multicast_dr smallint not null,
|
||||
@ -54,9 +56,16 @@ create table fuota_deployment_job (
|
||||
max_retry_count smallint not null,
|
||||
attempt_count smallint not null,
|
||||
scheduler_run_after timestamp with time zone not null,
|
||||
return_msg text not null,
|
||||
|
||||
primary key (fuota_deployment_id, job)
|
||||
);
|
||||
|
||||
create index idx_fuota_deployment_job_completed_at on fuota_deployment_job(completed_at);
|
||||
create index idx_fuota_deployment_job_scheduler_run_after on fuota_deployment_job(scheduler_run_after);
|
||||
|
||||
alter table device_keys
|
||||
add column gen_app_key bytea not null default decode('00000000000000000000000000000000', 'hex');
|
||||
|
||||
alter table device_keys
|
||||
alter column gen_app_key drop default;
|
||||
|
@ -1,3 +1,6 @@
|
||||
alter table device_keys
|
||||
drop column gen_app_key;
|
||||
|
||||
drop table fuota_deployment_job;
|
||||
drop table fuota_deployment_gateway;
|
||||
drop table fuota_deployment_device;
|
||||
|
@ -7,6 +7,8 @@ create table fuota_deployment (
|
||||
name varchar(100) not null,
|
||||
application_id text not null references application on delete cascade,
|
||||
device_profile_id text not null references device_profile on delete cascade,
|
||||
multicast_addr blob not null,
|
||||
multicast_key blob not null,
|
||||
multicast_group_type char(1) not null,
|
||||
multicast_class_c_scheduling_type varchar(20) not null,
|
||||
multicast_dr smallint not null,
|
||||
@ -54,9 +56,13 @@ create table fuota_deployment_job (
|
||||
max_retry_count smallint not null,
|
||||
attempt_count smallint not null,
|
||||
scheduler_run_after datetime not null,
|
||||
return_msg text not null,
|
||||
|
||||
primary key (fuota_deployment_id, job)
|
||||
);
|
||||
|
||||
create index idx_fuota_deployment_job_completed_at on fuota_deployment_job(completed_at);
|
||||
create index idx_fuota_deployment_job_scheduler_run_after on fuota_deployment_job(scheduler_run_after);
|
||||
|
||||
alter table device_keys
|
||||
add column gen_app_key blob not null default x'00000000000000000000000000000000';
|
||||
|
@ -354,11 +354,8 @@ impl DeviceService for Device {
|
||||
let dk = device_keys::DeviceKeys {
|
||||
dev_eui,
|
||||
nwk_key: AES128Key::from_str(&req_dk.nwk_key).map_err(|e| e.status())?,
|
||||
app_key: if !req_dk.app_key.is_empty() {
|
||||
AES128Key::from_str(&req_dk.app_key).map_err(|e| e.status())?
|
||||
} else {
|
||||
AES128Key::null()
|
||||
},
|
||||
app_key: AES128Key::from_str(&req_dk.app_key).map_err(|e| e.status())?,
|
||||
gen_app_key: AES128Key::from_str(&req_dk.gen_app_key).map_err(|e| e.status())?,
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
@ -392,6 +389,7 @@ impl DeviceService for Device {
|
||||
dev_eui: dk.dev_eui.to_string(),
|
||||
nwk_key: dk.nwk_key.to_string(),
|
||||
app_key: dk.app_key.to_string(),
|
||||
gen_app_key: dk.gen_app_key.to_string(),
|
||||
}),
|
||||
created_at: Some(helpers::datetime_to_prost_timestamp(&dk.created_at)),
|
||||
updated_at: Some(helpers::datetime_to_prost_timestamp(&dk.updated_at)),
|
||||
@ -428,11 +426,8 @@ impl DeviceService for Device {
|
||||
dev_nonces: dk.dev_nonces,
|
||||
join_nonce: dk.join_nonce,
|
||||
nwk_key: AES128Key::from_str(&req_dk.nwk_key).map_err(|e| e.status())?,
|
||||
app_key: if !req_dk.app_key.is_empty() {
|
||||
AES128Key::from_str(&req_dk.app_key).map_err(|e| e.status())?
|
||||
} else {
|
||||
AES128Key::null()
|
||||
},
|
||||
app_key: AES128Key::from_str(&req_dk.app_key).map_err(|e| e.status())?,
|
||||
gen_app_key: AES128Key::from_str(&req_dk.gen_app_key).map_err(|e| e.status())?,
|
||||
..Default::default()
|
||||
};
|
||||
let _ = device_keys::update(dk).await.map_err(|e| e.status())?;
|
||||
@ -1393,6 +1388,7 @@ pub mod test {
|
||||
dev_eui: "0102030405060708".into(),
|
||||
nwk_key: "01020304050607080102030405060708".into(),
|
||||
app_key: "02020304050607080202030405060708".into(),
|
||||
gen_app_key: "03020304050607080202030405060708".into(),
|
||||
}),
|
||||
},
|
||||
);
|
||||
@ -1411,6 +1407,7 @@ pub mod test {
|
||||
dev_eui: "0102030405060708".into(),
|
||||
nwk_key: "01020304050607080102030405060708".into(),
|
||||
app_key: "02020304050607080202030405060708".into(),
|
||||
gen_app_key: "03020304050607080202030405060708".into(),
|
||||
}),
|
||||
get_keys_resp.get_ref().device_keys
|
||||
);
|
||||
@ -1423,6 +1420,7 @@ pub mod test {
|
||||
dev_eui: "0102030405060708".into(),
|
||||
nwk_key: "01020304050607080102030405060708".into(),
|
||||
app_key: "03020304050607080302030405060708".into(),
|
||||
gen_app_key: "03020304050607080202030405060708".into(),
|
||||
}),
|
||||
},
|
||||
);
|
||||
@ -1441,6 +1439,7 @@ pub mod test {
|
||||
dev_eui: "0102030405060708".into(),
|
||||
nwk_key: "01020304050607080102030405060708".into(),
|
||||
app_key: "03020304050607080302030405060708".into(),
|
||||
gen_app_key: "03020304050607080202030405060708".into(),
|
||||
}),
|
||||
get_keys_resp.get_ref().device_keys
|
||||
);
|
||||
|
@ -8,9 +8,11 @@ use chirpstack_api::api;
|
||||
use chirpstack_api::api::fuota_service_server::FuotaService;
|
||||
use lrwn::EUI64;
|
||||
|
||||
use crate::aeskey::get_random_aes_key;
|
||||
use crate::api::auth::validator;
|
||||
use crate::api::error::ToStatus;
|
||||
use crate::api::helpers::{self, FromProto, ToProto};
|
||||
use crate::devaddr::get_random_dev_addr;
|
||||
use crate::storage::{fields, fuota};
|
||||
|
||||
pub struct Fuota {
|
||||
@ -50,6 +52,8 @@ impl FuotaService for Fuota {
|
||||
name: req_dp.name.clone(),
|
||||
application_id: app_id.into(),
|
||||
device_profile_id: dp_id.into(),
|
||||
multicast_addr: get_random_dev_addr(),
|
||||
multicast_key: get_random_aes_key(),
|
||||
multicast_group_type: match req_dp.multicast_group_type() {
|
||||
api::MulticastGroupType::ClassB => "B",
|
||||
api::MulticastGroupType::ClassC => "C",
|
||||
@ -284,8 +288,7 @@ impl FuotaService for Fuota {
|
||||
|
||||
fuota::create_job(fuota::FuotaDeploymentJob {
|
||||
fuota_deployment_id: d.id,
|
||||
job: fields::FuotaJob::McGroupSetup,
|
||||
max_retry_count: d.unicast_max_retry_count,
|
||||
job: fields::FuotaJob::CreateMcGroup,
|
||||
..Default::default()
|
||||
})
|
||||
.await
|
||||
@ -733,7 +736,7 @@ mod test {
|
||||
.unwrap();
|
||||
assert_eq!(1, jobs.len());
|
||||
assert_eq!(create_resp.id, jobs[0].fuota_deployment_id.to_string());
|
||||
assert_eq!(fields::FuotaJob::McGroupSetup, jobs[0].job);
|
||||
assert_eq!(fields::FuotaJob::CreateMcGroup, jobs[0].job);
|
||||
|
||||
// add device
|
||||
let add_dev_req = get_request(
|
||||
|
@ -19,7 +19,9 @@ use tracing_subscriber::{filter, prelude::*};
|
||||
use lrwn::EUI64;
|
||||
|
||||
mod adr;
|
||||
mod aeskey;
|
||||
mod api;
|
||||
mod applayer;
|
||||
mod backend;
|
||||
mod certificate;
|
||||
mod cmd;
|
||||
|
@ -1140,6 +1140,8 @@ pub mod test {
|
||||
count: 1,
|
||||
limit: 10,
|
||||
offset: 0,
|
||||
order: OrderBy::Name,
|
||||
order_by_desc: false,
|
||||
},
|
||||
FilterTest {
|
||||
name: "filter by tags - 1.2.0".into(),
|
||||
@ -1154,6 +1156,8 @@ pub mod test {
|
||||
count: 1,
|
||||
limit: 10,
|
||||
offset: 0,
|
||||
order: OrderBy::Name,
|
||||
order_by_desc: false,
|
||||
},
|
||||
];
|
||||
|
||||
|
@ -20,6 +20,7 @@ pub struct DeviceKeys {
|
||||
pub app_key: AES128Key,
|
||||
pub dev_nonces: fields::DevNonces,
|
||||
pub join_nonce: i32,
|
||||
pub gen_app_key: AES128Key,
|
||||
}
|
||||
|
||||
impl Default for DeviceKeys {
|
||||
@ -27,19 +28,14 @@ impl Default for DeviceKeys {
|
||||
let now = Utc::now();
|
||||
|
||||
DeviceKeys {
|
||||
dev_eui: EUI64::from_be_bytes([0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]),
|
||||
dev_eui: Default::default(),
|
||||
created_at: now,
|
||||
updated_at: now,
|
||||
nwk_key: AES128Key::from_bytes([
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00,
|
||||
]),
|
||||
app_key: AES128Key::from_bytes([
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00,
|
||||
]),
|
||||
dev_nonces: fields::DevNonces::default(),
|
||||
nwk_key: Default::default(),
|
||||
app_key: Default::default(),
|
||||
dev_nonces: Default::default(),
|
||||
join_nonce: 0,
|
||||
gen_app_key: Default::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -83,6 +83,9 @@ impl serialize::ToSql<Text, Sqlite> for RequestFragmentationSessionStatus {
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, AsExpression, FromSqlRow)]
|
||||
#[diesel(sql_type = Text)]
|
||||
pub enum FuotaJob {
|
||||
CreateMcGroup,
|
||||
AddDevsToMcGroup,
|
||||
AddGwsToMcGroup,
|
||||
McGroupSetup,
|
||||
McSession,
|
||||
FragSessionSetup,
|
||||
@ -99,6 +102,9 @@ impl fmt::Display for FuotaJob {
|
||||
impl From<&FuotaJob> for String {
|
||||
fn from(value: &FuotaJob) -> Self {
|
||||
match value {
|
||||
FuotaJob::CreateMcGroup => "CREATE_MC_GROUP",
|
||||
FuotaJob::AddDevsToMcGroup => "ADD_DEVS_TO_MC_GROUP",
|
||||
FuotaJob::AddGwsToMcGroup => "ADD_GWS_TO_MC_GROUP",
|
||||
FuotaJob::McGroupSetup => "MC_GROUP_SETUP",
|
||||
FuotaJob::McSession => "MC_SESSION",
|
||||
FuotaJob::FragSessionSetup => "FRAG_SESSION_SETUP",
|
||||
@ -114,6 +120,9 @@ impl TryFrom<&str> for FuotaJob {
|
||||
|
||||
fn try_from(value: &str) -> Result<Self, Self::Error> {
|
||||
Ok(match value {
|
||||
"CREATE_MC_GROUP" => Self::CreateMcGroup,
|
||||
"ADD_DEVS_TO_MC_GROUP" => Self::AddDevsToMcGroup,
|
||||
"ADD_GWS_TO_MC_GROUP" => Self::AddGwsToMcGroup,
|
||||
"MC_GROUP_SETUP" => Self::McGroupSetup,
|
||||
"MC_SESSION" => Self::McSession,
|
||||
"FRAG_SESSION_SETUP" => Self::FragSessionSetup,
|
||||
|
@ -13,7 +13,7 @@ use crate::storage::schema::{
|
||||
fuota_deployment_job, gateway, tenant,
|
||||
};
|
||||
use crate::storage::{self, db_transaction, device_profile, fields, get_async_db_conn};
|
||||
use lrwn::EUI64;
|
||||
use lrwn::{AES128Key, DevAddr, EUI64};
|
||||
|
||||
#[derive(Clone, Queryable, Insertable, Debug, PartialEq, Eq, Validate)]
|
||||
#[diesel(table_name = fuota_deployment)]
|
||||
@ -26,6 +26,8 @@ pub struct FuotaDeployment {
|
||||
pub name: String,
|
||||
pub application_id: fields::Uuid,
|
||||
pub device_profile_id: fields::Uuid,
|
||||
pub multicast_addr: DevAddr,
|
||||
pub multicast_key: AES128Key,
|
||||
pub multicast_group_type: String,
|
||||
pub multicast_class_c_scheduling_type: fields::MulticastGroupSchedulingType,
|
||||
pub multicast_dr: i16,
|
||||
@ -56,6 +58,8 @@ impl Default for FuotaDeployment {
|
||||
name: "".into(),
|
||||
application_id: Uuid::nil().into(),
|
||||
device_profile_id: Uuid::nil().into(),
|
||||
multicast_addr: Default::default(),
|
||||
multicast_key: Default::default(),
|
||||
multicast_group_type: "".into(),
|
||||
multicast_class_c_scheduling_type: fields::MulticastGroupSchedulingType::DELAY,
|
||||
multicast_dr: 0,
|
||||
@ -144,6 +148,7 @@ pub struct FuotaDeploymentJob {
|
||||
pub max_retry_count: i16,
|
||||
pub attempt_count: i16,
|
||||
pub scheduler_run_after: DateTime<Utc>,
|
||||
pub return_msg: String,
|
||||
}
|
||||
|
||||
impl Default for FuotaDeploymentJob {
|
||||
@ -158,6 +163,7 @@ impl Default for FuotaDeploymentJob {
|
||||
max_retry_count: 0,
|
||||
attempt_count: 0,
|
||||
scheduler_run_after: now,
|
||||
return_msg: "".into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -324,15 +330,21 @@ pub async fn get_devices(
|
||||
limit: i64,
|
||||
offset: i64,
|
||||
) -> Result<Vec<FuotaDeploymentDevice>, Error> {
|
||||
fuota_deployment_device::dsl::fuota_deployment_device
|
||||
let mut q = fuota_deployment_device::dsl::fuota_deployment_device
|
||||
.filter(
|
||||
fuota_deployment_device::dsl::fuota_deployment_id
|
||||
.eq(fields::Uuid::from(fuota_deployment_id)),
|
||||
)
|
||||
.order_by(fuota_deployment_device::dsl::dev_eui)
|
||||
.limit(limit)
|
||||
.offset(offset)
|
||||
.load(&mut get_async_db_conn().await?)
|
||||
.into_boxed();
|
||||
|
||||
if limit != -1 {
|
||||
q = q
|
||||
.order_by(fuota_deployment_device::dsl::dev_eui)
|
||||
.limit(limit)
|
||||
.offset(offset);
|
||||
}
|
||||
|
||||
q.load(&mut get_async_db_conn().await?)
|
||||
.await
|
||||
.map_err(|e| Error::from_diesel(e, "".into()))
|
||||
}
|
||||
@ -449,15 +461,21 @@ pub async fn get_gateways(
|
||||
limit: i64,
|
||||
offset: i64,
|
||||
) -> Result<Vec<FuotaDeploymentGateway>, Error> {
|
||||
fuota_deployment_gateway::dsl::fuota_deployment_gateway
|
||||
let mut q = fuota_deployment_gateway::dsl::fuota_deployment_gateway
|
||||
.filter(
|
||||
fuota_deployment_gateway::dsl::fuota_deployment_id
|
||||
.eq(fields::Uuid::from(fuota_deployment_id)),
|
||||
)
|
||||
.order_by(fuota_deployment_gateway::dsl::gateway_id)
|
||||
.limit(limit)
|
||||
.offset(offset)
|
||||
.load(&mut get_async_db_conn().await?)
|
||||
.into_boxed();
|
||||
|
||||
if limit != -1 {
|
||||
q = q
|
||||
.order_by(fuota_deployment_gateway::dsl::gateway_id)
|
||||
.limit(limit)
|
||||
.offset(offset);
|
||||
}
|
||||
|
||||
q.load(&mut get_async_db_conn().await?)
|
||||
.await
|
||||
.map_err(|e| Error::from_diesel(e, "".into()))
|
||||
}
|
||||
@ -501,6 +519,7 @@ pub async fn update_job(j: FuotaDeploymentJob) -> Result<FuotaDeploymentJob, Err
|
||||
fuota_deployment_job::completed_at.eq(&j.completed_at),
|
||||
fuota_deployment_job::attempt_count.eq(&j.attempt_count),
|
||||
fuota_deployment_job::scheduler_run_after.eq(&j.scheduler_run_after),
|
||||
fuota_deployment_job::return_msg.eq(&j.return_msg),
|
||||
))
|
||||
.get_result(&mut get_async_db_conn().await?)
|
||||
.await
|
||||
@ -636,7 +655,7 @@ pub fn get_multicast_timeout(d: &FuotaDeployment) -> Result<usize> {
|
||||
// Multiply by the number of fragments (+1 for additional margin).
|
||||
let mc_class_c_duration_secs = mc_class_c_margin_secs * (total_fragments + 1 as usize);
|
||||
|
||||
// Calculate the timeout value. In case of Class-B, timeout is defined as seconds,
|
||||
// Calculate the timeout value. In case of Class-C, timeout is defined as seconds,
|
||||
// where the number of seconds is 2^timeout.
|
||||
for i in 0..16 {
|
||||
// i = 0-15
|
||||
@ -1115,7 +1134,7 @@ mod test {
|
||||
payload: vec![0; 10],
|
||||
..Default::default()
|
||||
},
|
||||
expected_timeout: 3,
|
||||
expected_timeout: 4,
|
||||
expected_error: None,
|
||||
},
|
||||
];
|
||||
|
@ -77,6 +77,7 @@ diesel::table! {
|
||||
app_key -> Bytea,
|
||||
dev_nonces -> Jsonb,
|
||||
join_nonce -> Int4,
|
||||
gen_app_key -> Bytea,
|
||||
}
|
||||
}
|
||||
|
||||
@ -192,6 +193,8 @@ diesel::table! {
|
||||
name -> Varchar,
|
||||
application_id -> Uuid,
|
||||
device_profile_id -> Uuid,
|
||||
multicast_addr -> Bytea,
|
||||
multicast_key -> Bytea,
|
||||
#[max_length = 1]
|
||||
multicast_group_type -> Bpchar,
|
||||
#[max_length = 20]
|
||||
@ -244,6 +247,7 @@ diesel::table! {
|
||||
max_retry_count -> Int2,
|
||||
attempt_count -> Int2,
|
||||
scheduler_run_after -> Timestamptz,
|
||||
return_msg -> Text,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -72,6 +72,7 @@ diesel::table! {
|
||||
app_key -> Binary,
|
||||
dev_nonces -> Text,
|
||||
join_nonce -> Integer,
|
||||
gen_app_key -> Binary,
|
||||
}
|
||||
}
|
||||
|
||||
@ -171,6 +172,8 @@ diesel::table! {
|
||||
name -> Text,
|
||||
application_id -> Text,
|
||||
device_profile_id -> Text,
|
||||
multicast_addr -> Binary,
|
||||
multicast_key -> Binary,
|
||||
multicast_group_type -> Text,
|
||||
multicast_class_c_scheduling_type -> Text,
|
||||
multicast_dr -> SmallInt,
|
||||
@ -219,6 +222,7 @@ diesel::table! {
|
||||
max_retry_count -> SmallInt,
|
||||
attempt_count -> SmallInt,
|
||||
scheduler_run_after -> TimestamptzSqlite,
|
||||
return_msg -> Text,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -37,6 +37,7 @@ function LW10DeviceKeysForm(props: FormProps) {
|
||||
// NOTE: this is not an error! In the LoRaWAN 1.1 specs, the what was previously
|
||||
// the AppKey has been renamed to the NwkKey and a new value AppKey was added.
|
||||
dk.setNwkKey(v.nwkKey);
|
||||
dk.setGenAppKey(v.genAppKey);
|
||||
|
||||
props.onFinish(dk);
|
||||
};
|
||||
@ -56,6 +57,12 @@ function LW10DeviceKeysForm(props: FormProps) {
|
||||
value={props.initialValues.getNwkKey()}
|
||||
required
|
||||
/>
|
||||
<AesKeyInput
|
||||
label="Gen App Key (for Remote Multicast Setup)"
|
||||
name="genAppKey"
|
||||
tooltip="For LoRaWAN 1.0 devices. In case your device supports LoRaWAN 1.1, update the device-profile first."
|
||||
value={props.initialValues.getGenAppKey()}
|
||||
/>
|
||||
<Form.Item>
|
||||
<Button type="primary" htmlType="submit">
|
||||
Submit
|
||||
|
Loading…
x
Reference in New Issue
Block a user