From c7218e8550d11833eb7f4b144d97ecb2d894b351 Mon Sep 17 00:00:00 2001 From: Orne Brocaar Date: Tue, 22 Apr 2025 13:13:54 +0100 Subject: [PATCH 01/17] Update dependencies. --- Cargo.lock | 370 ++++++++++++++++++++------------------------ Cargo.toml | 10 +- rust-toolchain.toml | 2 +- 3 files changed, 173 insertions(+), 209 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index fd322ee..594e84f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -45,15 +45,15 @@ checksum = "55cc3b69f167a1ef2e161439aa98aed94e6028e5f9a59be9a6ffb47aef1651f9" [[package]] name = "anyhow" -version = "1.0.95" +version = "1.0.98" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34ac096ce696dc2fcabef30516bb13c0a68a11d30131d3df6f04711467681b04" +checksum = "e16d2d3311acee920a9eb8d33b8cbc1787ce4a264e85f964c2404b969bdcd487" [[package]] name = "async-trait" -version = "0.1.86" +version = "0.1.88" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "644dd749086bf3771a2fbc5f256fdb982d53f011c7d5d560304eafeecebce79d" +checksum = "e539d3fca749fcee5236ab05e93a52867dd549cc157c8cb7f99595f3cedffdb5" dependencies = [ "proc-macro2", "quote", @@ -102,9 +102,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.8.0" +version = "2.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f68f53c83ab957f72c32642f3868eec03eb974d1fb82e453128456482613d36" +checksum = "5c8214115b7bf84099f1309324e63141d4c5d7cc26862f97a0a857dbefe165bd" [[package]] name = "block-buffer" @@ -115,23 +115,17 @@ dependencies = [ "generic-array", ] -[[package]] -name = "byteorder" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" - [[package]] name = "bytes" -version = "1.10.0" +version = "1.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f61dac84819c6588b558454b194026eb1f09c293b9036ae9b159e74e73ab6cf9" +checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" [[package]] name = "cc" -version = "1.2.13" +version = "1.2.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7777341816418c02e033934a09f20dc0ccaf65a5201ef8a450ae0105a573fda" +checksum = "8e3a13707ac958681c13b39b458c073d0d9bc8a22cb1b2f4c8e55eb72c13f362" dependencies = [ "jobserver", "libc", @@ -171,7 +165,7 @@ dependencies = [ "log", "lrwn_filters", "prost-types", - "rand 0.9.0", + "rand 0.9.1", "serde", "signal-hook", "signal-hook-tokio", @@ -185,15 +179,15 @@ dependencies = [ [[package]] name = "chirpstack_api" -version = "4.11.1" +version = "4.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9fd068c5bb5c6d359416af5f011c8ee35f0578c57dd16f3319b0c6405c91c078" +checksum = "88e36da3fd2016e1859993d9b8ff6758e38bd754489d289571c23ffe75b5d0b7" dependencies = [ "hex", "pbjson-build", "prost", "prost-types", - "rand 0.8.5", + "rand 0.9.1", "tonic-build", ] @@ -209,9 +203,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.29" +version = "4.5.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8acebd8ad879283633b343856142139f2da2317c96b05b4dd6181c61e2480184" +checksum = "eccb054f56cbd38340b380d4a8e69ef1f02f1af43db2f0cc817a4774d80ae071" dependencies = [ "clap_builder", "clap_derive", @@ -219,9 +213,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.29" +version = "4.5.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6ba32cbda51c7e1dfd49acc1457ba1a7dec5b64fe360e828acb13ca8dc9c2f9" +checksum = "efd9466fac8543255d3b1fcad4762c5e116ffe808c8a3043d4263cd4fd4862a2" dependencies = [ "anstyle", "clap_lex", @@ -229,9 +223,9 @@ dependencies = [ [[package]] name = "clap_derive" -version = "4.5.28" +version = "4.5.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf4ced95c6f4a675af3da73304b9ac4ed991640c36374e4b46795c49e17cf1ed" +checksum = "09176aae279615badda0765c0c0b3f6ed53f4709118af73cf4655d85d1530cd7" dependencies = [ "heck", "proc-macro2", @@ -290,9 +284,9 @@ dependencies = [ [[package]] name = "crossbeam-channel" -version = "0.5.14" +version = "0.5.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06ba6d68e24814cb8de6bb986db8222d3a027d15872cabc0d18817bc3c0e4471" +checksum = "82b8f8f868b36967f9606790d1903570de9ceaf870a7bf9fbbd3016d636a2cb2" dependencies = [ "crossbeam-utils", ] @@ -343,9 +337,9 @@ dependencies = [ [[package]] name = "darling" -version = "0.20.10" +version = "0.20.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f63b86c8a8826a49b8c21f08a2d07338eec8d900540f8630dc76284be802989" +checksum = "fc7f46116c46ff9ab3eb1597a45688b6715c6e628b5c133e288e709a29bcb4ee" dependencies = [ "darling_core", "darling_macro", @@ -353,9 +347,9 @@ dependencies = [ [[package]] name = "darling_core" -version = "0.20.10" +version = "0.20.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95133861a8032aaea082871032f5815eb9e98cef03fa916ab4500513994df9e5" +checksum = "0d00b9596d185e565c2207a0b01f8bd1a135483d02d9b7b0a54b11da8d53412e" dependencies = [ "fnv", "ident_case", @@ -367,9 +361,9 @@ dependencies = [ [[package]] name = "darling_macro" -version = "0.20.10" +version = "0.20.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d336a2a514f6ccccaa3e09b02d41d35330c07ddf03a62165fcec10bb561c7806" +checksum = "fc34b93ccb385b40dc71c6fceac4b2ad23662c7eeb248cf10d529b7e055b6ead" dependencies = [ "darling_core", "quote", @@ -400,9 +394,9 @@ dependencies = [ [[package]] name = "deranged" -version = "0.3.11" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b42b6fa04a440b495c8b04d0e71b707c585f83cb9cb28cf8cd0d976c315e31b4" +checksum = "9c9e6a11ca8224451684bc0d7d5a7adbf8f2fd6887261a1cfc3c0432f9d4068e" dependencies = [ "powerfmt", ] @@ -462,21 +456,21 @@ dependencies = [ [[package]] name = "either" -version = "1.13.0" +version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" +checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" [[package]] name = "equivalent" -version = "1.0.1" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" [[package]] name = "errno" -version = "0.3.10" +version = "0.3.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33d852cb9b869c2a9b3df2f71a3074817f01e1844f839a144f5fcef059a4eb5d" +checksum = "976dd42dc7e85965fe702eb8164f21f450704bdde31faefd6471dba214cb594e" dependencies = [ "libc", "windows-sys 0.59.0", @@ -612,14 +606,14 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.3.1" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43a49c392881ce6d5c3b8cb70f98717b7c07aabbdff06687b9030dbfbe2725f8" +checksum = "73fea8450eea4bac3940448fb7ae50d91f034f941199fcd9d909a5a07aa455f0" dependencies = [ "cfg-if", "libc", - "wasi 0.13.3+wasi-0.2.2", - "windows-targets 0.52.6", + "r-efi", + "wasi 0.14.2+wasi-0.2.4", ] [[package]] @@ -630,9 +624,9 @@ checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" [[package]] name = "handlebars" -version = "6.3.1" +version = "6.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d752747ddabc4c1a70dd28e72f2e3c218a816773e0d7faf67433f1acfa6cba7c" +checksum = "759e2d5aea3287cb1190c8ec394f42866cb5bf74fcbf213f354e3c856ea26098" dependencies = [ "derive_builder", "log", @@ -641,7 +635,7 @@ dependencies = [ "pest_derive", "serde", "serde_json", - "thiserror 2.0.11", + "thiserror 2.0.12", ] [[package]] @@ -670,20 +664,20 @@ checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" [[package]] name = "hostname" -version = "0.4.0" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f9c7c7c8ac16c798734b8a24560c1362120597c40d5e1459f09498f8f6c8f2ba" +checksum = "a56f203cd1c76362b69e3863fd987520ac36cf70a8c92627449b2f64a8cf7d65" dependencies = [ "cfg-if", "libc", - "windows", + "windows-link", ] [[package]] name = "humantime" -version = "2.1.0" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" +checksum = "9b112acc8b3adf4b107a8ec20977da0273a8c386765a3ec0229bd500a1443f9f" [[package]] name = "humantime-serde" @@ -703,9 +697,9 @@ checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" [[package]] name = "indexmap" -version = "2.7.1" +version = "2.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c9c992b02b5b4c94ea26e32fe5bccb7aa7d9f390ab5c1221ff895bc7ea8b652" +checksum = "cea70ddb795996207ad57735b50c5982d8844f38ba9ee5f1aedcfb708a2aa11e" dependencies = [ "equivalent", "hashbrown 0.15.2", @@ -713,9 +707,9 @@ dependencies = [ [[package]] name = "inout" -version = "0.1.3" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a0c10553d664a4d0bcff9f4215d0aac67a639cc68ef660840afe309b807bc9f5" +checksum = "879f10e63c20629ecabbb64a8010319738c66a5cd0c29b02d63d272b03751d01" dependencies = [ "generic-array", ] @@ -740,16 +734,17 @@ dependencies = [ [[package]] name = "itoa" -version = "1.0.14" +version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d75a2a4b1b190afb6f5425f10f6a8f959d2ea0b9c2b1d79553551850539e4674" +checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" [[package]] name = "jobserver" -version = "0.1.32" +version = "0.1.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48d1dbcbbeb6a7fec7e059840aa538bd62aaccf972c7346c4d9d2059312853d0" +checksum = "38f262f097c174adebe41eb73d66ae9c06b2844fb0da69969647bbddd9b0538a" dependencies = [ + "getrandom 0.3.2", "libc", ] @@ -771,15 +766,15 @@ checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" [[package]] name = "libc" -version = "0.2.169" +version = "0.2.172" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5aba8db14291edd000dfcc4d620c7ebfb122c613afb886ca8803fa4e128a20a" +checksum = "d750af042f7ef4f724306de029d18836c26c1765a54a6a3f094cbd23a7267ffa" [[package]] name = "linux-raw-sys" -version = "0.4.15" +version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab" +checksum = "cd945864f07fe9f5371a27ad7b52a172b4b499999f1d97574c9fa68373937e12" [[package]] name = "lock_api" @@ -793,19 +788,19 @@ dependencies = [ [[package]] name = "log" -version = "0.4.25" +version = "0.4.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04cbf5b083de1c7e0222a7a51dbfdba1cbe1c6ab0b15e29fff3f6c077fd9cd9f" +checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94" [[package]] name = "lrwn_filters" -version = "4.9.0" +version = "4.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "470ec7a1a238a47a5ed8b7a0f765ccb870b812dea76621c35b0bae1118b831cb" +checksum = "5da176e42cd97f9f298624ccb6266c0d24591ba9ed83b7d85519d1024a901c53" dependencies = [ "hex", "serde", - "thiserror 1.0.69", + "thiserror 2.0.12", ] [[package]] @@ -816,9 +811,9 @@ checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" [[package]] name = "miniz_oxide" -version = "0.8.4" +version = "0.8.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b3b1c9bd4fe1f0f8b387f6eb9eb3b4a1aa26185e5750efb9140301703f62cd1b" +checksum = "3be647b768db090acb35d5ec5db2b0e1f1de11133ca123b9eacf5137868f892a" dependencies = [ "adler2", ] @@ -890,9 +885,9 @@ dependencies = [ [[package]] name = "once_cell" -version = "1.20.3" +version = "1.21.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "945462a4b81e43c4e3ba96bd7b49d834c6f61198356aa858733bc4acf3cbe62e" +checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" [[package]] name = "parking_lot" @@ -931,20 +926,20 @@ dependencies = [ [[package]] name = "pest" -version = "2.7.15" +version = "2.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b7cafe60d6cf8e62e1b9b2ea516a089c008945bb5a275416789e7db0bc199dc" +checksum = "198db74531d58c70a361c42201efde7e2591e976d518caf7662a47dc5720e7b6" dependencies = [ "memchr", - "thiserror 2.0.11", + "thiserror 2.0.12", "ucd-trie", ] [[package]] name = "pest_derive" -version = "2.7.15" +version = "2.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "816518421cfc6887a0d62bf441b6ffb4536fcc926395a69e1a85852d4363f57e" +checksum = "d725d9cfd79e87dccc9341a2ef39d1b6f6353d68c4b33c177febbe1a402c97c5" dependencies = [ "pest", "pest_generator", @@ -952,9 +947,9 @@ dependencies = [ [[package]] name = "pest_generator" -version = "2.7.15" +version = "2.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d1396fd3a870fc7838768d171b4616d5c91f6cc25e377b673d714567d99377b" +checksum = "db7d01726be8ab66ab32f9df467ae8b1148906685bbe75c82d1e65d7f5b3f841" dependencies = [ "pest", "pest_meta", @@ -965,9 +960,9 @@ dependencies = [ [[package]] name = "pest_meta" -version = "2.7.15" +version = "2.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e1e58089ea25d717bfd31fb534e4f3afcc2cc569c70de3e239778991ea3b7dea" +checksum = "7f9f832470494906d1fca5329f8ab5791cc60beb230c74815dff541cbd2b5ca0" dependencies = [ "once_cell", "pest", @@ -998,9 +993,9 @@ checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" [[package]] name = "pkg-config" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "953ec861398dccce10c670dfeaf3ec4911ca479e9c02154b3a215178c5f566f2" +checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" [[package]] name = "powerfmt" @@ -1010,18 +1005,18 @@ checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" [[package]] name = "ppv-lite86" -version = "0.2.20" +version = "0.2.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04" +checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" dependencies = [ - "zerocopy 0.7.35", + "zerocopy", ] [[package]] name = "prettyplease" -version = "0.2.29" +version = "0.2.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6924ced06e1f7dfe3fa48d57b9f74f55d8915f5036121bef647ef4b204895fac" +checksum = "664ec5419c51e34154eec046ebcba56312d5a2fc3b09a06da188e1ad21afadf6" dependencies = [ "proc-macro2", "syn", @@ -1029,9 +1024,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.93" +version = "1.0.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60946a68e5f9d28b0dc1c21bb8a97ee7d018a8b322fa57838ba31cc878e22d99" +checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778" dependencies = [ "unicode-ident", ] @@ -1090,13 +1085,19 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.38" +version = "1.0.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e4dccaaaf89514f546c693ddc140f729f958c247918a13380cccc6078391acc" +checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" dependencies = [ "proc-macro2", ] +[[package]] +name = "r-efi" +version = "5.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74765f6d916ee2faa39bc8e68e4f3ed8949b48cccdac59983d287a7cb71ce9c5" + [[package]] name = "rand" version = "0.8.5" @@ -1110,13 +1111,12 @@ dependencies = [ [[package]] name = "rand" -version = "0.9.0" +version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3779b94aeb87e8bd4e834cee3650289ee9e0d5677f976ecdb6d219e5f4f6cd94" +checksum = "9fbfd9d094a40bf3ae768db9361049ace4c0e04a4fd6b359518bd7b73a73dd97" dependencies = [ "rand_chacha 0.9.0", - "rand_core 0.9.0", - "zerocopy 0.8.17", + "rand_core 0.9.3", ] [[package]] @@ -1136,7 +1136,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" dependencies = [ "ppv-lite86", - "rand_core 0.9.0", + "rand_core 0.9.3", ] [[package]] @@ -1150,12 +1150,11 @@ dependencies = [ [[package]] name = "rand_core" -version = "0.9.0" +version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b08f3c9802962f7e1b25113931d94f43ed9725bebc59db9d0c3e9a23b67e15ff" +checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38" dependencies = [ - "getrandom 0.3.1", - "zerocopy 0.8.17", + "getrandom 0.3.2", ] [[package]] @@ -1180,11 +1179,11 @@ dependencies = [ [[package]] name = "redox_syscall" -version = "0.5.8" +version = "0.5.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03a862b389f93e68874fbf580b9de08dd02facb9a788ebadaf4a3fd33cf58834" +checksum = "d2f103c6d277498fbceb16e84d317e2a400f160f46904d5f5410848c829511a3" dependencies = [ - "bitflags 2.8.0", + "bitflags 2.9.0", ] [[package]] @@ -1224,11 +1223,11 @@ checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" [[package]] name = "rustix" -version = "0.38.44" +version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154" +checksum = "d97817398dd4bb2e6da002002db259209759911da105da92bec29ccb12cf58bf" dependencies = [ - "bitflags 2.8.0", + "bitflags 2.9.0", "errno", "libc", "linux-raw-sys", @@ -1237,9 +1236,9 @@ dependencies = [ [[package]] name = "ryu" -version = "1.0.19" +version = "1.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ea1a2d0a644769cc99faa24c3ad26b379b786fe7c36fd3c546254801650e6dd" +checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" [[package]] name = "same-file" @@ -1258,18 +1257,18 @@ checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" [[package]] name = "serde" -version = "1.0.217" +version = "1.0.219" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02fc4265df13d6fa1d00ecff087228cc0a2b5f3c0e87e258d8b94a156e984c70" +checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.217" +version = "1.0.219" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a9bf7cf98d04a2b28aead066b7496853d4779c9cc183c440dbac457641e19a0" +checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" dependencies = [ "proc-macro2", "quote", @@ -1278,9 +1277,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.138" +version = "1.0.140" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d434192e7da787e94a6ea7e9670b26a036d0ca41e0b7efb2676dd32bae872949" +checksum = "20068b6e96dc6c9bd23e01df8827e6c7e1f2fddd43c21810382803c136b99373" dependencies = [ "itoa", "memchr", @@ -1326,9 +1325,9 @@ dependencies = [ [[package]] name = "signal-hook-registry" -version = "1.4.2" +version = "1.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9e9e0b4211b72e7b8b6e85c807d36c212bdb33ea8587f7569562a84df5465b1" +checksum = "9203b8055f63a2a00e2f593bb0510367fe707d7ff1e5c872de2f537b339e5410" dependencies = [ "libc", ] @@ -1368,15 +1367,15 @@ dependencies = [ [[package]] name = "smallvec" -version = "1.13.2" +version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" +checksum = "8917285742e9f3e1683f0a9c4e6b57960b7314d0b08d30d1ecd426713ee2eee9" [[package]] name = "socket2" -version = "0.5.8" +version = "0.5.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c970269d99b64e60ec3bd6ad27270092a5394c4e309314b18ae3fe575695fbe8" +checksum = "4f5fd57c80058a56cf5c777ab8a126398ece8e442983605d280a44ce79d0edef" dependencies = [ "libc", "windows-sys 0.52.0", @@ -1396,9 +1395,9 @@ checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" [[package]] name = "syn" -version = "2.0.98" +version = "2.0.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "36147f1a48ae0ec2b5b3bc5b537d267457555a10dc06f3dbc8cb11ba3006d3b1" +checksum = "b09a44accad81e1ba1cd74a32461ba89dee89095ba17b32f5d03683b1b1fc2a0" dependencies = [ "proc-macro2", "quote", @@ -1438,13 +1437,12 @@ checksum = "61c41af27dd6d1e27b1b16b489db798443478cef1f06a660c96db617ba5de3b1" [[package]] name = "tempfile" -version = "3.16.0" +version = "3.19.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38c246215d7d24f48ae091a2902398798e05d978b24315d6efbc00ede9a8bb91" +checksum = "7437ac7763b9b123ccf33c338a5cc1bac6f69b45a136c19bdd8a65e3916435bf" dependencies = [ - "cfg-if", "fastrand", - "getrandom 0.3.1", + "getrandom 0.3.2", "once_cell", "rustix", "windows-sys 0.59.0", @@ -1461,11 +1459,11 @@ dependencies = [ [[package]] name = "thiserror" -version = "2.0.11" +version = "2.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d452f284b73e6d76dd36758a0c8684b1d5be31f92b89d07fd5822175732206fc" +checksum = "567b8a2dae586314f7be2a752ec7474332959c6460e02bde30d702a66d488708" dependencies = [ - "thiserror-impl 2.0.11", + "thiserror-impl 2.0.12", ] [[package]] @@ -1481,9 +1479,9 @@ dependencies = [ [[package]] name = "thiserror-impl" -version = "2.0.11" +version = "2.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26afc1baea8a989337eeb52b6e72a039780ce45c3edfcc9c5b9d112feeb173c2" +checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d" dependencies = [ "proc-macro2", "quote", @@ -1492,9 +1490,9 @@ dependencies = [ [[package]] name = "time" -version = "0.3.37" +version = "0.3.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "35e7868883861bd0e56d9ac6efcaaca0d6d5d82a2a7ec8209ff492c07cf37b21" +checksum = "8a7619e19bc266e0f9c5e6686659d394bc57973859340060a69221e57dbc0c40" dependencies = [ "deranged", "itoa", @@ -1509,15 +1507,15 @@ dependencies = [ [[package]] name = "time-core" -version = "0.1.2" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" +checksum = "c9e9a38711f559d9e3ce1cdb06dd7c5b8ea546bc90052da6d06bb76da74bb07c" [[package]] name = "time-macros" -version = "0.2.19" +version = "0.2.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2834e6017e3e5e4b9834939793b282bc03b37a3336245fa820e35e233e2a85de" +checksum = "3526739392ec93fd8b359c8e98514cb3e8e021beb4e5f597b00a0221f8ed8a49" dependencies = [ "num-conv", "time-core", @@ -1525,9 +1523,9 @@ dependencies = [ [[package]] name = "tokio" -version = "1.43.0" +version = "1.44.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d61fa4ffa3de412bfea335c6ecff681de2b609ba3c77ef3e00e521813a9ed9e" +checksum = "e6b88822cbe49de4185e3a4cbf8321dd487cf5fe0c5c65695fef6346371e9c48" dependencies = [ "backtrace", "bytes", @@ -1554,9 +1552,9 @@ dependencies = [ [[package]] name = "tokio-util" -version = "0.7.13" +version = "0.7.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7fcaa8d55a2bdd6b83ace262b016eca0d79ee02818c5c1bcdf0305114081078" +checksum = "6b9590b93e6fcc1739458317cccd391ad3955e2bde8913edf6f95f9e65a8f034" dependencies = [ "bytes", "futures-core", @@ -1602,9 +1600,9 @@ dependencies = [ [[package]] name = "tonic-build" -version = "0.12.3" +version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9557ce109ea773b399c9b9e5dca39294110b74f1f342cb347a80d1fce8c26a11" +checksum = "d85f0383fadd15609306383a90e85eaed44169f931a5d2be1b42c76ceff1825e" dependencies = [ "prettyplease", "proc-macro2", @@ -1616,9 +1614,9 @@ dependencies = [ [[package]] name = "typenum" -version = "1.17.0" +version = "1.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" +checksum = "1dccffe3ce07af9386bfd29e80c0ab1a8205a2fc34e4bcd40364df902cfa8f3f" [[package]] name = "ucd-trie" @@ -1628,17 +1626,17 @@ checksum = "2896d95c02a80c6d6a5d6e953d479f5ddf2dfdb6a244441010e373ac0fb88971" [[package]] name = "unicode-ident" -version = "1.0.16" +version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a210d160f08b701c8721ba1c726c11662f877ea6b7094007e1ca9a1041945034" +checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" [[package]] name = "uuid" -version = "1.13.1" +version = "1.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ced87ca4be083373936a67f8de945faa23b6b42384bd5b64434850802c6dccd0" +checksum = "458f7a779bf54acc9f347480ac654f68407d3aab21269a6e3c9f922acd9e2da9" dependencies = [ - "getrandom 0.3.1", + "getrandom 0.3.2", ] [[package]] @@ -1671,9 +1669,9 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasi" -version = "0.13.3+wasi-0.2.2" +version = "0.14.2+wasi-0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26816d2e1a4a36a2940b96c5296ce403917633dff8f3440e9b236ed6f6bacad2" +checksum = "9683f9a5a998d873c0d21fcbe3c083009670149a8fab228644b8bd36b2c48cb3" dependencies = [ "wit-bindgen-rt", ] @@ -1688,23 +1686,10 @@ dependencies = [ ] [[package]] -name = "windows" -version = "0.52.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e48a53791691ab099e5e2ad123536d0fff50652600abaf43bbf952894110d0be" -dependencies = [ - "windows-core", - "windows-targets 0.52.6", -] - -[[package]] -name = "windows-core" -version = "0.52.0" +name = "windows-link" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" -dependencies = [ - "windows-targets 0.52.6", -] +checksum = "76840935b766e1b0a05c0066835fb9ec80071d4c09a16f6bd5f7e655e3c14c38" [[package]] name = "windows-sys" @@ -1856,57 +1841,36 @@ checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" [[package]] name = "winnow" -version = "0.7.2" +version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59690dea168f2198d1a3b0cac23b8063efcd11012f10ae4698f284808c8ef603" +checksum = "63d3fcd9bba44b03821e7d699eeee959f3126dcc4aa8e4ae18ec617c2a5cea10" dependencies = [ "memchr", ] [[package]] name = "wit-bindgen-rt" -version = "0.33.0" +version = "0.39.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3268f3d866458b787f390cf61f4bbb563b922d091359f9608842999eaee3943c" +checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1" dependencies = [ - "bitflags 2.8.0", + "bitflags 2.9.0", ] [[package]] name = "zerocopy" -version = "0.7.35" +version = "0.8.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" +checksum = "2586fea28e186957ef732a5f8b3be2da217d65c5969d4b1e17f973ebbe876879" dependencies = [ - "byteorder", - "zerocopy-derive 0.7.35", -] - -[[package]] -name = "zerocopy" -version = "0.8.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa91407dacce3a68c56de03abe2760159582b846c6a4acd2f456618087f12713" -dependencies = [ - "zerocopy-derive 0.8.17", -] - -[[package]] -name = "zerocopy-derive" -version = "0.7.35" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" -dependencies = [ - "proc-macro2", - "quote", - "syn", + "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.17" +version = "0.8.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06718a168365cad3d5ff0bb133aad346959a2074bd4a85c121255a11304a8626" +checksum = "a996a8f63c5c4448cd959ac1bab0aaa3306ccfd060472f85943ee0750f0169be" dependencies = [ "proc-macro2", "quote", diff --git a/Cargo.toml b/Cargo.toml index 0f74525..4b9fc31 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,8 +16,8 @@ "usage", "derive", ] } - chirpstack_api = { version = "4.11.1", default-features = false } - lrwn_filters = { version = "4.9", features = ["serde"] } + chirpstack_api = { version = "4.12", default-features = false } + lrwn_filters = { version = "4.12", features = ["serde"] } log = "0.4" simple_logger = "5.0" syslog = "7.0" @@ -26,7 +26,7 @@ anyhow = "1.0" humantime-serde = "1.1" serde = { version = "1.0", features = ["derive"] } - tokio = { version = "1.43", features = [ + tokio = { version = "1.44", features = [ "macros", "rt-multi-thread", "time", @@ -40,8 +40,8 @@ futures = "0.3" prost-types = "0.13" zmq = "0.10" - cmac = { version = "0.7" } - aes = { version = "0.8" } + cmac = "0.7" + aes = "0.8" [dev-dependencies] zeromq = "0.4" diff --git a/rust-toolchain.toml b/rust-toolchain.toml index 604045c..a98f086 100644 --- a/rust-toolchain.toml +++ b/rust-toolchain.toml @@ -1,4 +1,4 @@ [toolchain] - channel = "1.84.1" + channel = "1.85.0" components = ["rustfmt", "clippy"] profile = "default" From 20193a9d95c512860cc2dd9c154d48fe190c4701 Mon Sep 17 00:00:00 2001 From: Orne Brocaar Date: Tue, 22 Apr 2025 13:14:13 +0100 Subject: [PATCH 02/17] Update GitHub workflow. --- .github/workflows/main.yml | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 09fa12a..fa1cf6f 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -16,9 +16,9 @@ jobs: uses: actions/checkout@v4 - name: Install Nix - uses: cachix/install-nix-action@v27 + uses: cachix/install-nix-action@v31 with: - nix_path: nixpkgs=channel:nixos-24.05 + nix_path: nixpkgs=channel:nixos-24.11 - name: Cargo cache uses: actions/cache@v4 @@ -30,6 +30,9 @@ jobs: ~/.cargo/git/db/ target/ key: ${{ runner.os }}-cargo-test-${{ hashFiles('**/Cargo.lock') }} + - + name: Install dev dependencies + run: nix-shell --command "make dev-dependencies" - name: Run tests run: nix-shell --command "make test" @@ -53,9 +56,9 @@ jobs: uses: actions/checkout@v4 - name: Install Nix - uses: cachix/install-nix-action@v27 + uses: cachix/install-nix-action@v31 with: - nix_path: nixpkgs=channel:nixos-24.05 + nix_path: nixpkgs=channel:nixos-24.11 - name: Cargo cache uses: actions/cache@v4 From b208a8ed5aa9941f61cd477ba22dabe5ce2218f6 Mon Sep 17 00:00:00 2001 From: Orne Brocaar Date: Tue, 22 Apr 2025 13:14:46 +0100 Subject: [PATCH 03/17] Fix cargo clippy feedback. --- src/packets.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/packets.rs b/src/packets.rs index 43002e4..c4e3d30 100644 --- a/src/packets.rs +++ b/src/packets.rs @@ -191,7 +191,7 @@ impl MHDR { return Err(anyhow!("Max hop_count is 8")); } - Ok(0x07 << 5 | self.payload_type.to_byte() << 3 | (self.hop_count - 1)) + Ok((0x07 << 5) | (self.payload_type.to_byte() << 3) | (self.hop_count - 1)) } } From 6add5992e00a173ec9e521a695373daeb6908127 Mon Sep 17 00:00:00 2001 From: Orne Brocaar Date: Mon, 12 May 2025 12:36:59 +0100 Subject: [PATCH 04/17] Refactor heartbeat + Concentratord ZMQ interface. This refactors the heartbeat payload into an event payload, of which a heartbeat is one of the possible options. This also refactors the Concentratord ZMQ interface, moving away from multipart messages in favor of a single message containing an Event / Command Protobuf message. --- Cargo.lock | 2 - Cargo.toml | 2 +- src/backend.rs | 192 +++++++++---------- src/cache.rs | 2 +- src/heartbeat.rs | 10 +- src/mesh.rs | 115 ++++++++---- src/packets.rs | 193 +++++++++++++------- src/proxy.rs | 89 +++------ tests/border_gateway_downlink_lora.rs | 22 ++- tests/border_gateway_downlink_mesh.rs | 22 ++- tests/border_gateway_mesh_heartbeat.rs | 88 +++++---- tests/border_gateway_uplink_lora.rs | 25 +-- tests/border_gateway_uplink_mesh.rs | 22 ++- tests/common/mod.rs | 17 +- tests/relay_gateway_downlink_lora.rs | 24 +-- tests/relay_gateway_mesh_heartbeat.rs | 20 +- tests/relay_gateway_relay_mesh_heartbeat.rs | 58 +++--- tests/relay_gateway_uplink_lora.rs | 22 ++- tests/relay_gateway_uplink_mesh.rs | 22 ++- 19 files changed, 523 insertions(+), 424 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 594e84f..9c1bfb6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -180,8 +180,6 @@ dependencies = [ [[package]] name = "chirpstack_api" version = "4.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88e36da3fd2016e1859993d9b8ff6758e38bd754489d289571c23ffe75b5d0b7" dependencies = [ "hex", "pbjson-build", diff --git a/Cargo.toml b/Cargo.toml index 4b9fc31..92d17af 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,7 +16,7 @@ "usage", "derive", ] } - chirpstack_api = { version = "4.12", default-features = false } + chirpstack_api = { version = "4.12.0", default-features = false, path = "../chirpstack/api/rust" } lrwn_filters = { version = "4.12", features = ["serde"] } log = "0.4" simple_logger = "5.0" diff --git a/src/backend.rs b/src/backend.rs index fc9e6f7..bca10cc 100644 --- a/src/backend.rs +++ b/src/backend.rs @@ -16,8 +16,7 @@ static RELAY_ID: OnceLock> = OnceLock::new(); static CONCENTRATORD_CMD_CHAN: OnceLock = OnceLock::new(); static MESH_CONCENTRATORD_CMD_CHAN: OnceLock = OnceLock::new(); -type Event = (String, Vec); -type Command = ((String, Vec), oneshot::Sender>>); +type Command = (gw::Command, oneshot::Sender>>); type CommandChannel = mpsc::UnboundedSender; pub async fn setup(conf: &Configuration) -> Result<()> { @@ -47,7 +46,7 @@ async fn setup_concentratord(conf: &Configuration) -> Result<()> { sock.connect(&command_url).unwrap(); while let Some(cmd) = cmd_rx.blocking_recv() { - let resp = send_zmq_command(&mut sock, &cmd); + let resp = send_zmq_command(&mut sock, &cmd.0); cmd.1.send(resp).unwrap(); } @@ -60,23 +59,30 @@ async fn setup_concentratord(conf: &Configuration) -> Result<()> { trace!("Reading Gateway ID"); let mut gateway_id: [u8; 8] = [0; 8]; let (gateway_id_tx, gateway_id_rx) = oneshot::channel::>>(); - cmd_tx.send((("gateway_id".to_string(), vec![]), gateway_id_tx))?; + cmd_tx.send(( + gw::Command { + command: Some(gw::command::Command::GetGatewayId( + gw::GetGatewayIdRequest {}, + )), + }, + gateway_id_tx, + ))?; let resp = gateway_id_rx.await??; - gateway_id.copy_from_slice(&resp); - info!("Retrieved Gateway ID: {}", hex::encode(gateway_id)); + + let resp = gw::GetGatewayIdResponse::decode(resp.as_slice())?; + gateway_id.copy_from_slice(&hex::decode(&resp.gateway_id)?); + info!("Retrieved Gateway ID: {}", resp.gateway_id); GATEWAY_ID .set(Mutex::new(gateway_id)) .map_err(|e| anyhow!("OnceLock error: {:?}", e))?; // Set CMD channel. - CONCENTRATORD_CMD_CHAN .set(cmd_tx) .map_err(|e| anyhow!("OnceLock error: {:?}", e))?; // Setup ZMQ event. - - let (event_tx, event_rx) = mpsc::unbounded_channel::(); + let (event_tx, event_rx) = mpsc::unbounded_channel::(); // Spawn the zmq event handler to a dedicated thread. thread::spawn({ @@ -143,7 +149,7 @@ async fn setup_mesh_conncentratord(conf: &Configuration) -> Result<()> { sock.connect(&command_url).unwrap(); while let Some(cmd) = cmd_rx.blocking_recv() { - let resp = send_zmq_command(&mut sock, &cmd); + let resp = send_zmq_command(&mut sock, &cmd.0); cmd.1.send(resp).unwrap(); } @@ -155,12 +161,20 @@ async fn setup_mesh_conncentratord(conf: &Configuration) -> Result<()> { trace!("Reading Gateway ID"); let (gateway_id_tx, gateway_id_rx) = oneshot::channel::>>(); - cmd_tx.send((("gateway_id".to_string(), vec![]), gateway_id_tx))?; + cmd_tx.send(( + gw::Command { + command: Some(gw::command::Command::GetGatewayId( + gw::GetGatewayIdRequest {}, + )), + }, + gateway_id_tx, + ))?; let resp = gateway_id_rx.await??; - info!("Retrieved Gateway ID: {}", hex::encode(&resp)); + let resp = gw::GetGatewayIdResponse::decode(resp.as_slice())?; + info!("Retrieved Gateway ID: {}", resp.gateway_id); let mut relay_id: [u8; 4] = [0; 4]; - relay_id.copy_from_slice(&resp[4..]); + relay_id.copy_from_slice(&hex::decode(&resp.gateway_id)?[4..]); RELAY_ID .set(Mutex::new(relay_id)) .map_err(|e| anyhow!("OnceLock error: {:?}", e))?; @@ -173,7 +187,7 @@ async fn setup_mesh_conncentratord(conf: &Configuration) -> Result<()> { // Setup ZMQ event. - let (event_tx, event_rx) = mpsc::unbounded_channel::(); + let (event_tx, event_rx) = mpsc::unbounded_channel::(); // Spawn the zmq event handler to a dedicated thread; thread::spawn({ @@ -211,7 +225,7 @@ async fn setup_mesh_conncentratord(conf: &Configuration) -> Result<()> { async fn event_loop( border_gateway: bool, border_gateway_ignore_direct_uplinks: bool, - mut event_rx: mpsc::UnboundedReceiver, + mut event_rx: mpsc::UnboundedReceiver, filters: lrwn_filters::Filters, ) { trace!("Starting event loop"); @@ -219,7 +233,7 @@ async fn event_loop( if let Err(e) = handle_event_msg( border_gateway, border_gateway_ignore_direct_uplinks, - &event, + event, &filters, ) .await @@ -230,10 +244,10 @@ async fn event_loop( } } -async fn mesh_event_loop(border_gateway: bool, mut event_rx: mpsc::UnboundedReceiver) { +async fn mesh_event_loop(border_gateway: bool, mut event_rx: mpsc::UnboundedReceiver) { trace!("Starting mesh event loop"); while let Some(event) = event_rx.recv().await { - if let Err(e) = handle_mesh_event_msg(border_gateway, &event).await { + if let Err(e) = handle_mesh_event_msg(border_gateway, event).await { error!("Handle mesh event error: {}", e); continue; } @@ -243,20 +257,14 @@ async fn mesh_event_loop(border_gateway: bool, mut event_rx: mpsc::UnboundedRece async fn handle_event_msg( border_gateway: bool, border_gateway_ignore_direct_uplinks: bool, - event: &Event, + event: gw::Event, filters: &lrwn_filters::Filters, ) -> Result<()> { - trace!( - "Handling event, event: {}, data: {}", - event.0, - hex::encode(&event.1) - ); - - match event.0.as_str() { - "up" => { - let pl = gw::UplinkFrame::decode(event.1.as_slice())?; + trace!("Handling event, event: {:?}", event,); - if let Some(rx_info) = &pl.rx_info { + match &event.event { + Some(gw::event::Event::UplinkFrame(v)) => { + if let Some(rx_info) = &v.rx_info { // Filter out frames with invalid CRC. if rx_info.crc_status() != gw::CrcStatus::CrcOk { debug!( @@ -267,7 +275,7 @@ async fn handle_event_msg( } // Filter out proprietary payloads. - if pl.phy_payload.first().cloned().unwrap_or_default() & 0xe0 == 0xe0 { + if v.phy_payload.first().cloned().unwrap_or_default() & 0xe0 == 0xe0 { debug!( "Discarding proprietary uplink, uplink_id: {}", rx_info.uplink_id @@ -282,44 +290,34 @@ async fn handle_event_msg( } // Filter uplinks based on DevAddr and JoinEUI filters. - if !lrwn_filters::matches(&pl.phy_payload, filters) { + if !lrwn_filters::matches(&v.phy_payload, filters) { debug!( "Discarding uplink because of dev_addr and join_eui filters, uplink_id: {}", rx_info.uplink_id ) } - info!("Frame received - {}", helpers::format_uplink(&pl)?); - mesh::handle_uplink(border_gateway, pl).await?; + info!("Frame received - {}", helpers::format_uplink(&v)?); + mesh::handle_uplink(border_gateway, v).await?; } } - "stats" => { + Some(gw::event::Event::GatewayStats(_)) => { if border_gateway { - let pl = gw::GatewayStats::decode(event.1.as_slice())?; - info!("Gateway stats received, gateway_id: {}", pl.gateway_id); - proxy::send_stats(&pl).await?; + proxy::send_event(event).await?; } } - _ => { - return Ok(()); - } + _ => {} } Ok(()) } -async fn handle_mesh_event_msg(border_gateway: bool, event: &Event) -> Result<()> { - trace!( - "Handling mesh event, event: {}, data: {}", - event.0, - hex::encode(&event.1) - ); - - match event.0.as_str() { - "up" => { - let pl = gw::UplinkFrame::decode(event.1.as_slice())?; +async fn handle_mesh_event_msg(border_gateway: bool, event: gw::Event) -> Result<()> { + trace!("Handling mesh event, event: {:?}", event); - if let Some(rx_info) = &pl.rx_info { + match &event.event { + Some(gw::event::Event::UplinkFrame(v)) => { + if let Some(rx_info) = &v.rx_info { // Filter out frames with invalid CRC. if rx_info.crc_status() != gw::CrcStatus::CrcOk { debug!( @@ -331,79 +329,79 @@ async fn handle_mesh_event_msg(border_gateway: bool, event: &Event) -> Result<() } // The mesh event msg must always be a proprietary payload. - if pl.phy_payload.first().cloned().unwrap_or_default() & 0xe0 == 0xe0 { - info!("Mesh frame received - {}", helpers::format_uplink(&pl)?); - mesh::handle_mesh(border_gateway, pl).await?; + if v.phy_payload.first().cloned().unwrap_or_default() & 0xe0 == 0xe0 { + info!("Mesh frame received - {}", helpers::format_uplink(v)?); + mesh::handle_mesh(border_gateway, v).await?; } } - _ => { - return Ok(()); - } + _ => {} } Ok(()) } -async fn send_command(cmd: &str, b: &[u8]) -> Result> { - trace!( - "Sending command, command: {}, data: {}", - cmd, - hex::encode(b) - ); +async fn send_command(cmd: gw::Command) -> Result> { + trace!("Sending command, command: {:?}", cmd,); let cmd_chan = CONCENTRATORD_CMD_CHAN .get() .ok_or_else(|| anyhow!("CONCENTRATORD_CMD_CHAN is not set"))?; let (cmd_tx, cmd_rx) = oneshot::channel::>>(); - cmd_chan.send(((cmd.to_string(), b.to_vec()), cmd_tx))?; + cmd_chan.send((cmd, cmd_tx))?; cmd_rx.await? } -async fn send_mesh_command(cmd: &str, b: &[u8]) -> Result> { - trace!( - "Sending mesh command, command: {}, data: {}", - cmd, - hex::encode(b) - ); +async fn send_mesh_command(cmd: gw::Command) -> Result> { + trace!("Sending mesh command, command: {:?}", cmd); let cmd_chan = MESH_CONCENTRATORD_CMD_CHAN .get() .ok_or_else(|| anyhow!("MESH_CONCENTRATORD_CMD_CHAN is not set"))?; let (cmd_tx, cmd_rx) = oneshot::channel::>>(); - cmd_chan.send(((cmd.to_string(), b.to_vec()), cmd_tx))?; + cmd_chan.send((cmd, cmd_tx))?; cmd_rx.await? } -pub async fn mesh(pl: &gw::DownlinkFrame) -> Result<()> { - info!("Sending mesh frame - {}", helpers::format_downlink(pl)?); +pub async fn mesh(pl: gw::DownlinkFrame) -> Result<()> { + info!("Sending mesh frame - {}", helpers::format_downlink(&pl)?); + let downlink_id = pl.downlink_id; let tx_ack = { - let b = pl.encode_to_vec(); - let resp_b = send_mesh_command("down", &b).await?; + let pl = gw::Command { + command: Some(gw::command::Command::SendDownlinkFrame(pl)), + }; + let resp_b = send_mesh_command(pl).await?; gw::DownlinkTxAck::decode(resp_b.as_slice())? }; helpers::tx_ack_to_err(&tx_ack)?; - info!("Enqueue acknowledged, downlink_id: {}", pl.downlink_id); + info!("Enqueue acknowledged, downlink_id: {}", downlink_id); Ok(()) } -pub async fn send_downlink(pl: &gw::DownlinkFrame) -> Result { - info!("Sending downlink frame - {}", helpers::format_downlink(pl)?); +pub async fn send_downlink(pl: gw::DownlinkFrame) -> Result { + info!( + "Sending downlink frame - {}", + helpers::format_downlink(&pl)? + ); - let b = pl.encode_to_vec(); - let resp_b = send_command("down", &b).await?; + let pl = gw::Command { + command: Some(gw::command::Command::SendDownlinkFrame(pl)), + }; + let resp_b = send_command(pl).await?; let tx_ack = gw::DownlinkTxAck::decode(resp_b.as_slice())?; Ok(tx_ack) } -pub async fn send_gateway_configuration(pl: &gw::GatewayConfiguration) -> Result<()> { +pub async fn send_gateway_configuration(pl: gw::GatewayConfiguration) -> Result<()> { info!("Sending gateway configuration, version: {}", pl.version); - let b = pl.encode_to_vec(); - let _ = send_command("config", &b).await?; + let pl = gw::Command { + command: Some(gw::command::Command::SetGatewayConfiguration(pl)), + }; + let _ = send_command(pl).await?; Ok(()) } @@ -428,15 +426,9 @@ pub async fn get_gateway_id() -> Result<[u8; 8]> { .await) } -fn send_zmq_command(sock: &mut zmq::Socket, cmd: &Command) -> Result> { - debug!( - "Sending command to socket, command: {}, payload: {}", - &cmd.0 .0, - hex::encode(&cmd.0 .1) - ); - - sock.send(&cmd.0 .0, zmq::SNDMORE)?; - sock.send(&cmd.0 .1, 0)?; +fn send_zmq_command(sock: &mut zmq::Socket, cmd: &gw::Command) -> Result> { + debug!("Sending command to socket, command: {:?}", &cmd,); + sock.send(cmd.encode_to_vec(), 0)?; // set poller so that we can timeout after 100ms let mut items = [sock.as_poll_item(zmq::POLLIN)]; @@ -445,19 +437,13 @@ fn send_zmq_command(sock: &mut zmq::Socket, cmd: &Command) -> Result> { return Err(anyhow!("Could not read down response")); } - // red tx ack response + // read tx ack response let resp_b: &[u8] = &sock.recv_bytes(0)?; Ok(resp_b.to_vec()) } -fn receive_zmq_event(sock: &mut zmq::Socket) -> Result { - let msg = sock.recv_multipart(0)?; - if msg.len() != 2 { - return Err(anyhow!("Event must have 2 frames")); - } - - let event = String::from_utf8(msg[0].to_vec())?; - let b = msg[1].to_vec(); - - Ok((event, b)) +fn receive_zmq_event(sock: &mut zmq::Socket) -> Result { + let b = sock.recv_bytes(0)?; + let event = gw::Event::decode(b.as_slice())?; + Ok(event) } diff --git a/src/cache.rs b/src/cache.rs index 3a6a50b..ff96e07 100644 --- a/src/cache.rs +++ b/src/cache.rs @@ -59,7 +59,7 @@ impl From<&packets::MeshPacket> for PayloadCache { relay_id: v.relay_id, timestamp: 0, }, - packets::Payload::Heartbeat(v) => PayloadCache { + packets::Payload::Event(v) => PayloadCache { p_type, uplink_id: 0, relay_id: v.relay_id, diff --git a/src/heartbeat.rs b/src/heartbeat.rs index aa1bdb8..4e0cf66 100644 --- a/src/heartbeat.rs +++ b/src/heartbeat.rs @@ -45,13 +45,15 @@ pub async fn report_heartbeat() -> Result<()> { let mut packet = packets::MeshPacket { mhdr: packets::MHDR { - payload_type: packets::PayloadType::Heartbeat, + payload_type: packets::PayloadType::Event, hop_count: 1, }, - payload: packets::Payload::Heartbeat(packets::HeartbeatPayload { + payload: packets::Payload::Event(packets::EventPayload { timestamp: SystemTime::now(), relay_id: backend::get_relay_id().await.unwrap_or_default(), - relay_path: vec![], + events: vec![packets::Event::Heartbeat(packets::HeartbeatPayload { + relay_path: vec![], + })], }), mic: None, }; @@ -84,5 +86,5 @@ pub async fn report_heartbeat() -> Result<()> { "Sending heartbeat packet, downlink_id: {}, mesh_packet: {}", pl.downlink_id, packet ); - backend::mesh(&pl).await + backend::mesh(pl).await } diff --git a/src/mesh.rs b/src/mesh.rs index c46702a..ff16789 100644 --- a/src/mesh.rs +++ b/src/mesh.rs @@ -13,8 +13,8 @@ use crate::{ config::{self, Configuration}, helpers, packets::{ - self, DownlinkMetadata, MeshPacket, Payload, PayloadType, UplinkMetadata, UplinkPayload, - MHDR, + self, DownlinkMetadata, Event, MeshPacket, Payload, PayloadType, UplinkMetadata, + UplinkPayload, MHDR, }, proxy, }; @@ -28,15 +28,15 @@ static PAYLOAD_CACHE: LazyLock>> = LazyLock::new(|| Mutex::new(Cache::new(64))); // Handle LoRaWAN payload (non-proprietary). -pub async fn handle_uplink(border_gateway: bool, pl: gw::UplinkFrame) -> Result<()> { +pub async fn handle_uplink(border_gateway: bool, pl: &gw::UplinkFrame) -> Result<()> { match border_gateway { - true => proxy_uplink_lora_packet(&pl).await, - false => relay_uplink_lora_packet(&pl).await, + true => proxy_uplink_lora_packet(pl).await, + false => relay_uplink_lora_packet(pl).await, } } // Handle Proprietary LoRaWAN payload (mesh encapsulated). -pub async fn handle_mesh(border_gateway: bool, pl: gw::UplinkFrame) -> Result<()> { +pub async fn handle_mesh(border_gateway: bool, pl: &gw::UplinkFrame) -> Result<()> { let conf = config::get(); let packet = MeshPacket::from_slice(&pl.phy_payload)?; if !packet.validate_mic(conf.mesh.signing_key)? { @@ -58,7 +58,7 @@ pub async fn handle_mesh(border_gateway: bool, pl: gw::UplinkFrame) -> Result<() // Proxy relayed uplink true => match packet.mhdr.payload_type { PayloadType::Uplink => proxy_uplink_mesh_packet(&pl, packet).await, - PayloadType::Heartbeat => proxy_heartbeat_mesh_packet(&pl, packet).await, + PayloadType::Event => proxy_event_mesh_packet(&pl, packet).await, _ => Ok(()), }, false => relay_mesh_packet(&pl, packet).await, @@ -76,17 +76,17 @@ pub async fn handle_downlink(pl: gw::DownlinkFrame) -> Result if tx_info.context.len() != CTX_PREFIX.len() + 6 || !tx_info.context[0..CTX_PREFIX.len()].eq(&CTX_PREFIX) { - return proxy_downlink_lora_packet(&pl).await; + return proxy_downlink_lora_packet(pl).await; } } relay_downlink_lora_packet(&pl).await } -async fn proxy_downlink_lora_packet(pl: &gw::DownlinkFrame) -> Result { +async fn proxy_downlink_lora_packet(pl: gw::DownlinkFrame) -> Result { info!( "Proxying LoRaWAN downlink, downlink: {}", - helpers::format_downlink(pl)? + helpers::format_downlink(&pl)? ); backend::send_downlink(pl).await } @@ -96,7 +96,12 @@ async fn proxy_uplink_lora_packet(pl: &gw::UplinkFrame) -> Result<()> { "Proxying LoRaWAN uplink, uplink: {}", helpers::format_uplink(pl)? ); - proxy::send_uplink(pl).await + + let pl = gw::Event { + event: Some(gw::event::Event::UplinkFrame(pl.clone())), + }; + + proxy::send_event(pl).await } async fn proxy_uplink_mesh_packet(pl: &gw::UplinkFrame, packet: MeshPacket) -> Result<()> { @@ -150,39 +155,65 @@ async fn proxy_uplink_mesh_packet(pl: &gw::UplinkFrame, packet: MeshPacket) -> R // Set original PHYPayload. pl.phy_payload.clone_from(&mesh_pl.phy_payload); - proxy::send_uplink(&pl).await + let pl = gw::Event { + event: Some(gw::event::Event::UplinkFrame(pl)), + }; + + proxy::send_event(pl).await } -async fn proxy_heartbeat_mesh_packet(pl: &gw::UplinkFrame, packet: MeshPacket) -> Result<()> { +async fn proxy_event_mesh_packet(pl: &gw::UplinkFrame, packet: MeshPacket) -> Result<()> { let mesh_pl = match &packet.payload { - Payload::Heartbeat(v) => v, + Payload::Event(v) => v, _ => { return Err(anyhow!("Expected Heartbeat payload")); } }; info!( - "Unwrapping relay heartbeat packet, uplink_id: {}, mesh_packet: {}", + "Unwrapping relay event packet, uplink_id: {}, mesh_packet: {}", pl.rx_info.as_ref().map(|v| v.uplink_id).unwrap_or_default(), packet ); - let heartbeat_pl = gw::MeshHeartbeat { - gateway_id: hex::encode(backend::get_gateway_id().await?), - relay_id: hex::encode(mesh_pl.relay_id), - relay_path: mesh_pl - .relay_path - .iter() - .map(|v| gw::MeshHeartbeatRelayPath { - relay_id: hex::encode(v.relay_id), - rssi: v.rssi.into(), - snr: v.snr.into(), - }) - .collect(), - time: Some(mesh_pl.timestamp.into()), + let event = gw::Event { + event: Some(gw::event::Event::Mesh(gw::Mesh { + gateway_id: hex::encode(backend::get_gateway_id().await?), + relay_id: hex::encode(mesh_pl.relay_id), + time: Some(mesh_pl.timestamp.into()), + events: mesh_pl + .events + .iter() + .map(|e| gw::MeshEvent { + event: Some(match e { + Event::Heartbeat(v) => { + gw::mesh_event::Event::Heartbeat(gw::MeshEventHeartbeat { + relay_path: v + .relay_path + .iter() + .map(|v| gw::MeshEventHeartbeatRelayPath { + relay_id: hex::encode(v.relay_id), + rssi: v.rssi.into(), + snr: v.snr.into(), + }) + .collect(), + }) + } + Event::Proprietary(v) => { + gw::mesh_event::Event::Proprietary(gw::MeshEventProprietary { + event_type: v.0.into(), + payload: v.1.clone(), + }) + } + }), + }) + .collect(), + })), }; - proxy::send_mesh_heartbeat(&heartbeat_pl).await + proxy::send_event(event).await?; + + Ok(()) } async fn relay_mesh_packet(pl: &gw::UplinkFrame, mut packet: MeshPacket) -> Result<()> { @@ -238,10 +269,10 @@ async fn relay_mesh_packet(pl: &gw::UplinkFrame, mut packet: MeshPacket) -> Resu "Unwrapping relayed downlink, downlink_id: {}, mesh_packet: {}", pl.downlink_id, packet ); - return helpers::tx_ack_to_err(&backend::send_downlink(&pl).await?); + return helpers::tx_ack_to_err(&backend::send_downlink(pl).await?); } } - packets::Payload::Heartbeat(pl) => { + packets::Payload::Event(pl) => { if pl.relay_id == relay_id { trace!("Dropping packet as this relay was the sender"); @@ -249,12 +280,16 @@ async fn relay_mesh_packet(pl: &gw::UplinkFrame, mut packet: MeshPacket) -> Resu return Ok(()); } - // Add our Relay ID to the path. - pl.relay_path.push(packets::RelayPath { - relay_id, - rssi: rx_info.rssi as i16, - snr: rx_info.snr as i8, - }); + for event in &mut pl.events { + // Add our Relay ID to the path in case of heartbeat event. + if let Event::Heartbeat(v) = event { + v.relay_path.push(packets::RelayPath { + relay_id, + rssi: rx_info.rssi as i16, + snr: rx_info.snr as i8, + }); + } + } } } @@ -299,7 +334,7 @@ async fn relay_mesh_packet(pl: &gw::UplinkFrame, mut packet: MeshPacket) -> Resu "Re-relaying mesh packet, downlink_id: {}, mesh_packet: {}", pl.downlink_id, packet ); - backend::mesh(&pl).await + backend::mesh(pl).await } async fn relay_uplink_lora_packet(pl: &gw::UplinkFrame) -> Result<()> { @@ -366,7 +401,7 @@ async fn relay_uplink_lora_packet(pl: &gw::UplinkFrame) -> Result<()> { rx_info.uplink_id, pl.downlink_id, packet, ); - backend::mesh(&pl).await + backend::mesh(pl).await } async fn relay_downlink_lora_packet(pl: &gw::DownlinkFrame) -> Result { @@ -465,7 +500,7 @@ async fn relay_downlink_lora_packet(pl: &gw::DownlinkFrame) -> Result { tx_ack_items[i].status = gw::TxAckStatus::Ok.into(); break; diff --git a/src/packets.rs b/src/packets.rs index c4e3d30..952911d 100644 --- a/src/packets.rs +++ b/src/packets.rs @@ -1,9 +1,11 @@ use std::fmt; +use std::io::{Cursor, Read}; use std::time::{Duration, SystemTime, UNIX_EPOCH}; use aes::Aes128; use anyhow::Result; use cmac::{Cmac, Mac}; +use log::warn; use crate::aes128::Aes128Key; @@ -62,9 +64,7 @@ impl MeshPacket { PayloadType::Downlink => { Payload::Downlink(DownlinkPayload::from_slice(&b[1..len - 4])?) } - PayloadType::Heartbeat => { - Payload::Heartbeat(HeartbeatPayload::from_slice(&b[1..len - 4])?) - } + PayloadType::Event => Payload::Event(EventPayload::from_slice(&b[1..len - 4])?), }, mic: Some(mic), mhdr, @@ -76,7 +76,7 @@ impl MeshPacket { b.extend_from_slice(&match &self.payload { Payload::Uplink(v) => v.to_vec()?, Payload::Downlink(v) => v.to_vec()?, - Payload::Heartbeat(v) => v.to_vec()?, + Payload::Event(v) => v.to_vec()?, }); if let Some(mic) = self.mic { @@ -93,7 +93,7 @@ impl MeshPacket { b.extend_from_slice(&match &self.payload { Payload::Uplink(v) => v.to_vec()?, Payload::Downlink(v) => v.to_vec()?, - Payload::Heartbeat(v) => v.to_vec()?, + Payload::Event(v) => v.to_vec()?, }); Ok(b) @@ -152,7 +152,7 @@ impl fmt::Display for MeshPacket { hex::encode(v.relay_id), self.mic.map(hex::encode).unwrap_or_default(), ), - Payload::Heartbeat(v) => write!( + Payload::Event(v) => write!( f, "[{:?} hop_count: {}, timestamp: {:?}, relay_id: {}]", self.mhdr.payload_type, @@ -199,7 +199,7 @@ impl MHDR { pub enum PayloadType { Uplink, Downlink, - Heartbeat, + Event, } impl PayloadType { @@ -207,7 +207,7 @@ impl PayloadType { Ok(match b { 0x00 => PayloadType::Uplink, 0x01 => PayloadType::Downlink, - 0x02 => PayloadType::Heartbeat, + 0x02 => PayloadType::Event, _ => return Err(anyhow!("Unexpected PayloadType: {}", b)), }) } @@ -216,7 +216,7 @@ impl PayloadType { match self { PayloadType::Uplink => 0x00, PayloadType::Downlink => 0x01, - PayloadType::Heartbeat => 0x02, + PayloadType::Event => 0x02, } } } @@ -225,7 +225,7 @@ impl PayloadType { pub enum Payload { Uplink(UplinkPayload), Downlink(DownlinkPayload), - Heartbeat(HeartbeatPayload), + Event(EventPayload), } #[derive(Debug, PartialEq, Eq, Clone)] @@ -417,33 +417,102 @@ impl DownlinkMetadata { } #[derive(Debug, PartialEq, Eq, Clone)] -pub struct HeartbeatPayload { +pub struct EventPayload { pub timestamp: SystemTime, pub relay_id: [u8; 4], - pub relay_path: Vec, + pub events: Vec, } -impl HeartbeatPayload { - pub fn from_slice(b: &[u8]) -> Result { - if b.len() < 8 { - return Err(anyhow!("At least 8 bytes are expected")); - } - - if (b.len() - 8) % 6 != 0 { - return Err(anyhow!("Invalid amount of Relay path bytes")); - } - +impl EventPayload { + pub fn from_slice(b: &[u8]) -> Result { + let b_len = b.len() as u64; + let mut cur = Cursor::new(b); let mut ts_b: [u8; 4] = [0; 4]; - ts_b.copy_from_slice(&b[0..4]); + + cur.read_exact(&mut ts_b)?; let timestamp = u32::from_be_bytes(ts_b); let timestamp = UNIX_EPOCH .checked_add(Duration::from_secs(timestamp.into())) .ok_or_else(|| anyhow!("Invalid timestamp"))?; let mut relay_id: [u8; 4] = [0; 4]; - relay_id.copy_from_slice(&b[4..8]); + cur.read_exact(&mut relay_id)?; + + let mut events = Vec::new(); + while cur.position() < b_len { + match Event::decode(&mut cur) { + Ok(v) => events.push(v), + Err(e) => warn!("Decode event error: {}", e), + } + } + + Ok(EventPayload { + timestamp, + relay_id, + events, + }) + } + + pub fn to_vec(&self) -> Result> { + let timestamp = self.timestamp.duration_since(UNIX_EPOCH)?.as_secs() as u32; + let mut b = timestamp.to_be_bytes().to_vec(); + b.extend_from_slice(&self.relay_id); + + for event in &self.events { + b.extend_from_slice(&event.encode()?); + } + + Ok(b) + } +} + +#[derive(Debug, PartialEq, Eq, Clone)] +pub enum Event { + Heartbeat(HeartbeatPayload), + Proprietary((u8, Vec)), +} + +impl Event { + pub fn decode(cur: &mut Cursor<&[u8]>) -> Result { + let mut tag_length: [u8; 2] = [0; 2]; + cur.read_exact(&mut tag_length)?; + + let mut value = vec![0; tag_length[1] as usize]; + cur.read_exact(&mut value)?; + + Ok(match tag_length[0] { + 0x00 => Event::Heartbeat(HeartbeatPayload::from_slice(&value)?), + _ => Event::Proprietary((tag_length[0], value)), + }) + } + + pub fn encode(&self) -> Result> { + let (t, v) = match self { + Event::Heartbeat(v) => (0x00, v.to_vec()?), + Event::Proprietary((t, v)) => (*t, v.clone()), + }; + + let mut b = Vec::with_capacity(2 + v.len()); + b.push(t); + b.push(v.len() as u8); + b.extend_from_slice(&v); + + Ok(b) + } +} + +#[derive(Debug, PartialEq, Eq, Clone)] +pub struct HeartbeatPayload { + pub relay_path: Vec, +} + +impl HeartbeatPayload { + pub fn from_slice(b: &[u8]) -> Result { + if b.len() % 6 != 0 { + return Err(anyhow!("Invalid amount of Relay path bytes")); + } - let relay_path: Vec = b[8..] + let relay_path: Vec = b .chunks(6) .map(|v| { let mut b: [u8; 6] = [0; 6]; @@ -452,17 +521,11 @@ impl HeartbeatPayload { }) .collect(); - Ok(HeartbeatPayload { - timestamp, - relay_id, - relay_path, - }) + Ok(HeartbeatPayload { relay_path }) } pub fn to_vec(&self) -> Result> { - let timestamp = self.timestamp.duration_since(UNIX_EPOCH)?.as_secs() as u32; - let mut b = timestamp.to_be_bytes().to_vec(); - b.extend_from_slice(&self.relay_id); + let mut b = Vec::with_capacity(self.relay_path.len() * 6); for relay_path in &self.relay_path { b.extend_from_slice(&relay_path.to_bytes()?); } @@ -1018,17 +1081,44 @@ mod test { } #[test] - fn test_heartbeat_payload_from_slice() { + fn test_event_heartbeat_payload_from_slice() { let b = vec![ - 59, 154, 202, 0, 1, 2, 3, 4, 5, 6, 7, 8, 120, 52, 9, 10, 11, 12, 120, 52, + 59, 154, 202, 0, 1, 2, 3, 4, 0, 12, 5, 6, 7, 8, 120, 52, 9, 10, 11, 12, 120, 52, ]; - let heartbeat_pl = HeartbeatPayload::from_slice(&b).unwrap(); + let event_pl = EventPayload::from_slice(&b).unwrap(); assert_eq!( - HeartbeatPayload { + EventPayload { timestamp: UNIX_EPOCH .checked_add(Duration::from_secs(1_000_000_000)) .unwrap(), relay_id: [1, 2, 3, 4], + events: vec![Event::Heartbeat(HeartbeatPayload { + relay_path: vec![ + RelayPath { + relay_id: [5, 6, 7, 8], + rssi: -120, + snr: -12, + }, + RelayPath { + relay_id: [9, 10, 11, 12], + rssi: -120, + snr: -12, + }, + ] + }),], + }, + event_pl, + ); + } + + #[test] + fn test_heartbeat_payload_to_vec() { + let event_pl = EventPayload { + timestamp: UNIX_EPOCH + .checked_add(Duration::from_secs(1_000_000_000)) + .unwrap(), + relay_id: [1, 2, 3, 4], + events: vec![Event::Heartbeat(HeartbeatPayload { relay_path: vec![ RelayPath { relay_id: [5, 6, 7, 8], @@ -1041,34 +1131,11 @@ mod test { snr: -12, }, ], - }, - heartbeat_pl, - ); - } - - #[test] - fn test_heartbeat_payload_to_vec() { - let heartbeat_pl = HeartbeatPayload { - timestamp: UNIX_EPOCH - .checked_add(Duration::from_secs(1_000_000_000)) - .unwrap(), - relay_id: [1, 2, 3, 4], - relay_path: vec![ - RelayPath { - relay_id: [5, 6, 7, 8], - rssi: -120, - snr: -12, - }, - RelayPath { - relay_id: [9, 10, 11, 12], - rssi: -120, - snr: -12, - }, - ], + })], }; - let b = heartbeat_pl.to_vec().unwrap(); + let b = event_pl.to_vec().unwrap(); assert_eq!( - vec![59, 154, 202, 0, 1, 2, 3, 4, 5, 6, 7, 8, 120, 52, 9, 10, 11, 12, 120, 52], + vec![59, 154, 202, 0, 1, 2, 3, 4, 0, 12, 5, 6, 7, 8, 120, 52, 9, 10, 11, 12, 120, 52], b ); } diff --git a/src/proxy.rs b/src/proxy.rs index 62111d2..3387a8b 100644 --- a/src/proxy.rs +++ b/src/proxy.rs @@ -14,9 +14,8 @@ use crate::mesh; static EVENT_CHAN: OnceLock = OnceLock::new(); -type Event = (String, Vec); -type Command = ((String, Vec), oneshot::Sender>); -type EventChannel = mpsc::UnboundedSender; +type EventChannel = mpsc::UnboundedSender; +type Command = (gw::Command, oneshot::Sender>); type CommandChannel = mpsc::UnboundedReceiver; pub async fn setup(conf: &Configuration) -> Result<()> { @@ -32,7 +31,7 @@ pub async fn setup(conf: &Configuration) -> Result<()> { // Setup ZMQ event. // As the zmq::Context can't be shared between threads, we use a channel. - let (event_tx, mut event_rx) = mpsc::unbounded_channel::(); + let (event_tx, mut event_rx) = mpsc::unbounded_channel::(); // Spawn the zmq event handler to a dedicated thread. thread::spawn({ @@ -44,20 +43,17 @@ pub async fn setup(conf: &Configuration) -> Result<()> { sock.bind(&event_bind).unwrap(); while let Some(event) = event_rx.blocking_recv() { - sock.send(&event.0, zmq::SNDMORE).unwrap(); - sock.send(&event.1, 0).unwrap(); + sock.send(&event.encode_to_vec(), 0).unwrap(); } } }); // Set event channel. - EVENT_CHAN .set(event_tx) .map_err(|e| anyhow!("OnceLock error: {:?}", e))?; // Setup ZMQ command. - let (command_tx, command_rx) = mpsc::unbounded_channel::(); // Spawn the zmq command handler to a dedicated thread. @@ -73,7 +69,7 @@ pub async fn setup(conf: &Configuration) -> Result<()> { match receive_zmq_command(&mut sock) { Ok(v) => { let (resp_tx, resp_rx) = oneshot::channel::>(); - command_tx.send(((v.0, v.1), resp_tx)).unwrap(); + command_tx.send((v, resp_tx)).unwrap(); match resp_rx.blocking_recv() { Ok(v) => sock.send(&v, 0).unwrap(), @@ -102,38 +98,14 @@ pub async fn setup(conf: &Configuration) -> Result<()> { Ok(()) } -pub async fn send_uplink(pl: &gw::UplinkFrame) -> Result<()> { - info!("Sending uplink event - {}", helpers::format_uplink(pl)?); +pub async fn send_event(pl: gw::Event) -> Result<()> { + info!("Sending event"); let event_chan = EVENT_CHAN .get() .ok_or_else(|| anyhow!("EVENT_CHAN is not set"))?; - event_chan.send(("up".to_string(), pl.encode_to_vec()))?; - - Ok(()) -} - -pub async fn send_stats(pl: &gw::GatewayStats) -> Result<()> { - info!("Sending gateway stats event"); - - let event_chan = EVENT_CHAN - .get() - .ok_or_else(|| anyhow!("EVENT_CHAN is not set"))?; - - event_chan.send(("stats".to_string(), pl.encode_to_vec()))?; - - Ok(()) -} - -pub async fn send_mesh_heartbeat(pl: &gw::MeshHeartbeat) -> Result<()> { - info!("Sending mesh heartbeat event"); - - let event_chan = EVENT_CHAN - .get() - .ok_or_else(|| anyhow!("EVENT_CHAN is not set"))?; - - event_chan.send(("mesh_heartbeat".to_string(), pl.encode_to_vec()))?; + event_chan.send(pl)?; Ok(()) } @@ -142,7 +114,7 @@ async fn command_loop(mut command_rx: CommandChannel) { trace!("Starting command loop"); while let Some(cmd) = command_rx.recv().await { - match handle_command(&cmd).await { + match handle_command(cmd.0).await { Ok(v) => { _ = cmd.1.send(v); } @@ -156,40 +128,33 @@ async fn command_loop(mut command_rx: CommandChannel) { error!("Command loop has been interrupted"); } -async fn handle_command(cmd: &Command) -> Result> { - Ok(match cmd.0 .0.as_str() { - "config" => { - let pl = gw::GatewayConfiguration::decode(cmd.0 .1.as_slice())?; - info!("Configuration command received, version: {}", pl.version); - backend::send_gateway_configuration(&pl).await?; +async fn handle_command(cmd: gw::Command) -> Result> { + Ok(match cmd.command { + Some(gw::command::Command::SetGatewayConfiguration(v)) => { + info!("Configuration command received, version: {}", v.version); + backend::send_gateway_configuration(v).await?; Vec::new() } - "down" => { - let pl = gw::DownlinkFrame::decode(cmd.0 .1.as_slice())?; + Some(gw::command::Command::SendDownlinkFrame(v)) => { info!( "Downlink command received - {}", - helpers::format_downlink(&pl)? + helpers::format_downlink(&v)? ); - mesh::handle_downlink(pl).await.map(|v| v.encode_to_vec())? + mesh::handle_downlink(v).await.map(|v| v.encode_to_vec())? } - "gateway_id" => { + Some(gw::command::Command::GetGatewayId(_)) => { info!("Get gateway id command received"); - backend::get_gateway_id().await.map(|v| v.to_vec())? - } - _ => { - return Err(anyhow!("Unexpected command: {}", cmd.0 .0)); + gw::GetGatewayIdResponse { + gateway_id: hex::encode(&backend::get_gateway_id().await.unwrap_or_default()), + } + .encode_to_vec() } + _ => return Err(anyhow!("Unexpected command: {:?}", cmd.command)), }) } -fn receive_zmq_command(sock: &mut zmq::Socket) -> Result<(String, Vec)> { - let msg = sock.recv_multipart(0).unwrap(); - if msg.len() != 2 { - return Err(anyhow!("Command must have 2 frames")); - } - - let cmd = String::from_utf8(msg[0].to_vec())?; - let b = msg[1].to_vec(); - - Ok((cmd, b)) +fn receive_zmq_command(sock: &mut zmq::Socket) -> Result { + let b = sock.recv_bytes(0)?; + let cmd = gw::Command::decode(b.as_slice())?; + Ok(cmd) } diff --git a/tests/border_gateway_downlink_lora.rs b/tests/border_gateway_downlink_lora.rs index 3d08a8f..808262e 100644 --- a/tests/border_gateway_downlink_lora.rs +++ b/tests/border_gateway_downlink_lora.rs @@ -52,14 +52,14 @@ async fn test_border_gateway_downlink_lora() { // Publish downlink command. { let mut cmd_sock = common::FORWARDER_COMMAND_SOCK.get().unwrap().lock().await; + let cmd = gw::Command { + command: Some(gw::command::Command::SendDownlinkFrame(down.clone())), + }; cmd_sock .send( - vec![ - bytes::Bytes::from("down"), - bytes::Bytes::from(down.encode_to_vec()), - ] - .try_into() - .unwrap(), + vec![bytes::Bytes::from(cmd.encode_to_vec())] + .try_into() + .unwrap(), ) .await .unwrap(); @@ -70,10 +70,12 @@ async fn test_border_gateway_downlink_lora() { let mut cmd_sock = common::BACKEND_COMMAND_SOCK.get().unwrap().lock().await; let msg = cmd_sock.recv().await.unwrap(); - let cmd = String::from_utf8(msg.get(0).map(|v| v.to_vec()).unwrap()).unwrap(); - assert_eq!("down", cmd); - - gw::DownlinkFrame::decode(msg.get(1).cloned().unwrap()).unwrap() + let cmd = gw::Command::decode(msg.get(0).cloned().unwrap()).unwrap(); + if let Some(gw::command::Command::SendDownlinkFrame(v)) = cmd.command { + v + } else { + panic!("No DownlinkFrame"); + } }; assert_eq!(down, down_received); diff --git a/tests/border_gateway_downlink_mesh.rs b/tests/border_gateway_downlink_mesh.rs index 7c21d66..4037ddb 100644 --- a/tests/border_gateway_downlink_mesh.rs +++ b/tests/border_gateway_downlink_mesh.rs @@ -54,14 +54,14 @@ async fn test_border_gateway_downlink_mesh() { // Publish downlink command. { let mut cmd_sock = common::FORWARDER_COMMAND_SOCK.get().unwrap().lock().await; + let cmd = gw::Command { + command: Some(gw::command::Command::SendDownlinkFrame(down.clone())), + }; cmd_sock .send( - vec![ - bytes::Bytes::from("down"), - bytes::Bytes::from(down.encode_to_vec()), - ] - .try_into() - .unwrap(), + vec![bytes::Bytes::from(cmd.encode_to_vec())] + .try_into() + .unwrap(), ) .await .unwrap(); @@ -76,10 +76,12 @@ async fn test_border_gateway_downlink_mesh() { .await; let msg = cmd_sock.recv().await.unwrap(); - let cmd = String::from_utf8(msg.get(0).map(|v| v.to_vec()).unwrap()).unwrap(); - assert_eq!("down", cmd); - - gw::DownlinkFrame::decode(msg.get(1).cloned().unwrap()).unwrap() + let cmd = gw::Command::decode(msg.get(0).cloned().unwrap()).unwrap(); + if let Some(gw::command::Command::SendDownlinkFrame(v)) = cmd.command { + v + } else { + panic!("No DownlinkFrame"); + } }; let down_item = down.items.first().unwrap(); diff --git a/tests/border_gateway_mesh_heartbeat.rs b/tests/border_gateway_mesh_heartbeat.rs index 58daa2e..40dcc72 100644 --- a/tests/border_gateway_mesh_heartbeat.rs +++ b/tests/border_gateway_mesh_heartbeat.rs @@ -22,24 +22,26 @@ async fn test_border_gateway_mesh_heartbeat() { let mut packet = packets::MeshPacket { mhdr: packets::MHDR { - payload_type: packets::PayloadType::Heartbeat, + payload_type: packets::PayloadType::Event, hop_count: 1, }, - payload: packets::Payload::Heartbeat(packets::HeartbeatPayload { + payload: packets::Payload::Event(packets::EventPayload { relay_id: [2, 2, 2, 2], timestamp: UNIX_EPOCH, - relay_path: vec![ - packets::RelayPath { - relay_id: [1, 2, 3, 4], - rssi: -120, - snr: -12, - }, - packets::RelayPath { - relay_id: [5, 6, 7, 8], - rssi: -120, - snr: -12, - }, - ], + events: vec![packets::Event::Heartbeat(packets::HeartbeatPayload { + relay_path: vec![ + packets::RelayPath { + relay_id: [1, 2, 3, 4], + rssi: -120, + snr: -12, + }, + packets::RelayPath { + relay_id: [5, 6, 7, 8], + rssi: -120, + snr: -12, + }, + ], + })], }), mic: None, }; @@ -68,48 +70,54 @@ async fn test_border_gateway_mesh_heartbeat() { // Publish uplink event. { let mut event_sock = common::MESH_BACKEND_EVENT_SOCK.get().unwrap().lock().await; + let event = gw::Event { + event: Some(gw::event::Event::UplinkFrame(up.clone())), + }; event_sock .send( - vec![ - bytes::Bytes::from("up"), - bytes::Bytes::from(up.encode_to_vec()), - ] - .try_into() - .unwrap(), + vec![bytes::Bytes::from(event.encode_to_vec())] + .try_into() + .unwrap(), ) .await .unwrap(); } - // We expect to receive the MeshHeartbeat to be received by the forwarder. - let mesh_heartbeat: gw::MeshHeartbeat = { + // We expect to receive a Mesh Event. + let mesh_event: gw::Mesh = { let mut event_sock = common::FORWARDER_EVENT_SOCK.get().unwrap().lock().await; let msg = event_sock.recv().await.unwrap(); + let event = gw::Event::decode(msg.get(0).cloned().unwrap()).unwrap(); - let cmd = String::from_utf8(msg.get(0).map(|v| v.to_vec()).unwrap()).unwrap(); - assert_eq!("mesh_heartbeat", cmd); - - gw::MeshHeartbeat::decode(msg.get(1).cloned().unwrap()).unwrap() + if let Some(gw::event::Event::Mesh(v)) = event.event { + v + } else { + panic!("Event does not contain MeshEvent"); + } }; assert_eq!( - gw::MeshHeartbeat { + gw::Mesh { gateway_id: "0101010101010101".to_string(), time: Some(UNIX_EPOCH.into()), relay_id: "02020202".to_string(), - relay_path: vec![ - gw::MeshHeartbeatRelayPath { - relay_id: "01020304".into(), - rssi: -120, - snr: -12, - }, - gw::MeshHeartbeatRelayPath { - relay_id: "05060708".into(), - rssi: -120, - snr: -12, - }, - ], + events: vec![gw::MeshEvent { + event: Some(gw::mesh_event::Event::Heartbeat(gw::MeshEventHeartbeat { + relay_path: vec![ + gw::MeshEventHeartbeatRelayPath { + relay_id: "01020304".into(), + rssi: -120, + snr: -12, + }, + gw::MeshEventHeartbeatRelayPath { + relay_id: "05060708".into(), + rssi: -120, + snr: -12, + }, + ], + },)), + },], }, - mesh_heartbeat + mesh_event ); } diff --git a/tests/border_gateway_uplink_lora.rs b/tests/border_gateway_uplink_lora.rs index 113179b..0f95a56 100644 --- a/tests/border_gateway_uplink_lora.rs +++ b/tests/border_gateway_uplink_lora.rs @@ -39,29 +39,30 @@ async fn test_border_gateway_uplink_lora() { // Publish uplink event. { let mut event_sock = common::BACKEND_EVENT_SOCK.get().unwrap().lock().await; + let event = gw::Event { + event: Some(gw::event::Event::UplinkFrame(up.clone())), + }; event_sock .send( - vec![ - bytes::Bytes::from("up"), - bytes::Bytes::from(up.encode_to_vec()), - ] - .try_into() - .unwrap(), + vec![bytes::Bytes::from(event.encode_to_vec())] + .try_into() + .unwrap(), ) .await .unwrap(); } // We expect to receive the same uplink. - let up_received = { + let up_received: gw::UplinkFrame = { let mut event_sock = common::FORWARDER_EVENT_SOCK.get().unwrap().lock().await; - let msg = event_sock.recv().await.unwrap(); - let cmd = String::from_utf8(msg.get(0).map(|v| v.to_vec()).unwrap()).unwrap(); - assert_eq!("up", cmd); - - gw::UplinkFrame::decode(msg.get(1).cloned().unwrap()).unwrap() + let event = gw::Event::decode(msg.get(0).cloned().unwrap()).unwrap(); + if let Some(gw::event::Event::UplinkFrame(v)) = event.event { + v + } else { + panic!("No UplinkFrame"); + } }; // Validate they are equal. diff --git a/tests/border_gateway_uplink_mesh.rs b/tests/border_gateway_uplink_mesh.rs index 9a0c025..6c02f2c 100644 --- a/tests/border_gateway_uplink_mesh.rs +++ b/tests/border_gateway_uplink_mesh.rs @@ -62,14 +62,14 @@ async fn test_border_gateway_uplink_mesh() { // Publish uplink event. { let mut event_sock = common::MESH_BACKEND_EVENT_SOCK.get().unwrap().lock().await; + let event = gw::Event { + event: Some(gw::event::Event::UplinkFrame(up.clone())), + }; event_sock .send( - vec![ - bytes::Bytes::from("up"), - bytes::Bytes::from(up.encode_to_vec()), - ] - .try_into() - .unwrap(), + vec![bytes::Bytes::from(event.encode_to_vec())] + .try_into() + .unwrap(), ) .await .unwrap(); @@ -80,10 +80,12 @@ async fn test_border_gateway_uplink_mesh() { let mut event_sock = common::FORWARDER_EVENT_SOCK.get().unwrap().lock().await; let msg = event_sock.recv().await.unwrap(); - let cmd = String::from_utf8(msg.get(0).map(|v| v.to_vec()).unwrap()).unwrap(); - assert_eq!("up", cmd); - - gw::UplinkFrame::decode(msg.get(1).cloned().unwrap()).unwrap() + let event = gw::Event::decode(msg.get(0).cloned().unwrap()).unwrap(); + if let Some(gw::event::Event::UplinkFrame(v)) = event.event { + v + } else { + panic!("No UplinkFrame"); + } }; // Validate PHYPayload diff --git a/tests/common/mod.rs b/tests/common/mod.rs index abab5f7..e9e25e3 100644 --- a/tests/common/mod.rs +++ b/tests/common/mod.rs @@ -6,6 +6,7 @@ use tokio::sync::Mutex; use tokio::time::sleep; use zeromq::{Socket, SocketRecv, SocketSend}; +use chirpstack_api::{gw, prost::Message}; use chirpstack_gateway_mesh::config::{self, Configuration}; pub static FORWARDER_EVENT_SOCK: OnceLock> = OnceLock::new(); @@ -177,7 +178,13 @@ async fn init_mesh() { let mut cmd_sock = BACKEND_COMMAND_SOCK.get().unwrap().lock().await; let _ = cmd_sock.recv().await; cmd_sock - .send(vec![1, 1, 1, 1, 1, 1, 1, 1].into()) + .send( + gw::GetGatewayIdResponse { + gateway_id: "0101010101010101".into(), + } + .encode_to_vec() + .into(), + ) .await .unwrap(); }); @@ -186,7 +193,13 @@ async fn init_mesh() { let mut cmd_sock = MESH_BACKEND_COMMAND_SOCK.get().unwrap().lock().await; let _ = cmd_sock.recv().await; cmd_sock - .send(vec![2, 2, 2, 2, 2, 2, 2, 2].into()) + .send( + gw::GetGatewayIdResponse { + gateway_id: "0202020202020202".into(), + } + .encode_to_vec() + .into(), + ) .await .unwrap(); }); diff --git a/tests/relay_gateway_downlink_lora.rs b/tests/relay_gateway_downlink_lora.rs index a8b0f5d..17b0816 100644 --- a/tests/relay_gateway_downlink_lora.rs +++ b/tests/relay_gateway_downlink_lora.rs @@ -68,28 +68,30 @@ async fn test_relay_gateway_downlink_lora() { // (we simulate that we receive the Mesh encapsulated downlink) { let mut event_sock = common::MESH_BACKEND_EVENT_SOCK.get().unwrap().lock().await; + let event = gw::Event { + event: Some(gw::event::Event::UplinkFrame(up.clone())), + }; event_sock .send( - vec![ - bytes::Bytes::from("up"), - bytes::Bytes::from(up.encode_to_vec()), - ] - .try_into() - .unwrap(), + vec![bytes::Bytes::from(event.encode_to_vec())] + .try_into() + .unwrap(), ) .await .unwrap(); } // We expect that the unwrapped downlink was sent to the concentratord. - let mut down = { + let mut down: gw::DownlinkFrame = { let mut cmd_sock = common::BACKEND_COMMAND_SOCK.get().unwrap().lock().await; let msg = cmd_sock.recv().await.unwrap(); - let cmd = String::from_utf8(msg.get(0).map(|v| v.to_vec()).unwrap()).unwrap(); - assert_eq!("down", cmd); - - gw::DownlinkFrame::decode(msg.get(1).cloned().unwrap()).unwrap() + let cmd = gw::Command::decode(msg.get(0).cloned().unwrap()).unwrap(); + if let Some(gw::command::Command::SendDownlinkFrame(v)) = cmd.command { + v + } else { + panic!("No DownlinkFrame"); + } }; assert_ne!(0, down.downlink_id); diff --git a/tests/relay_gateway_mesh_heartbeat.rs b/tests/relay_gateway_mesh_heartbeat.rs index 8997982..e1c33a8 100644 --- a/tests/relay_gateway_mesh_heartbeat.rs +++ b/tests/relay_gateway_mesh_heartbeat.rs @@ -30,10 +30,12 @@ async fn test_relay_gateway_mesh_heartbeat() { .await; let msg = cmd_sock.recv().await.unwrap(); - let cmd = String::from_utf8(msg.get(0).map(|v| v.to_vec()).unwrap()).unwrap(); - assert_eq!("down", cmd); - - gw::DownlinkFrame::decode(msg.get(1).cloned().unwrap()).unwrap() + let cmd = gw::Command::decode(msg.get(0).cloned().unwrap()).unwrap(); + if let Some(gw::command::Command::SendDownlinkFrame(v)) = cmd.command { + v + } else { + panic!("No DownlinkFrame"); + } }; let down_item = down.items.first().unwrap(); @@ -41,7 +43,7 @@ async fn test_relay_gateway_mesh_heartbeat() { assert_ne!([0, 0, 0, 0], mesh_packet.mic.unwrap()); mesh_packet.mic = None; - if let packets::Payload::Heartbeat(v) = &mut mesh_packet.payload { + if let packets::Payload::Event(v) = &mut mesh_packet.payload { // Assert the time is ~ now() assert!( SystemTime::now() @@ -55,13 +57,15 @@ async fn test_relay_gateway_mesh_heartbeat() { assert_eq!( packets::MeshPacket { mhdr: packets::MHDR { - payload_type: packets::PayloadType::Heartbeat, + payload_type: packets::PayloadType::Event, hop_count: 1, }, - payload: packets::Payload::Heartbeat(packets::HeartbeatPayload { + payload: packets::Payload::Event(packets::EventPayload { relay_id: [2, 2, 2, 2], timestamp: UNIX_EPOCH, - relay_path: vec![], + events: vec![packets::Event::Heartbeat(packets::HeartbeatPayload { + relay_path: vec![] + }),], }), mic: None, }, diff --git a/tests/relay_gateway_relay_mesh_heartbeat.rs b/tests/relay_gateway_relay_mesh_heartbeat.rs index 8fe9cfe..901104e 100644 --- a/tests/relay_gateway_relay_mesh_heartbeat.rs +++ b/tests/relay_gateway_relay_mesh_heartbeat.rs @@ -23,13 +23,15 @@ async fn test_relay_gateway_relay_mesh_heartbeat() { let mut packet = packets::MeshPacket { mhdr: packets::MHDR { - payload_type: packets::PayloadType::Heartbeat, + payload_type: packets::PayloadType::Event, hop_count: 1, }, - payload: packets::Payload::Heartbeat(packets::HeartbeatPayload { + payload: packets::Payload::Event(packets::EventPayload { relay_id: [1, 2, 3, 4], timestamp: UNIX_EPOCH, - relay_path: vec![], + events: vec![packets::Event::Heartbeat(packets::HeartbeatPayload { + relay_path: vec![], + })], }), mic: None, }; @@ -61,14 +63,14 @@ async fn test_relay_gateway_relay_mesh_heartbeat() { // Publish Uplink { let mut event_sock = common::MESH_BACKEND_EVENT_SOCK.get().unwrap().lock().await; + let event = gw::Event { + event: Some(gw::event::Event::UplinkFrame(up.clone())), + }; event_sock .send( - vec![ - bytes::Bytes::from("up"), - bytes::Bytes::from(up.encode_to_vec()), - ] - .try_into() - .unwrap(), + vec![bytes::Bytes::from(event.encode_to_vec())] + .try_into() + .unwrap(), ) .await .unwrap(); @@ -84,22 +86,28 @@ async fn test_relay_gateway_relay_mesh_heartbeat() { .await; let msg = cmd_sock.recv().await.unwrap(); - let cmd = String::from_utf8(msg.get(0).map(|v| v.to_vec()).unwrap()).unwrap(); - assert_eq!("down", cmd); - - gw::DownlinkFrame::decode(msg.get(1).cloned().unwrap()).unwrap() + let cmd = gw::Command::decode(msg.get(0).cloned().unwrap()).unwrap(); + if let Some(gw::command::Command::SendDownlinkFrame(v)) = cmd.command { + v + } else { + panic!("No DownlinkFrame"); + } }; let down_item = down.items.first().unwrap(); let mesh_packet = packets::Packet::from_slice(&down_item.phy_payload).unwrap(); packet.mhdr.hop_count += 1; - if let packets::Payload::Heartbeat(v) = &mut packet.payload { - v.relay_path.push(packets::RelayPath { - relay_id: [2, 2, 2, 2], - rssi: -60, - snr: 12, - }); + if let packets::Payload::Event(v) = &mut packet.payload { + for event in &mut v.events { + if let packets::Event::Heartbeat(v) = event { + v.relay_path.push(packets::RelayPath { + relay_id: [2, 2, 2, 2], + rssi: -60, + snr: 12, + }); + } + } } packet.set_mic(Aes128Key::null()).unwrap(); @@ -108,14 +116,14 @@ async fn test_relay_gateway_relay_mesh_heartbeat() { // Publish the uplink one more time, this time we expect that it will be discarded. { let mut event_sock = common::MESH_BACKEND_EVENT_SOCK.get().unwrap().lock().await; + let event = gw::Event { + event: Some(gw::event::Event::UplinkFrame(up.clone())), + }; event_sock .send( - vec![ - bytes::Bytes::from("up"), - bytes::Bytes::from(up.encode_to_vec()), - ] - .try_into() - .unwrap(), + vec![bytes::Bytes::from(event.encode_to_vec())] + .try_into() + .unwrap(), ) .await .unwrap(); diff --git a/tests/relay_gateway_uplink_lora.rs b/tests/relay_gateway_uplink_lora.rs index ff3491a..dabcc2f 100644 --- a/tests/relay_gateway_uplink_lora.rs +++ b/tests/relay_gateway_uplink_lora.rs @@ -45,14 +45,14 @@ async fn test_relay_gateway_uplink_lora() { // Publish uplink event. { let mut event_sock = common::BACKEND_EVENT_SOCK.get().unwrap().lock().await; + let event = gw::Event { + event: Some(gw::event::Event::UplinkFrame(up.clone())), + }; event_sock .send( - vec![ - bytes::Bytes::from("up"), - bytes::Bytes::from(up.encode_to_vec()), - ] - .try_into() - .unwrap(), + vec![bytes::Bytes::from(event.encode_to_vec())] + .try_into() + .unwrap(), ) .await .unwrap(); @@ -68,10 +68,12 @@ async fn test_relay_gateway_uplink_lora() { .await; let msg = cmd_sock.recv().await.unwrap(); - let cmd = String::from_utf8(msg.get(0).map(|v| v.to_vec()).unwrap()).unwrap(); - assert_eq!("down", cmd); - - gw::DownlinkFrame::decode(msg.get(1).cloned().unwrap()).unwrap() + let cmd = gw::Command::decode(msg.get(0).cloned().unwrap()).unwrap(); + if let Some(gw::command::Command::SendDownlinkFrame(v)) = cmd.command { + v + } else { + panic!("No DownlinkFrame"); + } }; let down_item = down.items.first().unwrap(); diff --git a/tests/relay_gateway_uplink_mesh.rs b/tests/relay_gateway_uplink_mesh.rs index 7b5e1d6..dd71fcf 100644 --- a/tests/relay_gateway_uplink_mesh.rs +++ b/tests/relay_gateway_uplink_mesh.rs @@ -68,14 +68,14 @@ async fn test_relay_gateway_uplink_mesh() { // Publish uplink event { let mut event_sock = common::MESH_BACKEND_EVENT_SOCK.get().unwrap().lock().await; + let event = gw::Event { + event: Some(gw::event::Event::UplinkFrame(up.clone())), + }; event_sock .send( - vec![ - bytes::Bytes::from("up"), - bytes::Bytes::from(up.encode_to_vec()), - ] - .try_into() - .unwrap(), + vec![bytes::Bytes::from(event.encode_to_vec())] + .try_into() + .unwrap(), ) .await .unwrap(); @@ -91,10 +91,12 @@ async fn test_relay_gateway_uplink_mesh() { .await; let msg = cmd_sock.recv().await.unwrap(); - let cmd = String::from_utf8(msg.get(0).map(|v| v.to_vec()).unwrap()).unwrap(); - assert_eq!("down", cmd); - - gw::DownlinkFrame::decode(msg.get(1).cloned().unwrap()).unwrap() + let cmd = gw::Command::decode(msg.get(0).cloned().unwrap()).unwrap(); + if let Some(gw::command::Command::SendDownlinkFrame(v)) = cmd.command { + v + } else { + panic!("No DownlinkFrame"); + } }; let down_item = down.items.first().unwrap(); From b003a344a420ca815d39a01279d396ac57b9900a Mon Sep 17 00:00:00 2001 From: Orne Brocaar Date: Mon, 12 May 2025 12:41:11 +0100 Subject: [PATCH 05/17] Move heartbeat_interval config option. --- src/config.rs | 19 ++++++++++++++++--- src/heartbeat.rs | 6 +++--- 2 files changed, 19 insertions(+), 6 deletions(-) diff --git a/src/config.rs b/src/config.rs index 41e8532..7e0e0b2 100644 --- a/src/config.rs +++ b/src/config.rs @@ -17,6 +17,7 @@ pub struct Configuration { pub mesh: Mesh, pub backend: Backend, pub mappings: Mappings, + pub events: Events, } impl Configuration { @@ -56,8 +57,6 @@ impl Default for Logging { #[serde(default)] pub struct Mesh { pub signing_key: Aes128Key, - #[serde(with = "humantime_serde")] - pub heartbeat_interval: Duration, pub frequencies: Vec, pub data_rate: DataRate, pub tx_power: i32, @@ -72,7 +71,6 @@ impl Default for Mesh { fn default() -> Self { Mesh { signing_key: Aes128Key::null(), - heartbeat_interval: Duration::from_secs(300), frequencies: vec![868100000, 868300000, 868500000], data_rate: DataRate { modulation: Modulation::LORA, @@ -155,6 +153,21 @@ pub struct DataRate { pub bitrate: u32, } +#[derive(Serialize, Deserialize, PartialEq, Eq)] +#[serde(default)] +pub struct Events { + #[serde(with = "humantime_serde")] + pub heartbeat_interval: Duration, +} + +impl Default for Events { + fn default() -> Self { + Events { + heartbeat_interval: Duration::from_secs(300), + } + } +} + #[derive(Serialize, Deserialize, Clone, Copy, PartialEq, Eq, Default)] #[allow(non_camel_case_types)] #[allow(clippy::upper_case_acronyms)] diff --git a/src/heartbeat.rs b/src/heartbeat.rs index 4e0cf66..2720956 100644 --- a/src/heartbeat.rs +++ b/src/heartbeat.rs @@ -15,17 +15,17 @@ use crate::packets; pub async fn setup(conf: &Configuration) -> Result<()> { // Only Relay gatewways need to report heartbeat as the Border Gateway is already internet // connected and reports status through the Concentratord. - if conf.mesh.border_gateway || conf.mesh.heartbeat_interval.is_zero() { + if conf.mesh.border_gateway || conf.events.heartbeat_interval.is_zero() { return Ok(()); } info!( "Starting heartbeat loop, heartbeat_interval: {:?}", - conf.mesh.heartbeat_interval + conf.events.heartbeat_interval ); tokio::spawn({ - let heartbeat_interval = conf.mesh.heartbeat_interval; + let heartbeat_interval = conf.events.heartbeat_interval; async move { loop { From fbcde0266d43f1a88b83144459e4db12728d7f6e Mon Sep 17 00:00:00 2001 From: Orne Brocaar Date: Tue, 20 May 2025 13:44:45 +0100 Subject: [PATCH 06/17] Implement event / command framework. The events feature makes it possible to periodically send (proprietary) events from the Relay gateways. The commands feature makes it possible to send commands to the Relay gateways, in which case the output is returned as an event. --- Cargo.toml | 2 + src/cache.rs | 10 ++ src/cmd/configfile.rs | 63 ++++++- src/cmd/root.rs | 5 +- src/commands.rs | 119 +++++++++++++ src/config.rs | 20 +++ src/events.rs | 167 ++++++++++++++++++ src/heartbeat.rs | 90 ---------- src/lib.rs | 3 +- src/mesh.rs | 90 +++++++++- src/packets.rs | 135 ++++++++++++++ src/proxy.rs | 5 + ...border_gateway_mesh_command_proprietary.rs | 100 +++++++++++ ...=> border_gateway_mesh_event_heartbeat.rs} | 36 ++-- tests/common/mod.rs | 18 +- .../relay_gateway_mesh_command_proprietary.rs | 132 ++++++++++++++ ... => relay_gateway_mesh_event_heartbeat.rs} | 6 +- tests/relay_gateway_mesh_event_proprietary.rs | 76 ++++++++ ...t.rs => relay_gateway_relay_mesh_event.rs} | 0 tests/relay_gateway_uplink_mesh.rs | 12 +- 20 files changed, 956 insertions(+), 133 deletions(-) create mode 100644 src/commands.rs create mode 100644 src/events.rs delete mode 100644 src/heartbeat.rs create mode 100644 tests/border_gateway_mesh_command_proprietary.rs rename tests/{border_gateway_mesh_heartbeat.rs => border_gateway_mesh_event_heartbeat.rs} (79%) create mode 100644 tests/relay_gateway_mesh_command_proprietary.rs rename tests/{relay_gateway_mesh_heartbeat.rs => relay_gateway_mesh_event_heartbeat.rs} (93%) create mode 100644 tests/relay_gateway_mesh_event_proprietary.rs rename tests/{relay_gateway_relay_mesh_heartbeat.rs => relay_gateway_relay_mesh_event.rs} (100%) diff --git a/Cargo.toml b/Cargo.toml index 92d17af..53b6016 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -32,6 +32,8 @@ "time", "fs", "sync", + "process", + "io-util", ] } hex = "0.4" rand = "0.9" diff --git a/src/cache.rs b/src/cache.rs index ff96e07..1c46af0 100644 --- a/src/cache.rs +++ b/src/cache.rs @@ -69,6 +69,16 @@ impl From<&packets::MeshPacket> for PayloadCache { .unwrap_or_default() .as_secs() as u32, }, + packets::Payload::Command(v) => PayloadCache { + p_type, + uplink_id: 0, + relay_id: v.relay_id, + timestamp: v + .timestamp + .duration_since(UNIX_EPOCH) + .unwrap_or_default() + .as_secs() as u32, + }, } } } diff --git a/src/cmd/configfile.rs b/src/cmd/configfile.rs index 659ad7c..9e84a91 100644 --- a/src/cmd/configfile.rs +++ b/src/cmd/configfile.rs @@ -37,12 +37,6 @@ pub fn run() { # uplinks and forward these to the proxy API, rather than relaying these. border_gateway={{ mesh.border_gateway }} - # Heartbeat interval (Relay Gateway only). - # - # This defines the interval in which a Relay Gateway (border_gateway=false) - # will emit heartbeat messages. - heartbeat_interval="{{ mesh.heartbeat_interval }}" - # Max hop count. # # This defines the maximum number of hops a relayed payload will pass. @@ -142,6 +136,63 @@ pub fn run() { # Command API URL. command_url="{{ backend.mesh_concentratord.command_url }}" + + +# Events configuration (Relay only). +[events] + + # Heartbeat interval (Relay Gateway only). + # + # This defines the interval in which a Relay Gateway (border_gateway=false) + # will emit heartbeat messages. + heartbeat_interval="{{ events.heartbeat_interval }}" + + # Commands. + # + # This configures for each event type the command that must be executed. The + # stdout of the command will be used as event payload. Example: + # + # 128 = ["/path/to/command", "arg1", "arg2"] + # + [events.commands] + + {{#each events.commands}} + {{@key}} = [{{#each this}}"{{this}}", {{/each}}] + {{/each}} + + # Event sets (can be repeated). + # + # This configures sets of events that will be periodically sent by the + # relay. Example: + # + # [[events.sets]] + # interval = "5min" + # events = [128, 129, 130] + # + {{#each events.sets}} + [[events.sets]] + interval = "{{this.interval}}" + events = [{{#each this.events}}{{this}}, {{/each}}] + {{/each}} + + +# Commands configuration (Relay only). +[commands] + + # Commands. + # + # On receiving a command, the Gateway Mesh will execute the command matching + # the command type (128 - 255 is for proprietary commands). The payload will + # be provided to the command using stdin. The returned stdout will be sent + # back as event (using the same type). Example: + # + # "129" = ["/path/to/command", "arg1", "arg2"] + # + [events.commands] + + {{#each events.commands}} + {{@key}} = [{{#each this}}"{{this}}", {{/each}}] + {{/each}} "#; let conf = config::get(); diff --git a/src/cmd/root.rs b/src/cmd/root.rs index 028c3e7..ea7b421 100644 --- a/src/cmd/root.rs +++ b/src/cmd/root.rs @@ -4,12 +4,13 @@ use signal_hook::consts::signal::*; use signal_hook_tokio::Signals; use crate::config::Configuration; -use crate::{backend, heartbeat, proxy}; +use crate::{backend, commands, events, proxy}; pub async fn run(conf: &Configuration) -> Result<()> { proxy::setup(conf).await?; backend::setup(conf).await?; - heartbeat::setup(conf).await?; + events::setup(conf).await?; + commands::setup(conf).await?; let mut signals = Signals::new([SIGINT, SIGTERM])?; let handle = signals.handle(); diff --git a/src/commands.rs b/src/commands.rs new file mode 100644 index 0000000..0476eec --- /dev/null +++ b/src/commands.rs @@ -0,0 +1,119 @@ +use std::collections::HashMap; +use std::process::Stdio; +use std::time::SystemTime; + +use anyhow::Result; +use log::error; +use tokio::io::AsyncWriteExt; +use tokio::process::Command; +use tokio::sync::{Mutex, OnceCell}; + +use crate::{config::Configuration, packets}; + +static COMMANDS: OnceCell>> = OnceCell::const_new(); +static LAST_TIMESTAMP: OnceCell>> = OnceCell::const_new(); + +pub async fn setup(conf: &Configuration) -> Result<()> { + // Only Relay Gateways process commands. + if conf.mesh.border_gateway { + return Ok(()); + } + + // Set commands. + COMMANDS + .set( + conf.commands + .commands + .iter() + .map(|(k, v)| (k.parse().unwrap(), v.clone())) + .collect(), + ) + .map_err(|_| anyhow!("OnceCell set error"))?; + + Ok(()) +} + +pub async fn execute_commands(pl: &packets::CommandPayload) -> Result> { + // Validate that the command timestamp did increment, compared to previous + // command payload. + if let Some(ts) = get_last_timestamp().await { + if ts >= pl.timestamp { + return Err(anyhow!( + "Command timestamp did not increment compared to previous command payload" + )); + } + } + + // Store the command timestamp. + set_last_timestamp(pl.timestamp).await; + + // Execute the commands and capture the response events. + let mut out = vec![]; + for cmd in &pl.commands { + let resp = match cmd { + packets::Command::Proprietary((t, v)) => execute_proprietary(*t, v).await, + }; + + match resp { + Ok(v) => out.push(v), + Err(e) => error!("Execute command error: {}", e), + } + } + + Ok(out) +} + +async fn execute_proprietary(typ: u8, value: &[u8]) -> Result { + let args = COMMANDS + .get() + .ok_or_else(|| anyhow!("COMMANDS is not set"))? + .get(&typ) + .ok_or_else(|| anyhow!("Command type {} is not configured", typ))?; + + if args.is_empty() { + return Err(anyhow!("Command for command type {} is empty", typ,)); + } + + let mut cmd = Command::new(&args[0]); + cmd.stdin(Stdio::piped()); + cmd.stdout(Stdio::piped()); + cmd.stderr(Stdio::piped()); + + // Add addition args. + if args.len() > 1 { + cmd.args(&args[1..]); + } + + // Spawn process + let mut child = cmd.spawn()?; + + // Write stdin + let mut stdin = child.stdin.take().unwrap(); + tokio::spawn({ + let b = value.to_vec(); + async move { stdin.write(&b).await } + }); + + // Wait for output + let out = child.wait_with_output().await?; + Ok(packets::Event::Proprietary((typ, out.stdout))) +} + +async fn get_last_timestamp() -> Option { + LAST_TIMESTAMP + .get_or_init(|| async { Mutex::new(None) }) + .await + .lock() + .await + .clone() +} + +async fn set_last_timestamp(ts: SystemTime) { + let mut last_ts = LAST_TIMESTAMP + .get_or_init(|| async { Mutex::new(None) }) + .await + .lock() + .await; + + *last_ts = Some(ts); +} diff --git a/src/config.rs b/src/config.rs index 7e0e0b2..c2cdc4f 100644 --- a/src/config.rs +++ b/src/config.rs @@ -1,3 +1,4 @@ +use std::collections::HashMap; use std::sync::{Arc, Mutex, OnceLock}; use std::time::Duration; use std::{env, fs}; @@ -18,6 +19,7 @@ pub struct Configuration { pub backend: Backend, pub mappings: Mappings, pub events: Events, + pub commands: Commands, } impl Configuration { @@ -158,16 +160,34 @@ pub struct DataRate { pub struct Events { #[serde(with = "humantime_serde")] pub heartbeat_interval: Duration, + pub commands: HashMap>, + pub sets: Vec, } impl Default for Events { fn default() -> Self { Events { heartbeat_interval: Duration::from_secs(300), + commands: Default::default(), + sets: Vec::new(), } } } +#[derive(Default, Serialize, Deserialize, PartialEq, Eq)] +#[serde(default)] +pub struct EventsSet { + #[serde(with = "humantime_serde")] + pub interval: Duration, + pub events: Vec, +} + +#[derive(Default, Serialize, Deserialize, PartialEq, Eq)] +#[serde(default)] +pub struct Commands { + pub commands: HashMap>, +} + #[derive(Serialize, Deserialize, Clone, Copy, PartialEq, Eq, Default)] #[allow(non_camel_case_types)] #[allow(clippy::upper_case_acronyms)] diff --git a/src/events.rs b/src/events.rs new file mode 100644 index 0000000..e49778a --- /dev/null +++ b/src/events.rs @@ -0,0 +1,167 @@ +use std::collections::HashMap; +use std::time::SystemTime; + +use anyhow::Result; +use chirpstack_api::gw; +use log::{error, info}; +use rand::random; +use tokio::process::Command; +use tokio::sync::OnceCell; +use tokio::time::sleep; + +use crate::backend; +use crate::config::{self, Configuration}; +use crate::helpers; +use crate::mesh::get_mesh_frequency; +use crate::packets; + +static COMMANDS: OnceCell>> = OnceCell::const_new(); + +pub async fn setup(conf: &Configuration) -> Result<()> { + // Only Relay Gateways report events. + if conf.mesh.border_gateway { + return Ok(()); + } + + // Set commands. + COMMANDS + .set( + conf.events + .commands + .iter() + .map(|(k, v)| (k.parse().unwrap(), v.clone())) + .collect(), + ) + .map_err(|_| anyhow!("OnceCell set error"))?; + + // Setup heartbeat event loop. + if !conf.events.heartbeat_interval.is_zero() { + info!( + "Starting heartbeat loop, heartbeat_interval: {:?}", + conf.events.heartbeat_interval + ); + + tokio::spawn({ + let heartbeat_interval = conf.events.heartbeat_interval; + + async move { + loop { + if let Err(e) = report_heartbeat().await { + error!("Report heartbeat error, error: {}", e); + } + sleep(heartbeat_interval).await; + } + } + }); + } + + // Setup event-set loops. + for event_set in &conf.events.sets { + info!( + "Starting event-set loop, events: {:?}, interval: {:?}", + event_set.events, event_set.interval + ); + + tokio::spawn({ + let events = event_set.events.clone(); + let interval = event_set.interval; + + async move { + loop { + sleep(interval).await; + if let Err(e) = report_events(&events).await { + error!("Report event-set error, error: {}", e); + } + } + } + }); + } + + Ok(()) +} + +pub async fn report_heartbeat() -> Result<()> { + info!("Sending heartbeat event"); + send_events(vec![packets::Event::Heartbeat(packets::HeartbeatPayload { + relay_path: vec![], + })]) + .await +} + +pub async fn report_events(typs: &[u8]) -> Result<()> { + let mut events = Vec::new(); + for typ in typs { + events.push(get_event(*typ).await?); + } + info!("Sending events, events: {:?}", typs); + send_events(events).await +} + +async fn get_event(typ: u8) -> Result { + let args = COMMANDS + .get() + .ok_or_else(|| anyhow!("COMMANDS is not set"))? + .get(&typ) + .ok_or_else(|| anyhow!("Event type {} is not configured", typ))?; + + if args.is_empty() { + return Err(anyhow!("Command for event type {} is empty", typ)); + } + + let mut cmd = Command::new(&args[0]); + if args.len() > 1 { + cmd.args(&args[1..]); + } + + let output = cmd.output().await?; + + Ok(packets::Event::Proprietary((typ, output.stdout))) +} + +pub async fn send_events(events: Vec) -> Result<()> { + let conf = config::get(); + + let mut packet = packets::MeshPacket { + mhdr: packets::MHDR { + payload_type: packets::PayloadType::Event, + hop_count: 1, + }, + payload: packets::Payload::Event(packets::EventPayload { + timestamp: SystemTime::now(), + relay_id: backend::get_relay_id().await?, + events, + }), + mic: None, + }; + packet.set_mic(conf.mesh.signing_key)?; + + let pl = gw::DownlinkFrame { + downlink_id: random(), + items: vec![gw::DownlinkFrameItem { + phy_payload: packet.to_vec()?, + tx_info: Some(gw::DownlinkTxInfo { + frequency: get_mesh_frequency(&conf)?, + modulation: Some(helpers::data_rate_to_gw_modulation( + &conf.mesh.data_rate, + false, + )), + power: conf.mesh.tx_power, + timing: Some(gw::Timing { + parameters: Some(gw::timing::Parameters::Immediately( + gw::ImmediatelyTimingInfo {}, + )), + }), + ..Default::default() + }), + ..Default::default() + }], + ..Default::default() + }; + + info!( + "Sending event packet, downlink_id: {}, mesh_packet: {}", + pl.downlink_id, packet + ); + + backend::mesh(pl).await +} diff --git a/src/heartbeat.rs b/src/heartbeat.rs deleted file mode 100644 index 2720956..0000000 --- a/src/heartbeat.rs +++ /dev/null @@ -1,90 +0,0 @@ -use std::time::SystemTime; - -use anyhow::Result; -use chirpstack_api::gw; -use log::{error, info}; -use rand::random; -use tokio::time::sleep; - -use crate::backend; -use crate::config::{self, Configuration}; -use crate::helpers; -use crate::mesh::get_mesh_frequency; -use crate::packets; - -pub async fn setup(conf: &Configuration) -> Result<()> { - // Only Relay gatewways need to report heartbeat as the Border Gateway is already internet - // connected and reports status through the Concentratord. - if conf.mesh.border_gateway || conf.events.heartbeat_interval.is_zero() { - return Ok(()); - } - - info!( - "Starting heartbeat loop, heartbeat_interval: {:?}", - conf.events.heartbeat_interval - ); - - tokio::spawn({ - let heartbeat_interval = conf.events.heartbeat_interval; - - async move { - loop { - if let Err(e) = report_heartbeat().await { - error!("Report heartbeat error, error: {}", e); - } - sleep(heartbeat_interval).await; - } - } - }); - - Ok(()) -} - -pub async fn report_heartbeat() -> Result<()> { - let conf = config::get(); - - let mut packet = packets::MeshPacket { - mhdr: packets::MHDR { - payload_type: packets::PayloadType::Event, - hop_count: 1, - }, - payload: packets::Payload::Event(packets::EventPayload { - timestamp: SystemTime::now(), - relay_id: backend::get_relay_id().await.unwrap_or_default(), - events: vec![packets::Event::Heartbeat(packets::HeartbeatPayload { - relay_path: vec![], - })], - }), - mic: None, - }; - packet.set_mic(conf.mesh.signing_key)?; - - let pl = gw::DownlinkFrame { - downlink_id: random(), - items: vec![gw::DownlinkFrameItem { - phy_payload: packet.to_vec()?, - tx_info: Some(gw::DownlinkTxInfo { - frequency: get_mesh_frequency(&conf)?, - modulation: Some(helpers::data_rate_to_gw_modulation( - &conf.mesh.data_rate, - false, - )), - power: conf.mesh.tx_power, - timing: Some(gw::Timing { - parameters: Some(gw::timing::Parameters::Immediately( - gw::ImmediatelyTimingInfo {}, - )), - }), - ..Default::default() - }), - ..Default::default() - }], - ..Default::default() - }; - - info!( - "Sending heartbeat packet, downlink_id: {}, mesh_packet: {}", - pl.downlink_id, packet - ); - backend::mesh(pl).await -} diff --git a/src/lib.rs b/src/lib.rs index 8d58cf1..e893ca7 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -5,8 +5,9 @@ pub mod aes128; pub mod backend; pub mod cache; pub mod cmd; +pub mod commands; pub mod config; -pub mod heartbeat; +pub mod events; pub mod helpers; pub mod logging; pub mod mesh; diff --git a/src/mesh.rs b/src/mesh.rs index ff16789..0ad3bbf 100644 --- a/src/mesh.rs +++ b/src/mesh.rs @@ -1,6 +1,6 @@ use std::collections::HashMap; -use std::sync::LazyLock; -use std::sync::Mutex; +use std::sync::{LazyLock, Mutex}; +use std::time::SystemTime; use anyhow::Result; use chirpstack_api::gw; @@ -10,8 +10,9 @@ use rand::random; use crate::{ backend, cache::{Cache, PayloadCache}, + commands, config::{self, Configuration}, - helpers, + events, helpers, packets::{ self, DownlinkMetadata, Event, MeshPacket, Payload, PayloadType, UplinkMetadata, UplinkPayload, MHDR, @@ -83,6 +84,67 @@ pub async fn handle_downlink(pl: gw::DownlinkFrame) -> Result relay_downlink_lora_packet(&pl).await } +pub async fn send_mesh_command(pl: gw::MeshCommand) -> Result<()> { + let conf = config::get(); + + let mut packet = packets::MeshPacket { + mhdr: packets::MHDR { + payload_type: packets::PayloadType::Command, + hop_count: 1, + }, + payload: packets::Payload::Command(packets::CommandPayload { + timestamp: SystemTime::now(), + relay_id: { + let mut relay_id: [u8; 4] = [0; 4]; + hex::decode_to_slice(&pl.relay_id, &mut relay_id)?; + relay_id + }, + commands: pl + .commands + .iter() + .map(|v| match &v.command { + Some(gw::mesh_command_item::Command::Proprietary(v)) => Some( + packets::Command::Proprietary((v.command_type as u8, v.payload.clone())), + ), + None => None, + }) + .flatten() + .collect(), + }), + mic: None, + }; + packet.set_mic(conf.mesh.signing_key)?; + + let pl = gw::DownlinkFrame { + downlink_id: random(), + items: vec![gw::DownlinkFrameItem { + phy_payload: packet.to_vec()?, + tx_info: Some(gw::DownlinkTxInfo { + frequency: get_mesh_frequency(&conf)?, + modulation: Some(helpers::data_rate_to_gw_modulation( + &conf.mesh.data_rate, + false, + )), + power: conf.mesh.tx_power, + timing: Some(gw::Timing { + parameters: Some(gw::timing::Parameters::Immediately( + gw::ImmediatelyTimingInfo {}, + )), + }), + ..Default::default() + }), + ..Default::default() + }], + ..Default::default() + }; + + info!( + "Sending mesh packet, downlink_id: {}, mesh_packet: {}", + pl.downlink_id, packet + ); + backend::mesh(pl).await +} + async fn proxy_downlink_lora_packet(pl: gw::DownlinkFrame) -> Result { info!( "Proxying LoRaWAN downlink, downlink: {}", @@ -177,17 +239,17 @@ async fn proxy_event_mesh_packet(pl: &gw::UplinkFrame, packet: MeshPacket) -> Re ); let event = gw::Event { - event: Some(gw::event::Event::Mesh(gw::Mesh { + event: Some(gw::event::Event::Mesh(gw::MeshEvent { gateway_id: hex::encode(backend::get_gateway_id().await?), relay_id: hex::encode(mesh_pl.relay_id), time: Some(mesh_pl.timestamp.into()), events: mesh_pl .events .iter() - .map(|e| gw::MeshEvent { + .map(|e| gw::MeshEventItem { event: Some(match e { Event::Heartbeat(v) => { - gw::mesh_event::Event::Heartbeat(gw::MeshEventHeartbeat { + gw::mesh_event_item::Event::Heartbeat(gw::MeshEventHeartbeat { relay_path: v .relay_path .iter() @@ -200,7 +262,7 @@ async fn proxy_event_mesh_packet(pl: &gw::UplinkFrame, packet: MeshPacket) -> Re }) } Event::Proprietary(v) => { - gw::mesh_event::Event::Proprietary(gw::MeshEventProprietary { + gw::mesh_event_item::Event::Proprietary(gw::MeshEventProprietary { event_type: v.0.into(), payload: v.1.clone(), }) @@ -291,6 +353,20 @@ async fn relay_mesh_packet(pl: &gw::UplinkFrame, mut packet: MeshPacket) -> Resu } } } + packets::Payload::Command(pl) => { + if pl.relay_id == relay_id { + // The command payload was intended for this gateway, execute + // the commands. + let resp = commands::execute_commands(&pl).await?; + + // Send back the responses (events). + if !resp.is_empty() { + events::send_events(resp).await?; + } + + return Ok(()); + } + } } // In any other case, we increment the hop_count and re-transmit the mesh encapsulated diff --git a/src/packets.rs b/src/packets.rs index 952911d..cc6f0d9 100644 --- a/src/packets.rs +++ b/src/packets.rs @@ -65,6 +65,9 @@ impl MeshPacket { Payload::Downlink(DownlinkPayload::from_slice(&b[1..len - 4])?) } PayloadType::Event => Payload::Event(EventPayload::from_slice(&b[1..len - 4])?), + PayloadType::Command => { + Payload::Command(CommandPayload::from_slice(&b[1..len - 4])?) + } }, mic: Some(mic), mhdr, @@ -77,6 +80,7 @@ impl MeshPacket { Payload::Uplink(v) => v.to_vec()?, Payload::Downlink(v) => v.to_vec()?, Payload::Event(v) => v.to_vec()?, + Payload::Command(v) => v.to_vec()?, }); if let Some(mic) = self.mic { @@ -94,6 +98,7 @@ impl MeshPacket { Payload::Uplink(v) => v.to_vec()?, Payload::Downlink(v) => v.to_vec()?, Payload::Event(v) => v.to_vec()?, + Payload::Command(v) => v.to_vec()?, }); Ok(b) @@ -160,6 +165,14 @@ impl fmt::Display for MeshPacket { v.timestamp, hex::encode(v.relay_id), ), + Payload::Command(v) => write!( + f, + "[{:?} hop_count: {}, timestamp: {:?}, relay_id: {}]", + self.mhdr.payload_type, + self.mhdr.hop_count, + v.timestamp, + hex::encode(v.relay_id), + ), } } } @@ -200,6 +213,7 @@ pub enum PayloadType { Uplink, Downlink, Event, + Command, } impl PayloadType { @@ -208,6 +222,7 @@ impl PayloadType { 0x00 => PayloadType::Uplink, 0x01 => PayloadType::Downlink, 0x02 => PayloadType::Event, + 0x03 => PayloadType::Command, _ => return Err(anyhow!("Unexpected PayloadType: {}", b)), }) } @@ -217,6 +232,7 @@ impl PayloadType { PayloadType::Uplink => 0x00, PayloadType::Downlink => 0x01, PayloadType::Event => 0x02, + PayloadType::Command => 0x03, } } } @@ -226,6 +242,7 @@ pub enum Payload { Uplink(UplinkPayload), Downlink(DownlinkPayload), Event(EventPayload), + Command(CommandPayload), } #[derive(Debug, PartialEq, Eq, Clone)] @@ -588,6 +605,95 @@ impl RelayPath { } } +#[derive(Debug, PartialEq, Eq, Clone)] +pub struct CommandPayload { + pub timestamp: SystemTime, + pub relay_id: [u8; 4], + pub commands: Vec, +} + +impl CommandPayload { + pub fn from_slice(b: &[u8]) -> Result { + let b_len = b.len() as u64; + let mut cur = Cursor::new(b); + let mut ts_b: [u8; 4] = [0; 4]; + + cur.read_exact(&mut ts_b)?; + let timestamp = u32::from_be_bytes(ts_b); + let timestamp = UNIX_EPOCH + .checked_add(Duration::from_secs(timestamp.into())) + .ok_or_else(|| anyhow!("Invalid timestamp"))?; + + let mut relay_id: [u8; 4] = [0; 4]; + cur.read_exact(&mut relay_id)?; + + let mut commands = Vec::new(); + while cur.position() < b_len { + match Command::decode(&mut cur) { + Ok(v) => commands.push(v), + Err(e) => warn!("Decode command error: {}", e), + } + } + + Ok(CommandPayload { + timestamp, + relay_id, + commands, + }) + } + + pub fn to_vec(&self) -> Result> { + let timestamp = self.timestamp.duration_since(UNIX_EPOCH)?.as_secs() as u32; + let mut b = timestamp.to_be_bytes().to_vec(); + b.extend_from_slice(&self.relay_id); + + for cmd in &self.commands { + b.extend_from_slice(&cmd.encode()?); + } + + Ok(b) + } +} + +#[derive(Debug, PartialEq, Eq, Clone)] +pub enum Command { + Proprietary((u8, Vec)), +} + +impl Command { + pub fn decode(cur: &mut Cursor<&[u8]>) -> Result { + let mut tag_length: [u8; 2] = [0; 2]; + cur.read_exact(&mut tag_length)?; + + let mut value = vec![0; tag_length[1] as usize]; + cur.read_exact(&mut value)?; + + Ok(match tag_length[0] { + // Add known types here + _ => Command::Proprietary((tag_length[0], value)), + }) + } + + pub fn encode(&self) -> Result> { + let (t, v) = match self { + Command::Proprietary((t, v)) => (*t, v.clone()), + }; + + let mut b = Vec::with_capacity(2 + v.len()); + b.push(t); + b.push(v.len() as u8); + b.extend_from_slice(&v); + + Ok(b) + } + + pub fn get_type(&self) -> u8 { + match self { + Command::Proprietary((typ, _)) => *typ, + } + } +} + pub fn encode_freq(freq: u32) -> Result<[u8; 3]> { let mut freq = freq; // Support LoRaWAN 2.4GHz, in which case the stepping is 200Hz: @@ -1140,6 +1246,35 @@ mod test { ); } + #[test] + fn test_proprietary_command_from_slice() { + let b = vec![59, 154, 202, 0, 1, 2, 3, 4, 128, 4, 4, 3, 2, 1]; + let cmd_pl = CommandPayload::from_slice(&b).unwrap(); + assert_eq!( + CommandPayload { + timestamp: UNIX_EPOCH + .checked_add(Duration::from_secs(1_000_000_000)) + .unwrap(), + relay_id: [1, 2, 3, 4], + commands: vec![Command::Proprietary((128, vec![4, 3, 2, 1]))], + }, + cmd_pl + ); + } + + #[test] + fn test_proprietary_command_to_vec() { + let cmd_pl = CommandPayload { + timestamp: UNIX_EPOCH + .checked_add(Duration::from_secs(1_000_000_000)) + .unwrap(), + relay_id: [1, 2, 3, 4], + commands: vec![Command::Proprietary((128, vec![4, 3, 2, 1]))], + }; + let b = cmd_pl.to_vec().unwrap(); + assert_eq!(vec![59, 154, 202, 0, 1, 2, 3, 4, 128, 4, 4, 3, 2, 1], b); + } + #[test] fn test_mesh_packet_from_slice() { struct Test { diff --git a/src/proxy.rs b/src/proxy.rs index 3387a8b..0865094 100644 --- a/src/proxy.rs +++ b/src/proxy.rs @@ -149,6 +149,11 @@ async fn handle_command(cmd: gw::Command) -> Result> { } .encode_to_vec() } + Some(gw::command::Command::Mesh(v)) => { + info!("Mesh command received"); + mesh::send_mesh_command(v).await?; + Vec::new() + } _ => return Err(anyhow!("Unexpected command: {:?}", cmd.command)), }) } diff --git a/tests/border_gateway_mesh_command_proprietary.rs b/tests/border_gateway_mesh_command_proprietary.rs new file mode 100644 index 0000000..53ea5ad --- /dev/null +++ b/tests/border_gateway_mesh_command_proprietary.rs @@ -0,0 +1,100 @@ +#[macro_use] +extern crate anyhow; + +use std::time::{Duration, SystemTime, UNIX_EPOCH}; + +use chirpstack_api::gw; +use chirpstack_api::prost::Message; +use zeromq::{SocketRecv, SocketSend}; + +use chirpstack_gateway_mesh::packets; + +mod common; + +/* + This tests the scenario when the Border Gateway receives a Mesh Command which + must be sent to a Relay Gateway. +*/ +#[tokio::test] +async fn border_gateway_mesh_command_proprietary() { + common::setup(true).await; + + let cmd = gw::MeshCommand { + gateway_id: "0101010101010101".into(), + relay_id: "02020202".into(), + commands: vec![gw::MeshCommandItem { + command: Some(gw::mesh_command_item::Command::Proprietary( + gw::MeshCommandProprietary { + command_type: 200, + payload: vec![4, 3, 2, 1], + }, + )), + }], + }; + + // Publish command. + { + let mut cmd_sock = common::FORWARDER_COMMAND_SOCK.get().unwrap().lock().await; + let cmd = gw::Command { + command: Some(gw::command::Command::Mesh(cmd.clone())), + }; + cmd_sock + .send( + vec![bytes::Bytes::from(cmd.encode_to_vec())] + .try_into() + .unwrap(), + ) + .await + .unwrap(); + } + + // We expect the wrapped downlink to be received by the mesh concentratord. + let down: gw::DownlinkFrame = { + let mut cmd_sock = common::MESH_BACKEND_COMMAND_SOCK + .get() + .unwrap() + .lock() + .await; + let msg = cmd_sock.recv().await.unwrap(); + + let cmd = gw::Command::decode(msg.get(0).cloned().unwrap()).unwrap(); + if let Some(gw::command::Command::SendDownlinkFrame(v)) = cmd.command { + v + } else { + panic!("No DownlinkFrame"); + } + }; + + let down_item = down.items.first().unwrap(); + let mut mesh_packet = packets::MeshPacket::from_slice(&down_item.phy_payload).unwrap(); + + assert_ne!([0, 0, 0, 0], mesh_packet.mic.unwrap()); + mesh_packet.mic = None; + + if let packets::Payload::Command(v) = &mut mesh_packet.payload { + // Asser time is ~ now() + assert!( + SystemTime::now() + .duration_since(v.timestamp) + .unwrap_or_default() + < Duration::from_secs(5) + ); + v.timestamp = UNIX_EPOCH; + } + + assert_eq!( + packets::MeshPacket { + mhdr: packets::MHDR { + payload_type: packets::PayloadType::Command, + hop_count: 1 + }, + payload: packets::Payload::Command(packets::CommandPayload { + timestamp: UNIX_EPOCH, + relay_id: [2, 2, 2, 2], + commands: vec![packets::Command::Proprietary((200, vec![4, 3, 2, 1])),] + }), + mic: None, + }, + mesh_packet + ); +} diff --git a/tests/border_gateway_mesh_heartbeat.rs b/tests/border_gateway_mesh_event_heartbeat.rs similarity index 79% rename from tests/border_gateway_mesh_heartbeat.rs rename to tests/border_gateway_mesh_event_heartbeat.rs index 40dcc72..bff96c8 100644 --- a/tests/border_gateway_mesh_heartbeat.rs +++ b/tests/border_gateway_mesh_event_heartbeat.rs @@ -84,7 +84,7 @@ async fn test_border_gateway_mesh_heartbeat() { } // We expect to receive a Mesh Event. - let mesh_event: gw::Mesh = { + let mesh_event: gw::MeshEvent = { let mut event_sock = common::FORWARDER_EVENT_SOCK.get().unwrap().lock().await; let msg = event_sock.recv().await.unwrap(); let event = gw::Event::decode(msg.get(0).cloned().unwrap()).unwrap(); @@ -97,25 +97,27 @@ async fn test_border_gateway_mesh_heartbeat() { }; assert_eq!( - gw::Mesh { + gw::MeshEvent { gateway_id: "0101010101010101".to_string(), time: Some(UNIX_EPOCH.into()), relay_id: "02020202".to_string(), - events: vec![gw::MeshEvent { - event: Some(gw::mesh_event::Event::Heartbeat(gw::MeshEventHeartbeat { - relay_path: vec![ - gw::MeshEventHeartbeatRelayPath { - relay_id: "01020304".into(), - rssi: -120, - snr: -12, - }, - gw::MeshEventHeartbeatRelayPath { - relay_id: "05060708".into(), - rssi: -120, - snr: -12, - }, - ], - },)), + events: vec![gw::MeshEventItem { + event: Some(gw::mesh_event_item::Event::Heartbeat( + gw::MeshEventHeartbeat { + relay_path: vec![ + gw::MeshEventHeartbeatRelayPath { + relay_id: "01020304".into(), + rssi: -120, + snr: -12, + }, + gw::MeshEventHeartbeatRelayPath { + relay_id: "05060708".into(), + rssi: -120, + snr: -12, + }, + ], + }, + )), },], }, mesh_event diff --git a/tests/common/mod.rs b/tests/common/mod.rs index e9e25e3..b66cf61 100644 --- a/tests/common/mod.rs +++ b/tests/common/mod.rs @@ -30,7 +30,6 @@ pub fn get_config(border_gateway: bool) -> Configuration { Configuration { mesh: config::Mesh { border_gateway, - heartbeat_interval: Duration::ZERO, frequencies: vec![868100000], data_rate: config::DataRate { modulation: config::Modulation::LORA, @@ -68,6 +67,23 @@ pub fn get_config(border_gateway: bool) -> Configuration { }], tx_power: vec![27, 16], }, + events: config::Events { + heartbeat_interval: Duration::ZERO, + commands: [ + ("128".into(), vec!["echo".into(), "foo".into()]), + ("129".into(), vec!["echo".into(), "bar".into()]), + ] + .iter() + .cloned() + .collect(), + ..Default::default() + }, + commands: config::Commands { + commands: [("130".into(), vec!["wc".into(), "-m".into()])] + .iter() + .cloned() + .collect(), + }, ..Default::default() } } diff --git a/tests/relay_gateway_mesh_command_proprietary.rs b/tests/relay_gateway_mesh_command_proprietary.rs new file mode 100644 index 0000000..779b3b9 --- /dev/null +++ b/tests/relay_gateway_mesh_command_proprietary.rs @@ -0,0 +1,132 @@ +#[macro_use] +extern crate anyhow; + +use std::time::{Duration, SystemTime, UNIX_EPOCH}; + +use chirpstack_api::gw; +use chirpstack_api::prost::Message; +use zeromq::{SocketRecv, SocketSend}; + +use chirpstack_gateway_mesh::aes128::Aes128Key; +use chirpstack_gateway_mesh::packets; + +mod common; + +/* + This test the scenario when the Relay Gateway receives a Mesh Command. THe + Relay Gateway will execute this command and then sends back the response as + a Mesh Event. +*/ +#[tokio::test] +async fn test_relay_gateway_mesh_command_proprietary() { + common::setup(false).await; + + let mut cmd_packet = packets::MeshPacket { + mhdr: packets::MHDR { + payload_type: packets::PayloadType::Command, + hop_count: 1, + }, + payload: packets::Payload::Command(packets::CommandPayload { + timestamp: SystemTime::now(), + relay_id: [2, 2, 2, 2], + commands: vec![packets::Command::Proprietary(( + 130, + "hello".as_bytes().to_vec(), + ))], + }), + mic: None, + }; + cmd_packet.set_mic(Aes128Key::null()).unwrap(); + + // The packet that we received from the Border Gateway. + let up = gw::UplinkFrame { + phy_payload: cmd_packet.to_vec().unwrap(), + tx_info: Some(gw::UplinkTxInfo { + frequency: 868100000, + modulation: Some(gw::Modulation { + parameters: Some(gw::modulation::Parameters::Lora(gw::LoraModulationInfo { + bandwidth: 125000, + spreading_factor: 12, + code_rate: gw::CodeRate::Cr45.into(), + ..Default::default() + })), + }), + }), + rx_info: Some(gw::UplinkRxInfo { + gateway_id: "0101010101010101".into(), + crc_status: gw::CrcStatus::CrcOk.into(), + ..Default::default() + }), + ..Default::default() + }; + + // Publish uplink. + // (we simulate that we receive the Mesh command) + { + let mut event_sock = common::MESH_BACKEND_EVENT_SOCK.get().unwrap().lock().await; + let event = gw::Event { + event: Some(gw::event::Event::UplinkFrame(up.clone())), + }; + event_sock + .send( + vec![bytes::Bytes::from(event.encode_to_vec())] + .try_into() + .unwrap(), + ) + .await + .unwrap(); + } + + // We expect that the Relay Gateway responds to the Mesh Command by sending + // a Mesh Event back. + let mut down: gw::DownlinkFrame = { + let mut cmd_sock = common::MESH_BACKEND_COMMAND_SOCK + .get() + .unwrap() + .lock() + .await; + let msg = cmd_sock.recv().await.unwrap(); + + let cmd = gw::Command::decode(msg.get(0).cloned().unwrap()).unwrap(); + if let Some(gw::command::Command::SendDownlinkFrame(v)) = cmd.command { + v + } else { + panic!("No DownlinkFrame"); + } + }; + + assert_ne!(0, down.downlink_id); + down.downlink_id = 0; + + let down_item = down.items.first().unwrap(); + let mut mesh_packet = packets::MeshPacket::from_slice(&down_item.phy_payload).unwrap(); + assert_ne!([0, 0, 0, 0], mesh_packet.mic.unwrap()); + mesh_packet.mic = None; + + if let packets::Payload::Event(v) = &mut mesh_packet.payload { + // Assert the time is ~ now() + assert!( + SystemTime::now() + .duration_since(v.timestamp) + .unwrap_or_default() + < Duration::from_secs(5) + ); + v.timestamp = UNIX_EPOCH; + } + + assert_eq!( + packets::MeshPacket { + mhdr: packets::MHDR { + payload_type: packets::PayloadType::Event, + hop_count: 1, + }, + payload: packets::Payload::Event(packets::EventPayload { + relay_id: [2, 2, 2, 2], + timestamp: UNIX_EPOCH, + events: vec![packets::Event::Proprietary((130, vec![53, 10])),], // 53 = 5 in ascii + }), + mic: None, + }, + mesh_packet + ); +} diff --git a/tests/relay_gateway_mesh_heartbeat.rs b/tests/relay_gateway_mesh_event_heartbeat.rs similarity index 93% rename from tests/relay_gateway_mesh_heartbeat.rs rename to tests/relay_gateway_mesh_event_heartbeat.rs index e1c33a8..6e44ae0 100644 --- a/tests/relay_gateway_mesh_heartbeat.rs +++ b/tests/relay_gateway_mesh_event_heartbeat.rs @@ -8,7 +8,7 @@ use chirpstack_api::prost::Message; use chirpstack_gateway_mesh::packets; use zeromq::SocketRecv; -use chirpstack_gateway_mesh::heartbeat; +use chirpstack_gateway_mesh::events; mod common; @@ -16,9 +16,9 @@ mod common; This tests the scenario when the Relay Gateway sends its periodic heartbeat. */ #[tokio::test] -async fn test_relay_gateway_mesh_heartbeat() { +async fn test_relay_gateway_mesh_event_heartbeat() { common::setup(false).await; - let _ = heartbeat::report_heartbeat().await; + let _ = events::report_heartbeat().await; // We expect the heartbeat to be received by the mesh concentratord as // a downlink frame. diff --git a/tests/relay_gateway_mesh_event_proprietary.rs b/tests/relay_gateway_mesh_event_proprietary.rs new file mode 100644 index 0000000..c23bf8a --- /dev/null +++ b/tests/relay_gateway_mesh_event_proprietary.rs @@ -0,0 +1,76 @@ +use std::time::{Duration, SystemTime, UNIX_EPOCH}; + +#[macro_use] +extern crate anyhow; + +use chirpstack_api::gw; +use chirpstack_api::prost::Message; +use chirpstack_gateway_mesh::packets; +use zeromq::SocketRecv; + +use chirpstack_gateway_mesh::events; + +mod common; + +/* + This tests the scenario when the Relay Gateway sends proprietary events. +*/ +#[tokio::test] +async fn test_relay_gateway_mesh_event_proprietary() { + common::setup(false).await; + let _ = events::report_events(&[128, 129]).await; + + // We expect the proprietary events to be received by the mesh concentratord + // as a downlink frame. + + let down: gw::DownlinkFrame = { + let mut cmd_sock = common::MESH_BACKEND_COMMAND_SOCK + .get() + .unwrap() + .lock() + .await; + let msg = cmd_sock.recv().await.unwrap(); + + let cmd = gw::Command::decode(msg.get(0).cloned().unwrap()).unwrap(); + if let Some(gw::command::Command::SendDownlinkFrame(v)) = cmd.command { + v + } else { + panic!("No DownlinkFrame"); + } + }; + + let down_item = down.items.first().unwrap(); + let mut mesh_packet = packets::MeshPacket::from_slice(&down_item.phy_payload).unwrap(); + assert_ne!([0, 0, 0, 0], mesh_packet.mic.unwrap()); + mesh_packet.mic = None; + + if let packets::Payload::Event(v) = &mut mesh_packet.payload { + // Assert the time is ~ now() + assert!( + SystemTime::now() + .duration_since(v.timestamp) + .unwrap_or_default() + < Duration::from_secs(5) + ); + v.timestamp = UNIX_EPOCH; + } + + assert_eq!( + packets::MeshPacket { + mhdr: packets::MHDR { + payload_type: packets::PayloadType::Event, + hop_count: 1, + }, + payload: packets::Payload::Event(packets::EventPayload { + relay_id: [2, 2, 2, 2], + timestamp: UNIX_EPOCH, + events: vec![ + packets::Event::Proprietary((128, vec![102, 111, 111, 10])), + packets::Event::Proprietary((129, vec![98, 97, 114, 10])), + ], + }), + mic: None, + }, + mesh_packet + ); +} diff --git a/tests/relay_gateway_relay_mesh_heartbeat.rs b/tests/relay_gateway_relay_mesh_event.rs similarity index 100% rename from tests/relay_gateway_relay_mesh_heartbeat.rs rename to tests/relay_gateway_relay_mesh_event.rs diff --git a/tests/relay_gateway_uplink_mesh.rs b/tests/relay_gateway_uplink_mesh.rs index dd71fcf..67cf754 100644 --- a/tests/relay_gateway_uplink_mesh.rs +++ b/tests/relay_gateway_uplink_mesh.rs @@ -113,14 +113,14 @@ async fn test_relay_gateway_uplink_mesh() { // Publish the uplink one more time, this time we expect that it will be discarded. { let mut event_sock = common::MESH_BACKEND_EVENT_SOCK.get().unwrap().lock().await; + let event = gw::Event { + event: Some(gw::event::Event::UplinkFrame(up.clone())), + }; event_sock .send( - vec![ - bytes::Bytes::from("up"), - bytes::Bytes::from(up.encode_to_vec()), - ] - .try_into() - .unwrap(), + vec![bytes::Bytes::from(event.encode_to_vec())] + .try_into() + .unwrap(), ) .await .unwrap(); From 2419a0a8eee9cf5446c9fbc6755f812e4b48f25b Mon Sep 17 00:00:00 2001 From: Orne Brocaar Date: Wed, 21 May 2025 09:26:52 +0100 Subject: [PATCH 07/17] Fix typo in config template. --- src/cmd/configfile.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/cmd/configfile.rs b/src/cmd/configfile.rs index 9e84a91..fbbaae8 100644 --- a/src/cmd/configfile.rs +++ b/src/cmd/configfile.rs @@ -188,9 +188,9 @@ pub fn run() { # # "129" = ["/path/to/command", "arg1", "arg2"] # - [events.commands] + [commands.commands] - {{#each events.commands}} + {{#each commands.commands}} {{@key}} = [{{#each this}}"{{this}}", {{/each}}] {{/each}} "#; From fa13744949b4e8354b0563792bb926ea40300e29 Mon Sep 17 00:00:00 2001 From: Orne Brocaar Date: Fri, 23 May 2025 09:03:17 +0100 Subject: [PATCH 08/17] Implement encryption of event / command payloads. This implements the encryption of event and command payloads, to avoid the exposure of possible sensitive data (e.g. configuration parameters). This also adds a new root_key configuration, which will be used to derrive the keys for mic calculation and encryption. In case the signing_key configuration is present, then this key will be used for mic calculation (for backwards compatibility). --- src/aes128.rs | 27 ++ src/cmd/configfile.rs | 11 +- src/commands.rs | 1 + src/config.rs | 2 + src/events.rs | 4 +- src/mesh.rs | 41 ++- src/packets.rs | 259 +++++++++++++++++- tests/border_gateway_downlink_mesh.rs | 4 +- ...border_gateway_mesh_command_proprietary.rs | 7 + tests/border_gateway_mesh_event_heartbeat.rs | 7 +- tests/border_gateway_uplink_mesh.rs | 4 +- tests/relay_gateway_downlink_lora.rs | 6 +- .../relay_gateway_mesh_command_proprietary.rs | 16 +- tests/relay_gateway_mesh_event_heartbeat.rs | 6 + tests/relay_gateway_mesh_event_proprietary.rs | 6 + tests/relay_gateway_relay_mesh_event.rs | 24 +- tests/relay_gateway_uplink_lora.rs | 4 +- tests/relay_gateway_uplink_mesh.rs | 6 +- 18 files changed, 394 insertions(+), 41 deletions(-) diff --git a/src/aes128.rs b/src/aes128.rs index be22fed..1b1784b 100644 --- a/src/aes128.rs +++ b/src/aes128.rs @@ -1,6 +1,10 @@ use std::fmt; use std::str::FromStr; +use aes::{ + cipher::{generic_array::GenericArray, BlockEncrypt, KeyInit}, + Aes128, Block, +}; use anyhow::{Error, Result}; use serde::{ de::{self, Visitor}, @@ -95,3 +99,26 @@ impl Visitor<'_> for Aes128KeyVisitor { Aes128Key::from_str(value).map_err(|e| E::custom(format!("{}", e))) } } + +pub fn get_signing_key(key: Aes128Key) -> Aes128Key { + let b: [u8; 16] = [0; 16]; + get_key(key, b) +} + +pub fn get_encryption_key(key: Aes128Key) -> Aes128Key { + let mut b: [u8; 16] = [0; 16]; + b[0] = 0x01; + get_key(key, b) +} + +fn get_key(key: Aes128Key, b: [u8; 16]) -> Aes128Key { + let key_bytes = key.to_bytes(); + let key = GenericArray::from_slice(&key_bytes); + let cipher = Aes128::new(key); + + let mut b = b; + let block = Block::from_mut_slice(&mut b); + cipher.encrypt_block(block); + + Aes128Key(b) +} diff --git a/src/cmd/configfile.rs b/src/cmd/configfile.rs index fbbaae8..7a23477 100644 --- a/src/cmd/configfile.rs +++ b/src/cmd/configfile.rs @@ -24,10 +24,19 @@ pub fn run() { # Mesh configuration. [mesh] - # Signing key (AES128, HEX encoded). + # Mesh root key (AES128, HEX encoded). + # + # This key is used to derive the signing and encryption keys. The same key + # must be configured on every Border and Relay gateway. + root_key="{{ mesh.root_key }}" + + # Signing key (AES128, HEX encoded) (deprecated). # # This key is used to sign and validate each mesh packet. This key must be # configured on every Border / Relay gateway equally. + # + # Deprecation note: If set, the signing key will not be derrived from the + # above root_key, but this key will be used. signing_key="{{ mesh.signing_key }}" # Border Gateway. diff --git a/src/commands.rs b/src/commands.rs index 0476eec..f4e5941 100644 --- a/src/commands.rs +++ b/src/commands.rs @@ -52,6 +52,7 @@ pub async fn execute_commands(pl: &packets::CommandPayload) -> Result execute_proprietary(*t, v).await, + packets::Command::Encrypted(_) => panic!("Commands must be decrypted first"), }; match resp { diff --git a/src/config.rs b/src/config.rs index c2cdc4f..0bae5c9 100644 --- a/src/config.rs +++ b/src/config.rs @@ -58,6 +58,7 @@ impl Default for Logging { #[derive(Serialize, Deserialize)] #[serde(default)] pub struct Mesh { + pub root_key: Aes128Key, pub signing_key: Aes128Key, pub frequencies: Vec, pub data_rate: DataRate, @@ -72,6 +73,7 @@ pub struct Mesh { impl Default for Mesh { fn default() -> Self { Mesh { + root_key: Aes128Key::null(), signing_key: Aes128Key::null(), frequencies: vec![868100000, 868300000, 868500000], data_rate: DataRate { diff --git a/src/events.rs b/src/events.rs index e49778a..a4658e3 100644 --- a/src/events.rs +++ b/src/events.rs @@ -9,6 +9,7 @@ use tokio::process::Command; use tokio::sync::OnceCell; use tokio::time::sleep; +use crate::aes128::{get_encryption_key, get_signing_key, Aes128Key}; use crate::backend; use crate::config::{self, Configuration}; use crate::helpers; @@ -133,7 +134,8 @@ pub async fn send_events(events: Vec) -> Result<()> { }), mic: None, }; - packet.set_mic(conf.mesh.signing_key)?; + packet.encrypt(get_encryption_key(Aes128Key::null()))?; + packet.set_mic(get_signing_key(conf.mesh.signing_key))?; let pl = gw::DownlinkFrame { downlink_id: random(), diff --git a/src/mesh.rs b/src/mesh.rs index 0ad3bbf..9223ac1 100644 --- a/src/mesh.rs +++ b/src/mesh.rs @@ -8,6 +8,7 @@ use log::{info, trace, warn}; use rand::random; use crate::{ + aes128::{get_encryption_key, get_signing_key, Aes128Key}, backend, cache::{Cache, PayloadCache}, commands, @@ -39,8 +40,12 @@ pub async fn handle_uplink(border_gateway: bool, pl: &gw::UplinkFrame) -> Result // Handle Proprietary LoRaWAN payload (mesh encapsulated). pub async fn handle_mesh(border_gateway: bool, pl: &gw::UplinkFrame) -> Result<()> { let conf = config::get(); - let packet = MeshPacket::from_slice(&pl.phy_payload)?; - if !packet.validate_mic(conf.mesh.signing_key)? { + let mut packet = MeshPacket::from_slice(&pl.phy_payload)?; + if !packet.validate_mic(if conf.mesh.signing_key != Aes128Key::null() { + conf.mesh.signing_key + } else { + get_signing_key(conf.mesh.root_key) + })? { warn!("Dropping packet, invalid MIC, mesh_packet: {}", packet); return Ok(()); } @@ -55,6 +60,9 @@ pub async fn handle_mesh(border_gateway: bool, pl: &gw::UplinkFrame) -> Result<( return Ok(()); }; + // Decrypt the packet (in case it contains an encrypted payload). + packet.decrypt(get_encryption_key(conf.mesh.root_key))?; + match border_gateway { // Proxy relayed uplink true => match packet.mhdr.payload_type { @@ -113,7 +121,12 @@ pub async fn send_mesh_command(pl: gw::MeshCommand) -> Result<()> { }), mic: None, }; - packet.set_mic(conf.mesh.signing_key)?; + packet.encrypt(get_encryption_key(conf.mesh.root_key))?; + packet.set_mic(if conf.mesh.signing_key != Aes128Key::null() { + conf.mesh.signing_key + } else { + get_signing_key(conf.mesh.root_key) + })?; let pl = gw::DownlinkFrame { downlink_id: random(), @@ -267,6 +280,7 @@ async fn proxy_event_mesh_packet(pl: &gw::UplinkFrame, packet: MeshPacket) -> Re payload: v.1.clone(), }) } + Event::Encrypted(_) => panic!("Events must be decrypted first"), }), }) .collect(), @@ -375,9 +389,16 @@ async fn relay_mesh_packet(pl: &gw::UplinkFrame, mut packet: MeshPacket) -> Resu // Increment hop count. packet.mhdr.hop_count += 1; + // Encrypt. + packet.encrypt(get_encryption_key(conf.mesh.root_key))?; + // We need to re-set the MIC as we have changed the payload by incrementing // the hop count (and in casee of heartbeat, we have modified the Relay path). - packet.set_mic(conf.mesh.signing_key)?; + packet.set_mic(if conf.mesh.signing_key != Aes128Key::null() { + conf.mesh.signing_key + } else { + get_signing_key(conf.mesh.root_key) + })?; if packet.mhdr.hop_count > conf.mesh.max_hop_count { return Err(anyhow!("Max hop count exceeded")); @@ -447,7 +468,11 @@ async fn relay_uplink_lora_packet(pl: &gw::UplinkFrame) -> Result<()> { }), mic: None, }; - packet.set_mic(conf.mesh.signing_key)?; + packet.set_mic(if conf.mesh.signing_key != Aes128Key::null() { + conf.mesh.signing_key + } else { + get_signing_key(conf.mesh.root_key) + })?; let pl = gw::DownlinkFrame { downlink_id: random(), @@ -546,7 +571,11 @@ async fn relay_downlink_lora_packet(pl: &gw::DownlinkFrame) -> Result Result<()> { + match &mut self.payload { + Payload::Event(pl) => pl.encrypt(key), + Payload::Command(pl) => pl.encrypt(key), + _ => Ok(()), + } + } + + pub fn decrypt(&mut self, key: Aes128Key) -> Result<()> { + match &mut self.payload { + Payload::Event(pl) => pl.decrypt(key), + Payload::Command(pl) => pl.decrypt(key), + _ => Ok(()), + } + } + + // Calculate and set the message integrity code. + // Note: If applicable, the payload must be encrypted first! pub fn set_mic(&mut self, key: Aes128Key) -> Result<()> { self.mic = Some(self.calculate_mic(key)?); Ok(()) @@ -456,11 +477,11 @@ impl EventPayload { cur.read_exact(&mut relay_id)?; let mut events = Vec::new(); - while cur.position() < b_len { - match Event::decode(&mut cur) { - Ok(v) => events.push(v), - Err(e) => warn!("Decode event error: {}", e), - } + + if cur.position() < b_len { + let mut b = Vec::new(); + cur.read_to_end(&mut b)?; + events.push(Event::Encrypted(b)); } Ok(EventPayload { @@ -481,10 +502,88 @@ impl EventPayload { Ok(b) } + + pub fn encrypt(&mut self, key: Aes128Key) -> Result<()> { + if self.events.is_empty() { + return Ok(()); + } + + // Buffer to encrypt + let mut b = Vec::new(); + for event in &self.events { + b.extend_from_slice(&event.encode()?); + } + + self.events = vec![Event::Encrypted(encrypt_events_commands( + key, + false, + &self.relay_id, + self.timestamp, + &b, + )?)]; + + Ok(()) + } + + pub fn decrypt(&mut self, key: Aes128Key) -> Result<()> { + if self.events.is_empty() { + return Ok(()); + } + + if self.events.len() != 1 { + return Err(anyhow!("Exactly 1 event item expected")); + } + + if let Event::Encrypted(b) = &self.events[0] { + let b = encrypt_events_commands(key, false, &self.relay_id, self.timestamp, b)?; + let b_len = b.len() as u64; + + let mut cur = Cursor::new(b.as_slice()); + let mut events = Vec::new(); + + while cur.position() < b_len { + match Event::decode(&mut cur) { + Ok(v) => events.push(v), + Err(e) => warn!("Decode event error: {}", e), + } + } + + self.events = events; + } else { + return Err(anyhow!("Encrypted event expected")); + } + + Ok(()) + } + + pub fn decode(&mut self) -> Result<()> { + if self.events.is_empty() { + return Ok(()); + } + + if let Event::Encrypted(b) = &self.events[0] { + let b_len = b.len() as u64; + + let mut cur = Cursor::new(b.as_slice()); + let mut events = Vec::new(); + + while cur.position() < b_len { + match Event::decode(&mut cur) { + Ok(v) => events.push(v), + Err(e) => warn!("Decode event error: {}", e), + } + } + + self.events = events; + } + + Ok(()) + } } #[derive(Debug, PartialEq, Eq, Clone)] pub enum Event { + Encrypted(Vec), Heartbeat(HeartbeatPayload), Proprietary((u8, Vec)), } @@ -505,6 +604,7 @@ impl Event { pub fn encode(&self) -> Result> { let (t, v) = match self { + Event::Encrypted(v) => return Ok(v.clone()), Event::Heartbeat(v) => (0x00, v.to_vec()?), Event::Proprietary((t, v)) => (*t, v.clone()), }; @@ -628,11 +728,11 @@ impl CommandPayload { cur.read_exact(&mut relay_id)?; let mut commands = Vec::new(); - while cur.position() < b_len { - match Command::decode(&mut cur) { - Ok(v) => commands.push(v), - Err(e) => warn!("Decode command error: {}", e), - } + + if cur.position() < b_len { + let mut b = Vec::new(); + cur.read_to_end(&mut b)?; + commands.push(Command::Encrypted(b)); } Ok(CommandPayload { @@ -653,10 +753,88 @@ impl CommandPayload { Ok(b) } + + pub fn encrypt(&mut self, key: Aes128Key) -> Result<()> { + if self.commands.is_empty() { + return Ok(()); + } + + // Buffer to encrypt + let mut b = Vec::new(); + for command in &self.commands { + b.extend_from_slice(&command.encode()?); + } + + self.commands = vec![Command::Encrypted(encrypt_events_commands( + key, + true, + &self.relay_id, + self.timestamp, + &b, + )?)]; + + Ok(()) + } + + pub fn decrypt(&mut self, key: Aes128Key) -> Result<()> { + if self.commands.is_empty() { + return Ok(()); + } + + if self.commands.len() != 1 { + return Err(anyhow!("Exactly 1 command item expected")); + } + + if let Command::Encrypted(b) = &self.commands[0] { + let b = encrypt_events_commands(key, true, &self.relay_id, self.timestamp, b)?; + let b_len = b.len() as u64; + + let mut cur = Cursor::new(b.as_slice()); + let mut commands = Vec::new(); + + while cur.position() < b_len { + match Command::decode(&mut cur) { + Ok(v) => commands.push(v), + Err(e) => warn!("Decode command error: {}", e), + } + } + + self.commands = commands; + } else { + return Err(anyhow!("Encrypted command exepcted")); + } + + Ok(()) + } + + pub fn decode(&mut self) -> Result<()> { + if self.commands.is_empty() { + return Ok(()); + } + + if let Command::Encrypted(b) = &self.commands[0] { + let b_len = b.len() as u64; + + let mut cur = Cursor::new(b.as_slice()); + let mut commands = Vec::new(); + + while cur.position() < b_len { + match Command::decode(&mut cur) { + Ok(v) => commands.push(v), + Err(e) => warn!("Decode command error: {}", e), + } + } + + self.commands = commands; + } + + Ok(()) + } } #[derive(Debug, PartialEq, Eq, Clone)] pub enum Command { + Encrypted(Vec), Proprietary((u8, Vec)), } @@ -676,6 +854,7 @@ impl Command { pub fn encode(&self) -> Result> { let (t, v) = match self { + Command::Encrypted(v) => return Ok(v.clone()), Command::Proprietary((t, v)) => (*t, v.clone()), }; @@ -689,6 +868,7 @@ impl Command { pub fn get_type(&self) -> u8 { match self { + Command::Encrypted(_) => 0x00, // this should never be the case Command::Proprietary((typ, _)) => *typ, } } @@ -733,6 +913,55 @@ pub fn decode_freq(b: &[u8]) -> Result { Ok(freq) } +pub fn encrypt_events_commands( + key: Aes128Key, + is_command: bool, + relay_id: &[u8], + timestamp: SystemTime, + payload: &[u8], +) -> Result> { + use aes::cipher::KeyInit; + + if payload.is_empty() { + return Ok(Vec::new()); + } + + let mut b = payload.to_vec(); + let b_len = b.len(); + + // Make buffer length multiple of 16. + b.append(&mut vec![0; 16 - (b_len % 16)]); + + // Encode timestamp. + let timestamp = timestamp.duration_since(UNIX_EPOCH)?.as_secs() as u32; + + let key_bytes = key.to_bytes(); + let key = GenericArray::from_slice(&key_bytes); + let cipher = Aes128::new(key); + + let mut a = vec![0; 16]; + a[0] = 0x01; + if is_command { + a[5] = 0x01; + } + a[6..10].clone_from_slice(relay_id); + a[10..14].clone_from_slice(×tamp.to_be_bytes()); + + // Encrypt blocks + for i in 0..(b.len() / 16) { + a[15] = (i + 1) as u8; + + let mut block = Block::clone_from_slice(&a); + cipher.encrypt_block(&mut block); + + for j in 0..16 { + b[(i * 16) + j] ^= block[j]; + } + } + + Ok(b[0..b_len].to_vec()) +} + #[cfg(test)] mod test { use super::*; @@ -1191,7 +1420,9 @@ mod test { let b = vec![ 59, 154, 202, 0, 1, 2, 3, 4, 0, 12, 5, 6, 7, 8, 120, 52, 9, 10, 11, 12, 120, 52, ]; - let event_pl = EventPayload::from_slice(&b).unwrap(); + let mut event_pl = EventPayload::from_slice(&b).unwrap(); + event_pl.decode().unwrap(); + assert_eq!( EventPayload { timestamp: UNIX_EPOCH @@ -1249,7 +1480,9 @@ mod test { #[test] fn test_proprietary_command_from_slice() { let b = vec![59, 154, 202, 0, 1, 2, 3, 4, 128, 4, 4, 3, 2, 1]; - let cmd_pl = CommandPayload::from_slice(&b).unwrap(); + let mut cmd_pl = CommandPayload::from_slice(&b).unwrap(); + cmd_pl.decode().unwrap(); + assert_eq!( CommandPayload { timestamp: UNIX_EPOCH diff --git a/tests/border_gateway_downlink_mesh.rs b/tests/border_gateway_downlink_mesh.rs index 4037ddb..cab0aa0 100644 --- a/tests/border_gateway_downlink_mesh.rs +++ b/tests/border_gateway_downlink_mesh.rs @@ -5,7 +5,7 @@ use chirpstack_api::gw; use chirpstack_api::prost::Message; use zeromq::{SocketRecv, SocketSend}; -use chirpstack_gateway_mesh::aes128::Aes128Key; +use chirpstack_gateway_mesh::aes128::{get_signing_key, Aes128Key}; use chirpstack_gateway_mesh::packets; mod common; @@ -107,7 +107,7 @@ async fn test_border_gateway_downlink_mesh() { }), mic: None, }; - packet.set_mic(Aes128Key::null()).unwrap(); + packet.set_mic(get_signing_key(Aes128Key::null())).unwrap(); packet }, mesh_packet diff --git a/tests/border_gateway_mesh_command_proprietary.rs b/tests/border_gateway_mesh_command_proprietary.rs index 53ea5ad..fd23d25 100644 --- a/tests/border_gateway_mesh_command_proprietary.rs +++ b/tests/border_gateway_mesh_command_proprietary.rs @@ -7,6 +7,7 @@ use chirpstack_api::gw; use chirpstack_api::prost::Message; use zeromq::{SocketRecv, SocketSend}; +use chirpstack_gateway_mesh::aes128::{get_encryption_key, Aes128Key}; use chirpstack_gateway_mesh::packets; mod common; @@ -68,9 +69,15 @@ async fn border_gateway_mesh_command_proprietary() { let down_item = down.items.first().unwrap(); let mut mesh_packet = packets::MeshPacket::from_slice(&down_item.phy_payload).unwrap(); + // MIC check. assert_ne!([0, 0, 0, 0], mesh_packet.mic.unwrap()); mesh_packet.mic = None; + // Decrypt. + mesh_packet + .decrypt(get_encryption_key(Aes128Key::null())) + .unwrap(); + if let packets::Payload::Command(v) = &mut mesh_packet.payload { // Asser time is ~ now() assert!( diff --git a/tests/border_gateway_mesh_event_heartbeat.rs b/tests/border_gateway_mesh_event_heartbeat.rs index bff96c8..699c690 100644 --- a/tests/border_gateway_mesh_event_heartbeat.rs +++ b/tests/border_gateway_mesh_event_heartbeat.rs @@ -7,7 +7,7 @@ use chirpstack_api::gw; use chirpstack_api::prost::Message; use zeromq::{SocketRecv, SocketSend}; -use chirpstack_gateway_mesh::aes128::Aes128Key; +use chirpstack_gateway_mesh::aes128::{get_encryption_key, get_signing_key, Aes128Key}; use chirpstack_gateway_mesh::packets; mod common; @@ -45,7 +45,10 @@ async fn test_border_gateway_mesh_heartbeat() { }), mic: None, }; - packet.set_mic(Aes128Key::null()).unwrap(); + packet + .encrypt(get_encryption_key(Aes128Key::null())) + .unwrap(); + packet.set_mic(get_signing_key(Aes128Key::null())).unwrap(); let up = gw::UplinkFrame { phy_payload: packet.to_vec().unwrap(), diff --git a/tests/border_gateway_uplink_mesh.rs b/tests/border_gateway_uplink_mesh.rs index 6c02f2c..1b91395 100644 --- a/tests/border_gateway_uplink_mesh.rs +++ b/tests/border_gateway_uplink_mesh.rs @@ -5,7 +5,7 @@ use chirpstack_api::gw; use chirpstack_api::prost::Message; use zeromq::{SocketRecv, SocketSend}; -use chirpstack_gateway_mesh::aes128::Aes128Key; +use chirpstack_gateway_mesh::aes128::{get_signing_key, Aes128Key}; use chirpstack_gateway_mesh::packets; mod common; @@ -37,7 +37,7 @@ async fn test_border_gateway_uplink_mesh() { }), mic: None, }; - packet.set_mic(Aes128Key::null()).unwrap(); + packet.set_mic(get_signing_key(Aes128Key::null())).unwrap(); let up = gw::UplinkFrame { phy_payload: packet.to_vec().unwrap(), diff --git a/tests/relay_gateway_downlink_lora.rs b/tests/relay_gateway_downlink_lora.rs index 17b0816..f33a7f2 100644 --- a/tests/relay_gateway_downlink_lora.rs +++ b/tests/relay_gateway_downlink_lora.rs @@ -5,7 +5,7 @@ use chirpstack_api::gw; use chirpstack_api::prost::Message; use zeromq::{SocketRecv, SocketSend}; -use chirpstack_gateway_mesh::aes128::Aes128Key; +use chirpstack_gateway_mesh::aes128::{get_signing_key, Aes128Key}; use chirpstack_gateway_mesh::{mesh, packets}; mod common; @@ -39,7 +39,9 @@ async fn test_relay_gateway_downlink_lora() { }), mic: None, }; - down_packet.set_mic(Aes128Key::null()).unwrap(); + down_packet + .set_mic(get_signing_key(Aes128Key::null())) + .unwrap(); // The packet that we received from the Border Gateway that must be relayed to // the End Device. diff --git a/tests/relay_gateway_mesh_command_proprietary.rs b/tests/relay_gateway_mesh_command_proprietary.rs index 779b3b9..9663ad5 100644 --- a/tests/relay_gateway_mesh_command_proprietary.rs +++ b/tests/relay_gateway_mesh_command_proprietary.rs @@ -7,7 +7,7 @@ use chirpstack_api::gw; use chirpstack_api::prost::Message; use zeromq::{SocketRecv, SocketSend}; -use chirpstack_gateway_mesh::aes128::Aes128Key; +use chirpstack_gateway_mesh::aes128::{get_encryption_key, get_signing_key, Aes128Key}; use chirpstack_gateway_mesh::packets; mod common; @@ -36,7 +36,12 @@ async fn test_relay_gateway_mesh_command_proprietary() { }), mic: None, }; - cmd_packet.set_mic(Aes128Key::null()).unwrap(); + cmd_packet + .encrypt(get_encryption_key(Aes128Key::null())) + .unwrap(); + cmd_packet + .set_mic(get_signing_key(Aes128Key::null())) + .unwrap(); // The packet that we received from the Border Gateway. let up = gw::UplinkFrame { @@ -100,6 +105,13 @@ async fn test_relay_gateway_mesh_command_proprietary() { let down_item = down.items.first().unwrap(); let mut mesh_packet = packets::MeshPacket::from_slice(&down_item.phy_payload).unwrap(); + + // Decrypt. + mesh_packet + .decrypt(get_encryption_key(Aes128Key::null())) + .unwrap(); + + // MIC. assert_ne!([0, 0, 0, 0], mesh_packet.mic.unwrap()); mesh_packet.mic = None; diff --git a/tests/relay_gateway_mesh_event_heartbeat.rs b/tests/relay_gateway_mesh_event_heartbeat.rs index 6e44ae0..a5760d1 100644 --- a/tests/relay_gateway_mesh_event_heartbeat.rs +++ b/tests/relay_gateway_mesh_event_heartbeat.rs @@ -8,6 +8,7 @@ use chirpstack_api::prost::Message; use chirpstack_gateway_mesh::packets; use zeromq::SocketRecv; +use chirpstack_gateway_mesh::aes128::{get_encryption_key, Aes128Key}; use chirpstack_gateway_mesh::events; mod common; @@ -40,6 +41,11 @@ async fn test_relay_gateway_mesh_event_heartbeat() { let down_item = down.items.first().unwrap(); let mut mesh_packet = packets::MeshPacket::from_slice(&down_item.phy_payload).unwrap(); + + mesh_packet + .decrypt(get_encryption_key(Aes128Key::null())) + .unwrap(); + assert_ne!([0, 0, 0, 0], mesh_packet.mic.unwrap()); mesh_packet.mic = None; diff --git a/tests/relay_gateway_mesh_event_proprietary.rs b/tests/relay_gateway_mesh_event_proprietary.rs index c23bf8a..e07a9ea 100644 --- a/tests/relay_gateway_mesh_event_proprietary.rs +++ b/tests/relay_gateway_mesh_event_proprietary.rs @@ -8,6 +8,7 @@ use chirpstack_api::prost::Message; use chirpstack_gateway_mesh::packets; use zeromq::SocketRecv; +use chirpstack_gateway_mesh::aes128::{get_encryption_key, Aes128Key}; use chirpstack_gateway_mesh::events; mod common; @@ -41,6 +42,11 @@ async fn test_relay_gateway_mesh_event_proprietary() { let down_item = down.items.first().unwrap(); let mut mesh_packet = packets::MeshPacket::from_slice(&down_item.phy_payload).unwrap(); + + mesh_packet + .decrypt(get_encryption_key(Aes128Key::null())) + .unwrap(); + assert_ne!([0, 0, 0, 0], mesh_packet.mic.unwrap()); mesh_packet.mic = None; diff --git a/tests/relay_gateway_relay_mesh_event.rs b/tests/relay_gateway_relay_mesh_event.rs index 901104e..0b15d82 100644 --- a/tests/relay_gateway_relay_mesh_event.rs +++ b/tests/relay_gateway_relay_mesh_event.rs @@ -9,7 +9,7 @@ use chirpstack_gateway_mesh::packets; use tokio::time::{timeout, Duration}; use zeromq::{SocketRecv, SocketSend}; -use chirpstack_gateway_mesh::aes128::Aes128Key; +use chirpstack_gateway_mesh::aes128::{get_encryption_key, get_signing_key, Aes128Key}; mod common; @@ -35,7 +35,10 @@ async fn test_relay_gateway_relay_mesh_heartbeat() { }), mic: None, }; - packet.set_mic(Aes128Key::null()).unwrap(); + packet + .encrypt(get_encryption_key(Aes128Key::null())) + .unwrap(); + packet.set_mic(get_signing_key(Aes128Key::null())).unwrap(); let up = gw::UplinkFrame { phy_payload: packet.to_vec().unwrap(), @@ -95,8 +98,14 @@ async fn test_relay_gateway_relay_mesh_heartbeat() { }; let down_item = down.items.first().unwrap(); - let mesh_packet = packets::Packet::from_slice(&down_item.phy_payload).unwrap(); + let mut mesh_packet = packets::Packet::from_slice(&down_item.phy_payload).unwrap(); + if let packets::Packet::Mesh(pl) = &mut mesh_packet { + pl.decrypt(get_encryption_key(Aes128Key::null())).unwrap(); + } + packet + .decrypt(get_encryption_key(Aes128Key::null())) + .unwrap(); packet.mhdr.hop_count += 1; if let packets::Payload::Event(v) = &mut packet.payload { for event in &mut v.events { @@ -109,8 +118,13 @@ async fn test_relay_gateway_relay_mesh_heartbeat() { } } } - packet.set_mic(Aes128Key::null()).unwrap(); - + packet + .encrypt(get_encryption_key(Aes128Key::null())) + .unwrap(); + packet.set_mic(get_signing_key(Aes128Key::null())).unwrap(); + packet + .decrypt(get_encryption_key(Aes128Key::null())) + .unwrap(); assert_eq!(packets::Packet::Mesh(packet), mesh_packet); // Publish the uplink one more time, this time we expect that it will be discarded. diff --git a/tests/relay_gateway_uplink_lora.rs b/tests/relay_gateway_uplink_lora.rs index dabcc2f..fb98bb1 100644 --- a/tests/relay_gateway_uplink_lora.rs +++ b/tests/relay_gateway_uplink_lora.rs @@ -6,7 +6,7 @@ use chirpstack_api::prost::Message; use chirpstack_gateway_mesh::packets; use zeromq::{SocketRecv, SocketSend}; -use chirpstack_gateway_mesh::aes128::Aes128Key; +use chirpstack_gateway_mesh::aes128::{get_signing_key, Aes128Key}; mod common; @@ -99,7 +99,7 @@ async fn test_relay_gateway_uplink_lora() { }), mic: None, }; - packet.set_mic(Aes128Key::null()).unwrap(); + packet.set_mic(get_signing_key(Aes128Key::null())).unwrap(); packet }, mesh_packet diff --git a/tests/relay_gateway_uplink_mesh.rs b/tests/relay_gateway_uplink_mesh.rs index 67cf754..d987c99 100644 --- a/tests/relay_gateway_uplink_mesh.rs +++ b/tests/relay_gateway_uplink_mesh.rs @@ -7,7 +7,7 @@ use chirpstack_gateway_mesh::packets; use tokio::time::{timeout, Duration}; use zeromq::{SocketRecv, SocketSend}; -use chirpstack_gateway_mesh::aes128::Aes128Key; +use chirpstack_gateway_mesh::aes128::{get_signing_key, Aes128Key}; mod common; @@ -38,7 +38,7 @@ async fn test_relay_gateway_uplink_mesh() { }), mic: None, }; - packet.set_mic(Aes128Key::null()).unwrap(); + packet.set_mic(get_signing_key(Aes128Key::null())).unwrap(); packet }); @@ -105,7 +105,7 @@ async fn test_relay_gateway_uplink_mesh() { // The hop_count must be incremented. if let packets::Packet::Mesh(v) = &mut packet { v.mhdr.hop_count += 1; - v.set_mic(Aes128Key::null()).unwrap(); + v.set_mic(get_signing_key(Aes128Key::null())).unwrap(); } assert_eq!(packet, mesh_packet); From 4ae0173cbcac77b34ea58d989f1b947daae711f6 Mon Sep 17 00:00:00 2001 From: Orne Brocaar Date: Fri, 23 May 2025 10:34:56 +0100 Subject: [PATCH 09/17] Update chirpstack_api dependency. --- Cargo.lock | 4 +++- Cargo.toml | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 9c1bfb6..9b4b49d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -179,7 +179,9 @@ dependencies = [ [[package]] name = "chirpstack_api" -version = "4.12.0" +version = "4.13.0-test.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "245477390d7fecdd491ad4bc5e42c5769ba341b38ff9298533f3374d6dc1580d" dependencies = [ "hex", "pbjson-build", diff --git a/Cargo.toml b/Cargo.toml index 53b6016..e8a3713 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,7 +16,7 @@ "usage", "derive", ] } - chirpstack_api = { version = "4.12.0", default-features = false, path = "../chirpstack/api/rust" } + chirpstack_api = { version = "4.13.0-test.1", default-features = false } lrwn_filters = { version = "4.12", features = ["serde"] } log = "0.4" simple_logger = "5.0" From 7071ecac34dbd269389c40c711ab2a6bb33ab8f7 Mon Sep 17 00:00:00 2001 From: Orne Brocaar Date: Fri, 23 May 2025 10:36:47 +0100 Subject: [PATCH 10/17] Fix cargo clippy feedback. --- src/backend.rs | 33 +++++++++++++++------------------ src/commands.rs | 3 +-- src/mesh.rs | 16 +++++----------- src/packets.rs | 5 +---- src/proxy.rs | 4 ++-- 5 files changed, 24 insertions(+), 37 deletions(-) diff --git a/src/backend.rs b/src/backend.rs index bca10cc..a4dbffa 100644 --- a/src/backend.rs +++ b/src/backend.rs @@ -297,7 +297,7 @@ async fn handle_event_msg( ) } - info!("Frame received - {}", helpers::format_uplink(&v)?); + info!("Frame received - {}", helpers::format_uplink(v)?); mesh::handle_uplink(border_gateway, v).await?; } } @@ -315,26 +315,23 @@ async fn handle_event_msg( async fn handle_mesh_event_msg(border_gateway: bool, event: gw::Event) -> Result<()> { trace!("Handling mesh event, event: {:?}", event); - match &event.event { - Some(gw::event::Event::UplinkFrame(v)) => { - if let Some(rx_info) = &v.rx_info { - // Filter out frames with invalid CRC. - if rx_info.crc_status() != gw::CrcStatus::CrcOk { - debug!( - "Discarding uplink, CRC != OK, uplink_id: {}", - rx_info.uplink_id - ); - return Ok(()); - } + if let Some(gw::event::Event::UplinkFrame(v)) = &event.event { + if let Some(rx_info) = &v.rx_info { + // Filter out frames with invalid CRC. + if rx_info.crc_status() != gw::CrcStatus::CrcOk { + debug!( + "Discarding uplink, CRC != OK, uplink_id: {}", + rx_info.uplink_id + ); + return Ok(()); } + } - // The mesh event msg must always be a proprietary payload. - if v.phy_payload.first().cloned().unwrap_or_default() & 0xe0 == 0xe0 { - info!("Mesh frame received - {}", helpers::format_uplink(v)?); - mesh::handle_mesh(border_gateway, v).await?; - } + // The mesh event msg must always be a proprietary payload. + if v.phy_payload.first().cloned().unwrap_or_default() & 0xe0 == 0xe0 { + info!("Mesh frame received - {}", helpers::format_uplink(v)?); + mesh::handle_mesh(border_gateway, v).await?; } - _ => {} } Ok(()) diff --git a/src/commands.rs b/src/commands.rs index f4e5941..36b6627 100644 --- a/src/commands.rs +++ b/src/commands.rs @@ -101,12 +101,11 @@ async fn execute_proprietary(typ: u8, value: &[u8]) -> Result { } async fn get_last_timestamp() -> Option { - LAST_TIMESTAMP + *LAST_TIMESTAMP .get_or_init(|| async { Mutex::new(None) }) .await .lock() .await - .clone() } async fn set_last_timestamp(ts: SystemTime) { diff --git a/src/mesh.rs b/src/mesh.rs index 9223ac1..94d2f9f 100644 --- a/src/mesh.rs +++ b/src/mesh.rs @@ -66,11 +66,11 @@ pub async fn handle_mesh(border_gateway: bool, pl: &gw::UplinkFrame) -> Result<( match border_gateway { // Proxy relayed uplink true => match packet.mhdr.payload_type { - PayloadType::Uplink => proxy_uplink_mesh_packet(&pl, packet).await, - PayloadType::Event => proxy_event_mesh_packet(&pl, packet).await, + PayloadType::Uplink => proxy_uplink_mesh_packet(pl, packet).await, + PayloadType::Event => proxy_event_mesh_packet(pl, packet).await, _ => Ok(()), }, - false => relay_mesh_packet(&pl, packet).await, + false => relay_mesh_packet(pl, packet).await, } } @@ -110,13 +110,7 @@ pub async fn send_mesh_command(pl: gw::MeshCommand) -> Result<()> { commands: pl .commands .iter() - .map(|v| match &v.command { - Some(gw::mesh_command_item::Command::Proprietary(v)) => Some( - packets::Command::Proprietary((v.command_type as u8, v.payload.clone())), - ), - None => None, - }) - .flatten() + .filter_map(|v| v.command.as_ref().map(|gw::mesh_command_item::Command::Proprietary(v)| packets::Command::Proprietary((v.command_type as u8, v.payload.clone())))) .collect(), }), mic: None, @@ -371,7 +365,7 @@ async fn relay_mesh_packet(pl: &gw::UplinkFrame, mut packet: MeshPacket) -> Resu if pl.relay_id == relay_id { // The command payload was intended for this gateway, execute // the commands. - let resp = commands::execute_commands(&pl).await?; + let resp = commands::execute_commands(pl).await?; // Send back the responses (events). if !resp.is_empty() { diff --git a/src/packets.rs b/src/packets.rs index e8b2594..6788992 100644 --- a/src/packets.rs +++ b/src/packets.rs @@ -846,10 +846,7 @@ impl Command { let mut value = vec![0; tag_length[1] as usize]; cur.read_exact(&mut value)?; - Ok(match tag_length[0] { - // Add known types here - _ => Command::Proprietary((tag_length[0], value)), - }) + Ok(Command::Proprietary((tag_length[0], value))) } pub fn encode(&self) -> Result> { diff --git a/src/proxy.rs b/src/proxy.rs index 0865094..e79544e 100644 --- a/src/proxy.rs +++ b/src/proxy.rs @@ -43,7 +43,7 @@ pub async fn setup(conf: &Configuration) -> Result<()> { sock.bind(&event_bind).unwrap(); while let Some(event) = event_rx.blocking_recv() { - sock.send(&event.encode_to_vec(), 0).unwrap(); + sock.send(event.encode_to_vec(), 0).unwrap(); } } }); @@ -145,7 +145,7 @@ async fn handle_command(cmd: gw::Command) -> Result> { Some(gw::command::Command::GetGatewayId(_)) => { info!("Get gateway id command received"); gw::GetGatewayIdResponse { - gateway_id: hex::encode(&backend::get_gateway_id().await.unwrap_or_default()), + gateway_id: hex::encode(backend::get_gateway_id().await.unwrap_or_default()), } .encode_to_vec() } From 33796a815d22af9640153204a9533be71a92869d Mon Sep 17 00:00:00 2001 From: Orne Brocaar Date: Fri, 23 May 2025 11:24:43 +0100 Subject: [PATCH 11/17] Fix setting correct encryption key + fallback signing key. --- src/events.rs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/events.rs b/src/events.rs index a4658e3..7cc0a60 100644 --- a/src/events.rs +++ b/src/events.rs @@ -134,8 +134,12 @@ pub async fn send_events(events: Vec) -> Result<()> { }), mic: None, }; - packet.encrypt(get_encryption_key(Aes128Key::null()))?; - packet.set_mic(get_signing_key(conf.mesh.signing_key))?; + packet.encrypt(get_encryption_key(conf.mesh.root_key))?; + packet.set_mic(if conf.mesh.signing_key != Aes128Key::null() { + conf.mesh.signing_key + } else { + get_signing_key(conf.mesh.root_key) + })?; let pl = gw::DownlinkFrame { downlink_id: random(), From 10af18f7d0915432ebb089570484a8c8458f98a5 Mon Sep 17 00:00:00 2001 From: Orne Brocaar Date: Mon, 16 Jun 2025 14:47:05 +0100 Subject: [PATCH 12/17] Update (dev) dependencies. --- .github/workflows/main.yml | 4 +- Cargo.lock | 321 +++++++++++++++++++++++-------------- Cargo.toml | 4 +- shell.nix | 2 +- 4 files changed, 208 insertions(+), 123 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index fa1cf6f..eaeebb2 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -18,7 +18,7 @@ jobs: name: Install Nix uses: cachix/install-nix-action@v31 with: - nix_path: nixpkgs=channel:nixos-24.11 + nix_path: nixpkgs=channel:nixos-25.05 - name: Cargo cache uses: actions/cache@v4 @@ -58,7 +58,7 @@ jobs: name: Install Nix uses: cachix/install-nix-action@v31 with: - nix_path: nixpkgs=channel:nixos-24.11 + nix_path: nixpkgs=channel:nixos-25.05 - name: Cargo cache uses: actions/cache@v4 diff --git a/Cargo.lock b/Cargo.lock index 9b4b49d..70843e2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -13,9 +13,9 @@ dependencies = [ [[package]] name = "adler2" -version = "2.0.0" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" +checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" [[package]] name = "aes" @@ -39,9 +39,9 @@ dependencies = [ [[package]] name = "anstyle" -version = "1.0.10" +version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55cc3b69f167a1ef2e161439aa98aed94e6028e5f9a59be9a6ffb47aef1651f9" +checksum = "862ed96ca487e809f1c8e5a8447f6ee2cf102f846893800b20cebdf541fc6bbd" [[package]] name = "anyhow" @@ -81,9 +81,9 @@ checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" [[package]] name = "backtrace" -version = "0.3.74" +version = "0.3.75" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d82cb332cdfaed17ae235a638438ac4d4839913cc2af585c3c6746e8f8bee1a" +checksum = "6806a6321ec58106fea15becdad98371e28d92ccbc7c8f1b3b6dd724fe8f1002" dependencies = [ "addr2line", "cfg-if", @@ -102,9 +102,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.9.0" +version = "2.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c8214115b7bf84099f1309324e63141d4c5d7cc26862f97a0a857dbefe165bd" +checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967" [[package]] name = "block-buffer" @@ -115,6 +115,12 @@ dependencies = [ "generic-array", ] +[[package]] +name = "bumpalo" +version = "3.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "793db76d6187cd04dff33004d8e6c9cc4e05cd330500379d2394209271b4aeee" + [[package]] name = "bytes" version = "1.10.1" @@ -123,9 +129,9 @@ checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" [[package]] name = "cc" -version = "1.2.19" +version = "1.2.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e3a13707ac958681c13b39b458c073d0d9bc8a22cb1b2f4c8e55eb72c13f362" +checksum = "d487aa071b5f64da6f19a3e848e3578944b726ee5a4854b82172f02aa876bfdc" dependencies = [ "jobserver", "libc", @@ -144,9 +150,9 @@ dependencies = [ [[package]] name = "cfg-if" -version = "1.0.0" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +checksum = "9555578bc9e57714c812a1f84e4fc5b4d21fcb063490c624de019f7464c91268" [[package]] name = "chirpstack-gateway-mesh" @@ -179,9 +185,9 @@ dependencies = [ [[package]] name = "chirpstack_api" -version = "4.13.0-test.1" +version = "4.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "245477390d7fecdd491ad4bc5e42c5769ba341b38ff9298533f3374d6dc1580d" +checksum = "93cfc11003ad9d3d763dd5ad3753cd31baf211f52e7c326b266e3d32fdf0fe23" dependencies = [ "hex", "pbjson-build", @@ -203,9 +209,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.37" +version = "4.5.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eccb054f56cbd38340b380d4a8e69ef1f02f1af43db2f0cc817a4774d80ae071" +checksum = "40b6887a1d8685cebccf115538db5c0efe625ccac9696ad45c409d96566e910f" dependencies = [ "clap_builder", "clap_derive", @@ -213,9 +219,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.37" +version = "4.5.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "efd9466fac8543255d3b1fcad4762c5e116ffe808c8a3043d4263cd4fd4862a2" +checksum = "e0c66c08ce9f0c698cbce5c0279d0bb6ac936d8674174fe48f736533b964f59e" dependencies = [ "anstyle", "clap_lex", @@ -223,9 +229,9 @@ dependencies = [ [[package]] name = "clap_derive" -version = "4.5.32" +version = "4.5.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09176aae279615badda0765c0c0b3f6ed53f4709118af73cf4655d85d1530cd7" +checksum = "d2c7947ae4cc3d851207c1adb5b5e260ff0cca11446b1d6d1423788e442257ce" dependencies = [ "heck", "proc-macro2", @@ -235,9 +241,9 @@ dependencies = [ [[package]] name = "clap_lex" -version = "0.7.4" +version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f46ad14479a25103f283c0f10005961cf086d8dc42205bb44c46ac563475dca6" +checksum = "b94f61472cee1439c0b966b47e3aca9ae07e45d070759512cd390ea2bebc6675" [[package]] name = "cmac" @@ -468,9 +474,9 @@ checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" [[package]] name = "errno" -version = "0.3.11" +version = "0.3.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "976dd42dc7e85965fe702eb8164f21f450704bdde31faefd6471dba214cb594e" +checksum = "cea14ef9355e3beab063703aa9dab15afd25f0667c341310c1e5274bb1d0da18" dependencies = [ "libc", "windows-sys 0.59.0", @@ -595,20 +601,20 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.2.15" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" +checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592" dependencies = [ "cfg-if", "libc", - "wasi 0.11.0+wasi-snapshot-preview1", + "wasi 0.11.1+wasi-snapshot-preview1", ] [[package]] name = "getrandom" -version = "0.3.2" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73fea8450eea4bac3940448fb7ae50d91f034f941199fcd9d909a5a07aa455f0" +checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4" dependencies = [ "cfg-if", "libc", @@ -646,9 +652,9 @@ checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" [[package]] name = "hashbrown" -version = "0.15.2" +version = "0.15.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289" +checksum = "5971ac85611da7067dbfcabef3c70ebb5606018acd9e2a3903a0da507521e0d5" [[package]] name = "heck" @@ -702,7 +708,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cea70ddb795996207ad57735b50c5982d8844f38ba9ee5f1aedcfb708a2aa11e" dependencies = [ "equivalent", - "hashbrown 0.15.2", + "hashbrown 0.15.4", ] [[package]] @@ -744,10 +750,20 @@ version = "0.1.33" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "38f262f097c174adebe41eb73d66ae9c06b2844fb0da69969647bbddd9b0538a" dependencies = [ - "getrandom 0.3.2", + "getrandom 0.3.3", "libc", ] +[[package]] +name = "js-sys" +version = "0.3.77" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f" +dependencies = [ + "once_cell", + "wasm-bindgen", +] + [[package]] name = "jwalk" version = "0.8.1" @@ -766,9 +782,9 @@ checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" [[package]] name = "libc" -version = "0.2.172" +version = "0.2.173" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d750af042f7ef4f724306de029d18836c26c1765a54a6a3f094cbd23a7267ffa" +checksum = "d8cfeafaffdbc32176b64fb251369d52ea9f0a8fbc6f8759edffef7b525d64bb" [[package]] name = "linux-raw-sys" @@ -778,9 +794,9 @@ checksum = "cd945864f07fe9f5371a27ad7b52a172b4b499999f1d97574c9fa68373937e12" [[package]] name = "lock_api" -version = "0.4.12" +version = "0.4.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" +checksum = "96936507f153605bddfcda068dd804796c84324ed2510809e5b2a624c81da765" dependencies = [ "autocfg", "scopeguard", @@ -805,35 +821,35 @@ dependencies = [ [[package]] name = "memchr" -version = "2.7.4" +version = "2.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" +checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0" [[package]] name = "miniz_oxide" -version = "0.8.8" +version = "0.8.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3be647b768db090acb35d5ec5db2b0e1f1de11133ca123b9eacf5137868f892a" +checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" dependencies = [ "adler2", ] [[package]] name = "mio" -version = "1.0.3" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2886843bf800fba2e3377cff24abf6379b4c4d5c6681eaf9ea5b0d15090450bd" +checksum = "78bed444cc8a2160f01cbcf811ef18cac863ad68ae8ca62092e8db51d51c761c" dependencies = [ "libc", - "wasi 0.11.0+wasi-snapshot-preview1", - "windows-sys 0.52.0", + "wasi 0.11.1+wasi-snapshot-preview1", + "windows-sys 0.59.0", ] [[package]] name = "multimap" -version = "0.10.0" +version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "defc4c55412d89136f966bbb339008b474350e5e6e78d2714439c386b3137a03" +checksum = "1d87ecb2933e8aeadb3e3a02b828fed80a7528047e68b4f424523a0981a3a084" [[package]] name = "num-conv" @@ -891,9 +907,9 @@ checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" [[package]] name = "parking_lot" -version = "0.12.3" +version = "0.12.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27" +checksum = "70d58bf43669b5795d1576d0641cfb6fbb2057bf629506267a92807158584a13" dependencies = [ "lock_api", "parking_lot_core", @@ -901,9 +917,9 @@ dependencies = [ [[package]] name = "parking_lot_core" -version = "0.9.10" +version = "0.9.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" +checksum = "bc838d2a56b5b1a6c25f55575dfc605fabb63bb2365f6c2353ef9159aa69e4a5" dependencies = [ "cfg-if", "libc", @@ -926,9 +942,9 @@ dependencies = [ [[package]] name = "pest" -version = "2.8.0" +version = "2.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "198db74531d58c70a361c42201efde7e2591e976d518caf7662a47dc5720e7b6" +checksum = "1db05f56d34358a8b1066f67cbb203ee3e7ed2ba674a6263a1d5ec6db2204323" dependencies = [ "memchr", "thiserror 2.0.12", @@ -937,9 +953,9 @@ dependencies = [ [[package]] name = "pest_derive" -version = "2.8.0" +version = "2.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d725d9cfd79e87dccc9341a2ef39d1b6f6353d68c4b33c177febbe1a402c97c5" +checksum = "bb056d9e8ea77922845ec74a1c4e8fb17e7c218cc4fc11a15c5d25e189aa40bc" dependencies = [ "pest", "pest_generator", @@ -947,9 +963,9 @@ dependencies = [ [[package]] name = "pest_generator" -version = "2.8.0" +version = "2.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db7d01726be8ab66ab32f9df467ae8b1148906685bbe75c82d1e65d7f5b3f841" +checksum = "87e404e638f781eb3202dc82db6760c8ae8a1eeef7fb3fa8264b2ef280504966" dependencies = [ "pest", "pest_meta", @@ -960,11 +976,10 @@ dependencies = [ [[package]] name = "pest_meta" -version = "2.8.0" +version = "2.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f9f832470494906d1fca5329f8ab5791cc60beb230c74815dff541cbd2b5ca0" +checksum = "edd1101f170f5903fde0914f899bb503d9ff5271d7ba76bbb70bea63690cc0d5" dependencies = [ - "once_cell", "pest", "sha2", ] @@ -1014,9 +1029,9 @@ dependencies = [ [[package]] name = "prettyplease" -version = "0.2.32" +version = "0.2.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "664ec5419c51e34154eec046ebcba56312d5a2fc3b09a06da188e1ad21afadf6" +checksum = "6837b9e10d61f45f987d50808f83d1ee3d206c66acf650c3e4ae2e1f6ddedf55" dependencies = [ "proc-macro2", "syn", @@ -1145,7 +1160,7 @@ version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" dependencies = [ - "getrandom 0.2.15", + "getrandom 0.2.16", ] [[package]] @@ -1154,7 +1169,7 @@ version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38" dependencies = [ - "getrandom 0.3.2", + "getrandom 0.3.3", ] [[package]] @@ -1179,11 +1194,11 @@ dependencies = [ [[package]] name = "redox_syscall" -version = "0.5.11" +version = "0.5.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2f103c6d277498fbceb16e84d317e2a400f160f46904d5f5410848c829511a3" +checksum = "0d04b7d0ee6b4a0207a0a7adb104d23ecb0b47d6beae7152d0fa34b692b29fd6" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.9.1", ] [[package]] @@ -1217,23 +1232,29 @@ checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" [[package]] name = "rustc-demangle" -version = "0.1.24" +version = "0.1.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" +checksum = "989e6739f80c4ad5b13e0fd7fe89531180375b18520cc8c82080e4dc4035b84f" [[package]] name = "rustix" -version = "1.0.5" +version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d97817398dd4bb2e6da002002db259209759911da105da92bec29ccb12cf58bf" +checksum = "c71e83d6afe7ff64890ec6b71d6a69bb8a610ab78ce364b3352876bb4c801266" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.9.1", "errno", "libc", "linux-raw-sys", "windows-sys 0.59.0", ] +[[package]] +name = "rustversion" +version = "1.0.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a0d197bd2c9dc6e53b84da9556a69ba4cdfab8619eb41a8bd1cc2027a0f6b1d" + [[package]] name = "ryu" version = "1.0.20" @@ -1289,18 +1310,18 @@ dependencies = [ [[package]] name = "serde_spanned" -version = "0.6.8" +version = "0.6.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87607cb1398ed59d48732e575a4c28a7a8ebf2454b964fe3f224f2afc07909e1" +checksum = "bf41e0cfaf7226dca15e8197172c295a782857fcb97fad1808a166870dee75a3" dependencies = [ "serde", ] [[package]] name = "sha2" -version = "0.10.8" +version = "0.10.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" +checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" dependencies = [ "cfg-if", "cpufeatures", @@ -1315,9 +1336,9 @@ checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" [[package]] name = "signal-hook" -version = "0.3.17" +version = "0.3.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8621587d4798caf8eb44879d42e56b9a93ea5dcd315a6487c357130095b62801" +checksum = "d881a16cf4426aa584979d30bd82cb33429027e42122b169753d6ef1085ed6e2" dependencies = [ "libc", "signal-hook-registry", @@ -1358,24 +1379,21 @@ dependencies = [ [[package]] name = "slab" -version = "0.4.9" +version = "0.4.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" -dependencies = [ - "autocfg", -] +checksum = "04dc19736151f35336d325007ac991178d504a119863a2fcb3758cdb5e52c50d" [[package]] name = "smallvec" -version = "1.15.0" +version = "1.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8917285742e9f3e1683f0a9c4e6b57960b7314d0b08d30d1ecd426713ee2eee9" +checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" [[package]] name = "socket2" -version = "0.5.9" +version = "0.5.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f5fd57c80058a56cf5c777ab8a126398ece8e442983605d280a44ce79d0edef" +checksum = "e22376abed350d73dd1cd119b57ffccad95b4e585a7cda43e286245ce23c0678" dependencies = [ "libc", "windows-sys 0.52.0", @@ -1395,9 +1413,9 @@ checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" [[package]] name = "syn" -version = "2.0.100" +version = "2.0.103" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b09a44accad81e1ba1cd74a32461ba89dee89095ba17b32f5d03683b1b1fc2a0" +checksum = "e4307e30089d6fd6aff212f2da3a1f9e32f3223b1f010fb09b7c95f90f3ca1e8" dependencies = [ "proc-macro2", "quote", @@ -1437,12 +1455,12 @@ checksum = "61c41af27dd6d1e27b1b16b489db798443478cef1f06a660c96db617ba5de3b1" [[package]] name = "tempfile" -version = "3.19.1" +version = "3.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7437ac7763b9b123ccf33c338a5cc1bac6f69b45a136c19bdd8a65e3916435bf" +checksum = "e8a64e3985349f2441a1a9ef0b853f869006c3855f2cda6862a94d26ebb9d6a1" dependencies = [ "fastrand", - "getrandom 0.3.2", + "getrandom 0.3.3", "once_cell", "rustix", "windows-sys 0.59.0", @@ -1523,9 +1541,9 @@ dependencies = [ [[package]] name = "tokio" -version = "1.44.2" +version = "1.45.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6b88822cbe49de4185e3a4cbf8321dd487cf5fe0c5c65695fef6346371e9c48" +checksum = "75ef51a33ef1da925cea3e4eb122833cb377c61439ca401b770f54902b806779" dependencies = [ "backtrace", "bytes", @@ -1552,9 +1570,9 @@ dependencies = [ [[package]] name = "tokio-util" -version = "0.7.14" +version = "0.7.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b9590b93e6fcc1739458317cccd391ad3955e2bde8913edf6f95f9e65a8f034" +checksum = "66a539a9ad6d5d281510d5bd368c973d636c02dbf8a67300bfb6b950696ad7df" dependencies = [ "bytes", "futures-core", @@ -1566,9 +1584,9 @@ dependencies = [ [[package]] name = "toml" -version = "0.8.20" +version = "0.8.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd87a5cdd6ffab733b2f74bc4fd7ee5fff6634124999ac278c35fc78c6120148" +checksum = "dc1beb996b9d83529a9e75c17a1686767d148d70663143c7854d8b4a09ced362" dependencies = [ "serde", "serde_spanned", @@ -1578,31 +1596,38 @@ dependencies = [ [[package]] name = "toml_datetime" -version = "0.6.8" +version = "0.6.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41" +checksum = "22cddaf88f4fbc13c51aebbf5f8eceb5c7c5a9da2ac40a13519eb5b0a0e8f11c" dependencies = [ "serde", ] [[package]] name = "toml_edit" -version = "0.22.24" +version = "0.22.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17b4795ff5edd201c7cd6dca065ae59972ce77d1b80fa0a84d94950ece7d1474" +checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a" dependencies = [ "indexmap", "serde", "serde_spanned", "toml_datetime", + "toml_write", "winnow", ] +[[package]] +name = "toml_write" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d99f8c9a7727884afe522e9bd5edbfc91a3312b36a77b5fb8926e4c31a41801" + [[package]] name = "tonic-build" -version = "0.13.0" +version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d85f0383fadd15609306383a90e85eaed44169f931a5d2be1b42c76ceff1825e" +checksum = "eac6f67be712d12f0b41328db3137e0d0757645d8904b4cb7d51cd9c2279e847" dependencies = [ "prettyplease", "proc-macro2", @@ -1632,11 +1657,13 @@ checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" [[package]] name = "uuid" -version = "1.16.0" +version = "1.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "458f7a779bf54acc9f347480ac654f68407d3aab21269a6e3c9f922acd9e2da9" +checksum = "3cf4199d1e5d15ddd86a694e4d0dffa9c323ce759fea589f00fef9d81cc1931d" dependencies = [ - "getrandom 0.3.2", + "getrandom 0.3.3", + "js-sys", + "wasm-bindgen", ] [[package]] @@ -1663,9 +1690,9 @@ dependencies = [ [[package]] name = "wasi" -version = "0.11.0+wasi-snapshot-preview1" +version = "0.11.1+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" +checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" [[package]] name = "wasi" @@ -1676,6 +1703,64 @@ dependencies = [ "wit-bindgen-rt", ] +[[package]] +name = "wasm-bindgen" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5" +dependencies = [ + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6" +dependencies = [ + "bumpalo", + "log", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d" +dependencies = [ + "unicode-ident", +] + [[package]] name = "winapi-util" version = "0.1.9" @@ -1687,9 +1772,9 @@ dependencies = [ [[package]] name = "windows-link" -version = "0.1.1" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76840935b766e1b0a05c0066835fb9ec80071d4c09a16f6bd5f7e655e3c14c38" +checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a" [[package]] name = "windows-sys" @@ -1841,9 +1926,9 @@ checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" [[package]] name = "winnow" -version = "0.7.6" +version = "0.7.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "63d3fcd9bba44b03821e7d699eeee959f3126dcc4aa8e4ae18ec617c2a5cea10" +checksum = "74c7b26e3480b707944fc872477815d29a8e429d2f93a1ce000f5fa84a15cbcd" dependencies = [ "memchr", ] @@ -1854,23 +1939,23 @@ version = "0.39.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.9.1", ] [[package]] name = "zerocopy" -version = "0.8.24" +version = "0.8.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2586fea28e186957ef732a5f8b3be2da217d65c5969d4b1e17f973ebbe876879" +checksum = "a1702d9583232ddb9174e01bb7c15a2ab8fb1bc6f227aa1233858c351a3ba0cb" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.24" +version = "0.8.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a996a8f63c5c4448cd959ac1bab0aaa3306ccfd060472f85943ee0750f0169be" +checksum = "28a6e20d751156648aa063f3800b706ee209a32c0b4d9f24be3d980b01be55ef" dependencies = [ "proc-macro2", "quote", diff --git a/Cargo.toml b/Cargo.toml index e8a3713..aa4d7d8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,7 +16,7 @@ "usage", "derive", ] } - chirpstack_api = { version = "4.13.0-test.1", default-features = false } + chirpstack_api = { version = "4.13", default-features = false } lrwn_filters = { version = "4.12", features = ["serde"] } log = "0.4" simple_logger = "5.0" @@ -26,7 +26,7 @@ anyhow = "1.0" humantime-serde = "1.1" serde = { version = "1.0", features = ["derive"] } - tokio = { version = "1.44", features = [ + tokio = { version = "1.45", features = [ "macros", "rt-multi-thread", "time", diff --git a/shell.nix b/shell.nix index 87c3cbd..5ea59e2 100644 --- a/shell.nix +++ b/shell.nix @@ -1,4 +1,4 @@ -{ pkgs ? import (fetchTarball "https://github.com/NixOS/nixpkgs/archive/nixos-24.11.tar.gz") {} }: +{ pkgs ? import (fetchTarball "https://github.com/NixOS/nixpkgs/archive/nixos-25.05.tar.gz") {} }: pkgs.mkShell { buildInputs = [ From 1123e6ac0d9cbe9f9e531e51b69f2469a9bb7ff2 Mon Sep 17 00:00:00 2001 From: Lim Jing Heng Date: Mon, 16 Jun 2025 08:54:58 -0500 Subject: [PATCH 13/17] Add `tx_power` to region_au915.toml (#67) --- configuration/region_au915.toml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/configuration/region_au915.toml b/configuration/region_au915.toml index ddad1da..c4ace04 100644 --- a/configuration/region_au915.toml +++ b/configuration/region_au915.toml @@ -75,6 +75,8 @@ 927100000, ] + tx_power = [12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27] + [[mappings.data_rates]] modulation = "LORA" spreading_factor = 12 From 63658f3624f10bf5b56e64a78db22ebcd585816b Mon Sep 17 00:00:00 2001 From: Orne Brocaar Date: Mon, 16 Jun 2025 14:55:57 +0100 Subject: [PATCH 14/17] Bump version to 4.1.0-test.1 --- Cargo.lock | 2 +- Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 70843e2..c6da617 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -156,7 +156,7 @@ checksum = "9555578bc9e57714c812a1f84e4fc5b4d21fcb063490c624de019f7464c91268" [[package]] name = "chirpstack-gateway-mesh" -version = "4.0.1" +version = "4.1.0-test.1" dependencies = [ "aes", "anyhow", diff --git a/Cargo.toml b/Cargo.toml index aa4d7d8..55679ae 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,7 +4,7 @@ repository = "https://github.com/chirpstack/chirpstack-gateway-mesh" homepage = "https://www.chirpstack.io/" license = "MIT" - version = "4.0.1" + version = "4.1.0-test.1" authors = ["Orne Brocaar "] edition = "2021" publish = false From 673a047dc5fbdf04b5eb4bfb0eddfa23981dd4bb Mon Sep 17 00:00:00 2001 From: Orne Brocaar Date: Mon, 4 Aug 2025 10:55:08 +0100 Subject: [PATCH 15/17] Add relay_id configuration option. This makes it possible to override the relay_id instead of (automatically) using the 4 least significant bytes of the Gateway ID. --- src/backend.rs | 11 ++++++++++- src/cmd/configfile.rs | 7 +++++++ src/config.rs | 2 ++ 3 files changed, 19 insertions(+), 1 deletion(-) diff --git a/src/backend.rs b/src/backend.rs index a4dbffa..34eeef1 100644 --- a/src/backend.rs +++ b/src/backend.rs @@ -174,7 +174,16 @@ async fn setup_mesh_conncentratord(conf: &Configuration) -> Result<()> { info!("Retrieved Gateway ID: {}", resp.gateway_id); let mut relay_id: [u8; 4] = [0; 4]; - relay_id.copy_from_slice(&hex::decode(&resp.gateway_id)?[4..]); + if conf.mesh.relay_id.is_empty() { + relay_id.copy_from_slice(&hex::decode(&resp.gateway_id)?[4..]); + } else { + info!("Using relay_id from configuration file"); + let b = hex::decode(&conf.mesh.relay_id)?; + if b.len() != 4 { + return Err(anyhow!("relay_id must be exactly 4 bytes!")); + } + relay_id.copy_from_slice(&b); + } RELAY_ID .set(Mutex::new(relay_id)) .map_err(|e| anyhow!("OnceLock error: {:?}", e))?; diff --git a/src/cmd/configfile.rs b/src/cmd/configfile.rs index 7a23477..0647a1b 100644 --- a/src/cmd/configfile.rs +++ b/src/cmd/configfile.rs @@ -39,6 +39,13 @@ pub fn run() { # above root_key, but this key will be used. signing_key="{{ mesh.signing_key }}" + # Relay ID. + # + # If set, this will override the Relay ID that is derived from the + # Gateway ID provided by the Concentratord backend (using the 4 least + # significant bytes). Example: "01020304". + relay_id="{{ mesh.relay_id }}" + # Border Gateway. # # If this is set to true, then the ChirpStack Gateway Mesh will consider diff --git a/src/config.rs b/src/config.rs index 0bae5c9..06ff20d 100644 --- a/src/config.rs +++ b/src/config.rs @@ -58,6 +58,7 @@ impl Default for Logging { #[derive(Serialize, Deserialize)] #[serde(default)] pub struct Mesh { + pub relay_id: String, pub root_key: Aes128Key, pub signing_key: Aes128Key, pub frequencies: Vec, @@ -73,6 +74,7 @@ pub struct Mesh { impl Default for Mesh { fn default() -> Self { Mesh { + relay_id: "".into(), root_key: Aes128Key::null(), signing_key: Aes128Key::null(), frequencies: vec![868100000, 868300000, 868500000], From 666faf3a1e48ea84fa5f1c3994d4324f21aaf763 Mon Sep 17 00:00:00 2001 From: Orne Brocaar Date: Mon, 4 Aug 2025 11:09:20 +0100 Subject: [PATCH 16/17] Update dependencies. --- Cargo.lock | 260 +++++++++++++++++++------- Cargo.toml | 5 +- src/mesh.rs | 10 +- tests/border_gateway_downlink_lora.rs | 2 +- tests/border_gateway_downlink_mesh.rs | 2 +- tests/relay_gateway_downlink_lora.rs | 2 +- 6 files changed, 202 insertions(+), 79 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index c6da617..c54c3dc 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -75,9 +75,9 @@ dependencies = [ [[package]] name = "autocfg" -version = "1.4.0" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" +checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" [[package]] name = "backtrace" @@ -117,9 +117,9 @@ dependencies = [ [[package]] name = "bumpalo" -version = "3.18.1" +version = "3.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "793db76d6187cd04dff33004d8e6c9cc4e05cd330500379d2394209271b4aeee" +checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43" [[package]] name = "bytes" @@ -129,9 +129,9 @@ checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" [[package]] name = "cc" -version = "1.2.27" +version = "1.2.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d487aa071b5f64da6f19a3e848e3578944b726ee5a4854b82172f02aa876bfdc" +checksum = "c3a42d84bb6b69d3a8b3eaacf0d88f179e1929695e1ad012b6cf64d9caaa5fd2" dependencies = [ "jobserver", "libc", @@ -170,15 +170,14 @@ dependencies = [ "humantime-serde", "log", "lrwn_filters", - "prost-types", - "rand 0.9.1", + "rand 0.9.2", "serde", "signal-hook", "signal-hook-tokio", "simple_logger", "syslog", "tokio", - "toml", + "toml 0.9.4", "zeromq", "zmq", ] @@ -193,7 +192,7 @@ dependencies = [ "pbjson-build", "prost", "prost-types", - "rand 0.9.1", + "rand 0.9.2", "tonic-build", ] @@ -209,9 +208,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.40" +version = "4.5.42" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40b6887a1d8685cebccf115538db5c0efe625ccac9696ad45c409d96566e910f" +checksum = "ed87a9d530bb41a67537289bafcac159cb3ee28460e0a4571123d2a778a6a882" dependencies = [ "clap_builder", "clap_derive", @@ -219,9 +218,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.40" +version = "4.5.42" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e0c66c08ce9f0c698cbce5c0279d0bb6ac936d8674174fe48f736533b964f59e" +checksum = "64f4f3f3c77c94aff3c7e9aac9a2ca1974a5adf392a8bb751e827d6d127ab966" dependencies = [ "anstyle", "clap_lex", @@ -229,9 +228,9 @@ dependencies = [ [[package]] name = "clap_derive" -version = "4.5.40" +version = "4.5.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2c7947ae4cc3d851207c1adb5b5e260ff0cca11446b1d6d1423788e442257ce" +checksum = "ef4f52386a59ca4c860f7393bcf8abd8dfd91ecccc0f774635ff68e92eeef491" dependencies = [ "heck", "proc-macro2", @@ -474,12 +473,12 @@ checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" [[package]] name = "errno" -version = "0.3.12" +version = "0.3.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cea14ef9355e3beab063703aa9dab15afd25f0667c341310c1e5274bb1d0da18" +checksum = "778e2ac28f6c47af28e4907f13ffd1e1ddbd400980a9abd7c8df189bf578a5ad" dependencies = [ "libc", - "windows-sys 0.59.0", + "windows-sys 0.60.2", ] [[package]] @@ -703,9 +702,9 @@ checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" [[package]] name = "indexmap" -version = "2.9.0" +version = "2.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cea70ddb795996207ad57735b50c5982d8844f38ba9ee5f1aedcfb708a2aa11e" +checksum = "fe4cd85333e22411419a0bcae1297d25e58c9443848b11dc6a86fefe8c78a661" dependencies = [ "equivalent", "hashbrown 0.15.4", @@ -720,6 +719,17 @@ dependencies = [ "generic-array", ] +[[package]] +name = "io-uring" +version = "0.7.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d93587f37623a1a17d94ef2bc9ada592f5465fe7732084ab7beefabe5c77c0c4" +dependencies = [ + "bitflags 2.9.1", + "cfg-if", + "libc", +] + [[package]] name = "itertools" version = "0.13.0" @@ -782,9 +792,9 @@ checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" [[package]] name = "libc" -version = "0.2.173" +version = "0.2.174" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8cfeafaffdbc32176b64fb251369d52ea9f0a8fbc6f8759edffef7b525d64bb" +checksum = "1171693293099992e19cddea4e8b849964e9846f4acee11b3948bcc337be8776" [[package]] name = "linux-raw-sys" @@ -1029,9 +1039,9 @@ dependencies = [ [[package]] name = "prettyplease" -version = "0.2.34" +version = "0.2.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6837b9e10d61f45f987d50808f83d1ee3d206c66acf650c3e4ae2e1f6ddedf55" +checksum = "ff24dfcda44452b9816fff4cd4227e1bb73ff5a2f1bc1105aa92fb8565ce44d2" dependencies = [ "proc-macro2", "syn", @@ -1109,9 +1119,9 @@ dependencies = [ [[package]] name = "r-efi" -version = "5.2.0" +version = "5.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74765f6d916ee2faa39bc8e68e4f3ed8949b48cccdac59983d287a7cb71ce9c5" +checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" [[package]] name = "rand" @@ -1126,9 +1136,9 @@ dependencies = [ [[package]] name = "rand" -version = "0.9.1" +version = "0.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9fbfd9d094a40bf3ae768db9361049ace4c0e04a4fd6b359518bd7b73a73dd97" +checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1" dependencies = [ "rand_chacha 0.9.0", "rand_core 0.9.3", @@ -1194,9 +1204,9 @@ dependencies = [ [[package]] name = "redox_syscall" -version = "0.5.13" +version = "0.5.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d04b7d0ee6b4a0207a0a7adb104d23ecb0b47d6beae7152d0fa34b692b29fd6" +checksum = "5407465600fb0548f1442edf71dd20683c6ed326200ace4b1ef0763521bb3b77" dependencies = [ "bitflags 2.9.1", ] @@ -1232,21 +1242,21 @@ checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" [[package]] name = "rustc-demangle" -version = "0.1.25" +version = "0.1.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "989e6739f80c4ad5b13e0fd7fe89531180375b18520cc8c82080e4dc4035b84f" +checksum = "56f7d92ca342cea22a06f2121d944b4fd82af56988c270852495420f961d4ace" [[package]] name = "rustix" -version = "1.0.7" +version = "1.0.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c71e83d6afe7ff64890ec6b71d6a69bb8a610ab78ce364b3352876bb4c801266" +checksum = "11181fbabf243db407ef8df94a6ce0b2f9a733bd8be4ad02b4eda9602296cac8" dependencies = [ "bitflags 2.9.1", "errno", "libc", "linux-raw-sys", - "windows-sys 0.59.0", + "windows-sys 0.60.2", ] [[package]] @@ -1298,9 +1308,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.140" +version = "1.0.142" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "20068b6e96dc6c9bd23e01df8827e6c7e1f2fddd43c21810382803c136b99373" +checksum = "030fedb782600dcbd6f02d479bf0d817ac3bb40d644745b769d6a96bc3afc5a7" dependencies = [ "itoa", "memchr", @@ -1317,6 +1327,15 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_spanned" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40734c41988f7306bb04f0ecf60ec0f3f1caa34290e4e8ea471dcd3346483b83" +dependencies = [ + "serde", +] + [[package]] name = "sha2" version = "0.10.9" @@ -1346,9 +1365,9 @@ dependencies = [ [[package]] name = "signal-hook-registry" -version = "1.4.5" +version = "1.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9203b8055f63a2a00e2f593bb0510367fe707d7ff1e5c872de2f537b339e5410" +checksum = "b2a4719bff48cee6b39d12c020eeb490953ad2443b7055bd0b21fca26bd8c28b" dependencies = [ "libc", ] @@ -1391,12 +1410,12 @@ checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" [[package]] name = "socket2" -version = "0.5.10" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e22376abed350d73dd1cd119b57ffccad95b4e585a7cda43e286245ce23c0678" +checksum = "233504af464074f9d066d7b5416c5f9b894a5862a6506e306f7b816cdd6f1807" dependencies = [ "libc", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -1413,9 +1432,9 @@ checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" [[package]] name = "syn" -version = "2.0.103" +version = "2.0.104" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e4307e30089d6fd6aff212f2da3a1f9e32f3223b1f010fb09b7c95f90f3ca1e8" +checksum = "17b6f705963418cdb9927482fa304bc562ece2fdd4f616084c50b7023b435a40" dependencies = [ "proc-macro2", "quote", @@ -1443,7 +1462,7 @@ dependencies = [ "cfg-expr", "heck", "pkg-config", - "toml", + "toml 0.8.23", "version-compare", ] @@ -1541,20 +1560,22 @@ dependencies = [ [[package]] name = "tokio" -version = "1.45.1" +version = "1.47.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75ef51a33ef1da925cea3e4eb122833cb377c61439ca401b770f54902b806779" +checksum = "89e49afdadebb872d3145a5638b59eb0691ea23e46ca484037cfab3b76b95038" dependencies = [ "backtrace", "bytes", + "io-uring", "libc", "mio", "parking_lot", "pin-project-lite", "signal-hook-registry", + "slab", "socket2", "tokio-macros", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -1570,9 +1591,9 @@ dependencies = [ [[package]] name = "tokio-util" -version = "0.7.15" +version = "0.7.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "66a539a9ad6d5d281510d5bd368c973d636c02dbf8a67300bfb6b950696ad7df" +checksum = "14307c986784f72ef81c89db7d9e28d6ac26d16213b109ea501696195e6e3ce5" dependencies = [ "bytes", "futures-core", @@ -1589,11 +1610,26 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dc1beb996b9d83529a9e75c17a1686767d148d70663143c7854d8b4a09ced362" dependencies = [ "serde", - "serde_spanned", - "toml_datetime", + "serde_spanned 0.6.9", + "toml_datetime 0.6.11", "toml_edit", ] +[[package]] +name = "toml" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41ae868b5a0f67631c14589f7e250c1ea2c574ee5ba21c6c8dd4b1485705a5a1" +dependencies = [ + "indexmap", + "serde", + "serde_spanned 1.0.0", + "toml_datetime 0.7.0", + "toml_parser", + "toml_writer", + "winnow", +] + [[package]] name = "toml_datetime" version = "0.6.11" @@ -1603,6 +1639,15 @@ dependencies = [ "serde", ] +[[package]] +name = "toml_datetime" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bade1c3e902f58d73d3f294cd7f20391c1cb2fbcb643b73566bc773971df91e3" +dependencies = [ + "serde", +] + [[package]] name = "toml_edit" version = "0.22.27" @@ -1611,17 +1656,25 @@ checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a" dependencies = [ "indexmap", "serde", - "serde_spanned", - "toml_datetime", - "toml_write", + "serde_spanned 0.6.9", + "toml_datetime 0.6.11", + "winnow", +] + +[[package]] +name = "toml_parser" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97200572db069e74c512a14117b296ba0a80a30123fbbb5aa1f4a348f639ca30" +dependencies = [ "winnow", ] [[package]] -name = "toml_write" -version = "0.1.2" +name = "toml_writer" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d99f8c9a7727884afe522e9bd5edbfc91a3312b36a77b5fb8926e4c31a41801" +checksum = "fcc842091f2def52017664b53082ecbbeb5c7731092bad69d2c63050401dfd64" [[package]] name = "tonic-build" @@ -1787,20 +1840,20 @@ dependencies = [ [[package]] name = "windows-sys" -version = "0.52.0" +version = "0.59.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" dependencies = [ "windows-targets 0.52.6", ] [[package]] name = "windows-sys" -version = "0.59.0" +version = "0.60.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" dependencies = [ - "windows-targets 0.52.6", + "windows-targets 0.53.3", ] [[package]] @@ -1827,13 +1880,30 @@ dependencies = [ "windows_aarch64_gnullvm 0.52.6", "windows_aarch64_msvc 0.52.6", "windows_i686_gnu 0.52.6", - "windows_i686_gnullvm", + "windows_i686_gnullvm 0.52.6", "windows_i686_msvc 0.52.6", "windows_x86_64_gnu 0.52.6", "windows_x86_64_gnullvm 0.52.6", "windows_x86_64_msvc 0.52.6", ] +[[package]] +name = "windows-targets" +version = "0.53.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d5fe6031c4041849d7c496a8ded650796e7b6ecc19df1a431c1a363342e5dc91" +dependencies = [ + "windows-link", + "windows_aarch64_gnullvm 0.53.0", + "windows_aarch64_msvc 0.53.0", + "windows_i686_gnu 0.53.0", + "windows_i686_gnullvm 0.53.0", + "windows_i686_msvc 0.53.0", + "windows_x86_64_gnu 0.53.0", + "windows_x86_64_gnullvm 0.53.0", + "windows_x86_64_msvc 0.53.0", +] + [[package]] name = "windows_aarch64_gnullvm" version = "0.48.5" @@ -1846,6 +1916,12 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86b8d5f90ddd19cb4a147a5fa63ca848db3df085e25fee3cc10b39b6eebae764" + [[package]] name = "windows_aarch64_msvc" version = "0.48.5" @@ -1858,6 +1934,12 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" +[[package]] +name = "windows_aarch64_msvc" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7651a1f62a11b8cbd5e0d42526e55f2c99886c77e007179efff86c2b137e66c" + [[package]] name = "windows_i686_gnu" version = "0.48.5" @@ -1870,12 +1952,24 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" +[[package]] +name = "windows_i686_gnu" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1dc67659d35f387f5f6c479dc4e28f1d4bb90ddd1a5d3da2e5d97b42d6272c3" + [[package]] name = "windows_i686_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" +[[package]] +name = "windows_i686_gnullvm" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ce6ccbdedbf6d6354471319e781c0dfef054c81fbc7cf83f338a4296c0cae11" + [[package]] name = "windows_i686_msvc" version = "0.48.5" @@ -1888,6 +1982,12 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" +[[package]] +name = "windows_i686_msvc" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "581fee95406bb13382d2f65cd4a908ca7b1e4c2f1917f143ba16efe98a589b5d" + [[package]] name = "windows_x86_64_gnu" version = "0.48.5" @@ -1900,6 +2000,12 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" +[[package]] +name = "windows_x86_64_gnu" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e55b5ac9ea33f2fc1716d1742db15574fd6fc8dadc51caab1c16a3d3b4190ba" + [[package]] name = "windows_x86_64_gnullvm" version = "0.48.5" @@ -1912,6 +2018,12 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a6e035dd0599267ce1ee132e51c27dd29437f63325753051e71dd9e42406c57" + [[package]] name = "windows_x86_64_msvc" version = "0.48.5" @@ -1924,11 +2036,17 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" +[[package]] +name = "windows_x86_64_msvc" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486" + [[package]] name = "winnow" -version = "0.7.11" +version = "0.7.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74c7b26e3480b707944fc872477815d29a8e429d2f93a1ce000f5fa84a15cbcd" +checksum = "f3edebf492c8125044983378ecb5766203ad3b4c2f7a922bd7dd207f6d443e95" dependencies = [ "memchr", ] @@ -1944,18 +2062,18 @@ dependencies = [ [[package]] name = "zerocopy" -version = "0.8.25" +version = "0.8.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1702d9583232ddb9174e01bb7c15a2ab8fb1bc6f227aa1233858c351a3ba0cb" +checksum = "1039dd0d3c310cf05de012d8a39ff557cb0d23087fd44cad61df08fc31907a2f" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.25" +version = "0.8.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28a6e20d751156648aa063f3800b706ee209a32c0b4d9f24be3d980b01be55ef" +checksum = "9ecf5b4cc5364572d7f4c329661bcc82724222973f2cab6f050a4e5c22f75181" dependencies = [ "proc-macro2", "quote", diff --git a/Cargo.toml b/Cargo.toml index 55679ae..86879d1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -21,12 +21,12 @@ log = "0.4" simple_logger = "5.0" syslog = "7.0" - toml = "0.8" + toml = "0.9" handlebars = "6.3" anyhow = "1.0" humantime-serde = "1.1" serde = { version = "1.0", features = ["derive"] } - tokio = { version = "1.45", features = [ + tokio = { version = "1.47", features = [ "macros", "rt-multi-thread", "time", @@ -40,7 +40,6 @@ signal-hook = "0.3" signal-hook-tokio = { version = "0.3", features = ["futures-v0_3"] } futures = "0.3" - prost-types = "0.13" zmq = "0.10" cmac = "0.7" aes = "0.8" diff --git a/src/mesh.rs b/src/mesh.rs index 94d2f9f..5df99c8 100644 --- a/src/mesh.rs +++ b/src/mesh.rs @@ -3,7 +3,7 @@ use std::sync::{LazyLock, Mutex}; use std::time::SystemTime; use anyhow::Result; -use chirpstack_api::gw; +use chirpstack_api::{gw, prost_types}; use log::{info, trace, warn}; use rand::random; @@ -110,7 +110,13 @@ pub async fn send_mesh_command(pl: gw::MeshCommand) -> Result<()> { commands: pl .commands .iter() - .filter_map(|v| v.command.as_ref().map(|gw::mesh_command_item::Command::Proprietary(v)| packets::Command::Proprietary((v.command_type as u8, v.payload.clone())))) + .filter_map(|v| { + v.command + .as_ref() + .map(|gw::mesh_command_item::Command::Proprietary(v)| { + packets::Command::Proprietary((v.command_type as u8, v.payload.clone())) + }) + }) .collect(), }), mic: None, diff --git a/tests/border_gateway_downlink_lora.rs b/tests/border_gateway_downlink_lora.rs index 808262e..6a55342 100644 --- a/tests/border_gateway_downlink_lora.rs +++ b/tests/border_gateway_downlink_lora.rs @@ -2,7 +2,7 @@ extern crate anyhow; use chirpstack_api::gw; -use chirpstack_api::prost::Message; +use chirpstack_api::{prost::Message, prost_types}; use zeromq::{SocketRecv, SocketSend}; mod common; diff --git a/tests/border_gateway_downlink_mesh.rs b/tests/border_gateway_downlink_mesh.rs index cab0aa0..2e1308e 100644 --- a/tests/border_gateway_downlink_mesh.rs +++ b/tests/border_gateway_downlink_mesh.rs @@ -2,7 +2,7 @@ extern crate anyhow; use chirpstack_api::gw; -use chirpstack_api::prost::Message; +use chirpstack_api::{prost::Message, prost_types}; use zeromq::{SocketRecv, SocketSend}; use chirpstack_gateway_mesh::aes128::{get_signing_key, Aes128Key}; diff --git a/tests/relay_gateway_downlink_lora.rs b/tests/relay_gateway_downlink_lora.rs index f33a7f2..87d8e59 100644 --- a/tests/relay_gateway_downlink_lora.rs +++ b/tests/relay_gateway_downlink_lora.rs @@ -2,7 +2,7 @@ extern crate anyhow; use chirpstack_api::gw; -use chirpstack_api::prost::Message; +use chirpstack_api::{prost::Message, prost_types}; use zeromq::{SocketRecv, SocketSend}; use chirpstack_gateway_mesh::aes128::{get_signing_key, Aes128Key}; From 288801662b1b8253e249c58d6f3a2a1712e40c44 Mon Sep 17 00:00:00 2001 From: Orne Brocaar Date: Mon, 4 Aug 2025 11:10:16 +0100 Subject: [PATCH 17/17] Bump version to 4.1.0-test.2 --- Cargo.lock | 2 +- Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index c54c3dc..b184787 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -156,7 +156,7 @@ checksum = "9555578bc9e57714c812a1f84e4fc5b4d21fcb063490c624de019f7464c91268" [[package]] name = "chirpstack-gateway-mesh" -version = "4.1.0-test.1" +version = "4.1.0-test.2" dependencies = [ "aes", "anyhow", diff --git a/Cargo.toml b/Cargo.toml index 86879d1..f1f0f4c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,7 +4,7 @@ repository = "https://github.com/chirpstack/chirpstack-gateway-mesh" homepage = "https://www.chirpstack.io/" license = "MIT" - version = "4.1.0-test.1" + version = "4.1.0-test.2" authors = ["Orne Brocaar "] edition = "2021" publish = false