Compare commits

..

5 Commits

Author SHA1 Message Date
411cd681a9 Add cmd to migrate ds from Redis to Pg. 2024-02-22 12:44:14 +00:00
8a986d04ce Fix tests after device-session refactor. 2024-02-21 16:57:03 +00:00
da6b7e1b37 Use Device.device_session field for DS (WIP).
This is work-in-progress, still need to refactor all the tests.
2024-02-21 13:30:08 +00:00
fae182aa3d Migrate code to store device-session in PG (WIP). 2024-02-19 15:27:25 +00:00
5c3624cfbe Work-in-progress test.
This is work-in-progress and only contains a partial implementation.
Downlink (other than OTAA) is not yet implemented. Therefore you should
disable ADR in the region_.toml config when testing.
2024-02-07 15:06:28 +00:00
53 changed files with 193 additions and 161 deletions

View File

@ -32,7 +32,7 @@ jobs:
key: ${{ runner.os }}-cargo-test-${{ hashFiles('**/Cargo.lock') }} key: ${{ runner.os }}-cargo-test-${{ hashFiles('**/Cargo.lock') }}
- -
name: Start dependency services name: Start dependency services
run: docker compose up -d run: docker-compose up -d
- -
name: Build UI name: Build UI
run: make build-ui run: make build-ui

10
Cargo.lock generated
View File

@ -570,7 +570,7 @@ dependencies = [
[[package]] [[package]]
name = "backend" name = "backend"
version = "4.7.0" version = "4.7.0-test.3"
dependencies = [ dependencies = [
"aes-kw", "aes-kw",
"anyhow", "anyhow",
@ -787,7 +787,7 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]] [[package]]
name = "chirpstack" name = "chirpstack"
version = "4.7.0" version = "4.7.0-test.3"
dependencies = [ dependencies = [
"aes", "aes",
"anyhow", "anyhow",
@ -874,7 +874,7 @@ dependencies = [
[[package]] [[package]]
name = "chirpstack_api" name = "chirpstack_api"
version = "4.7.0" version = "4.7.0-test.3"
dependencies = [ dependencies = [
"diesel", "diesel",
"hex", "hex",
@ -2434,7 +2434,7 @@ dependencies = [
[[package]] [[package]]
name = "lrwn" name = "lrwn"
version = "4.7.0" version = "4.7.0-test.3"
dependencies = [ dependencies = [
"aes", "aes",
"anyhow", "anyhow",
@ -2448,7 +2448,7 @@ dependencies = [
[[package]] [[package]]
name = "lrwn_filters" name = "lrwn_filters"
version = "4.7.0" version = "4.7.0-test.3"
dependencies = [ dependencies = [
"hex", "hex",
"lrwn", "lrwn",

View File

@ -40,7 +40,7 @@ api: version
# Builds the UI. # Builds the UI.
build-ui: build-ui:
docker compose run --rm --no-deps chirpstack-ui make build docker-compose run --rm --no-deps chirpstack-ui make build
# Enter the devshell. # Enter the devshell.
devshell: devshell:
@ -48,11 +48,11 @@ devshell:
# Enters the Docker devshell for ChirpStack development. # Enters the Docker devshell for ChirpStack development.
docker-devshell: docker-devshell:
docker compose run --rm --service-ports --name chirpstack chirpstack docker-compose run --rm --service-ports --name chirpstack chirpstack
# Enters the devshell for ChirpStack UI development. # Enters the devshell for ChirpStack UI development.
docker-devshell-ui: docker-devshell-ui:
docker compose run --rm --service-ports --name chirpstack-ui chirpstack-ui bash docker-compose run --rm --service-ports --name chirpstack-ui chirpstack-ui bash
# Runs the tests # Runs the tests
test: test:

View File

@ -76,7 +76,7 @@ to be running before you can run the tests. You need to start these services
manually if you started the development shell using `nix-shell`: manually if you started the development shell using `nix-shell`:
```bash ```bash
docker compose up -d docker-compose up -d
``` ```
#### Run tests #### Run tests

20
api/Makefile vendored
View File

@ -1,31 +1,31 @@
.PHONY: rust grpc-web go js python md java kotlin csharp .PHONY: rust grpc-web go js python md java kotlin csharp
all: all:
docker compose up docker-compose up
rust: rust:
docker compose run --rm chirpstack-api-rust docker-compose run --rm chirpstack-api-rust
grpc-web: grpc-web:
docker compose run --rm chirpstack-api-grpc-web docker-compose run --rm chirpstack-api-grpc-web
go: go:
docker compose run --rm chirpstack-api-go docker-compose run --rm chirpstack-api-go
js: js:
docker compose run --rm chirpstack-api-js docker-compose run --rm chirpstack-api-js
python: python:
docker compose run --rm chirpstack-api-python docker-compose run --rm chirpstack-api-python
md: md:
docker compose run --rm chirpstack-api-md docker-compose run --rm chirpstack-api-md
java: java:
docker compose run --rm chirpstack-api-java docker-compose run --rm chirpstack-api-java
kotlin: kotlin:
docker compose run --rm chirpstack-api-kotlin docker-compose run --rm chirpstack-api-kotlin
csharp: csharp:
docker compose run --rm chirpstack-csharp docker-compose run --rm chirpstack-csharp

View File

@ -1,6 +1,6 @@
{ {
"name": "@chirpstack/chirpstack-api-grpc-web", "name": "@chirpstack/chirpstack-api-grpc-web",
"version": "4.7.0", "version": "4.7.0-test.3",
"description": "Chirpstack gRPC-web API", "description": "Chirpstack gRPC-web API",
"license": "MIT", "license": "MIT",
"devDependencies": { "devDependencies": {

View File

@ -8,7 +8,7 @@ plugins {
} }
group = "io.chirpstack" group = "io.chirpstack"
version = "4.7.0" version = "4.7.0-test.3"
repositories { repositories {
mavenCentral() mavenCentral()

2
api/js/package.json vendored
View File

@ -1,6 +1,6 @@
{ {
"name": "@chirpstack/chirpstack-api", "name": "@chirpstack/chirpstack-api",
"version": "4.7.0", "version": "4.7.0-test.3",
"description": "Chirpstack JS and TS API", "description": "Chirpstack JS and TS API",
"license": "MIT", "license": "MIT",
"devDependencies": { "devDependencies": {

View File

@ -9,7 +9,7 @@ plugins {
} }
group = "io.chirpstack" group = "io.chirpstack"
version = "4.7.0" version = "4.7.0-test.3"
repositories { repositories {
mavenCentral() mavenCentral()

View File

@ -7,9 +7,15 @@ import "chirpstack-api/gw/gw.proto";
import "google/protobuf/timestamp.proto"; import "google/protobuf/timestamp.proto";
message DeviceSession { message DeviceSession {
// Device EUI.
bytes dev_eui = 1;
// Device address. // Device address.
bytes dev_addr = 2; bytes dev_addr = 2;
// Join EUI.
bytes join_eui = 3;
// LoRaWAN mac-version. // LoRaWAN mac-version.
common.MacVersion mac_version = 4; common.MacVersion mac_version = 4;

View File

@ -18,7 +18,7 @@ CLASSIFIERS = [
setup( setup(
name='chirpstack-api', name='chirpstack-api',
version = "4.7.0", version = "4.7.0-test.3",
url='https://github.com/brocaar/chirpstack-api', url='https://github.com/brocaar/chirpstack-api',
author='Orne Brocaar', author='Orne Brocaar',
author_email='info@brocaar.com', author_email='info@brocaar.com',

57
api/rust/Cargo.lock generated vendored
View File

@ -149,12 +149,6 @@ version = "2.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b4682ae6287fcf752ecaabbfcc7b6f9b72aa33933dc23a554d853aea8eea8635" checksum = "b4682ae6287fcf752ecaabbfcc7b6f9b72aa33933dc23a554d853aea8eea8635"
[[package]]
name = "byteorder"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
[[package]] [[package]]
name = "bytes" name = "bytes"
version = "1.4.0" version = "1.4.0"
@ -178,9 +172,8 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]] [[package]]
name = "chirpstack_api" name = "chirpstack_api"
version = "4.7.0" version = "4.7.0-test.3"
dependencies = [ dependencies = [
"diesel",
"hex", "hex",
"pbjson", "pbjson",
"pbjson-build", "pbjson-build",
@ -203,39 +196,6 @@ dependencies = [
"num-traits", "num-traits",
] ]
[[package]]
name = "diesel"
version = "2.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "62c6fcf842f17f8c78ecf7c81d75c5ce84436b41ee07e03f490fbb5f5a8731d8"
dependencies = [
"bitflags 2.4.0",
"byteorder",
"diesel_derives",
"itoa",
]
[[package]]
name = "diesel_derives"
version = "2.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ef8337737574f55a468005a83499da720f20c65586241ffea339db9ecdfd2b44"
dependencies = [
"diesel_table_macro_syntax",
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "diesel_table_macro_syntax"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fc5557efc453706fed5e4fa85006fe9817c224c3f480a34c7e5959fd700921c5"
dependencies = [
"syn",
]
[[package]] [[package]]
name = "dirs" name = "dirs"
version = "5.0.1" version = "5.0.1"
@ -497,6 +457,15 @@ dependencies = [
"hashbrown 0.14.0", "hashbrown 0.14.0",
] ]
[[package]]
name = "itertools"
version = "0.10.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473"
dependencies = [
"either",
]
[[package]] [[package]]
name = "itertools" name = "itertools"
version = "0.11.0" version = "0.11.0"
@ -621,7 +590,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2580e33f2292d34be285c5bc3dba5259542b083cfad6037b6d70345f24dcb735" checksum = "2580e33f2292d34be285c5bc3dba5259542b083cfad6037b6d70345f24dcb735"
dependencies = [ dependencies = [
"heck", "heck",
"itertools", "itertools 0.11.0",
"prost", "prost",
"prost-types", "prost-types",
] ]
@ -732,7 +701,7 @@ checksum = "8bdf592881d821b83d471f8af290226c8d51402259e9bb5be7f9f8bdebbb11ac"
dependencies = [ dependencies = [
"bytes", "bytes",
"heck", "heck",
"itertools", "itertools 0.10.5",
"log", "log",
"multimap", "multimap",
"once_cell", "once_cell",
@ -753,7 +722,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "265baba7fabd416cf5078179f7d2cbeca4ce7a9041111900675ea7c4cb8a4c32" checksum = "265baba7fabd416cf5078179f7d2cbeca4ce7a9041111900675ea7c4cb8a4c32"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"itertools", "itertools 0.10.5",
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn", "syn",

2
api/rust/Cargo.toml vendored
View File

@ -1,7 +1,7 @@
[package] [package]
name = "chirpstack_api" name = "chirpstack_api"
description = "ChirpStack Protobuf / gRPC API definitions." description = "ChirpStack Protobuf / gRPC API definitions."
version = "4.7.0" version = "4.7.0-test.3"
authors = ["Orne Brocaar <info@brocaar.com>"] authors = ["Orne Brocaar <info@brocaar.com>"]
license = "MIT" license = "MIT"
homepage = "https://www.chirpstack.io" homepage = "https://www.chirpstack.io"

View File

@ -2,12 +2,13 @@ include!(concat!(env!("OUT_DIR"), "/internal/internal.rs"));
#[cfg(feature = "json")] #[cfg(feature = "json")]
include!(concat!(env!("OUT_DIR"), "/internal/internal.serde.rs")); include!(concat!(env!("OUT_DIR"), "/internal/internal.serde.rs"));
#[cfg(feature = "diesel")]
use std::io::Cursor;
#[cfg(feature = "diesel")] #[cfg(feature = "diesel")]
use diesel::{backend::Backend, deserialize, serialize, sql_types::Binary}; use diesel::{backend::Backend, deserialize, serialize, sql_types::Binary};
#[cfg(feature = "diesel")] #[cfg(feature = "diesel")]
use prost::Message; use prost::Message;
#[cfg(feature = "diesel")]
use std::io::Cursor;
impl DeviceSession { impl DeviceSession {
pub fn get_a_f_cnt_down(&self) -> u32 { pub fn get_a_f_cnt_down(&self) -> u32 {
@ -48,10 +49,10 @@ impl serialize::ToSql<Binary, diesel::pg::Pg> for DeviceSession
where where
[u8]: serialize::ToSql<Binary, diesel::pg::Pg>, [u8]: serialize::ToSql<Binary, diesel::pg::Pg>,
{ {
fn to_sql(&self, out: &mut serialize::Output<'_, '_, diesel::pg::Pg>) -> serialize::Result { fn to_sql<'b>(&self, out: &mut serialize::Output<'b, '_, diesel::pg::Pg>) -> serialize::Result {
<[u8] as serialize::ToSql<Binary, diesel::pg::Pg>>::to_sql( <[u8] as serialize::ToSql<Binary, diesel::pg::Pg>>::to_sql(
&self.encode_to_vec(), &self.encode_to_vec(),
&mut out.reborrow(), &mut out.reborrow(),
) )
} }
} }

View File

@ -1,6 +1,6 @@
[package] [package]
name = "backend" name = "backend"
version = "4.7.0" version = "4.7.0-test.3"
authors = ["Orne Brocaar <info@brocaar.com>"] authors = ["Orne Brocaar <info@brocaar.com>"]
edition = "2018" edition = "2018"
publish = false publish = false

View File

@ -423,9 +423,8 @@ impl Client {
} }
} }
#[derive(Default, Serialize, Deserialize, PartialEq, Eq, Debug, Copy, Clone)] #[derive(Serialize, Deserialize, PartialEq, Eq, Debug, Copy, Clone)]
pub enum MessageType { pub enum MessageType {
#[default]
JoinReq, JoinReq,
JoinAns, JoinAns,
RejoinReq, RejoinReq,
@ -442,9 +441,14 @@ pub enum MessageType {
XmitDataAns, XmitDataAns,
} }
#[derive(Default, Serialize, Deserialize, PartialEq, Eq, Debug, Copy, Clone)] impl Default for MessageType {
fn default() -> Self {
MessageType::JoinReq
}
}
#[derive(Serialize, Deserialize, PartialEq, Eq, Debug, Copy, Clone)]
pub enum ResultCode { pub enum ResultCode {
#[default]
Success, Success,
MICFailed, MICFailed,
JoinReqFailed, JoinReqFailed,
@ -467,6 +471,12 @@ pub enum ResultCode {
Other, Other,
} }
impl Default for ResultCode {
fn default() -> Self {
ResultCode::Success
}
}
#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Copy, Clone)] #[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Copy, Clone)]
pub enum RatePolicy { pub enum RatePolicy {
Drop, Drop,

View File

@ -3,7 +3,7 @@ name = "chirpstack"
description = "ChirpStack is an open-source LoRaWAN(TM) Network Server" description = "ChirpStack is an open-source LoRaWAN(TM) Network Server"
repository = "https://github.com/chirpstack/chirpstack" repository = "https://github.com/chirpstack/chirpstack"
homepage = "https://www.chirpstack.io/" homepage = "https://www.chirpstack.io/"
version = "4.7.0" version = "4.7.0-test.3"
authors = ["Orne Brocaar <info@brocaar.com>"] authors = ["Orne Brocaar <info@brocaar.com>"]
edition = "2021" edition = "2021"
publish = false publish = false

View File

@ -1736,7 +1736,7 @@ impl ApplicationService for Application {
application::IftttConfiguration { application::IftttConfiguration {
key: req_int.key.clone(), key: req_int.key.clone(),
uplink_values: [ uplink_values: [
req_int.uplink_values.first().cloned().unwrap_or_default(), req_int.uplink_values.get(0).cloned().unwrap_or_default(),
req_int.uplink_values.get(1).cloned().unwrap_or_default(), req_int.uplink_values.get(1).cloned().unwrap_or_default(),
], ],
arbitrary_json: req_int.arbitrary_json, arbitrary_json: req_int.arbitrary_json,
@ -1820,7 +1820,7 @@ impl ApplicationService for Application {
application::IftttConfiguration { application::IftttConfiguration {
key: req_int.key.clone(), key: req_int.key.clone(),
uplink_values: [ uplink_values: [
req_int.uplink_values.first().cloned().unwrap_or_default(), req_int.uplink_values.get(0).cloned().unwrap_or_default(),
req_int.uplink_values.get(1).cloned().unwrap_or_default(), req_int.uplink_values.get(1).cloned().unwrap_or_default(),
], ],
arbitrary_json: req_int.arbitrary_json, arbitrary_json: req_int.arbitrary_json,

View File

@ -104,7 +104,7 @@ pub async fn _handle_request(bp: BasePayload, b: Vec<u8>) -> http::Response<hype
} }
}; };
match joinserver::get(sender_id).await { match joinserver::get(sender_id) {
Ok(v) => v, Ok(v) => v,
Err(_) => { Err(_) => {
warn!("Unknown SenderID"); warn!("Unknown SenderID");

View File

@ -1,4 +1,3 @@
use std::collections::HashMap;
use std::str::FromStr; use std::str::FromStr;
use anyhow::{Context, Result}; use anyhow::{Context, Result};
@ -8,13 +7,12 @@ use openidconnect::core::{
CoreResponseType, CoreResponseType,
}; };
use openidconnect::reqwest::async_http_client; use openidconnect::reqwest::async_http_client;
use openidconnect::{AdditionalClaims, UserInfoClaims};
use openidconnect::{ use openidconnect::{
AuthenticationFlow, AuthorizationCode, ClientId, ClientSecret, CsrfToken, IssuerUrl, Nonce, AuthenticationFlow, AuthorizationCode, ClientId, ClientSecret, CsrfToken, IssuerUrl, Nonce,
OAuth2TokenResponse, RedirectUrl, Scope, OAuth2TokenResponse, RedirectUrl, Scope,
}; };
use openidconnect::{EmptyAdditionalClaims, UserInfoClaims};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use serde_json::Value;
use tracing::{error, trace}; use tracing::{error, trace};
use warp::{Rejection, Reply}; use warp::{Rejection, Reply};
@ -22,15 +20,7 @@ use crate::config;
use crate::helpers::errors::PrintFullError; use crate::helpers::errors::PrintFullError;
use crate::storage::{get_async_redis_conn, redis_key}; use crate::storage::{get_async_redis_conn, redis_key};
pub type User = UserInfoClaims<CustomClaims, CoreGenderClaim>; pub type User = UserInfoClaims<EmptyAdditionalClaims, CoreGenderClaim>;
#[derive(Debug, Serialize, Deserialize)]
pub struct CustomClaims {
#[serde(flatten)]
other: HashMap<String, Value>,
}
impl AdditionalClaims for CustomClaims {}
#[derive(Serialize, Deserialize)] #[derive(Serialize, Deserialize)]
pub struct CallbackArgs { pub struct CallbackArgs {

View File

@ -1,7 +1,6 @@
use std::sync::Arc; use std::sync::{Arc, RwLock};
use anyhow::Result; use anyhow::Result;
use tokio::sync::RwLock;
use tracing::info; use tracing::info;
use crate::{config, stream}; use crate::{config, stream};
@ -16,7 +15,7 @@ pub async fn setup() -> Result<()> {
info!("Setting up Join Server clients"); info!("Setting up Join Server clients");
let conf = config::get(); let conf = config::get();
let mut clients_w = CLIENTS.write().await; let mut clients_w = CLIENTS.write().unwrap();
*clients_w = vec![]; *clients_w = vec![];
for js in &conf.join_server.servers { for js in &conf.join_server.servers {
@ -39,8 +38,8 @@ pub async fn setup() -> Result<()> {
Ok(()) Ok(())
} }
pub async fn get(join_eui: EUI64) -> Result<Arc<Client>> { pub fn get(join_eui: EUI64) -> Result<Arc<Client>> {
let clients_r = CLIENTS.read().await; let clients_r = CLIENTS.read().unwrap();
for client in clients_r.iter() { for client in clients_r.iter() {
if client.0.matches(join_eui) { if client.0.matches(join_eui) {
return Ok(client.1.clone()); return Ok(client.1.clone());
@ -54,7 +53,7 @@ pub async fn get(join_eui: EUI64) -> Result<Arc<Client>> {
} }
#[cfg(test)] #[cfg(test)]
pub async fn reset() { pub fn reset() {
let mut clients_w = CLIENTS.write().await; let mut clients_w = CLIENTS.write().unwrap();
*clients_w = vec![]; *clients_w = vec![];
} }

View File

@ -51,7 +51,7 @@ async fn get_ca_cert(ca_cert_file: &str, ca_key_file: &str) -> Result<Certificat
let params = CertificateParams::from_ca_cert_pem(&ca_cert_s, ca_key) let params = CertificateParams::from_ca_cert_pem(&ca_cert_s, ca_key)
.context("Parse gateway CA certificate")?; .context("Parse gateway CA certificate")?;
Certificate::from_params(params).context("Init Certificate struct") Ok(Certificate::from_params(params).context("Init Certificate struct")?)
} }
// This returns the CA, certificate and private-key as PEM encoded strings. // This returns the CA, certificate and private-key as PEM encoded strings.

View File

@ -185,6 +185,12 @@ pub fn run() {
{{/each}} {{/each}}
] ]
# Device session expiration.
#
# The TTL value defines the time after which a device-session expires
# after no activity.
device_session_ttl="{{ network.device_session_ttl }}"
# Time to wait for uplink de-duplication. # Time to wait for uplink de-duplication.
# #
# This is the time that ChirpStack will wait for other gateways to receive # This is the time that ChirpStack will wait for other gateways to receive

View File

@ -363,7 +363,12 @@ impl Data {
let gw_down = helpers::select_downlink_gateway( let gw_down = helpers::select_downlink_gateway(
Some(self.tenant.id), Some(self.tenant.id),
&self.device.get_device_session()?.region_config_id, &self
.device
.device_session
.as_ref()
.unwrap()
.region_config_id,
self.network_conf.gateway_prefer_min_margin, self.network_conf.gateway_prefer_min_margin,
self.device_gateway_rx_info.as_mut().unwrap(), self.device_gateway_rx_info.as_mut().unwrap(),
)?; )?;
@ -623,7 +628,7 @@ impl Data {
let ds = self.device.get_device_session()?; let ds = self.device.get_device_session()?;
self.mac_commands = filter_mac_commands(ds, &self.mac_commands); self.mac_commands = filter_mac_commands(&ds, &self.mac_commands);
Ok(()) Ok(())
} }

View File

@ -38,7 +38,8 @@ pub async fn get_geoloc_buffer(
let rx_info: Vec<gw::UplinkRxInfo> = uplink let rx_info: Vec<gw::UplinkRxInfo> = uplink
.rx_info .rx_info
.iter() .iter()
.filter(|&rx_info| { .cloned()
.filter(|rx_info| {
let ts: DateTime<Utc> = match &rx_info.gw_time { let ts: DateTime<Utc> = match &rx_info.gw_time {
None => { None => {
return false; return false;
@ -54,7 +55,6 @@ pub async fn get_geoloc_buffer(
// The interval between now and then must be smaller than the TTL // The interval between now and then must be smaller than the TTL
(ts - Utc::now()) < ttl (ts - Utc::now()) < ttl
}) })
.cloned()
.collect(); .collect();
if rx_info.len() > 3 { if rx_info.len() > 3 {

View File

@ -134,7 +134,7 @@ impl<'a> Integration<'a> {
command: r"(?P<command>[\w]+)".to_string(), command: r"(?P<command>[\w]+)".to_string(),
}, },
)?)?, )?)?,
qos, qos: qos,
json: conf.json, json: conf.json,
client, client,
templates, templates,

View File

@ -119,7 +119,7 @@ pub async fn save(name: &str, record: &Record) -> Result<()> {
.unwrap(), .unwrap(),
}; };
let key = get_key(name, a, ts); let key = get_key(&name, a, ts);
for (k, v) in &record.metrics { for (k, v) in &record.metrics {
// Passing a reference to hincr will return a runtime error. // Passing a reference to hincr will return a runtime error.

View File

@ -119,7 +119,7 @@ pub async fn get_by_email_and_pw(email: &str, pw: &str) -> Result<User, Error> {
} }
}; };
if verify_password(pw, &u.password_hash) { if verify_password(&pw, &u.password_hash) {
return Ok(u); return Ok(u);
} }

View File

@ -112,7 +112,7 @@ async fn handle_stream(
k: &str, k: &str,
v: &redis::Value, v: &redis::Value,
) -> Result<()> { ) -> Result<()> {
match k { match k.as_ref() {
"up" => { "up" => {
trace!(key = %k, id = %stream_id, "Event-log received from stream"); trace!(key = %k, id = %stream_id, "Event-log received from stream");
if let redis::Value::Data(b) = v { if let redis::Value::Data(b) = v {

View File

@ -276,7 +276,7 @@ async fn handle_stream(
k: &str, k: &str,
v: &redis::Value, v: &redis::Value,
) -> Result<()> { ) -> Result<()> {
match k { match k.as_ref() {
"up" => { "up" => {
trace!(key = %k, id = %stream_id, "Frame-log received from stream"); trace!(key = %k, id = %stream_id, "Frame-log received from stream");
if let redis::Value::Data(b) = v { if let redis::Value::Data(b) = v {

View File

@ -167,7 +167,7 @@ async fn test_fns_uplink() {
sns_pr_start_req_mock.assert(); sns_pr_start_req_mock.assert();
sns_pr_start_req_mock.delete(); sns_pr_start_req_mock.delete();
joinserver::reset().await; joinserver::reset();
} }
#[tokio::test] #[tokio::test]

View File

@ -246,7 +246,7 @@ async fn test_fns() {
})() })()
.await; .await;
joinserver::reset().await; joinserver::reset();
} }
#[tokio::test] #[tokio::test]
@ -430,7 +430,7 @@ async fn test_sns() {
pr_start_ans pr_start_ans
); );
joinserver::reset().await; joinserver::reset();
} }
#[tokio::test] #[tokio::test]
@ -588,5 +588,5 @@ async fn test_sns_roaming_not_allowed() {
pr_start_ans pr_start_ans
); );
joinserver::reset().await; joinserver::reset();
} }

View File

@ -215,7 +215,7 @@ impl Data {
let mac = if let lrwn::Payload::MACPayload(pl) = &self.phy_payload.payload { let mac = if let lrwn::Payload::MACPayload(pl) = &self.phy_payload.payload {
pl pl
} else { } else {
return Err(Error::Anyhow(anyhow!("Expected MacPayload"))); return Err(Error::AnyhowError(anyhow!("Expected MacPayload")));
}; };
if roaming::is_roaming_dev_addr(mac.fhdr.devaddr) { if roaming::is_roaming_dev_addr(mac.fhdr.devaddr) {
@ -234,7 +234,7 @@ impl Data {
let dev_addr = if let lrwn::Payload::MACPayload(pl) = &self.phy_payload.payload { let dev_addr = if let lrwn::Payload::MACPayload(pl) = &self.phy_payload.payload {
pl.fhdr.devaddr pl.fhdr.devaddr
} else { } else {
return Err(Error::Anyhow(anyhow!("No MacPayload in PhyPayload"))); return Err(Error::AnyhowError(anyhow!("No MacPayload in PhyPayload")));
}; };
match device::get_for_phypayload_and_incr_f_cnt_up( match device::get_for_phypayload_and_incr_f_cnt_up(
@ -277,7 +277,7 @@ impl Data {
return Err(Error::Abort); return Err(Error::Abort);
} }
_ => { _ => {
return Err(Error::Anyhow( return Err(Error::AnyhowError(
anyhow::Error::new(e).context("Get device-session"), anyhow::Error::new(e).context("Get device-session"),
)); ));
} }
@ -295,7 +295,7 @@ impl Data {
let dev_addr = if let lrwn::Payload::MACPayload(pl) = &self.phy_payload.payload { let dev_addr = if let lrwn::Payload::MACPayload(pl) = &self.phy_payload.payload {
pl.fhdr.devaddr pl.fhdr.devaddr
} else { } else {
return Err(Error::Anyhow(anyhow!("No MacPayload in PhyPayload"))); return Err(Error::AnyhowError(anyhow!("No MacPayload in PhyPayload")));
}; };
let dr = relay_ctx.req.metadata.dr; let dr = relay_ctx.req.metadata.dr;
@ -334,7 +334,7 @@ impl Data {
return Err(Error::Abort); return Err(Error::Abort);
} }
_ => { _ => {
return Err(Error::Anyhow( return Err(Error::AnyhowError(
anyhow::Error::new(e).context("Get device-session"), anyhow::Error::new(e).context("Get device-session"),
)); ));
} }
@ -585,7 +585,13 @@ impl Data {
fn decrypt_f_opts_mac_commands(&mut self) -> Result<()> { fn decrypt_f_opts_mac_commands(&mut self) -> Result<()> {
trace!("Decrypting mac-commands"); trace!("Decrypting mac-commands");
let ds = self.device.as_ref().unwrap().get_device_session()?; let ds = self
.device
.as_ref()
.unwrap()
.device_session
.as_ref()
.unwrap();
if ds.mac_version().to_string().starts_with("1.0") { if ds.mac_version().to_string().starts_with("1.0") {
if let Err(e) = self.phy_payload.decode_f_opts_to_mac_commands() { if let Err(e) = self.phy_payload.decode_f_opts_to_mac_commands() {
// This avoids failing in case of a corrupted mac-command in the frm_payload. // This avoids failing in case of a corrupted mac-command in the frm_payload.
@ -604,7 +610,13 @@ impl Data {
fn decrypt_frm_payload(&mut self) -> Result<()> { fn decrypt_frm_payload(&mut self) -> Result<()> {
trace!("Decrypting FRMPayload"); trace!("Decrypting FRMPayload");
let ds = self.device.as_ref().unwrap().get_device_session()?; let ds = self
.device
.as_ref()
.unwrap()
.device_session
.as_ref()
.unwrap();
let mut f_port = 0; let mut f_port = 0;
if let lrwn::Payload::MACPayload(pl) = &self.phy_payload.payload { if let lrwn::Payload::MACPayload(pl) = &self.phy_payload.payload {
@ -644,7 +656,13 @@ impl Data {
fn set_adr(&mut self) -> Result<()> { fn set_adr(&mut self) -> Result<()> {
trace!("Set ADR flag in device-session"); trace!("Set ADR flag in device-session");
let ds = self.device.as_mut().unwrap().get_device_session_mut()?; let ds = self
.device
.as_mut()
.unwrap()
.device_session
.as_mut()
.unwrap();
if let lrwn::Payload::MACPayload(pl) = &self.phy_payload.payload { if let lrwn::Payload::MACPayload(pl) = &self.phy_payload.payload {
ds.adr = pl.fhdr.f_ctrl.adr; ds.adr = pl.fhdr.f_ctrl.adr;
} }
@ -772,7 +790,7 @@ impl Data {
if let lrwn::Payload::MACPayload(pl) = &self.phy_payload.payload { if let lrwn::Payload::MACPayload(pl) = &self.phy_payload.payload {
if pl.fhdr.f_ctrl.adr_ack_req { if pl.fhdr.f_ctrl.adr_ack_req {
let region_conf = region::get(&self.uplink_frame_set.region_config_id)?; let region_conf = region::get(&self.uplink_frame_set.region_config_id)?;
let ds = d.get_device_session_mut()?; let ds = d.device_session.as_mut().unwrap();
// We reset the device-session enabled_uplink_channel_indices and // We reset the device-session enabled_uplink_channel_indices and
// extra_uplink_channels. On the downlink path, the mac-command handling will // extra_uplink_channels. On the downlink path, the mac-command handling will
@ -843,7 +861,13 @@ impl Data {
} }
fn append_meta_data_to_uplink_history(&mut self) -> Result<()> { fn append_meta_data_to_uplink_history(&mut self) -> Result<()> {
let ds = self.device.as_mut().unwrap().get_device_session_mut()?; let ds = self
.device
.as_mut()
.unwrap()
.device_session
.as_mut()
.unwrap();
// ignore re-transmissions we don't know the source of the // ignore re-transmissions we don't know the source of the
// re-transmission (it might be a replay-attack) // re-transmission (it might be a replay-attack)
@ -888,7 +912,13 @@ impl Data {
fn append_meta_data_to_uplink_history_relayed(&mut self) -> Result<()> { fn append_meta_data_to_uplink_history_relayed(&mut self) -> Result<()> {
trace!("Apping meta-data of relayed uplink to upink history"); trace!("Apping meta-data of relayed uplink to upink history");
let ds = self.device.as_mut().unwrap().get_device_session_mut()?; let ds = self
.device
.as_mut()
.unwrap()
.device_session
.as_mut()
.unwrap();
let relay_ctx = self.relay_context.as_ref().unwrap(); let relay_ctx = self.relay_context.as_ref().unwrap();
// ignore re-transmissions we don't know the source of the // ignore re-transmissions we don't know the source of the
@ -1365,10 +1395,13 @@ impl Data {
} }
fn _is_end_to_end_encrypted(&self) -> bool { fn _is_end_to_end_encrypted(&self) -> bool {
let ds = match self.device.as_ref().unwrap().get_device_session() { let ds = self
Ok(v) => v, .device
Err(_) => return false, .as_ref()
}; .unwrap()
.device_session
.as_ref()
.unwrap();
if !ds.js_session_key_id.is_empty() { if !ds.js_session_key_id.is_empty() {
return true; return true;

View File

@ -9,5 +9,5 @@ pub enum Error {
RoamingIsNotAllowed, RoamingIsNotAllowed,
#[error(transparent)] #[error(transparent)]
Anyhow(#[from] anyhow::Error), AnyhowError(#[from] anyhow::Error),
} }

View File

@ -136,8 +136,8 @@ pub fn get_time_since_gps_epoch_chrono(rx_info: &[gw::UplinkRxInfo]) -> Option<c
pub fn get_start_location(rx_info: &[gw::UplinkRxInfo]) -> Option<common::Location> { pub fn get_start_location(rx_info: &[gw::UplinkRxInfo]) -> Option<common::Location> {
let mut with_loc: Vec<gw::UplinkRxInfo> = rx_info let mut with_loc: Vec<gw::UplinkRxInfo> = rx_info
.iter() .iter()
.filter(|&i| i.location.is_some())
.cloned() .cloned()
.filter(|i| i.location.is_some())
.collect(); .collect();
with_loc.sort_by(|a, b| a.snr.partial_cmp(&b.snr).unwrap()); with_loc.sort_by(|a, b| a.snr.partial_cmp(&b.snr).unwrap());
with_loc with_loc

View File

@ -258,7 +258,7 @@ impl JoinRequest {
if self.device_keys.is_none() { if self.device_keys.is_none() {
trace!(join_eui = %jr.join_eui, "Getting Join Server client"); trace!(join_eui = %jr.join_eui, "Getting Join Server client");
self.js_client = Some(joinserver::get(jr.join_eui).await?); self.js_client = Some(joinserver::get(jr.join_eui)?);
} }
Ok(()) Ok(())

View File

@ -58,7 +58,7 @@ impl JoinRequest {
trace!("Getting home netid"); trace!("Getting home netid");
trace!(join_eui = %self.join_request.join_eui, "Trying to get join-server client"); trace!(join_eui = %self.join_request.join_eui, "Trying to get join-server client");
let js_client = joinserver::get(self.join_request.join_eui).await?; let js_client = joinserver::get(self.join_request.join_eui)?;
let mut home_ns_req = backend::HomeNSReqPayload { let mut home_ns_req = backend::HomeNSReqPayload {
dev_eui: self.join_request.dev_eui.to_vec(), dev_eui: self.join_request.dev_eui.to_vec(),

View File

@ -162,7 +162,7 @@ impl JoinRequest {
if self.device_keys.is_none() { if self.device_keys.is_none() {
trace!(join_eui = %jr.join_eui, "Getting Join Server client"); trace!(join_eui = %jr.join_eui, "Getting Join Server client");
self.js_client = Some(joinserver::get(jr.join_eui).await?); self.js_client = Some(joinserver::get(jr.join_eui)?);
} }
Ok(()) Ok(())
@ -641,7 +641,13 @@ impl JoinRequest {
async fn update_device(&mut self) -> Result<()> { async fn update_device(&mut self) -> Result<()> {
trace!("Updating device"); trace!("Updating device");
let dp = self.device_profile.as_ref().unwrap(); let dp = self.device_profile.as_ref().unwrap();
let ds = self.device.as_ref().unwrap().get_device_session()?; let ds = self
.device
.as_ref()
.unwrap()
.device_session
.as_ref()
.unwrap();
self.device = Some( self.device = Some(
device::partial_update( device::partial_update(

View File

@ -248,7 +248,7 @@ async fn deduplicate_put(
async fn deduplicate_collect(key: &str) -> Result<gw::UplinkFrameSet> { async fn deduplicate_collect(key: &str) -> Result<gw::UplinkFrameSet> {
let items_b: Vec<Vec<u8>> = { let items_b: Vec<Vec<u8>> = {
redis::cmd("SMEMBERS") redis::cmd("SMEMBERS")
.arg(key) .arg(&key)
.query_async(&mut get_async_redis_conn().await?) .query_async(&mut get_async_redis_conn().await?)
.await .await
.context("Deduplication collect")? .context("Deduplication collect")?
@ -285,7 +285,7 @@ async fn deduplicate_collect(key: &str) -> Result<gw::UplinkFrameSet> {
pub async fn handle_uplink(deduplication_id: Uuid, uplink: gw::UplinkFrameSet) -> Result<()> { pub async fn handle_uplink(deduplication_id: Uuid, uplink: gw::UplinkFrameSet) -> Result<()> {
let rx_info = &uplink let rx_info = &uplink
.rx_info .rx_info
.first() .get(0)
.context("Unable to get first item from rx_info")?; .context("Unable to get first item from rx_info")?;
let region_config_id = rx_info let region_config_id = rx_info

View File

@ -3,7 +3,7 @@ name = "lrwn_filters"
description = "Library for filtering LoRaWAN payloads on DevAddr and JoinEUIs prefixes" description = "Library for filtering LoRaWAN payloads on DevAddr and JoinEUIs prefixes"
homepage = "https://www.chirpstack.io/" homepage = "https://www.chirpstack.io/"
license = "MIT" license = "MIT"
version = "4.7.0" version = "4.7.0-test.3"
authors = ["Orne Brocaar <info@brocaar.com>"] authors = ["Orne Brocaar <info@brocaar.com>"]
edition = "2021" edition = "2021"
repository = "https://github.com/chirpstack/chirpstack" repository = "https://github.com/chirpstack/chirpstack"

View File

@ -3,7 +3,7 @@ name = "lrwn"
description = "Library for encoding / decoding LoRaWAN frames." description = "Library for encoding / decoding LoRaWAN frames."
homepage = "https://www.chirpstack.io" homepage = "https://www.chirpstack.io"
license = "MIT" license = "MIT"
version = "4.7.0" version = "4.7.0-test.3"
authors = ["Orne Brocaar <info@brocaar.com>"] authors = ["Orne Brocaar <info@brocaar.com>"]
edition = "2018" edition = "2018"
repository = "https://github.com/chirpstack/chirpstack" repository = "https://github.com/chirpstack/chirpstack"

View File

@ -130,7 +130,7 @@ impl serialize::ToSql<Binary, diesel::pg::Pg> for AES128Key
where where
[u8]: serialize::ToSql<Binary, diesel::pg::Pg>, [u8]: serialize::ToSql<Binary, diesel::pg::Pg>,
{ {
fn to_sql(&self, out: &mut serialize::Output<'_, '_, diesel::pg::Pg>) -> serialize::Result { fn to_sql<'b>(&self, out: &mut serialize::Output<'b, '_, diesel::pg::Pg>) -> serialize::Result {
<[u8] as serialize::ToSql<Binary, diesel::pg::Pg>>::to_sql( <[u8] as serialize::ToSql<Binary, diesel::pg::Pg>>::to_sql(
&self.to_bytes(), &self.to_bytes(),
&mut out.reborrow(), &mut out.reborrow(),

View File

@ -273,7 +273,7 @@ impl serialize::ToSql<Binary, diesel::pg::Pg> for DevAddr
where where
[u8]: serialize::ToSql<Binary, diesel::pg::Pg>, [u8]: serialize::ToSql<Binary, diesel::pg::Pg>,
{ {
fn to_sql(&self, out: &mut serialize::Output<'_, '_, diesel::pg::Pg>) -> serialize::Result { fn to_sql<'b>(&self, out: &mut serialize::Output<'b, '_, diesel::pg::Pg>) -> serialize::Result {
<[u8] as serialize::ToSql<Binary, diesel::pg::Pg>>::to_sql( <[u8] as serialize::ToSql<Binary, diesel::pg::Pg>>::to_sql(
&self.to_be_bytes(), &self.to_be_bytes(),
&mut out.reborrow(), &mut out.reborrow(),

View File

@ -138,7 +138,7 @@ impl serialize::ToSql<Binary, diesel::pg::Pg> for EUI64
where where
[u8]: serialize::ToSql<Binary, diesel::pg::Pg>, [u8]: serialize::ToSql<Binary, diesel::pg::Pg>,
{ {
fn to_sql(&self, out: &mut serialize::Output<'_, '_, diesel::pg::Pg>) -> serialize::Result { fn to_sql<'b>(&self, out: &mut serialize::Output<'b, '_, diesel::pg::Pg>) -> serialize::Result {
<[u8] as serialize::ToSql<Binary, diesel::pg::Pg>>::to_sql( <[u8] as serialize::ToSql<Binary, diesel::pg::Pg>>::to_sql(
&self.to_be_bytes(), &self.to_be_bytes(),
&mut out.reborrow(), &mut out.reborrow(),
@ -197,7 +197,7 @@ impl FromStr for EUI64Prefix {
fn from_str(s: &str) -> Result<Self, Self::Err> { fn from_str(s: &str) -> Result<Self, Self::Err> {
let s = s.to_string(); let s = s.to_string();
let mut size: u64 = 64; let mut size: u64 = 64;
let parts: Vec<&str> = s.split('/').collect(); let parts: Vec<&str> = s.split("/").collect();
if parts.len() == 2 { if parts.len() == 2 {
size = parts[1].parse().map_err(|_| Error::EUI64PrefixFormat)?; size = parts[1].parse().map_err(|_| Error::EUI64PrefixFormat)?;
} }

View File

@ -1891,7 +1891,7 @@ impl serialize::ToSql<SmallInt, diesel::pg::Pg> for RelayModeActivation
where where
i16: serialize::ToSql<SmallInt, diesel::pg::Pg>, i16: serialize::ToSql<SmallInt, diesel::pg::Pg>,
{ {
fn to_sql(&self, out: &mut serialize::Output<'_, '_, diesel::pg::Pg>) -> serialize::Result { fn to_sql<'b>(&self, out: &mut serialize::Output<'b, '_, diesel::pg::Pg>) -> serialize::Result {
let i = self.to_u8() as i16; let i = self.to_u8() as i16;
<i16 as serialize::ToSql<SmallInt, diesel::pg::Pg>>::to_sql(&i, &mut out.reborrow()) <i16 as serialize::ToSql<SmallInt, diesel::pg::Pg>>::to_sql(&i, &mut out.reborrow())
} }
@ -2191,7 +2191,7 @@ impl PayloadCodec for UpdateUplinkListReqPayload {
let mut b = [0; 26]; let mut b = [0; 26];
cur.read_exact(&mut b)?; cur.read_exact(&mut b)?;
Ok(UpdateUplinkListReqPayload { return Ok(UpdateUplinkListReqPayload {
uplink_list_idx: b[0] & 0x0f, uplink_list_idx: b[0] & 0x0f,
uplink_limit: UplinkLimitPL::from_u8(b[1]), uplink_limit: UplinkLimitPL::from_u8(b[1]),
dev_addr: crate::DevAddr::from_le_bytes({ dev_addr: crate::DevAddr::from_le_bytes({
@ -2209,7 +2209,7 @@ impl PayloadCodec for UpdateUplinkListReqPayload {
bb.copy_from_slice(&b[10..26]); bb.copy_from_slice(&b[10..26]);
bb bb
}), }),
}) });
} }
fn encode(&self) -> Result<Vec<u8>> { fn encode(&self) -> Result<Vec<u8>> {
@ -2432,7 +2432,7 @@ impl PowerLevel {
pub fn from_bytes(b: [u8; 2]) -> Self { pub fn from_bytes(b: [u8; 2]) -> Self {
PowerLevel { PowerLevel {
wor_snr: (b[0] & 0x1f) as isize - 20, wor_snr: (b[0] & 0x1f) as isize - 20,
wor_rssi: -(((b[0] >> 5) | ((b[1] & 0x0f) << 3)) as isize) - 15, wor_rssi: -1 * ((b[0] >> 5) | ((b[1] & 0x0f) << 3)) as isize - 15,
} }
} }
@ -2456,7 +2456,7 @@ impl PowerLevel {
// Encode values // Encode values
let wor_snr = (wor_snr + 20) as u8; let wor_snr = (wor_snr + 20) as u8;
let wor_rssi = -(wor_rssi + 15) as u8; let wor_rssi = ((wor_rssi as isize + 15) * -1) as u8;
[wor_snr | wor_rssi << 5, wor_rssi >> 3] [wor_snr | wor_rssi << 5, wor_rssi >> 3]
} }

View File

@ -54,7 +54,7 @@ impl NetID {
match self.netid_type() { match self.netid_type() {
0 | 1 => self.get_id(6), 0 | 1 => self.get_id(6),
2 => self.get_id(9), 2 => self.get_id(9),
3..=7 => self.get_id(21), 3 | 4 | 5 | 6 | 7 => self.get_id(21),
_ => vec![], _ => vec![],
} }
} }

View File

@ -1176,7 +1176,11 @@ impl Region for Configuration {
for pl in pls { for pl in pls {
if pl.redundancy.ch_mask_cntl == 6 || pl.redundancy.ch_mask_cntl == 7 { if pl.redundancy.ch_mask_cntl == 6 || pl.redundancy.ch_mask_cntl == 7 {
for cm in ch_mask.iter_mut().take(64) { for cm in ch_mask.iter_mut().take(64) {
*cm = pl.redundancy.ch_mask_cntl == 6; if pl.redundancy.ch_mask_cntl == 6 {
*cm = true;
} else {
*cm = false;
}
} }
for (i, cm) in pl.ch_mask.into_iter().enumerate() { for (i, cm) in pl.ch_mask.into_iter().enumerate() {

View File

@ -864,7 +864,11 @@ impl Region for Configuration {
for pl in pls { for pl in pls {
if pl.redundancy.ch_mask_cntl == 6 || pl.redundancy.ch_mask_cntl == 7 { if pl.redundancy.ch_mask_cntl == 6 || pl.redundancy.ch_mask_cntl == 7 {
for cm in ch_mask.iter_mut().take(64) { for cm in ch_mask.iter_mut().take(64) {
*cm = pl.redundancy.ch_mask_cntl == 6; if pl.redundancy.ch_mask_cntl == 6 {
*cm = true;
} else {
*cm = false;
}
} }
for (i, cm) in pl.ch_mask.into_iter().enumerate() { for (i, cm) in pl.ch_mask.into_iter().enumerate() {

View File

@ -19,7 +19,7 @@ impl UplinkMetadata {
UplinkMetadata { UplinkMetadata {
dr: b[0] & 0x0f, dr: b[0] & 0x0f,
snr: ((b[0] >> 4) | ((b[1] & 0x01) << 4)) as isize - 20, snr: ((b[0] >> 4) | ((b[1] & 0x01) << 4)) as isize - 20,
rssi: -((b[1] >> 1) as isize) - 15, rssi: -1 * (b[1] >> 1) as isize - 15,
wor_channel: b[2] & 0x03, wor_channel: b[2] & 0x03,
} }
} }
@ -52,7 +52,7 @@ impl UplinkMetadata {
// Encode values // Encode values
let snr = (snr + 20) as u8; let snr = (snr + 20) as u8;
let rssi = -(rssi + 15) as u8; let rssi = ((rssi as isize + 15) * -1) as u8;
Ok([self.dr | snr << 4, snr >> 4 | rssi << 1, self.wor_channel]) Ok([self.dr | snr << 4, snr >> 4 | rssi << 1, self.wor_channel])
} }
@ -97,7 +97,7 @@ pub struct ForwardDownlinkReq {
impl ForwardDownlinkReq { impl ForwardDownlinkReq {
pub fn from_slice(b: &[u8]) -> Result<Self> { pub fn from_slice(b: &[u8]) -> Result<Self> {
Ok(ForwardDownlinkReq { Ok(ForwardDownlinkReq {
payload: Box::new(PhyPayload::from_slice(b)?), payload: Box::new(PhyPayload::from_slice(&b)?),
}) })
} }

View File

@ -1,4 +1,4 @@
[toolchain] [toolchain]
channel = "1.76.0" channel = "1.74.0"
components = ["rustfmt", "clippy"] components = ["rustfmt", "clippy"]
profile = "default" profile = "default"

View File

@ -1,4 +1,4 @@
{ pkgs ? import (fetchTarball "https://github.com/NixOS/nixpkgs/archive/nixos-23.11.tar.gz") {} }: { pkgs ? import (fetchTarball "https://github.com/NixOS/nixpkgs/archive/nixos-23.05.tar.gz") {} }:
pkgs.mkShell { pkgs.mkShell {
nativeBuildInputs = [ nativeBuildInputs = [

View File

@ -1,6 +1,6 @@
{ {
"name": "chirpstack-ui", "name": "chirpstack-ui",
"version": "4.7.0", "version": "4.7.0-test.3",
"private": true, "private": true,
"dependencies": { "dependencies": {
"@ant-design/colors": "^7.0.0", "@ant-design/colors": "^7.0.0",

View File

@ -75,11 +75,10 @@ function Header({ user }: { user: User }) {
} }
let oidcEnabled = settings!.getOpenidConnect()!.getEnabled(); let oidcEnabled = settings!.getOpenidConnect()!.getEnabled();
let oAuth2Enabled = settings!.getOauth2()!.getEnabled();
const menu = ( const menu = (
<Menu> <Menu>
{!(oidcEnabled || oAuth2Enabled) && ( {!oidcEnabled && (
<Menu.Item> <Menu.Item>
<Link to={`/users/${user.getId()}/password`}>Change password</Link> <Link to={`/users/${user.getId()}/password`}>Change password</Link>
</Menu.Item> </Menu.Item>