Compare commits

..

68 commits
main ... no_std

Author SHA1 Message Date
Kedar Sovani
7504f6f5b6
Merge pull request #65 from tomoyuki-nakabayashi/tlv_tool/add-hexstring-option
tlv_tool: Add hexstring option.
2023-07-12 15:21:11 +05:30
Restyled.io
a453610557 Restyled by prettier-markdown 2023-07-12 15:50:56 +09:00
tomoyuki nakabayashi
995bb95ac1 tlv_tool: Add hexstring option. 2023-07-10 06:23:46 +09:00
Kedar Sovani
0cc5fe97f5
Merge pull request #63 from thekuwayama/add__tools/tlv_tool_workspace.exclude
fix: add tools/tlv_tool to workspace.exclude
2023-07-03 09:29:14 +05:30
thekuwayama
f15e541e41 fix: add tools/tlv_tool workspace.exclude 2023-07-02 05:31:25 +09:00
Kedar Sovani
aa6aa476a9
Merge pull request #61 from ivmarkov/no_std
Fix #60
2023-07-01 10:35:10 +05:30
ivmarkov
24cc92aa11 Fix #60 2023-06-30 13:04:53 +00:00
ivmarkov
b40a0afbd0 Configurable parts_list in descriptor 2023-06-17 14:00:44 +00:00
ivmarkov
8e2340363f Add from/to TLV for i16, i32 and i64 2023-06-16 18:42:11 +00:00
ivmarkov
28ec278756 More comments for tailoring the example for no_std 2023-06-13 10:38:02 +00:00
ivmarkov
42470e1a34 Fix the no_std build 2023-06-13 10:12:42 +00:00
ivmarkov
864692845b Workaround broken join_multicast_v4 on ESP-IDF 2023-06-13 07:02:37 +00:00
ivmarkov
8d9bd1332c Support for ESP-IDF build 2023-06-12 11:41:33 +00:00
ivmarkov
9070e87944 Proper mDNS responder 2023-06-12 09:47:20 +00:00
ivmarkov
aa159d1772 Clippy 2023-06-10 18:51:34 +00:00
ivmarkov
df311ac6e0 Default mDns impl 2023-06-10 18:47:21 +00:00
ivmarkov
e2277a17a4 Make Matter covariant over its lifetime 2023-06-10 16:41:33 +00:00
ivmarkov
eb21772a09 Simplify main user-facing API 2023-06-09 10:16:07 +00:00
ivmarkov
6c6f74e2e0 Fix a bug in mDNS 2023-06-01 04:59:01 +00:00
ivmarkov
78f2282cd4 Make sure nix is not brought in no-std compiles 2023-05-31 12:51:37 +00:00
ivmarkov
526e592a5c Make the example working again 2023-05-28 14:05:43 +00:00
ivmarkov
c2e72e5f0a More inlines 2023-05-28 11:45:27 +00:00
ivmarkov
dcbfa1f0e3 Clippy 2023-05-28 11:13:02 +00:00
ivmarkov
1c26df0712 Control memory by removing implicit copy 2023-05-28 11:04:46 +00:00
ivmarkov
2e0a09b532 built-in mDNS; memory optimizations 2023-05-24 10:07:11 +00:00
ivmarkov
fccf9fa5f6 no_std needs default features switched off for several crates 2023-05-14 09:08:51 +00:00
ivmarkov
f89f77c3f3 Move MATTER_PORT outside of STD-only udp module 2023-05-14 09:08:51 +00:00
ivmarkov
d48b97d77f Just use time-rs in no_std mode 2023-05-14 09:08:51 +00:00
ivmarkov
86083bd831 Builds for STD with ESP IDF 2023-05-14 09:08:51 +00:00
ivmarkov
e817fa8411 Colorizing is now no_std compatible 2023-05-14 09:08:51 +00:00
ivmarkov
a539f4621e More crypto fixes 2023-05-14 09:08:51 +00:00
ivmarkov
2f2e332c75 Fix compilation errors in crypto 2023-05-14 09:08:51 +00:00
ivmarkov
86fb8ce1f0 Fix no_std errors 2023-05-14 09:08:51 +00:00
ivmarkov
5fc3d2d510 Remove heapless::String from QR API 2023-05-14 09:08:51 +00:00
imarkov
2a2bdab9c5 Optional feature to capture stacktrace on error 2023-05-14 09:08:51 +00:00
ivmarkov
0677a5938a Persistence - trace info 2023-05-14 09:08:51 +00:00
ivmarkov
fdea2863fa Persistence bugfixing 2023-05-14 09:08:51 +00:00
ivmarkov
7437cf2c94 Simple persistance via TLV 2023-05-14 09:08:51 +00:00
ivmarkov
1392810a6c Bugfix: unnecessary struct container 2023-05-14 09:08:51 +00:00
ivmarkov
bb275cd50a Bugfix: subscription_id was not sent 2023-05-14 09:08:51 +00:00
ivmarkov
b21f257c47 Bugfix: missing descriptor cluster 2023-05-14 09:08:51 +00:00
ivmarkov
6ea96c390e Error log on arm failure 2023-05-14 09:08:51 +00:00
ivmarkov
669ef8accc Bugfix: only report devtype for the queried endpoint 2023-05-14 09:08:51 +00:00
ivmarkov
1895f34439 TX packets are reused; need way to reset them 2023-05-14 09:08:51 +00:00
ivmarkov
fb2d5a4a23 Root cert buffer too short 2023-05-14 09:08:51 +00:00
ivmarkov
cf7fac7631 MRP standalone ack messages should not be acknowledged 2023-05-14 09:08:51 +00:00
ivmarkov
4c83112b33 Bugfix: fabric adding wrongly started at index 0 2023-05-14 09:08:51 +00:00
ivmarkov
b4f92b0063 Bugfix: two separate failsafe instances were used 2023-05-14 09:08:51 +00:00
ivmarkov
875ac697ad Restore transaction completion code 2023-05-14 09:08:51 +00:00
ivmarkov
40a476e0d9 Bugfix: arm failsafe was reporting wrong status 2023-05-14 09:08:51 +00:00
ivmarkov
d12e1cfa13 Heap-allocated packets not necessary; no_std and no-alloc build supported end-to-end 2023-05-14 09:08:51 +00:00
ivmarkov
e9b4dc5a5c Comm with chip-tool 2023-05-14 09:08:51 +00:00
ivmarkov
c28df04cb5 Actually add the bonjour feature 2023-05-14 09:08:51 +00:00
ivmarkov
52185ec9a4 Cleanup a bit the mDns story 2023-05-14 09:08:51 +00:00
ivmarkov
a7ca17fabc On-off example now buildable 2023-05-14 09:08:51 +00:00
ivmarkov
17002db7e1 no_std printing of QR code (kind of...) 2023-05-14 09:08:51 +00:00
ivmarkov
1ef431eceb Cleanup the dependencies as much as possible 2023-05-14 09:08:51 +00:00
ivmarkov
c594cf1c55 Fix compilation error since the introduction of UtcCalendar 2023-05-14 09:08:51 +00:00
ivmarkov
117c36ee61 More ergonomic api when STD is available 2023-05-14 09:08:51 +00:00
ivmarkov
625baa72a3 Create new secure channel sessions without async-channel 2023-05-14 09:08:51 +00:00
ivmarkov
2b6317a9e2 Chrono dep made optional 2023-05-14 09:08:51 +00:00
ivmarkov
0b807f03a6 Linux & MacOS mDNS services now implement the Mdns trait 2023-05-14 09:08:51 +00:00
ivmarkov
86a1b5ce7e Fix several no_std incompatibilities 2023-05-14 09:08:51 +00:00
ivmarkov
d82e9ec0af Remove allocations from Cert handling 2023-05-14 09:08:51 +00:00
ivmarkov
f7a887c1d2 Remove allocations from Base38 and QR calc 2023-05-14 09:08:51 +00:00
ivmarkov
26fb6b01c5 Long reads and subscriptions reintroduced 2023-05-14 09:08:51 +00:00
ivmarkov
c11a1a1372 Start reintroducing long reads and subscriptions from mainline 2023-05-14 09:08:51 +00:00
ivmarkov
d446007f6b Support for no_std
Support for no_std

Further no_std compat
2023-05-14 09:08:51 +00:00
152 changed files with 5056 additions and 6948 deletions

26
.github/workflows/build-tlv-tool.yml vendored Normal file
View file

@ -0,0 +1,26 @@
name: Build-TLV-Tool
on:
push:
branches: [ main ]
pull_request:
branches: [ main ]
env:
CARGO_TERM_COLOR: always
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Build
run: cd tools/tlv_tool; cargo build --verbose
- name: Archive artifacts
uses: actions/upload-artifact@v2
with:
name: tlv_tool
path: tools/tlv_tool/target/debug/tlv_tool

View file

@ -1,39 +0,0 @@
name: CITLVTool
on:
push:
branches: [ main ]
pull_request:
branches: [ main ]
schedule:
- cron: "20 7 * * *"
workflow_dispatch:
env:
CARGO_TERM_COLOR: always
jobs:
build_tlv_tool:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Fmt
run: cargo fmt -- --check
working-directory: tools/tlv
- name: Clippy
run: cargo clippy --no-deps -- -Dwarnings
working-directory: tools/tlv
- name: Build
run: cargo build
working-directory: tools/tlv
- name: Archive artifacts
uses: actions/upload-artifact@v2
with:
name: tlv
path: tools/tlv/target/debug/tlv

View file

@ -1,48 +0,0 @@
name: CI
on:
push:
branches: [ main ]
pull_request:
branches: [ main ]
schedule:
- cron: "50 6 * * *"
workflow_dispatch:
env:
RUST_TOOLCHAIN: nightly
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
CARGO_TERM_COLOR: always
jobs:
build_and_test:
runs-on: ubuntu-latest
strategy:
matrix:
crypto-backend: ['rustcrypto', 'mbedtls', 'openssl']
features: ['', 'alloc', 'os']
toolchain: ['stable', 'nightly']
steps:
- name: Rust
if: matrix.toolchain == 'nightly'
uses: dtolnay/rust-toolchain@v1
with:
toolchain: ${{ env.RUST_TOOLCHAIN }}
components: rustfmt, clippy, rust-src
- name: Checkout
uses: actions/checkout@v3
- name: Fmt
run: cargo +${{ matrix.toolchain == 'nightly' && env.RUST_TOOLCHAIN || 'stable'}} fmt -- --check
- name: Clippy
run: cargo +${{ matrix.toolchain == 'nightly' && env.RUST_TOOLCHAIN || 'stable'}} clippy --no-deps --no-default-features --features ${{matrix.crypto-backend}},${{matrix.features}},${{ matrix.toolchain == 'nightly' && 'nightly' || ''}} -- -Dwarnings
- name: Build
run: cargo +${{ matrix.toolchain == 'nightly' && env.RUST_TOOLCHAIN || 'stable'}} build --no-default-features --features ${{matrix.crypto-backend}},${{matrix.features}},${{ matrix.toolchain == 'nightly' && 'nightly' || ''}}
- name: Test
if: matrix.features == 'os'
run: cargo +${{ matrix.toolchain == 'nightly' && env.RUST_TOOLCHAIN || 'stable'}} test --no-default-features --features ${{matrix.crypto-backend}},${{matrix.features}},${{ matrix.toolchain == 'nightly' && 'nightly' || ''}} -- --test-threads=1

View file

@ -1,17 +0,0 @@
name: PublishDryRun
on: workflow_dispatch
env:
CRATE_NAME: rs-matter
jobs:
publish_dry_run:
name: PublishDryRun
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v3
- name: PublishDryRun
run: cargo publish -p rs-matter --dry-run

View file

@ -1,14 +0,0 @@
name: PublishMacrosDryRun
on: workflow_dispatch
jobs:
publish_macros_dry_run:
name: PublishMacrosDryRun
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v3
- name: PublishDryRun-Macros
run: cargo publish -p rs-matter-macros --dry-run

View file

@ -1,17 +0,0 @@
name: PublishMacros
on: workflow_dispatch
jobs:
publish_macros:
name: PublishMacros
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Login
run: cargo login ${{ secrets.CRATES_IO_TOKEN }}
- name: Publish-Macros
run: cargo publish -p rs-matter-macros

View file

@ -1,32 +0,0 @@
name: Publish
on: workflow_dispatch
env:
CRATE_NAME: rs-matter
jobs:
publish:
name: Publish
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Login
run: cargo login ${{ secrets.CRATES_IO_TOKEN }}
- name: Publish
run: cargo publish -p rs-matter
- name: Get the crate version from cargo
run: |
version=$(cd rs-matter; cargo metadata --format-version=1 --no-deps | jq -r ".packages[] | select(.name == \"${{env.CRATE_NAME}}\") | .version")
echo "crate_version=$version" >> $GITHUB_ENV
echo "${{env.CRATE_NAME}} version: $version"
- name: Tag the new release
uses: rickstaa/action-create-tag@v1
with:
tag: v${{env.crate_version}}
message: "Release v${{env.crate_version}}"

View file

@ -0,0 +1,22 @@
name: Test-Linux-mbedTLS
on:
push:
branches: [ main ]
pull_request:
branches: [ main ]
env:
CARGO_TERM_COLOR: always
jobs:
build_and_test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Build
run: cd matter; cargo build --verbose --no-default-features --features crypto_mbedtls
- name: Run tests
run: cd matter; cargo test --verbose --no-default-features --features crypto_mbedtls -- --test-threads=1

View file

@ -0,0 +1,22 @@
name: Test-Linux-OpenSSL
on:
push:
branches: [ main ]
pull_request:
branches: [ main ]
env:
CARGO_TERM_COLOR: always
jobs:
build_and_test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Build
run: cd matter; cargo build --verbose --no-default-features --features crypto_openssl
- name: Run tests
run: cd matter; cargo test --verbose --no-default-features --features crypto_openssl -- --test-threads=1

View file

@ -0,0 +1,22 @@
name: Test-Linux-RustCrypto
on:
push:
branches: [ main ]
pull_request:
branches: [ main ]
env:
CARGO_TERM_COLOR: always
jobs:
build_and_test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Build
run: cd matter; cargo build --verbose --no-default-features --features crypto_rustcrypto
- name: Run tests
run: cd matter; cargo test --verbose --no-default-features --features crypto_rustcrypto -- --test-threads=1

View file

@ -1,11 +1,10 @@
[workspace] [workspace]
resolver = "2" members = ["matter", "matter_macro_derive"]
members = ["rs-matter", "rs-matter-macros"] exclude = ["examples/*", "tools/tlv_tool"]
exclude = ["examples/*", "tools/tlv"]
# For compatibility with ESP IDF # For compatibility with ESP IDF
[patch.crates-io] [patch.crates-io]
smol = { git = "https://github.com/esp-rs-compat/smol" }
polling = { git = "https://github.com/esp-rs-compat/polling" } polling = { git = "https://github.com/esp-rs-compat/polling" }
socket2 = { git = "https://github.com/esp-rs-compat/socket2" } socket2 = { git = "https://github.com/esp-rs-compat/socket2" }

View file

@ -1,32 +1,29 @@
# rs-matter: The Rust Implementation of Matter # matter-rs: The Rust Implementation of Matter
![experimental](https://img.shields.io/badge/status-Experimental-red) ![experimental](https://img.shields.io/badge/status-Experimental-red) [![license](https://img.shields.io/badge/license-Apache2-green.svg)](https://raw.githubusercontent.com/project-chip/matter-rs/main/LICENSE)
[![license](https://img.shields.io/badge/license-Apache2-green.svg)](https://raw.githubusercontent.com/project-chip/matter-rs/main/LICENSE)
[![CI](https://github.com/project-chip/matter-rs/actions/workflows/ci.yml/badge.svg)](https://github.com/project-chip/matter-rs/actions/workflows/ci.yml) [![Test Linux (OpenSSL)](https://github.com/project-chip/matter-rs/actions/workflows/test-linux-openssl.yml/badge.svg)](https://github.com/project-chip/matter-rs/actions/workflows/test-linux-openssl.yml)
[![CI - TLV](https://github.com/project-chip/matter-rs/actions/workflows/ci-tlv-tool.yml/badge.svg)](https://github.com/project-chip/matter-rs/actions/workflows/ci-tlv-tool.yml) [![Test Linux (mbedTLS)](https://github.com/project-chip/matter-rs/actions/workflows/test-linux-mbedtls.yml/badge.svg)](https://github.com/project-chip/matter-rs/actions/workflows/test-linux-mbedtls.yml)
[![crates.io](https://img.shields.io/crates/v/rs-matter.svg)](https://crates.io/crates/rs-matter)
[![Matrix](https://img.shields.io/matrix/matter-rs:matrix.org?label=join%20matrix&color=BEC5C9&logo=matrix)](https://matrix.to/#/#matter-rs:matrix.org)
## Build ## Build
### Building the library Building the library:
``` ```
$ cargo build $ cargo build
``` ```
### Building and running the example (Linux, MacOS X) Building and running the example (Linux, MacOS X):
``` ```
$ cargo run --example onoff_light $ cargo run --example onoff_light
``` ```
### Building the example (Espressif's ESP-IDF) Building the example (Espressif's ESP-IDF):
* Install all build prerequisites described [here](https://github.com/esp-rs/esp-idf-template#prerequisites) * Install all build prerequisites described [here](https://github.com/esp-rs/esp-idf-template#prerequisites)
* Build with the following command line: * Build with the following command line:
``` ```
export MCU=esp32; export CARGO_TARGET_XTENSA_ESP32_ESPIDF_LINKER=ldproxy; export RUSTFLAGS="-C default-linker-libraries"; export WIFI_SSID=ssid;export WIFI_PASS=pass; cargo build --example onoff_light --no-default-features --features esp-idf --target xtensa-esp32-espidf -Zbuild-std=std,panic_abort export MCU=esp32; export CARGO_TARGET_XTENSA_ESP32_ESPIDF_LINKER=ldproxy; export RUSTFLAGS="-C default-linker-libraries"; export WIFI_SSID=ssid;export WIFI_PASS=pass; cargo build --example onoff_light --no-default-features --features std,crypto_rustcrypto --target xtensa-esp32-espidf -Zbuild-std=std,panic_abort
``` ```
* If you are building for a different Espressif MCU, change the `MCU` variable, the `xtensa-esp32-espidf` target and the name of the `CARGO_TARGET_<esp-idf-target-uppercase>_LINKER` variable to match your MCU and its Rust target. Available Espressif MCUs and targets are: * If you are building for a different Espressif MCU, change the `MCU` variable, the `xtensa-esp32-espidf` target and the name of the `CARGO_TARGET_<esp-idf-target-uppercase>_LINKER` variable to match your MCU and its Rust target. Available Espressif MCUs and targets are:
* esp32 / xtensa-esp32-espidf * esp32 / xtensa-esp32-espidf
@ -34,14 +31,10 @@ export MCU=esp32; export CARGO_TARGET_XTENSA_ESP32_ESPIDF_LINKER=ldproxy; export
* esp32s3 / xtensa-esp32s3-espidf * esp32s3 / xtensa-esp32s3-espidf
* esp32c3 / riscv32imc-esp-espidf * esp32c3 / riscv32imc-esp-espidf
* esp32c5 / riscv32imc-esp-espidf * esp32c5 / riscv32imc-esp-espidf
* esp32c6 / riscv32imac-esp-espidf * esp32c6 / risxcv32imac-esp-espidf
* Put in `WIFI_SSID` / `WIFI_PASS` the SSID & password for your wireless router * Put in `WIFI_SSID` / `WIFI_PASS` the SSID & password for your wireless router
* Flash using the `espflash` utility described in the build prerequsites' link above * Flash using the `espflash` utility described in the build prerequsites' link above
### Building the example (ESP32-XX baremetal or RP2040)
Coming soon!
## Test ## Test
With the `chip-tool` (the current tool for testing Matter) use the Ethernet commissioning mechanism: With the `chip-tool` (the current tool for testing Matter) use the Ethernet commissioning mechanism:

View file

@ -15,8 +15,8 @@
* limitations under the License. * limitations under the License.
*/ */
use rs_matter::data_model::sdm::dev_att::{DataType, DevAttDataFetcher}; use matter::data_model::sdm::dev_att::{DataType, DevAttDataFetcher};
use rs_matter::error::{Error, ErrorCode}; use matter::error::{Error, ErrorCode};
pub struct HardCodedDevAtt {} pub struct HardCodedDevAtt {}

View file

@ -0,0 +1,18 @@
/*
*
* Copyright (c) 2020-2022 Project CHIP Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
pub mod dev_att;

View file

@ -18,28 +18,33 @@
use core::borrow::Borrow; use core::borrow::Borrow;
use core::pin::pin; use core::pin::pin;
use embassy_futures::select::select3; use embassy_futures::select::select;
use log::info; use log::info;
use rs_matter::core::{CommissioningData, Matter}; use matter::core::{CommissioningData, Matter};
use rs_matter::data_model::cluster_basic_information::BasicInfoConfig; use matter::data_model::cluster_basic_information::BasicInfoConfig;
use rs_matter::data_model::cluster_on_off; use matter::data_model::cluster_on_off;
use rs_matter::data_model::device_types::DEV_TYPE_ON_OFF_LIGHT; use matter::data_model::core::DataModel;
use rs_matter::data_model::objects::*; use matter::data_model::device_types::DEV_TYPE_ON_OFF_LIGHT;
use rs_matter::data_model::root_endpoint; use matter::data_model::objects::*;
use rs_matter::data_model::system_model::descriptor; use matter::data_model::root_endpoint;
use rs_matter::error::Error; use matter::data_model::system_model::descriptor;
use rs_matter::mdns::{MdnsRunBuffers, MdnsService}; use matter::error::Error;
use rs_matter::secure_channel::spake2p::VerifierData; use matter::interaction_model::core::InteractionModel;
use rs_matter::transport::core::RunBuffers; use matter::mdns::{DefaultMdns, DefaultMdnsRunner};
use rs_matter::transport::network::{Ipv4Addr, Ipv6Addr, NetworkStack}; use matter::secure_channel::spake2p::VerifierData;
use rs_matter::utils::select::EitherUnwrap; use matter::transport::network::{Address, IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr};
use matter::transport::{
core::RecvAction, core::Transport, packet::MAX_RX_BUF_SIZE, packet::MAX_TX_BUF_SIZE,
udp::UdpListener,
};
use matter::utils::select::EitherUnwrap;
mod dev_att; mod dev_att;
#[cfg(feature = "std")] #[cfg(feature = "std")]
fn main() -> Result<(), Error> { fn main() -> Result<(), Error> {
let thread = std::thread::Builder::new() let thread = std::thread::Builder::new()
.stack_size(160 * 1024) .stack_size(120 * 1024)
.spawn(run) .spawn(run)
.unwrap(); .unwrap();
@ -57,11 +62,10 @@ fn run() -> Result<(), Error> {
initialize_logger(); initialize_logger();
info!( info!(
"Matter memory: mDNS={}, Matter={}, MdnsBuffers={}, RunBuffers={}", "Matter memory: mDNS={}, Matter={}, Transport={}",
core::mem::size_of::<MdnsService>(), core::mem::size_of::<DefaultMdns>(),
core::mem::size_of::<Matter>(), core::mem::size_of::<Matter>(),
core::mem::size_of::<MdnsRunBuffers>(), core::mem::size_of::<Transport>(),
core::mem::size_of::<RunBuffers>(),
); );
let dev_det = BasicInfoConfig { let dev_det = BasicInfoConfig {
@ -72,38 +76,37 @@ fn run() -> Result<(), Error> {
sw_ver_str: "1", sw_ver_str: "1",
serial_no: "aabbccdd", serial_no: "aabbccdd",
device_name: "OnOff Light", device_name: "OnOff Light",
product_name: "Light123",
vendor_name: "Vendor PQR",
}; };
let (ipv4_addr, ipv6_addr, interface) = initialize_network()?; let (ipv4_addr, ipv6_addr, interface) = initialize_network()?;
let mdns = DefaultMdns::new(
0,
"matter-demo",
ipv4_addr.octets(),
Some(ipv6_addr.octets()),
interface,
&dev_det,
matter::MATTER_PORT,
);
let mut mdns_runner = DefaultMdnsRunner::new(&mdns);
let dev_att = dev_att::HardCodedDevAtt::new(); let dev_att = dev_att::HardCodedDevAtt::new();
#[cfg(feature = "std")] #[cfg(feature = "std")]
let epoch = rs_matter::utils::epoch::sys_epoch; let epoch = matter::utils::epoch::sys_epoch;
#[cfg(feature = "std")] #[cfg(feature = "std")]
let rand = rs_matter::utils::rand::sys_rand; let rand = matter::utils::rand::sys_rand;
// NOTE (no_std): For no_std, provide your own function here // NOTE (no_std): For no_std, provide your own function here
#[cfg(not(feature = "std"))] #[cfg(not(feature = "std"))]
let epoch = rs_matter::utils::epoch::dummy_epoch; let epoch = matter::utils::epoch::dummy_epoch;
// NOTE (no_std): For no_std, provide your own function here // NOTE (no_std): For no_std, provide your own function here
#[cfg(not(feature = "std"))] #[cfg(not(feature = "std"))]
let rand = rs_matter::utils::rand::dummy_rand; let rand = matter::utils::rand::dummy_rand;
let mdns = MdnsService::new(
0,
"rs-matter-demo",
ipv4_addr.octets(),
Some((ipv6_addr.octets(), interface)),
&dev_det,
rs_matter::MATTER_PORT,
);
info!("mDNS initialized");
let matter = Matter::new( let matter = Matter::new(
// vid/pid should match those in the DAC // vid/pid should match those in the DAC
@ -112,93 +115,138 @@ fn run() -> Result<(), Error> {
&mdns, &mdns,
epoch, epoch,
rand, rand,
rs_matter::MATTER_PORT, matter::MATTER_PORT,
); );
info!("Matter initialized"); let psm_path = std::env::temp_dir().join("matter-iot");
info!("Persisting from/to {}", psm_path.display());
#[cfg(all(feature = "std", not(target_os = "espidf")))] #[cfg(all(feature = "std", not(target_os = "espidf")))]
let mut psm = rs_matter::persist::Psm::new(&matter, std::env::temp_dir().join("rs-matter"))?; let psm = matter::persist::FilePsm::new(psm_path)?;
let handler = HandlerCompat(handler(&matter)); let mut buf = [0; 4096];
let buf = &mut buf;
// When using a custom UDP stack, remove the network stack initialization below #[cfg(all(feature = "std", not(target_os = "espidf")))]
// and call `Matter::run_piped()` instead, by utilizing the TX & RX `Pipe` structs {
// to push/pull your UDP packets from/to the Matter stack. if let Some(data) = psm.load("acls", buf)? {
// Ditto for `MdnsService`. matter.load_acls(data)?;
// }
// When using the `embassy-net` feature (as opposed to the Rust Standard Library network stack),
// this initialization would be more complex.
let stack = NetworkStack::new();
let mut mdns_buffers = MdnsRunBuffers::new(); if let Some(data) = psm.load("fabrics", buf)? {
let mut mdns_runner = pin!(mdns.run(&stack, &mut mdns_buffers)); matter.load_fabrics(data)?;
}
}
let mut buffers = RunBuffers::new(); let mut transport = Transport::new(&matter);
let runner = matter.run(
&stack, transport.start(
&mut buffers,
CommissioningData { CommissioningData {
// TODO: Hard-coded for now // TODO: Hard-coded for now
verifier: VerifierData::new_with_pw(123456, *matter.borrow()), verifier: VerifierData::new_with_pw(123456, *matter.borrow()),
discriminator: 250, discriminator: 250,
}, },
&handler, buf,
); )?;
info!( let node = Node {
"Matter transport runner memory: {}", id: 0,
core::mem::size_of_val(&runner) endpoints: &[
); root_endpoint::endpoint(0),
Endpoint {
id: 1,
device_type: DEV_TYPE_ON_OFF_LIGHT,
clusters: &[descriptor::CLUSTER, cluster_on_off::CLUSTER],
},
],
};
let mut runner = pin!(runner); let mut handler = handler(&matter);
#[cfg(all(feature = "std", not(target_os = "espidf")))] let mut im = InteractionModel(DataModel::new(matter.borrow(), &node, &mut handler));
let mut psm_runner = pin!(psm.run());
#[cfg(not(all(feature = "std", not(target_os = "espidf"))))] let mut rx_buf = [0; MAX_RX_BUF_SIZE];
let mut psm_runner = pin!(core::future::pending()); let mut tx_buf = [0; MAX_TX_BUF_SIZE];
let runner = select3(&mut runner, &mut mdns_runner, &mut psm_runner); let im = &mut im;
let mdns_runner = &mut mdns_runner;
let transport = &mut transport;
let rx_buf = &mut rx_buf;
let tx_buf = &mut tx_buf;
let mut io_fut = pin!(async move {
// NOTE (no_std): On no_std, the `UdpListener` implementation is a no-op so you might want to
// replace it with your own UDP stack
let udp = UdpListener::new(SocketAddr::new(
IpAddr::V6(Ipv6Addr::UNSPECIFIED),
matter::MATTER_PORT,
))
.await?;
loop {
let (len, addr) = udp.recv(rx_buf).await?;
let mut completion = transport.recv(Address::Udp(addr), &mut rx_buf[..len], tx_buf);
while let Some(action) = completion.next_action()? {
match action {
RecvAction::Send(addr, buf) => {
udp.send(addr.unwrap_udp(), buf).await?;
}
RecvAction::Interact(mut ctx) => {
if im.handle(&mut ctx)? && ctx.send()? {
udp.send(ctx.tx.peer.unwrap_udp(), ctx.tx.as_slice())
.await?;
}
}
}
}
#[cfg(all(feature = "std", not(target_os = "espidf")))]
{
if let Some(data) = transport.matter().store_fabrics(buf)? {
psm.store("fabrics", data)?;
}
if let Some(data) = transport.matter().store_acls(buf)? {
psm.store("acls", data)?;
}
}
}
#[allow(unreachable_code)]
Ok::<_, matter::error::Error>(())
});
// NOTE (no_std): On no_std, the `run_udp` is a no-op so you might want to replace it with `run` and
// connect the pipes of the `run` method with your own UDP stack
let mut mdns_fut = pin!(async move { mdns_runner.run_udp().await });
let mut fut = pin!(async move { select(&mut io_fut, &mut mdns_fut).await.unwrap() });
#[cfg(feature = "std")] #[cfg(feature = "std")]
async_io::block_on(runner).unwrap()?; smol::block_on(&mut fut)?;
// NOTE (no_std): For no_std, replace with your own more efficient no_std executor, // NOTE (no_std): For no_std, replace with your own more efficient no_std executor,
// because the executor used below is a simple busy-loop poller // because the executor used below is a simple busy-loop poller
#[cfg(not(feature = "std"))] #[cfg(not(feature = "std"))]
embassy_futures::block_on(&mut runner).unwrap()?; embassy_futures::block_on(&mut fut)?;
Ok(()) Ok(())
} }
const NODE: Node<'static> = Node { fn handler<'a>(matter: &'a Matter<'a>) -> impl Handler + 'a {
id: 0, root_endpoint::handler(0, matter)
endpoints: &[ .chain(
root_endpoint::endpoint(0), 1,
Endpoint { descriptor::ID,
id: 1, descriptor::DescriptorCluster::new(*matter.borrow()),
device_type: DEV_TYPE_ON_OFF_LIGHT, )
clusters: &[descriptor::CLUSTER, cluster_on_off::CLUSTER], .chain(
}, 1,
], cluster_on_off::ID,
}; cluster_on_off::OnOffCluster::new(*matter.borrow()),
)
fn handler<'a>(matter: &'a Matter<'a>) -> impl Metadata + NonBlockingHandler + 'a {
(
NODE,
root_endpoint::handler(0, matter)
.chain(
1,
descriptor::ID,
descriptor::DescriptorCluster::new(*matter.borrow()),
)
.chain(
1,
cluster_on_off::ID,
cluster_on_off::OnOffCluster::new(*matter.borrow()),
),
)
} }
// NOTE (no_std): For no_std, implement here your own way of initializing the logger // NOTE (no_std): For no_std, implement here your own way of initializing the logger
@ -225,8 +273,8 @@ fn initialize_logger() {
#[inline(never)] #[inline(never)]
fn initialize_network() -> Result<(Ipv4Addr, Ipv6Addr, u32), Error> { fn initialize_network() -> Result<(Ipv4Addr, Ipv6Addr, u32), Error> {
use log::error; use log::error;
use matter::error::ErrorCode;
use nix::{net::if_::InterfaceFlags, sys::socket::SockaddrIn6}; use nix::{net::if_::InterfaceFlags, sys::socket::SockaddrIn6};
use rs_matter::error::ErrorCode;
let interfaces = || { let interfaces = || {
nix::ifaddrs::getifaddrs().unwrap().filter(|ia| { nix::ifaddrs::getifaddrs().unwrap().filter(|ia| {

View file

@ -15,8 +15,8 @@
* limitations under the License. * limitations under the License.
*/ */
use rs_matter::data_model::sdm::dev_att::{DataType, DevAttDataFetcher}; use matter::data_model::sdm::dev_att::{DataType, DevAttDataFetcher};
use rs_matter::error::Error; use matter::error::Error;
pub struct HardCodedDevAtt {} pub struct HardCodedDevAtt {}

View file

@ -0,0 +1,18 @@
/*
*
* Copyright (c) 2020-2022 Project CHIP Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// TODO pub mod dev_att;

View file

@ -17,11 +17,11 @@
// TODO // TODO
// mod dev_att; // mod dev_att;
// use rs_matter::core::{self, CommissioningData}; // use matter::core::{self, CommissioningData};
// use rs_matter::data_model::cluster_basic_information::BasicInfoConfig; // use matter::data_model::cluster_basic_information::BasicInfoConfig;
// use rs_matter::data_model::cluster_media_playback::{Commands, MediaPlaybackCluster}; // use matter::data_model::cluster_media_playback::{Commands, MediaPlaybackCluster};
// use rs_matter::data_model::device_types::DEV_TYPE_ON_SMART_SPEAKER; // use matter::data_model::device_types::DEV_TYPE_ON_SMART_SPEAKER;
// use rs_matter::secure_channel::spake2p::VerifierData; // use matter::secure_channel::spake2p::VerifierData;
fn main() { fn main() {
// env_logger::init(); // env_logger::init();

View file

@ -15,13 +15,12 @@
* limitations under the License. * limitations under the License.
*/ */
use rs_matter::core::{self, CommissioningData};
use rs_matter::data_model::cluster_basic_information::BasicInfoConfig;
use rs_matter::data_model::cluster_media_playback::{Commands, MediaPlaybackCluster};
use rs_matter::data_model::device_types::DEV_TYPE_ON_SMART_SPEAKER;
use rs_matter::secure_channel::spake2p::VerifierData;
mod dev_att; mod dev_att;
use matter::core::{self, CommissioningData};
use matter::data_model::cluster_basic_information::BasicInfoConfig;
use matter::data_model::cluster_media_playback::{Commands, MediaPlaybackCluster};
use matter::data_model::device_types::DEV_TYPE_ON_SMART_SPEAKER;
use matter::secure_channel::spake2p::VerifierData;
fn main() { fn main() {
env_logger::init(); env_logger::init();

View file

@ -1,31 +1,32 @@
[package] [package]
name = "rs-matter" name = "matter-iot"
version = "0.1.1" version = "0.1.0"
edition = "2021" edition = "2018"
authors = ["Kedar Sovani <kedars@gmail.com>", "Ivan Markov", "Project CHIP Authors"] authors = ["Kedar Sovani <kedars@gmail.com>"]
description = "Native Rust implementation of the Matter (Smart-Home) ecosystem" description = "Native RUST implementation of the Matter (Smart-Home) ecosystem"
repository = "https://github.com/project-chip/matter-rs" repository = "https://github.com/kedars/matter-rs"
readme = "README.md" readme = "README.md"
keywords = ["matter", "smart", "smart-home", "IoT", "ESP32"] keywords = ["matter", "smart", "smart-home", "IoT", "ESP32"]
categories = ["embedded", "network-programming"] categories = ["embedded", "network-programming"]
license = "Apache-2.0" license = "MIT"
[lib]
name = "matter"
path = "src/lib.rs"
[features] [features]
default = ["os", "mbedtls"] default = ["os", "crypto_rustcrypto"]
os = ["std", "backtrace", "env_logger", "nix", "critical-section/std", "embassy-sync/std", "embassy-time/std"] os = ["std", "backtrace", "env_logger", "nix", "critical-section/std", "embassy-sync/std", "embassy-time/std"]
esp-idf = ["std", "rustcrypto", "esp-idf-sys"] std = ["alloc", "rand", "qrcode", "async-io", "smol", "esp-idf-sys/std"]
std = ["alloc", "rand", "async-io", "esp-idf-sys?/std", "embassy-time/generic-queue-16"]
backtrace = [] backtrace = []
alloc = [] alloc = []
nightly = [] nightly = []
openssl = ["alloc", "dep:openssl", "foreign-types", "hmac", "sha2"] crypto_openssl = ["alloc", "openssl", "foreign-types", "hmac", "sha2"]
mbedtls = ["alloc", "dep:mbedtls"] crypto_mbedtls = ["alloc", "mbedtls"]
rustcrypto = ["alloc", "sha2", "hmac", "pbkdf2", "hkdf", "aes", "ccm", "p256", "elliptic-curve", "crypto-bigint", "x509-cert", "rand_core"] crypto_rustcrypto = ["alloc", "sha2", "hmac", "pbkdf2", "hkdf", "aes", "ccm", "p256", "elliptic-curve", "crypto-bigint", "x509-cert", "rand_core"]
embassy-net = ["dep:embassy-net", "dep:embassy-net-driver", "smoltcp"]
zeroconf = ["dep:zeroconf"]
[dependencies] [dependencies]
rs-matter-macros = { version = "0.1", path = "../rs-matter-macros" } matter_macro_derive = { path = "../matter_macro_derive" }
bitflags = { version = "1.3", default-features = false } bitflags = { version = "1.3", default-features = false }
byteorder = { version = "1.4.3", default-features = false } byteorder = { version = "1.4.3", default-features = false }
heapless = "0.7.16" heapless = "0.7.16"
@ -41,24 +42,19 @@ owo-colors = "3"
time = { version = "0.3", default-features = false } time = { version = "0.3", default-features = false }
verhoeff = { version = "1", default-features = false } verhoeff = { version = "1", default-features = false }
embassy-futures = "0.1" embassy-futures = "0.1"
embassy-time = "0.1.1" embassy-time = { version = "0.1.1", features = ["generic-queue-8"] }
embassy-sync = "0.2" embassy-sync = "0.2"
critical-section = "1.1.1" critical-section = "1.1.1"
domain = { version = "0.7.2", default_features = false, features = ["heapless"] } domain = { version = "0.7.2", default_features = false, features = ["heapless"] }
portable-atomic = "1"
qrcodegen-no-heap = "1.8"
# embassy-net dependencies
embassy-net = { version = "0.1", features = ["igmp", "proto-ipv6", "udp"], optional = true }
embassy-net-driver = { version = "0.1", optional = true }
smoltcp = { version = "0.10", default-features = false, optional = true }
# STD-only dependencies # STD-only dependencies
rand = { version = "0.8.5", optional = true } rand = { version = "0.8.5", optional = true }
async-io = { version = "=1.12", optional = true } # =1.12 for compatibility with ESP IDF qrcode = { version = "0.12", default-features = false, optional = true } # Print QR code
smol = { version = "1.2", optional = true } # =1.2 for compatibility with ESP IDF
async-io = { version = "=1.12", optional = true } # =1.2 for compatibility with ESP IDF
# crypto # crypto
openssl = { version = "0.10.55", optional = true } openssl = { git = "https://github.com/sfackler/rust-openssl", optional = true }
foreign-types = { version = "0.3.2", optional = true } foreign-types = { version = "0.3.2", optional = true }
# rust-crypto # rust-crypto
@ -77,26 +73,20 @@ x509-cert = { version = "0.2.0", default-features = false, features = ["pem"], o
[target.'cfg(target_os = "macos")'.dependencies] [target.'cfg(target_os = "macos")'.dependencies]
astro-dnssd = { version = "0.3" } astro-dnssd = { version = "0.3" }
[target.'cfg(target_os = "linux")'.dependencies]
zeroconf = { version = "0.12", optional = true }
[target.'cfg(not(target_os = "espidf"))'.dependencies] [target.'cfg(not(target_os = "espidf"))'.dependencies]
mbedtls = { version = "0.9", optional = true } mbedtls = { git = "https://github.com/fortanix/rust-mbedtls", optional = true }
env_logger = { version = "0.10.0", optional = true } env_logger = { version = "0.10.0", optional = true }
nix = { version = "0.26", features = ["net"], optional = true } nix = { version = "0.26", features = ["net"], optional = true }
[target.'cfg(target_os = "espidf")'.dependencies] [target.'cfg(target_os = "espidf")'.dependencies]
esp-idf-sys = { version = "0.33", optional = true, default-features = false, features = ["native"] } esp-idf-sys = { version = "0.33", default-features = false, features = ["native", "binstart"] }
esp-idf-hal = { version = "0.41", features = ["embassy-sync", "critical-section"] }
esp-idf-svc = { version = "0.46", features = ["embassy-time-driver"] }
embedded-svc = "0.25"
[build-dependencies] [build-dependencies]
embuild = "0.31.2" embuild = "0.31.2"
[target.'cfg(target_os = "espidf")'.dev-dependencies]
esp-idf-sys = { version = "0.33", default-features = false, features = ["binstart"] }
esp-idf-hal = { version = "0.41", features = ["embassy-sync", "critical-section"] }
esp-idf-svc = { version = "0.46", features = ["embassy-time-driver"] }
embedded-svc = { version = "0.25" }
[[example]] [[example]]
name = "onoff_light" name = "onoff_light"
path = "../examples/onoff_light/src/main.rs" path = "../examples/onoff_light/src/main.rs"

View file

@ -22,7 +22,7 @@ use crate::{
error::{Error, ErrorCode}, error::{Error, ErrorCode},
fabric, fabric,
interaction_model::messages::GenericPath, interaction_model::messages::GenericPath,
tlv::{self, FromTLV, Nullable, TLVElement, TLVList, TLVWriter, TagType, ToTLV}, tlv::{self, FromTLV, TLVElement, TLVList, TLVWriter, TagType, ToTLV},
transport::session::{Session, SessionMode, MAX_CAT_IDS_PER_NOC}, transport::session::{Session, SessionMode, MAX_CAT_IDS_PER_NOC},
utils::writebuf::WriteBuf, utils::writebuf::WriteBuf,
}; };
@ -282,15 +282,7 @@ impl Target {
} }
type Subjects = [Option<u64>; SUBJECTS_PER_ENTRY]; type Subjects = [Option<u64>; SUBJECTS_PER_ENTRY];
type Targets = [Option<Target>; TARGETS_PER_ENTRY];
type Targets = Nullable<[Option<Target>; TARGETS_PER_ENTRY]>;
impl Targets {
fn init_notnull() -> Self {
const INIT_TARGETS: Option<Target> = None;
Nullable::NotNull([INIT_TARGETS; TARGETS_PER_ENTRY])
}
}
#[derive(ToTLV, FromTLV, Clone, Debug, PartialEq)] #[derive(ToTLV, FromTLV, Clone, Debug, PartialEq)]
#[tlvargs(start = 1)] #[tlvargs(start = 1)]
pub struct AclEntry { pub struct AclEntry {
@ -306,12 +298,14 @@ pub struct AclEntry {
impl AclEntry { impl AclEntry {
pub fn new(fab_idx: u8, privilege: Privilege, auth_mode: AuthMode) -> Self { pub fn new(fab_idx: u8, privilege: Privilege, auth_mode: AuthMode) -> Self {
const INIT_SUBJECTS: Option<u64> = None; const INIT_SUBJECTS: Option<u64> = None;
const INIT_TARGETS: Option<Target> = None;
let privilege = privilege;
Self { Self {
fab_idx: Some(fab_idx), fab_idx: Some(fab_idx),
privilege, privilege,
auth_mode, auth_mode,
subjects: [INIT_SUBJECTS; SUBJECTS_PER_ENTRY], subjects: [INIT_SUBJECTS; SUBJECTS_PER_ENTRY],
targets: Targets::init_notnull(), targets: [INIT_TARGETS; TARGETS_PER_ENTRY],
} }
} }
@ -330,20 +324,12 @@ impl AclEntry {
} }
pub fn add_target(&mut self, target: Target) -> Result<(), Error> { pub fn add_target(&mut self, target: Target) -> Result<(), Error> {
if self.targets.is_null() {
self.targets = Targets::init_notnull();
}
let index = self let index = self
.targets .targets
.as_ref()
.notnull()
.unwrap()
.iter() .iter()
.position(|s| s.is_none()) .position(|s| s.is_none())
.ok_or(ErrorCode::NoSpace)?; .ok_or(ErrorCode::NoSpace)?;
self.targets[index] = Some(target);
self.targets.as_mut().notnull().unwrap()[index] = Some(target);
Ok(()) Ok(())
} }
@ -372,17 +358,12 @@ impl AclEntry {
fn match_access_desc(&self, object: &AccessDesc) -> bool { fn match_access_desc(&self, object: &AccessDesc) -> bool {
let mut allow = false; let mut allow = false;
let mut entries_exist = false; let mut entries_exist = false;
match self.targets.as_ref().notnull() { for t in self.targets.iter().flatten() {
None => allow = true, // Allow if targets are NULL entries_exist = true;
Some(targets) => { if (t.endpoint.is_none() || t.endpoint == object.path.endpoint)
for t in targets.iter().flatten() { && (t.cluster.is_none() || t.cluster == object.path.cluster)
entries_exist = true; {
if (t.endpoint.is_none() || t.endpoint == object.path.endpoint) allow = true
&& (t.cluster.is_none() || t.cluster == object.path.cluster)
{
allow = true
}
}
} }
} }
if !entries_exist { if !entries_exist {

View file

@ -264,8 +264,8 @@ impl<'a> CertConsumer for ASN1Writer<'a> {
self.write_str(0x06, oid) self.write_str(0x06, oid)
} }
fn utctime(&mut self, _tag: &str, epoch: u64) -> Result<(), Error> { fn utctime(&mut self, _tag: &str, epoch: u32) -> Result<(), Error> {
let matter_epoch = MATTER_EPOCH_SECS + epoch; let matter_epoch = MATTER_EPOCH_SECS + epoch as u64;
let dt = OffsetDateTime::from_unix_timestamp(matter_epoch as _).unwrap(); let dt = OffsetDateTime::from_unix_timestamp(matter_epoch as _).unwrap();

View file

@ -21,7 +21,7 @@ use crate::{
crypto::KeyPair, crypto::KeyPair,
error::{Error, ErrorCode}, error::{Error, ErrorCode},
tlv::{self, FromTLV, OctetStr, TLVArray, TLVElement, TLVWriter, TagType, ToTLV}, tlv::{self, FromTLV, OctetStr, TLVArray, TLVElement, TLVWriter, TagType, ToTLV},
utils::{epoch::MATTER_CERT_DOESNT_EXPIRE, writebuf::WriteBuf}, utils::writebuf::WriteBuf,
}; };
use log::error; use log::error;
use num_derive::FromPrimitive; use num_derive::FromPrimitive;
@ -175,7 +175,7 @@ fn encode_extended_key_usage(
w.end_seq() w.end_seq()
} }
#[derive(FromTLV, ToTLV, Default, Debug, PartialEq)] #[derive(FromTLV, ToTLV, Default, Debug)]
#[tlvargs(start = 1)] #[tlvargs(start = 1)]
struct BasicConstraints { struct BasicConstraints {
is_ca: bool, is_ca: bool,
@ -215,8 +215,8 @@ fn encode_extension_end(w: &mut dyn CertConsumer) -> Result<(), Error> {
w.end_seq() w.end_seq()
} }
#[derive(FromTLV, ToTLV, Default, Debug, PartialEq)] #[derive(FromTLV, ToTLV, Default, Debug)]
#[tlvargs(lifetime = "'a", start = 1, datatype = "list", unordered)] #[tlvargs(lifetime = "'a", start = 1, datatype = "list")]
struct Extensions<'a> { struct Extensions<'a> {
basic_const: Option<BasicConstraints>, basic_const: Option<BasicConstraints>,
key_usage: Option<u16>, key_usage: Option<u16>,
@ -298,7 +298,7 @@ enum DnTags {
NocCat = 22, NocCat = 22,
} }
#[derive(Debug, PartialEq)] #[derive(Debug)]
enum DistNameValue<'a> { enum DistNameValue<'a> {
Uint(u64), Uint(u64),
Utf8Str(&'a [u8]), Utf8Str(&'a [u8]),
@ -307,7 +307,7 @@ enum DistNameValue<'a> {
const MAX_DN_ENTRIES: usize = 5; const MAX_DN_ENTRIES: usize = 5;
#[derive(Default, Debug, PartialEq)] #[derive(Default, Debug)]
struct DistNames<'a> { struct DistNames<'a> {
// The order in which the DNs arrive is important, as the signing // The order in which the DNs arrive is important, as the signing
// requires that the ASN1 notation retains the same order // requires that the ASN1 notation retains the same order
@ -545,7 +545,7 @@ fn encode_dn_value(
w.end_set() w.end_set()
} }
#[derive(FromTLV, ToTLV, Default, Debug, PartialEq)] #[derive(FromTLV, ToTLV, Default, Debug)]
#[tlvargs(lifetime = "'a", start = 1)] #[tlvargs(lifetime = "'a", start = 1)]
pub struct Cert<'a> { pub struct Cert<'a> {
serial_no: OctetStr<'a>, serial_no: OctetStr<'a>,
@ -650,14 +650,8 @@ impl<'a> Cert<'a> {
self.issuer.encode("Issuer:", w)?; self.issuer.encode("Issuer:", w)?;
w.start_seq("Validity:")?; w.start_seq("Validity:")?;
w.utctime("Not Before:", self.not_before.into())?; w.utctime("Not Before:", self.not_before)?;
if self.not_after == 0 { w.utctime("Not After:", self.not_after)?;
// As per the spec a Not-After value of 0, indicates no well-defined
// expiration date and should return in GeneralizedTime of 99991231235959Z
w.utctime("Not After:", MATTER_CERT_DOESNT_EXPIRE)?;
} else {
w.utctime("Not After:", self.not_after.into())?;
}
w.end_seq()?; w.end_seq()?;
self.subject.encode("Subject:", w)?; self.subject.encode("Subject:", w)?;
@ -716,9 +710,8 @@ impl<'a> CertVerifier<'a> {
let k = KeyPair::new_from_public(parent.get_pubkey())?; let k = KeyPair::new_from_public(parent.get_pubkey())?;
k.verify_msg(asn1, self.cert.get_signature()).map_err(|e| { k.verify_msg(asn1, self.cert.get_signature()).map_err(|e| {
error!( error!(
"Error in signature verification of certificate: {:x?} by {:x?}", "Error in signature verification of certificate: {:x?}",
self.cert.get_subject_key_id(), self.cert.get_subject_key_id()
parent.get_subject_key_id()
); );
e e
})?; })?;
@ -751,7 +744,7 @@ pub trait CertConsumer {
fn start_ctx(&mut self, tag: &str, id: u8) -> Result<(), Error>; fn start_ctx(&mut self, tag: &str, id: u8) -> Result<(), Error>;
fn end_ctx(&mut self) -> Result<(), Error>; fn end_ctx(&mut self) -> Result<(), Error>;
fn oid(&mut self, tag: &str, oid: &[u8]) -> Result<(), Error>; fn oid(&mut self, tag: &str, oid: &[u8]) -> Result<(), Error>;
fn utctime(&mut self, tag: &str, epoch: u64) -> Result<(), Error>; fn utctime(&mut self, tag: &str, epoch: u32) -> Result<(), Error>;
} }
const MAX_DEPTH: usize = 10; const MAX_DEPTH: usize = 10;
@ -833,16 +826,6 @@ mod tests {
); );
} }
#[test]
fn test_zero_value_of_not_after_field() {
let noc = Cert::new(&test_vectors::NOC_NOT_AFTER_ZERO).unwrap();
let rca = Cert::new(&test_vectors::RCA_FOR_NOC_NOT_AFTER_ZERO).unwrap();
let v = noc.verify_chain_start();
let v = v.add_cert(&rca).unwrap();
v.finalise().unwrap();
}
#[test] #[test]
fn test_cert_corrupted() { fn test_cert_corrupted() {
use crate::error::ErrorCode; use crate::error::ErrorCode;
@ -858,10 +841,9 @@ mod tests {
#[test] #[test]
fn test_tlv_conversions() { fn test_tlv_conversions() {
let test_input: [&[u8]; 4] = [ let test_input: [&[u8]; 3] = [
&test_vectors::NOC1_SUCCESS, &test_vectors::NOC1_SUCCESS,
&test_vectors::ICAC1_SUCCESS, &test_vectors::ICAC1_SUCCESS,
&test_vectors::ICAC2_SUCCESS,
&test_vectors::RCA1_SUCCESS, &test_vectors::RCA1_SUCCESS,
]; ];
@ -873,10 +855,7 @@ mod tests {
let mut wb = WriteBuf::new(&mut buf); let mut wb = WriteBuf::new(&mut buf);
let mut tw = TLVWriter::new(&mut wb); let mut tw = TLVWriter::new(&mut wb);
cert.to_tlv(&mut tw, TagType::Anonymous).unwrap(); cert.to_tlv(&mut tw, TagType::Anonymous).unwrap();
assert_eq!(*input, wb.as_slice());
let root2 = tlv::get_root_node(wb.as_slice()).unwrap();
let cert2 = Cert::from_tlv(&root2).unwrap();
assert_eq!(cert, cert2);
} }
} }
@ -915,23 +894,6 @@ mod tests {
89, 175, 253, 78, 212, 7, 69, 207, 140, 45, 129, 249, 64, 104, 70, 68, 43, 164, 19, 89, 175, 253, 78, 212, 7, 69, 207, 140, 45, 129, 249, 64, 104, 70, 68, 43, 164, 19,
126, 114, 138, 79, 104, 238, 20, 226, 88, 118, 105, 56, 12, 92, 31, 171, 24, 126, 114, 138, 79, 104, 238, 20, 226, 88, 118, 105, 56, 12, 92, 31, 171, 24,
]; ];
// This cert has two of the fields in the extensions list swapped to a different order to be non-consecutive
pub const ICAC2_SUCCESS: [u8; 263] = [
21, 48, 1, 16, 67, 38, 73, 198, 26, 31, 20, 101, 57, 46, 16, 143, 77, 160, 128, 161,
36, 2, 1, 55, 3, 39, 20, 255, 90, 200, 17, 145, 105, 71, 215, 24, 38, 4, 123, 59, 211,
42, 38, 5, 35, 11, 27, 52, 55, 6, 39, 19, 254, 111, 27, 53, 189, 134, 103, 200, 24, 36,
7, 1, 36, 8, 1, 48, 9, 65, 4, 88, 188, 13, 87, 50, 3, 213, 248, 182, 12, 240, 164, 220,
127, 150, 65, 81, 244, 125, 24, 48, 203, 83, 111, 133, 175, 182, 10, 40, 80, 147, 28,
39, 121, 183, 61, 159, 178, 231, 133, 75, 189, 143, 136, 191, 254, 115, 228, 186, 129,
56, 137, 213, 177, 13, 46, 97, 202, 95, 41, 5, 16, 24, 228, 55, 10, 53, 1, 41, 1, 36,
2, 0, 24, 48, 5, 20, 243, 119, 107, 152, 3, 212, 205, 76, 85, 38, 158, 240, 27, 213,
11, 235, 33, 21, 38, 5, 48, 4, 20, 88, 240, 172, 159, 2, 82, 193, 71, 83, 67, 184, 97,
99, 61, 125, 67, 232, 202, 171, 107, 36, 2, 96, 24, 48, 11, 64, 70, 43, 150, 195, 194,
170, 43, 125, 91, 213, 210, 221, 175, 131, 131, 85, 22, 247, 213, 18, 101, 189, 30,
134, 20, 226, 217, 145, 41, 225, 181, 150, 28, 200, 52, 237, 218, 195, 144, 209, 205,
73, 88, 114, 139, 216, 85, 170, 63, 238, 164, 69, 35, 69, 39, 87, 211, 234, 57, 98, 19,
43, 13, 0, 24,
];
// A single byte in the auth key id is changed in this // A single byte in the auth key id is changed in this
pub const NOC1_AUTH_KEY_FAIL: [u8; 247] = [ pub const NOC1_AUTH_KEY_FAIL: [u8; 247] = [
0x15, 0x30, 0x1, 0x1, 0x1, 0x24, 0x2, 0x1, 0x37, 0x3, 0x24, 0x13, 0x1, 0x24, 0x15, 0x1, 0x15, 0x30, 0x1, 0x1, 0x1, 0x24, 0x2, 0x1, 0x37, 0x3, 0x24, 0x13, 0x1, 0x24, 0x15, 0x1,
@ -1150,47 +1112,5 @@ mod tests {
0x16, 0x80, 0x14, 0x72, 0xc2, 0x01, 0xf7, 0x57, 0x19, 0x13, 0xb3, 0x48, 0xca, 0x00, 0x16, 0x80, 0x14, 0x72, 0xc2, 0x01, 0xf7, 0x57, 0x19, 0x13, 0xb3, 0x48, 0xca, 0x00,
0xca, 0x7b, 0x45, 0xf4, 0x77, 0x46, 0x68, 0xc9, 0x7e, 0xca, 0x7b, 0x45, 0xf4, 0x77, 0x46, 0x68, 0xc9, 0x7e,
]; ];
/// An NOC that contains a Not-After validity field of '0'
pub const NOC_NOT_AFTER_ZERO: [u8; 251] = [
0x15, 0x30, 0x1, 0x1, 0x1, 0x24, 0x2, 0x1, 0x37, 0x3, 0x27, 0x14, 0xfc, 0x8d, 0xcf,
0x45, 0x19, 0xff, 0x9a, 0x9a, 0x24, 0x15, 0x1, 0x18, 0x26, 0x4, 0x21, 0x39, 0x5a, 0x2c,
0x24, 0x5, 0x0, 0x37, 0x6, 0x24, 0x15, 0x1, 0x26, 0x11, 0x6c, 0x4a, 0x95, 0xd2, 0x18,
0x24, 0x7, 0x1, 0x24, 0x8, 0x1, 0x30, 0x9, 0x41, 0x4, 0x41, 0x7f, 0xb1, 0x61, 0xb0,
0xbe, 0x19, 0x41, 0x81, 0xb9, 0x9f, 0xe8, 0x7b, 0xdd, 0xdf, 0xc4, 0x46, 0xe0, 0x74,
0xba, 0x83, 0x21, 0xda, 0x3d, 0xf7, 0x88, 0x68, 0x14, 0xa6, 0x9d, 0xa9, 0x14, 0x88,
0x94, 0x1e, 0xd3, 0x86, 0x62, 0xc7, 0x6f, 0xb4, 0x79, 0xd2, 0xaf, 0x34, 0xe7, 0xd6,
0x4d, 0x87, 0x29, 0x67, 0x10, 0x73, 0xb9, 0x81, 0xe0, 0x9, 0xe1, 0x13, 0xbb, 0x6a,
0xd2, 0x21, 0xaa, 0x37, 0xa, 0x35, 0x1, 0x28, 0x1, 0x18, 0x24, 0x2, 0x1, 0x36, 0x3,
0x4, 0x2, 0x4, 0x1, 0x18, 0x30, 0x4, 0x14, 0x98, 0xaf, 0xa1, 0x3d, 0x41, 0x67, 0x7a,
0x34, 0x8c, 0x67, 0x6c, 0xcc, 0x17, 0x6e, 0xd5, 0x58, 0xd8, 0x2b, 0x86, 0x8, 0x30, 0x5,
0x14, 0xf8, 0xcf, 0xd0, 0x45, 0x6b, 0xe, 0xd1, 0x6f, 0xc5, 0x67, 0xdf, 0x81, 0xd7,
0xe9, 0xb7, 0xeb, 0x39, 0x78, 0xec, 0x40, 0x18, 0x30, 0xb, 0x40, 0xf9, 0x80, 0x94,
0xbf, 0xcf, 0x72, 0xa5, 0x54, 0x87, 0x12, 0x35, 0xc, 0x38, 0x79, 0xa8, 0xb, 0x21, 0x94,
0xb5, 0x71, 0x2, 0xcb, 0xb, 0xda, 0xf9, 0x6c, 0x54, 0xcb, 0x50, 0x4b, 0x2, 0x5, 0xea,
0xff, 0xfd, 0xb2, 0x1b, 0x24, 0x30, 0x79, 0xb1, 0x69, 0x87, 0xa5, 0x7, 0xc6, 0x76,
0x15, 0x70, 0xc0, 0xec, 0x14, 0xd3, 0x9f, 0x1a, 0xa7, 0xe1, 0xca, 0x25, 0x2e, 0x44,
0xfc, 0x96, 0x4d, 0x18,
];
pub const RCA_FOR_NOC_NOT_AFTER_ZERO: [u8; 251] = [
0x15, 0x30, 0x1, 0x1, 0x0, 0x24, 0x2, 0x1, 0x37, 0x3, 0x27, 0x14, 0xfc, 0x8d, 0xcf,
0x45, 0x19, 0xff, 0x9a, 0x9a, 0x24, 0x15, 0x1, 0x18, 0x26, 0x4, 0xb1, 0x2a, 0x38, 0x2c,
0x26, 0x5, 0x31, 0x5e, 0x19, 0x2e, 0x37, 0x6, 0x27, 0x14, 0xfc, 0x8d, 0xcf, 0x45, 0x19,
0xff, 0x9a, 0x9a, 0x24, 0x15, 0x1, 0x18, 0x24, 0x7, 0x1, 0x24, 0x8, 0x1, 0x30, 0x9,
0x41, 0x4, 0x15, 0x69, 0x1e, 0x7b, 0x6a, 0xea, 0x5, 0xdb, 0xf8, 0x4b, 0xfd, 0xdc, 0x6c,
0x75, 0x46, 0x74, 0xb0, 0x60, 0xdb, 0x4, 0x71, 0xb6, 0xd0, 0x52, 0xf2, 0xf8, 0xe6,
0xbb, 0xd, 0xe5, 0x60, 0x1f, 0x84, 0x66, 0x4f, 0x3c, 0x90, 0x89, 0xa6, 0xc6, 0x99,
0x61, 0xfb, 0x89, 0xf7, 0xa, 0xa6, 0xe4, 0xa2, 0x21, 0xd3, 0x37, 0x30, 0x1b, 0xd2,
0x11, 0xc5, 0xcc, 0x0, 0xf4, 0x7a, 0x14, 0xfc, 0x3c, 0x37, 0xa, 0x35, 0x1, 0x29, 0x1,
0x18, 0x24, 0x2, 0x60, 0x30, 0x4, 0x14, 0xf8, 0xcf, 0xd0, 0x45, 0x6b, 0xe, 0xd1, 0x6f,
0xc5, 0x67, 0xdf, 0x81, 0xd7, 0xe9, 0xb7, 0xeb, 0x39, 0x78, 0xec, 0x40, 0x30, 0x5,
0x14, 0xf8, 0xcf, 0xd0, 0x45, 0x6b, 0xe, 0xd1, 0x6f, 0xc5, 0x67, 0xdf, 0x81, 0xd7,
0xe9, 0xb7, 0xeb, 0x39, 0x78, 0xec, 0x40, 0x18, 0x30, 0xb, 0x40, 0x4c, 0xae, 0xac,
0xc1, 0x26, 0xdd, 0x56, 0xc, 0x85, 0x86, 0xbc, 0xeb, 0xa2, 0xb5, 0xb7, 0xdf, 0x49,
0x92, 0x62, 0xcd, 0x2a, 0xb6, 0x4e, 0xc5, 0x31, 0x7c, 0xd9, 0xb, 0x1c, 0xe9, 0x6e,
0xe5, 0x82, 0xc7, 0xb8, 0xda, 0x22, 0x31, 0x7b, 0x23, 0x5a, 0x2a, 0xe6, 0x76, 0x28,
0xb6, 0xd4, 0xc7, 0x7b, 0x1c, 0x9c, 0x85, 0x71, 0x5f, 0xe6, 0xf6, 0x21, 0x50, 0x5c,
0xa7, 0x7c, 0xc7, 0x1d, 0x9a, 0x18,
];
} }
} }

View file

@ -122,8 +122,8 @@ impl<'a, 'b> CertConsumer for CertPrinter<'a, 'b> {
} }
Ok(()) Ok(())
} }
fn utctime(&mut self, tag: &str, epoch: u64) -> Result<(), Error> { fn utctime(&mut self, tag: &str, epoch: u32) -> Result<(), Error> {
let matter_epoch = MATTER_EPOCH_SECS + epoch; let matter_epoch = MATTER_EPOCH_SECS + epoch as u64;
let dt = OffsetDateTime::from_unix_timestamp(matter_epoch as _).unwrap(); let dt = OffsetDateTime::from_unix_timestamp(matter_epoch as _).unwrap();

View file

@ -17,8 +17,6 @@
use core::{borrow::Borrow, cell::RefCell}; use core::{borrow::Borrow, cell::RefCell};
use embassy_sync::{blocking_mutex::raw::NoopRawMutex, mutex::Mutex};
use crate::{ use crate::{
acl::AclMgr, acl::AclMgr,
data_model::{ data_model::{
@ -30,11 +28,7 @@ use crate::{
mdns::Mdns, mdns::Mdns,
pairing::{print_pairing_code_and_qr, DiscoveryCapabilities}, pairing::{print_pairing_code_and_qr, DiscoveryCapabilities},
secure_channel::{pake::PaseMgr, spake2p::VerifierData}, secure_channel::{pake::PaseMgr, spake2p::VerifierData},
transport::{ utils::{epoch::Epoch, rand::Rand},
exchange::{ExchangeCtx, MAX_EXCHANGES},
session::SessionMgr,
},
utils::{epoch::Epoch, rand::Rand, select::Notification},
}; };
/* The Matter Port */ /* The Matter Port */
@ -50,22 +44,16 @@ pub struct CommissioningData {
/// The primary Matter Object /// The primary Matter Object
pub struct Matter<'a> { pub struct Matter<'a> {
fabric_mgr: RefCell<FabricMgr>, pub fabric_mgr: RefCell<FabricMgr>,
pub acl_mgr: RefCell<AclMgr>, // Public for tests pub acl_mgr: RefCell<AclMgr>,
pase_mgr: RefCell<PaseMgr>, pub pase_mgr: RefCell<PaseMgr>,
failsafe: RefCell<FailSafe>, pub failsafe: RefCell<FailSafe>,
persist_notification: Notification, pub mdns: &'a dyn Mdns,
pub(crate) send_notification: Notification, pub epoch: Epoch,
mdns: &'a dyn Mdns, pub rand: Rand,
pub(crate) epoch: Epoch, pub dev_det: &'a BasicInfoConfig<'a>,
pub(crate) rand: Rand, pub dev_att: &'a dyn DevAttDataFetcher,
dev_det: &'a BasicInfoConfig<'a>, pub port: u16,
dev_att: &'a dyn DevAttDataFetcher,
pub(crate) port: u16,
pub(crate) exchanges: RefCell<heapless::Vec<ExchangeCtx, MAX_EXCHANGES>>,
pub(crate) ephemeral: RefCell<Option<ExchangeCtx>>,
pub(crate) ephemeral_mutex: Mutex<NoopRawMutex, ()>,
pub session_mgr: RefCell<SessionMgr>, // Public for tests
} }
impl<'a> Matter<'a> { impl<'a> Matter<'a> {
@ -103,18 +91,12 @@ impl<'a> Matter<'a> {
acl_mgr: RefCell::new(AclMgr::new()), acl_mgr: RefCell::new(AclMgr::new()),
pase_mgr: RefCell::new(PaseMgr::new(epoch, rand)), pase_mgr: RefCell::new(PaseMgr::new(epoch, rand)),
failsafe: RefCell::new(FailSafe::new()), failsafe: RefCell::new(FailSafe::new()),
persist_notification: Notification::new(),
send_notification: Notification::new(),
mdns, mdns,
epoch, epoch,
rand, rand,
dev_det, dev_det,
dev_att, dev_att,
port, port,
exchanges: RefCell::new(heapless::Vec::new()),
ephemeral: RefCell::new(None),
ephemeral_mutex: Mutex::new(()),
session_mgr: RefCell::new(SessionMgr::new(epoch, rand)),
} }
} }
@ -175,16 +157,6 @@ impl<'a> Matter<'a> {
Ok(false) Ok(false)
} }
} }
pub fn notify_changed(&self) {
if self.is_changed() {
self.persist_notification.signal(());
}
}
pub async fn wait_changed(&self) {
self.persist_notification.wait().await
}
} }
impl<'a> Borrow<RefCell<FabricMgr>> for Matter<'a> { impl<'a> Borrow<RefCell<FabricMgr>> for Matter<'a> {

View file

@ -17,8 +17,6 @@
extern crate alloc; extern crate alloc;
use core::fmt::{self, Debug};
use alloc::sync::Arc; use alloc::sync::Arc;
use log::{error, info}; use log::{error, info};
@ -357,9 +355,3 @@ impl Sha256 {
Ok(()) Ok(())
} }
} }
impl Debug for Sha256 {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
write!(f, "Sha256")
}
}

View file

@ -15,8 +15,6 @@
* limitations under the License. * limitations under the License.
*/ */
use core::fmt::{self, Debug};
use crate::error::{Error, ErrorCode}; use crate::error::{Error, ErrorCode};
use crate::utils::rand::Rand; use crate::utils::rand::Rand;
@ -59,8 +57,7 @@ impl HmacSha256 {
} }
pub fn update(&mut self, data: &[u8]) -> Result<(), Error> { pub fn update(&mut self, data: &[u8]) -> Result<(), Error> {
self.ctx.update(data); Ok(self.ctx.update(data))
Ok(())
} }
pub fn finish(self, out: &mut [u8]) -> Result<(), Error> { pub fn finish(self, out: &mut [u8]) -> Result<(), Error> {
@ -119,7 +116,7 @@ impl KeyPair {
fn private_key(&self) -> Result<&EcKey<Private>, Error> { fn private_key(&self) -> Result<&EcKey<Private>, Error> {
match &self.key { match &self.key {
KeyType::Public(_) => Err(ErrorCode::Invalid.into()), KeyType::Public(_) => Err(ErrorCode::Invalid.into()),
KeyType::Private(k) => Ok(k), KeyType::Private(k) => Ok(&k),
} }
} }
@ -228,7 +225,7 @@ impl KeyPair {
} }
const P256_KEY_LEN: usize = 256 / 8; const P256_KEY_LEN: usize = 256 / 8;
pub fn pubkey_from_der(der: &[u8], out_key: &mut [u8]) -> Result<(), Error> { pub fn pubkey_from_der<'a>(der: &'a [u8], out_key: &mut [u8]) -> Result<(), Error> {
if out_key.len() != P256_KEY_LEN { if out_key.len() != P256_KEY_LEN {
error!("Insufficient length"); error!("Insufficient length");
Err(ErrorCode::NoSpace.into()) Err(ErrorCode::NoSpace.into())
@ -287,7 +284,7 @@ pub fn decrypt_in_place(
) -> Result<usize, Error> { ) -> Result<usize, Error> {
let tag_start = data.len() - super::AEAD_MIC_LEN_BYTES; let tag_start = data.len() - super::AEAD_MIC_LEN_BYTES;
let (data, tag) = data.split_at_mut(tag_start); let (data, tag) = data.split_at_mut(tag_start);
let result = lowlevel_decrypt_aead(key, Some(nonce), ad, data, tag)?; let result = lowlevel_decrypt_aead(key, Some(nonce), ad, data, &tag)?;
data[..result.len()].copy_from_slice(result.as_slice()); data[..result.len()].copy_from_slice(result.as_slice());
Ok(result.len()) Ok(result.len())
} }
@ -394,9 +391,3 @@ impl Sha256 {
Ok(()) Ok(())
} }
} }
impl Debug for Sha256 {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
write!(f, "Sha256")
}
}

View file

@ -37,29 +37,37 @@ pub const ECDH_SHARED_SECRET_LEN_BYTES: usize = 32;
pub const EC_SIGNATURE_LEN_BYTES: usize = 64; pub const EC_SIGNATURE_LEN_BYTES: usize = 64;
#[cfg(all(feature = "mbedtls", target_os = "espidf"))] #[cfg(all(feature = "crypto_mbedtls", target_os = "espidf"))]
mod crypto_esp_mbedtls; mod crypto_esp_mbedtls;
#[cfg(all(feature = "mbedtls", target_os = "espidf"))] #[cfg(all(feature = "crypto_mbedtls", target_os = "espidf"))]
pub use self::crypto_esp_mbedtls::*; pub use self::crypto_esp_mbedtls::*;
#[cfg(all(feature = "mbedtls", not(target_os = "espidf")))] #[cfg(all(feature = "crypto_mbedtls", not(target_os = "espidf")))]
mod crypto_mbedtls; mod crypto_mbedtls;
#[cfg(all(feature = "mbedtls", not(target_os = "espidf")))] #[cfg(all(feature = "crypto_mbedtls", not(target_os = "espidf")))]
pub use self::crypto_mbedtls::*; pub use self::crypto_mbedtls::*;
#[cfg(feature = "openssl")] #[cfg(feature = "crypto_openssl")]
mod crypto_openssl; mod crypto_openssl;
#[cfg(feature = "openssl")] #[cfg(feature = "crypto_openssl")]
pub use self::crypto_openssl::*; pub use self::crypto_openssl::*;
#[cfg(feature = "rustcrypto")] #[cfg(feature = "crypto_rustcrypto")]
mod crypto_rustcrypto; mod crypto_rustcrypto;
#[cfg(feature = "rustcrypto")] #[cfg(feature = "crypto_rustcrypto")]
pub use self::crypto_rustcrypto::*; pub use self::crypto_rustcrypto::*;
#[cfg(not(any(feature = "openssl", feature = "mbedtls", feature = "rustcrypto")))] #[cfg(not(any(
feature = "crypto_openssl",
feature = "crypto_mbedtls",
feature = "crypto_rustcrypto"
)))]
pub mod crypto_dummy; pub mod crypto_dummy;
#[cfg(not(any(feature = "openssl", feature = "mbedtls", feature = "rustcrypto")))] #[cfg(not(any(
feature = "crypto_openssl",
feature = "crypto_mbedtls",
feature = "crypto_rustcrypto"
)))]
pub use self::crypto_dummy::*; pub use self::crypto_dummy::*;
impl<'a> FromTLV<'a> for KeyPair { impl<'a> FromTLV<'a> for KeyPair {

View file

@ -15,15 +15,10 @@
* limitations under the License. * limitations under the License.
*/ */
use core::{cell::RefCell, convert::TryInto}; use core::convert::TryInto;
use super::objects::*; use super::objects::*;
use crate::{ use crate::{attribute_enum, error::Error, utils::rand::Rand};
attribute_enum,
error::{Error, ErrorCode},
utils::rand::Rand,
};
use heapless::String;
use strum::FromRepr; use strum::FromRepr;
pub const ID: u32 = 0x0028; pub const ID: u32 = 0x0028;
@ -32,11 +27,8 @@ pub const ID: u32 = 0x0028;
#[repr(u16)] #[repr(u16)]
pub enum Attributes { pub enum Attributes {
DMRevision(AttrType<u8>) = 0, DMRevision(AttrType<u8>) = 0,
VendorName(AttrUtfType) = 1,
VendorId(AttrType<u16>) = 2, VendorId(AttrType<u16>) = 2,
ProductName(AttrUtfType) = 3,
ProductId(AttrType<u16>) = 4, ProductId(AttrType<u16>) = 4,
NodeLabel(AttrUtfType) = 5,
HwVer(AttrType<u16>) = 7, HwVer(AttrType<u16>) = 7,
SwVer(AttrType<u32>) = 9, SwVer(AttrType<u32>) = 9,
SwVerString(AttrUtfType) = 0xa, SwVerString(AttrUtfType) = 0xa,
@ -47,11 +39,8 @@ attribute_enum!(Attributes);
pub enum AttributesDiscriminants { pub enum AttributesDiscriminants {
DMRevision = 0, DMRevision = 0,
VendorName = 1,
VendorId = 2, VendorId = 2,
ProductName = 3,
ProductId = 4, ProductId = 4,
NodeLabel = 5,
HwVer = 7, HwVer = 7,
SwVer = 9, SwVer = 9,
SwVerString = 0xa, SwVerString = 0xa,
@ -68,8 +57,6 @@ pub struct BasicInfoConfig<'a> {
pub serial_no: &'a str, pub serial_no: &'a str,
/// Device name; up to 32 characters /// Device name; up to 32 characters
pub device_name: &'a str, pub device_name: &'a str,
pub vendor_name: &'a str,
pub product_name: &'a str,
} }
pub const CLUSTER: Cluster<'static> = Cluster { pub const CLUSTER: Cluster<'static> = Cluster {
@ -83,31 +70,16 @@ pub const CLUSTER: Cluster<'static> = Cluster {
Access::RV, Access::RV,
Quality::FIXED, Quality::FIXED,
), ),
Attribute::new(
AttributesDiscriminants::VendorName as u16,
Access::RV,
Quality::FIXED,
),
Attribute::new( Attribute::new(
AttributesDiscriminants::VendorId as u16, AttributesDiscriminants::VendorId as u16,
Access::RV, Access::RV,
Quality::FIXED, Quality::FIXED,
), ),
Attribute::new(
AttributesDiscriminants::ProductName as u16,
Access::RV,
Quality::FIXED,
),
Attribute::new( Attribute::new(
AttributesDiscriminants::ProductId as u16, AttributesDiscriminants::ProductId as u16,
Access::RV, Access::RV,
Quality::FIXED, Quality::FIXED,
), ),
Attribute::new(
AttributesDiscriminants::NodeLabel as u16,
Access::RWVM,
Quality::N,
),
Attribute::new( Attribute::new(
AttributesDiscriminants::HwVer as u16, AttributesDiscriminants::HwVer as u16,
Access::RV, Access::RV,
@ -135,16 +107,13 @@ pub const CLUSTER: Cluster<'static> = Cluster {
pub struct BasicInfoCluster<'a> { pub struct BasicInfoCluster<'a> {
data_ver: Dataver, data_ver: Dataver,
cfg: &'a BasicInfoConfig<'a>, cfg: &'a BasicInfoConfig<'a>,
node_label: RefCell<String<32>>, // Max node-label as per the spec
} }
impl<'a> BasicInfoCluster<'a> { impl<'a> BasicInfoCluster<'a> {
pub fn new(cfg: &'a BasicInfoConfig<'a>, rand: Rand) -> Self { pub fn new(cfg: &'a BasicInfoConfig<'a>, rand: Rand) -> Self {
let node_label = RefCell::new(String::from(""));
Self { Self {
data_ver: Dataver::new(rand), data_ver: Dataver::new(rand),
cfg, cfg,
node_label,
} }
} }
@ -155,13 +124,8 @@ impl<'a> BasicInfoCluster<'a> {
} else { } else {
match attr.attr_id.try_into()? { match attr.attr_id.try_into()? {
Attributes::DMRevision(codec) => codec.encode(writer, 1), Attributes::DMRevision(codec) => codec.encode(writer, 1),
Attributes::VendorName(codec) => codec.encode(writer, self.cfg.vendor_name),
Attributes::VendorId(codec) => codec.encode(writer, self.cfg.vid), Attributes::VendorId(codec) => codec.encode(writer, self.cfg.vid),
Attributes::ProductName(codec) => codec.encode(writer, self.cfg.product_name),
Attributes::ProductId(codec) => codec.encode(writer, self.cfg.pid), Attributes::ProductId(codec) => codec.encode(writer, self.cfg.pid),
Attributes::NodeLabel(codec) => {
codec.encode(writer, self.node_label.borrow().as_str())
}
Attributes::HwVer(codec) => codec.encode(writer, self.cfg.hw_ver), Attributes::HwVer(codec) => codec.encode(writer, self.cfg.hw_ver),
Attributes::SwVer(codec) => codec.encode(writer, self.cfg.sw_ver), Attributes::SwVer(codec) => codec.encode(writer, self.cfg.sw_ver),
Attributes::SwVerString(codec) => codec.encode(writer, self.cfg.sw_ver_str), Attributes::SwVerString(codec) => codec.encode(writer, self.cfg.sw_ver_str),
@ -172,35 +136,12 @@ impl<'a> BasicInfoCluster<'a> {
Ok(()) Ok(())
} }
} }
pub fn write(&self, attr: &AttrDetails, data: AttrData) -> Result<(), Error> {
let data = data.with_dataver(self.data_ver.get())?;
match attr.attr_id.try_into()? {
Attributes::NodeLabel(codec) => {
*self.node_label.borrow_mut() = String::from(
codec
.decode(data)
.map_err(|_| Error::new(ErrorCode::InvalidAction))?,
);
}
_ => return Err(Error::new(ErrorCode::InvalidAction)),
}
self.data_ver.changed();
Ok(())
}
} }
impl<'a> Handler for BasicInfoCluster<'a> { impl<'a> Handler for BasicInfoCluster<'a> {
fn read(&self, attr: &AttrDetails, encoder: AttrDataEncoder) -> Result<(), Error> { fn read(&self, attr: &AttrDetails, encoder: AttrDataEncoder) -> Result<(), Error> {
BasicInfoCluster::read(self, attr, encoder) BasicInfoCluster::read(self, attr, encoder)
} }
fn write(&self, attr: &AttrDetails, data: AttrData) -> Result<(), Error> {
BasicInfoCluster::write(self, attr, data)
}
} }
impl<'a> NonBlockingHandler for BasicInfoCluster<'a> {} impl<'a> NonBlockingHandler for BasicInfoCluster<'a> {}

View file

@ -15,12 +15,12 @@
* limitations under the License. * limitations under the License.
*/ */
use core::{cell::Cell, convert::TryInto}; use core::convert::TryInto;
use super::objects::*; use super::objects::*;
use crate::{ use crate::{
attribute_enum, cmd_enter, command_enum, error::Error, tlv::TLVElement, attribute_enum, cmd_enter, command_enum, error::Error, interaction_model::core::Transaction,
transport::exchange::Exchange, utils::rand::Rand, tlv::TLVElement, utils::rand::Rand,
}; };
use log::info; use log::info;
use strum::{EnumDiscriminants, FromRepr}; use strum::{EnumDiscriminants, FromRepr};
@ -54,7 +54,7 @@ pub const CLUSTER: Cluster<'static> = Cluster {
Attribute::new( Attribute::new(
AttributesDiscriminants::OnOff as u16, AttributesDiscriminants::OnOff as u16,
Access::RV, Access::RV,
Quality::SN, Quality::PERSISTENT,
), ),
], ],
commands: &[ commands: &[
@ -66,20 +66,20 @@ pub const CLUSTER: Cluster<'static> = Cluster {
pub struct OnOffCluster { pub struct OnOffCluster {
data_ver: Dataver, data_ver: Dataver,
on: Cell<bool>, on: bool,
} }
impl OnOffCluster { impl OnOffCluster {
pub fn new(rand: Rand) -> Self { pub fn new(rand: Rand) -> Self {
Self { Self {
data_ver: Dataver::new(rand), data_ver: Dataver::new(rand),
on: Cell::new(false), on: false,
} }
} }
pub fn set(&self, on: bool) { pub fn set(&mut self, on: bool) {
if self.on.get() != on { if self.on != on {
self.on.set(on); self.on = on;
self.data_ver.changed(); self.data_ver.changed();
} }
} }
@ -90,7 +90,7 @@ impl OnOffCluster {
CLUSTER.read(attr.attr_id, writer) CLUSTER.read(attr.attr_id, writer)
} else { } else {
match attr.attr_id.try_into()? { match attr.attr_id.try_into()? {
Attributes::OnOff(codec) => codec.encode(writer, self.on.get()), Attributes::OnOff(codec) => codec.encode(writer, self.on),
} }
} }
} else { } else {
@ -98,7 +98,7 @@ impl OnOffCluster {
} }
} }
pub fn write(&self, attr: &AttrDetails, data: AttrData) -> Result<(), Error> { pub fn write(&mut self, attr: &AttrDetails, data: AttrData) -> Result<(), Error> {
let data = data.with_dataver(self.data_ver.get())?; let data = data.with_dataver(self.data_ver.get())?;
match attr.attr_id.try_into()? { match attr.attr_id.try_into()? {
@ -111,8 +111,8 @@ impl OnOffCluster {
} }
pub fn invoke( pub fn invoke(
&self, &mut self,
_exchange: &Exchange, transaction: &mut Transaction,
cmd: &CmdDetails, cmd: &CmdDetails,
_data: &TLVElement, _data: &TLVElement,
_encoder: CmdDataEncoder, _encoder: CmdDataEncoder,
@ -128,10 +128,12 @@ impl OnOffCluster {
} }
Commands::Toggle => { Commands::Toggle => {
cmd_enter!("Toggle"); cmd_enter!("Toggle");
self.set(!self.on.get()); self.set(!self.on);
} }
} }
transaction.complete();
self.data_ver.changed(); self.data_ver.changed();
Ok(()) Ok(())
@ -143,18 +145,18 @@ impl Handler for OnOffCluster {
OnOffCluster::read(self, attr, encoder) OnOffCluster::read(self, attr, encoder)
} }
fn write(&self, attr: &AttrDetails, data: AttrData) -> Result<(), Error> { fn write(&mut self, attr: &AttrDetails, data: AttrData) -> Result<(), Error> {
OnOffCluster::write(self, attr, data) OnOffCluster::write(self, attr, data)
} }
fn invoke( fn invoke(
&self, &mut self,
exchange: &Exchange, transaction: &mut Transaction,
cmd: &CmdDetails, cmd: &CmdDetails,
data: &TLVElement, data: &TLVElement,
encoder: CmdDataEncoder, encoder: CmdDataEncoder,
) -> Result<(), Error> { ) -> Result<(), Error> {
OnOffCluster::invoke(self, exchange, cmd, data, encoder) OnOffCluster::invoke(self, transaction, cmd, data, encoder)
} }
} }

View file

@ -0,0 +1,301 @@
/*
*
* Copyright (c) 2020-2022 Project CHIP Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
use core::cell::RefCell;
use super::objects::*;
use crate::{
acl::{Accessor, AclMgr},
error::*,
interaction_model::core::{Interaction, Transaction},
tlv::TLVWriter,
transport::packet::Packet,
};
pub struct DataModel<'a, T> {
pub acl_mgr: &'a RefCell<AclMgr>,
pub node: &'a Node<'a>,
pub handler: T,
}
impl<'a, T> DataModel<'a, T> {
pub const fn new(acl_mgr: &'a RefCell<AclMgr>, node: &'a Node<'a>, handler: T) -> Self {
Self {
acl_mgr,
node,
handler,
}
}
pub fn handle(
&mut self,
interaction: Interaction,
tx: &mut Packet,
transaction: &mut Transaction,
) -> Result<bool, Error>
where
T: Handler,
{
let accessor = Accessor::for_session(transaction.session(), self.acl_mgr);
let mut tw = TLVWriter::new(tx.get_writebuf()?);
match interaction {
Interaction::Read(req) => {
let mut resume_path = None;
for item in self.node.read(&req, &accessor) {
if let Some(path) = AttrDataEncoder::handle_read(item, &self.handler, &mut tw)?
{
resume_path = Some(path);
break;
}
}
req.complete(tx, transaction, resume_path)
}
Interaction::Write(req) => {
for item in self.node.write(&req, &accessor) {
AttrDataEncoder::handle_write(item, &mut self.handler, &mut tw)?;
}
req.complete(tx, transaction)
}
Interaction::Invoke(req) => {
for item in self.node.invoke(&req, &accessor) {
CmdDataEncoder::handle(item, &mut self.handler, transaction, &mut tw)?;
}
req.complete(tx, transaction)
}
Interaction::Subscribe(req) => {
let mut resume_path = None;
for item in self.node.subscribing_read(&req, &accessor) {
if let Some(path) = AttrDataEncoder::handle_read(item, &self.handler, &mut tw)?
{
resume_path = Some(path);
break;
}
}
req.complete(tx, transaction, resume_path)
}
Interaction::Timed(_) => Ok(false),
Interaction::ResumeRead(req) => {
let mut resume_path = None;
for item in self.node.resume_read(&req, &accessor) {
if let Some(path) = AttrDataEncoder::handle_read(item, &self.handler, &mut tw)?
{
resume_path = Some(path);
break;
}
}
req.complete(tx, transaction, resume_path)
}
Interaction::ResumeSubscribe(req) => {
let mut resume_path = None;
for item in self.node.resume_subscribing_read(&req, &accessor) {
if let Some(path) = AttrDataEncoder::handle_read(item, &self.handler, &mut tw)?
{
resume_path = Some(path);
break;
}
}
req.complete(tx, transaction, resume_path)
}
}
}
#[cfg(feature = "nightly")]
pub async fn handle_async<'p>(
&mut self,
interaction: Interaction<'_>,
tx: &'p mut Packet<'_>,
transaction: &mut Transaction<'_, '_>,
) -> Result<bool, Error>
where
T: super::objects::asynch::AsyncHandler,
{
let accessor = Accessor::for_session(transaction.session(), self.acl_mgr);
let mut tw = TLVWriter::new(tx.get_writebuf()?);
match interaction {
Interaction::Read(req) => {
let mut resume_path = None;
for item in self.node.read(&req, &accessor) {
if let Some(path) =
AttrDataEncoder::handle_read_async(item, &self.handler, &mut tw).await?
{
resume_path = Some(path);
break;
}
}
req.complete(tx, transaction, resume_path)
}
Interaction::Write(req) => {
for item in self.node.write(&req, &accessor) {
AttrDataEncoder::handle_write_async(item, &mut self.handler, &mut tw).await?;
}
req.complete(tx, transaction)
}
Interaction::Invoke(req) => {
for item in self.node.invoke(&req, &accessor) {
CmdDataEncoder::handle_async(item, &mut self.handler, transaction, &mut tw)
.await?;
}
req.complete(tx, transaction)
}
Interaction::Subscribe(req) => {
let mut resume_path = None;
for item in self.node.subscribing_read(&req, &accessor) {
if let Some(path) =
AttrDataEncoder::handle_read_async(item, &self.handler, &mut tw).await?
{
resume_path = Some(path);
break;
}
}
req.complete(tx, transaction, resume_path)
}
Interaction::Timed(_) => Ok(false),
Interaction::ResumeRead(req) => {
let mut resume_path = None;
for item in self.node.resume_read(&req, &accessor) {
if let Some(path) =
AttrDataEncoder::handle_read_async(item, &self.handler, &mut tw).await?
{
resume_path = Some(path);
break;
}
}
req.complete(tx, transaction, resume_path)
}
Interaction::ResumeSubscribe(req) => {
let mut resume_path = None;
for item in self.node.resume_subscribing_read(&req, &accessor) {
if let Some(path) =
AttrDataEncoder::handle_read_async(item, &self.handler, &mut tw).await?
{
resume_path = Some(path);
break;
}
}
req.complete(tx, transaction, resume_path)
}
}
}
}
pub trait DataHandler {
fn handle(
&mut self,
interaction: Interaction,
tx: &mut Packet,
transaction: &mut Transaction,
) -> Result<bool, Error>;
}
impl<T> DataHandler for &mut T
where
T: DataHandler,
{
fn handle(
&mut self,
interaction: Interaction,
tx: &mut Packet,
transaction: &mut Transaction,
) -> Result<bool, Error> {
(**self).handle(interaction, tx, transaction)
}
}
impl<'a, T> DataHandler for DataModel<'a, T>
where
T: Handler,
{
fn handle(
&mut self,
interaction: Interaction,
tx: &mut Packet,
transaction: &mut Transaction,
) -> Result<bool, Error> {
DataModel::handle(self, interaction, tx, transaction)
}
}
#[cfg(feature = "nightly")]
pub mod asynch {
use crate::{
data_model::objects::asynch::AsyncHandler,
error::Error,
interaction_model::core::{Interaction, Transaction},
transport::packet::Packet,
};
use super::DataModel;
pub trait AsyncDataHandler {
async fn handle(
&mut self,
interaction: Interaction<'_>,
tx: &mut Packet,
transaction: &mut Transaction,
) -> Result<bool, Error>;
}
impl<T> AsyncDataHandler for &mut T
where
T: AsyncDataHandler,
{
async fn handle(
&mut self,
interaction: Interaction<'_>,
tx: &mut Packet<'_>,
transaction: &mut Transaction<'_, '_>,
) -> Result<bool, Error> {
(**self).handle(interaction, tx, transaction).await
}
}
impl<'a, T> AsyncDataHandler for DataModel<'a, T>
where
T: AsyncHandler,
{
async fn handle(
&mut self,
interaction: Interaction<'_>,
tx: &mut Packet<'_>,
transaction: &mut Transaction<'_, '_>,
) -> Result<bool, Error> {
DataModel::handle_async(self, interaction, tx, transaction).await
}
}
}

View file

@ -14,7 +14,6 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
#![allow(clippy::bad_bit_mask)]
use crate::data_model::objects::GlobalElements; use crate::data_model::objects::GlobalElements;
@ -40,12 +39,9 @@ bitflags! {
const READ_PRIVILEGE_MASK = Self::NEED_VIEW.bits | Self::NEED_MANAGE.bits | Self::NEED_OPERATE.bits | Self::NEED_ADMIN.bits; const READ_PRIVILEGE_MASK = Self::NEED_VIEW.bits | Self::NEED_MANAGE.bits | Self::NEED_OPERATE.bits | Self::NEED_ADMIN.bits;
const WRITE_PRIVILEGE_MASK = Self::NEED_MANAGE.bits | Self::NEED_OPERATE.bits | Self::NEED_ADMIN.bits; const WRITE_PRIVILEGE_MASK = Self::NEED_MANAGE.bits | Self::NEED_OPERATE.bits | Self::NEED_ADMIN.bits;
const RV = Self::READ.bits | Self::NEED_VIEW.bits; const RV = Self::READ.bits | Self::NEED_VIEW.bits;
const RF = Self::READ.bits | Self::FAB_SCOPED.bits;
const RA = Self::READ.bits | Self::NEED_ADMIN.bits;
const RWVA = Self::READ.bits | Self::WRITE.bits | Self::NEED_VIEW.bits | Self::NEED_ADMIN.bits; const RWVA = Self::READ.bits | Self::WRITE.bits | Self::NEED_VIEW.bits | Self::NEED_ADMIN.bits;
const RWFA = Self::READ.bits | Self::WRITE.bits | Self::FAB_SCOPED.bits | Self::NEED_ADMIN.bits; const RWFA = Self::READ.bits | Self::WRITE.bits | Self::FAB_SCOPED.bits | Self::NEED_ADMIN.bits;
const RWVM = Self::READ.bits | Self::WRITE.bits | Self::NEED_VIEW.bits | Self::NEED_MANAGE.bits; const RWVM = Self::READ.bits | Self::WRITE.bits | Self::NEED_VIEW.bits | Self::NEED_MANAGE.bits;
const RWFVM = Self::READ.bits | Self::WRITE.bits | Self::FAB_SCOPED.bits |Self::NEED_VIEW.bits | Self::NEED_MANAGE.bits;
} }
} }
@ -76,16 +72,10 @@ bitflags! {
#[derive(Default)] #[derive(Default)]
pub struct Quality: u8 { pub struct Quality: u8 {
const NONE = 0x00; const NONE = 0x00;
const SCENE = 0x01; // Short: S const SCENE = 0x01;
const PERSISTENT = 0x02; // Short: N const PERSISTENT = 0x02;
const FIXED = 0x04; // Short: F const FIXED = 0x03;
const NULLABLE = 0x08; // Short: X const NULLABLE = 0x04;
const SN = Self::SCENE.bits | Self::PERSISTENT.bits;
const S = Self::SCENE.bits;
const N = Self::PERSISTENT.bits;
const F = Self::FIXED.bits;
const X = Self::NULLABLE.bits;
} }
} }

View file

@ -15,13 +15,11 @@
* limitations under the License. * limitations under the License.
*/ */
use core::cell::Cell;
use crate::utils::rand::Rand; use crate::utils::rand::Rand;
pub struct Dataver { pub struct Dataver {
ver: Cell<u32>, ver: u32,
changed: Cell<bool>, changed: bool,
} }
impl Dataver { impl Dataver {
@ -30,25 +28,25 @@ impl Dataver {
rand(&mut buf); rand(&mut buf);
Self { Self {
ver: Cell::new(u32::from_be_bytes(buf)), ver: u32::from_be_bytes(buf),
changed: Cell::new(false), changed: false,
} }
} }
pub fn get(&self) -> u32 { pub fn get(&self) -> u32 {
self.ver.get() self.ver
} }
pub fn changed(&self) -> u32 { pub fn changed(&mut self) -> u32 {
self.ver.set(self.ver.get().overflowing_add(1).0); (self.ver, _) = self.ver.overflowing_add(1);
self.changed.set(true); self.changed = true;
self.get() self.get()
} }
pub fn consume_change<T>(&self, change: T) -> Option<T> { pub fn consume_change<T>(&mut self, change: T) -> Option<T> {
if self.changed.get() { if self.changed {
self.changed.set(false); self.changed = false;
Some(change) Some(change)
} else { } else {
None None

View file

@ -19,12 +19,12 @@ use core::fmt::{Debug, Formatter};
use core::marker::PhantomData; use core::marker::PhantomData;
use core::ops::{Deref, DerefMut}; use core::ops::{Deref, DerefMut};
use crate::interaction_model::core::IMStatusCode; use crate::interaction_model::core::{IMStatusCode, Transaction};
use crate::interaction_model::messages::ib::{ use crate::interaction_model::messages::ib::{
AttrPath, AttrResp, AttrStatus, CmdDataTag, CmdPath, CmdStatus, InvResp, InvRespTag, AttrPath, AttrResp, AttrStatus, CmdDataTag, CmdPath, CmdStatus, InvResp, InvRespTag,
}; };
use crate::interaction_model::messages::GenericPath;
use crate::tlv::UtfStr; use crate::tlv::UtfStr;
use crate::transport::exchange::Exchange;
use crate::{ use crate::{
error::{Error, ErrorCode}, error::{Error, ErrorCode},
interaction_model::messages::ib::{AttrDataTag, AttrRespTag}, interaction_model::messages::ib::{AttrDataTag, AttrRespTag},
@ -32,7 +32,7 @@ use crate::{
}; };
use log::error; use log::error;
use super::{AttrDetails, CmdDetails, DataModelHandler}; use super::{AttrDetails, CmdDetails, Handler};
// TODO: Should this return an IMStatusCode Error? But if yes, the higher layer // TODO: Should this return an IMStatusCode Error? But if yes, the higher layer
// may have already started encoding the 'success' headers, we might not want to manage // may have already started encoding the 'success' headers, we might not want to manage
@ -124,73 +124,47 @@ pub struct AttrDataEncoder<'a, 'b, 'c> {
} }
impl<'a, 'b, 'c> AttrDataEncoder<'a, 'b, 'c> { impl<'a, 'b, 'c> AttrDataEncoder<'a, 'b, 'c> {
pub async fn handle_read<T: DataModelHandler>( pub fn handle_read<T: Handler>(
item: &Result<AttrDetails<'_>, AttrStatus>, item: Result<AttrDetails, AttrStatus>,
handler: &T, handler: &T,
tw: &mut TLVWriter<'_, '_>, tw: &mut TLVWriter,
) -> Result<bool, Error> { ) -> Result<Option<GenericPath>, Error> {
let status = match item { let status = match item {
Ok(attr) => { Ok(attr) => {
let encoder = AttrDataEncoder::new(attr, tw); let encoder = AttrDataEncoder::new(&attr, tw);
let result = { match handler.read(&attr, encoder) {
#[cfg(not(feature = "nightly"))]
{
handler.read(attr, encoder)
}
#[cfg(feature = "nightly")]
{
handler.read(attr, encoder).await
}
};
match result {
Ok(()) => None, Ok(()) => None,
Err(e) => { Err(e) => {
if e.code() == ErrorCode::NoSpace { if e.code() == ErrorCode::NoSpace {
return Ok(false); return Ok(Some(attr.path().to_gp()));
} else { } else {
attr.status(e.into())? attr.status(e.into())?
} }
} }
} }
} }
Err(status) => Some(status.clone()), Err(status) => Some(status),
}; };
if let Some(status) = status { if let Some(status) = status {
AttrResp::Status(status).to_tlv(tw, TagType::Anonymous)?; AttrResp::Status(status).to_tlv(tw, TagType::Anonymous)?;
} }
Ok(true) Ok(None)
} }
pub async fn handle_write<T: DataModelHandler>( pub fn handle_write<T: Handler>(
item: &Result<(AttrDetails<'_>, TLVElement<'_>), AttrStatus>, item: Result<(AttrDetails, TLVElement), AttrStatus>,
handler: &T, handler: &mut T,
tw: &mut TLVWriter<'_, '_>, tw: &mut TLVWriter,
) -> Result<(), Error> { ) -> Result<(), Error> {
let status = match item { let status = match item {
Ok((attr, data)) => { Ok((attr, data)) => match handler.write(&attr, AttrData::new(attr.dataver, &data)) {
let result = { Ok(()) => attr.status(IMStatusCode::Success)?,
#[cfg(not(feature = "nightly"))] Err(error) => attr.status(error.into())?,
{ },
handler.write(attr, AttrData::new(attr.dataver, data)) Err(status) => Some(status),
}
#[cfg(feature = "nightly")]
{
handler.write(attr, AttrData::new(attr.dataver, data)).await
}
};
match result {
Ok(()) => attr.status(IMStatusCode::Success)?,
Err(error) => attr.status(error.into())?,
}
}
Err(status) => Some(status.clone()),
}; };
if let Some(status) = status { if let Some(status) = status {
@ -200,6 +174,61 @@ impl<'a, 'b, 'c> AttrDataEncoder<'a, 'b, 'c> {
Ok(()) Ok(())
} }
#[cfg(feature = "nightly")]
pub async fn handle_read_async<T: super::asynch::AsyncHandler>(
item: Result<AttrDetails<'_>, AttrStatus>,
handler: &T,
tw: &mut TLVWriter<'_, '_>,
) -> Result<Option<GenericPath>, Error> {
let status = match item {
Ok(attr) => {
let encoder = AttrDataEncoder::new(&attr, tw);
match handler.read(&attr, encoder).await {
Ok(()) => None,
Err(e) => {
if e.code() == ErrorCode::NoSpace {
return Ok(Some(attr.path().to_gp()));
} else {
attr.status(e.into())?
}
}
}
}
Err(status) => Some(status),
};
if let Some(status) = status {
AttrResp::Status(status).to_tlv(tw, TagType::Anonymous)?;
}
Ok(None)
}
#[cfg(feature = "nightly")]
pub async fn handle_write_async<T: super::asynch::AsyncHandler>(
item: Result<(AttrDetails<'_>, TLVElement<'_>), AttrStatus>,
handler: &mut T,
tw: &mut TLVWriter<'_, '_>,
) -> Result<(), Error> {
let status = match item {
Ok((attr, data)) => match handler
.write(&attr, AttrData::new(attr.dataver, &data))
.await
{
Ok(()) => attr.status(IMStatusCode::Success)?,
Err(error) => attr.status(error.into())?,
},
Err(status) => Some(status),
};
if let Some(status) = status {
AttrResp::Status(status).to_tlv(tw, TagType::Anonymous)?;
}
Ok(())
}
pub fn new(attr: &AttrDetails, tw: &'a mut TLVWriter<'b, 'c>) -> Self { pub fn new(attr: &AttrDetails, tw: &'a mut TLVWriter<'b, 'c>) -> Self {
Self { Self {
dataver_filter: attr.dataver, dataver_filter: attr.dataver,
@ -336,30 +365,18 @@ pub struct CmdDataEncoder<'a, 'b, 'c> {
} }
impl<'a, 'b, 'c> CmdDataEncoder<'a, 'b, 'c> { impl<'a, 'b, 'c> CmdDataEncoder<'a, 'b, 'c> {
pub async fn handle<T: DataModelHandler>( pub fn handle<T: Handler>(
item: &Result<(CmdDetails<'_>, TLVElement<'_>), CmdStatus>, item: Result<(CmdDetails, TLVElement), CmdStatus>,
handler: &T, handler: &mut T,
tw: &mut TLVWriter<'_, '_>, transaction: &mut Transaction,
exchange: &Exchange<'_>, tw: &mut TLVWriter,
) -> Result<(), Error> { ) -> Result<(), Error> {
let status = match item { let status = match item {
Ok((cmd, data)) => { Ok((cmd, data)) => {
let mut tracker = CmdDataTracker::new(); let mut tracker = CmdDataTracker::new();
let encoder = CmdDataEncoder::new(cmd, &mut tracker, tw); let encoder = CmdDataEncoder::new(&cmd, &mut tracker, tw);
let result = { match handler.invoke(transaction, &cmd, &data, encoder) {
#[cfg(not(feature = "nightly"))]
{
handler.invoke(exchange, cmd, data, encoder)
}
#[cfg(feature = "nightly")]
{
handler.invoke(exchange, cmd, data, encoder).await
}
};
match result {
Ok(()) => cmd.success(&tracker), Ok(()) => cmd.success(&tracker),
Err(error) => { Err(error) => {
error!("Error invoking command: {}", error); error!("Error invoking command: {}", error);
@ -369,7 +386,7 @@ impl<'a, 'b, 'c> CmdDataEncoder<'a, 'b, 'c> {
} }
Err(status) => { Err(status) => {
error!("Error invoking command: {:?}", status); error!("Error invoking command: {:?}", status);
Some(status.clone()) Some(status)
} }
}; };
@ -380,6 +397,33 @@ impl<'a, 'b, 'c> CmdDataEncoder<'a, 'b, 'c> {
Ok(()) Ok(())
} }
#[cfg(feature = "nightly")]
pub async fn handle_async<T: super::asynch::AsyncHandler>(
item: Result<(CmdDetails<'_>, TLVElement<'_>), CmdStatus>,
handler: &mut T,
transaction: &mut Transaction<'_, '_>,
tw: &mut TLVWriter<'_, '_>,
) -> Result<(), Error> {
let status = match item {
Ok((cmd, data)) => {
let mut tracker = CmdDataTracker::new();
let encoder = CmdDataEncoder::new(&cmd, &mut tracker, tw);
match handler.invoke(transaction, &cmd, &data, encoder).await {
Ok(()) => cmd.success(&tracker),
Err(error) => cmd.status(error.into()),
}
}
Err(status) => Some(status),
};
if let Some(status) = status {
InvResp::Status(status).to_tlv(tw, TagType::Anonymous)?;
}
Ok(())
}
pub fn new( pub fn new(
cmd: &CmdDetails, cmd: &CmdDetails,
tracker: &'a mut CmdDataTracker, tracker: &'a mut CmdDataTracker,

View file

@ -17,25 +17,12 @@
use crate::{ use crate::{
error::{Error, ErrorCode}, error::{Error, ErrorCode},
interaction_model::core::Transaction,
tlv::TLVElement, tlv::TLVElement,
transport::exchange::Exchange,
}; };
use super::{AttrData, AttrDataEncoder, AttrDetails, CmdDataEncoder, CmdDetails}; use super::{AttrData, AttrDataEncoder, AttrDetails, CmdDataEncoder, CmdDetails};
#[cfg(feature = "nightly")]
pub use asynch::*;
#[cfg(not(feature = "nightly"))]
pub trait DataModelHandler: super::Metadata + Handler {}
#[cfg(not(feature = "nightly"))]
impl<T> DataModelHandler for T where T: super::Metadata + Handler {}
#[cfg(feature = "nightly")]
pub trait DataModelHandler: super::asynch::AsyncMetadata + asynch::AsyncHandler {}
#[cfg(feature = "nightly")]
impl<T> DataModelHandler for T where T: super::asynch::AsyncMetadata + asynch::AsyncHandler {}
pub trait ChangeNotifier<T> { pub trait ChangeNotifier<T> {
fn consume_change(&mut self) -> Option<T>; fn consume_change(&mut self) -> Option<T>;
} }
@ -43,13 +30,13 @@ pub trait ChangeNotifier<T> {
pub trait Handler { pub trait Handler {
fn read(&self, attr: &AttrDetails, encoder: AttrDataEncoder) -> Result<(), Error>; fn read(&self, attr: &AttrDetails, encoder: AttrDataEncoder) -> Result<(), Error>;
fn write(&self, _attr: &AttrDetails, _data: AttrData) -> Result<(), Error> { fn write(&mut self, _attr: &AttrDetails, _data: AttrData) -> Result<(), Error> {
Err(ErrorCode::AttributeNotFound.into()) Err(ErrorCode::AttributeNotFound.into())
} }
fn invoke( fn invoke(
&self, &mut self,
_exchange: &Exchange, _transaction: &mut Transaction,
_cmd: &CmdDetails, _cmd: &CmdDetails,
_data: &TLVElement, _data: &TLVElement,
_encoder: CmdDataEncoder, _encoder: CmdDataEncoder,
@ -58,29 +45,6 @@ pub trait Handler {
} }
} }
impl<T> Handler for &T
where
T: Handler,
{
fn read(&self, attr: &AttrDetails, encoder: AttrDataEncoder) -> Result<(), Error> {
(**self).read(attr, encoder)
}
fn write(&self, attr: &AttrDetails, data: AttrData) -> Result<(), Error> {
(**self).write(attr, data)
}
fn invoke(
&self,
exchange: &Exchange,
cmd: &CmdDetails,
data: &TLVElement,
encoder: CmdDataEncoder,
) -> Result<(), Error> {
(**self).invoke(exchange, cmd, data, encoder)
}
}
impl<T> Handler for &mut T impl<T> Handler for &mut T
where where
T: Handler, T: Handler,
@ -89,52 +53,25 @@ where
(**self).read(attr, encoder) (**self).read(attr, encoder)
} }
fn write(&self, attr: &AttrDetails, data: AttrData) -> Result<(), Error> { fn write(&mut self, attr: &AttrDetails, data: AttrData) -> Result<(), Error> {
(**self).write(attr, data) (**self).write(attr, data)
} }
fn invoke( fn invoke(
&self, &mut self,
exchange: &Exchange, transaction: &mut Transaction,
cmd: &CmdDetails, cmd: &CmdDetails,
data: &TLVElement, data: &TLVElement,
encoder: CmdDataEncoder, encoder: CmdDataEncoder,
) -> Result<(), Error> { ) -> Result<(), Error> {
(**self).invoke(exchange, cmd, data, encoder) (**self).invoke(transaction, cmd, data, encoder)
} }
} }
pub trait NonBlockingHandler: Handler {} pub trait NonBlockingHandler: Handler {}
impl<T> NonBlockingHandler for &T where T: NonBlockingHandler {}
impl<T> NonBlockingHandler for &mut T where T: NonBlockingHandler {} impl<T> NonBlockingHandler for &mut T where T: NonBlockingHandler {}
impl<M, H> Handler for (M, H)
where
H: Handler,
{
fn read(&self, attr: &AttrDetails, encoder: AttrDataEncoder) -> Result<(), Error> {
self.1.read(attr, encoder)
}
fn write(&self, attr: &AttrDetails, data: AttrData) -> Result<(), Error> {
self.1.write(attr, data)
}
fn invoke(
&self,
exchange: &Exchange,
cmd: &CmdDetails,
data: &TLVElement,
encoder: CmdDataEncoder,
) -> Result<(), Error> {
self.1.invoke(exchange, cmd, data, encoder)
}
}
impl<M, H> NonBlockingHandler for (M, H) where H: NonBlockingHandler {}
pub struct EmptyHandler; pub struct EmptyHandler;
impl EmptyHandler { impl EmptyHandler {
@ -203,7 +140,7 @@ where
} }
} }
fn write(&self, attr: &AttrDetails, data: AttrData) -> Result<(), Error> { fn write(&mut self, attr: &AttrDetails, data: AttrData) -> Result<(), Error> {
if self.handler_endpoint == attr.endpoint_id && self.handler_cluster == attr.cluster_id { if self.handler_endpoint == attr.endpoint_id && self.handler_cluster == attr.cluster_id {
self.handler.write(attr, data) self.handler.write(attr, data)
} else { } else {
@ -212,16 +149,16 @@ where
} }
fn invoke( fn invoke(
&self, &mut self,
exchange: &Exchange, transaction: &mut Transaction,
cmd: &CmdDetails, cmd: &CmdDetails,
data: &TLVElement, data: &TLVElement,
encoder: CmdDataEncoder, encoder: CmdDataEncoder,
) -> Result<(), Error> { ) -> Result<(), Error> {
if self.handler_endpoint == cmd.endpoint_id && self.handler_cluster == cmd.cluster_id { if self.handler_endpoint == cmd.endpoint_id && self.handler_cluster == cmd.cluster_id {
self.handler.invoke(exchange, cmd, data, encoder) self.handler.invoke(transaction, cmd, data, encoder)
} else { } else {
self.next.invoke(exchange, cmd, data, encoder) self.next.invoke(transaction, cmd, data, encoder)
} }
} }
} }
@ -247,35 +184,6 @@ where
} }
} }
/// Wrap your `NonBlockingHandler` or `AsyncHandler` implementation in this struct
/// to get your code compilable with and without the `nightly` feature
pub struct HandlerCompat<T>(pub T);
impl<T> Handler for HandlerCompat<T>
where
T: Handler,
{
fn read(&self, attr: &AttrDetails, encoder: AttrDataEncoder) -> Result<(), Error> {
self.0.read(attr, encoder)
}
fn write(&self, attr: &AttrDetails, data: AttrData) -> Result<(), Error> {
self.0.write(attr, data)
}
fn invoke(
&self,
exchange: &Exchange,
cmd: &CmdDetails,
data: &TLVElement,
encoder: CmdDataEncoder,
) -> Result<(), Error> {
self.0.invoke(exchange, cmd, data, encoder)
}
}
impl<T> NonBlockingHandler for HandlerCompat<T> where T: NonBlockingHandler {}
#[allow(unused_macros)] #[allow(unused_macros)]
#[macro_export] #[macro_export]
macro_rules! handler_chain_type { macro_rules! handler_chain_type {
@ -295,15 +203,15 @@ macro_rules! handler_chain_type {
} }
#[cfg(feature = "nightly")] #[cfg(feature = "nightly")]
mod asynch { pub mod asynch {
use crate::{ use crate::{
data_model::objects::{AttrData, AttrDataEncoder, AttrDetails, CmdDataEncoder, CmdDetails}, data_model::objects::{AttrData, AttrDataEncoder, AttrDetails, CmdDataEncoder, CmdDetails},
error::{Error, ErrorCode}, error::{Error, ErrorCode},
interaction_model::core::Transaction,
tlv::TLVElement, tlv::TLVElement,
transport::exchange::Exchange,
}; };
use super::{ChainedHandler, EmptyHandler, Handler, HandlerCompat, NonBlockingHandler}; use super::{ChainedHandler, EmptyHandler, Handler, NonBlockingHandler};
pub trait AsyncHandler { pub trait AsyncHandler {
async fn read<'a>( async fn read<'a>(
@ -313,7 +221,7 @@ mod asynch {
) -> Result<(), Error>; ) -> Result<(), Error>;
async fn write<'a>( async fn write<'a>(
&'a self, &'a mut self,
_attr: &'a AttrDetails<'_>, _attr: &'a AttrDetails<'_>,
_data: AttrData<'a>, _data: AttrData<'a>,
) -> Result<(), Error> { ) -> Result<(), Error> {
@ -321,8 +229,8 @@ mod asynch {
} }
async fn invoke<'a>( async fn invoke<'a>(
&'a self, &'a mut self,
_exchange: &'a Exchange<'_>, _transaction: &'a mut Transaction<'_, '_>,
_cmd: &'a CmdDetails<'_>, _cmd: &'a CmdDetails<'_>,
_data: &'a TLVElement<'_>, _data: &'a TLVElement<'_>,
_encoder: CmdDataEncoder<'a, '_, '_>, _encoder: CmdDataEncoder<'a, '_, '_>,
@ -344,7 +252,7 @@ mod asynch {
} }
async fn write<'a>( async fn write<'a>(
&'a self, &'a mut self,
attr: &'a AttrDetails<'_>, attr: &'a AttrDetails<'_>,
data: AttrData<'a>, data: AttrData<'a>,
) -> Result<(), Error> { ) -> Result<(), Error> {
@ -352,79 +260,19 @@ mod asynch {
} }
async fn invoke<'a>( async fn invoke<'a>(
&'a self, &'a mut self,
exchange: &'a Exchange<'_>, transaction: &'a mut Transaction<'_, '_>,
cmd: &'a CmdDetails<'_>, cmd: &'a CmdDetails<'_>,
data: &'a TLVElement<'_>, data: &'a TLVElement<'_>,
encoder: CmdDataEncoder<'a, '_, '_>, encoder: CmdDataEncoder<'a, '_, '_>,
) -> Result<(), Error> { ) -> Result<(), Error> {
(**self).invoke(exchange, cmd, data, encoder).await (**self).invoke(transaction, cmd, data, encoder).await
} }
} }
impl<T> AsyncHandler for &T pub struct Asyncify<T>(pub T);
where
T: AsyncHandler,
{
async fn read<'a>(
&'a self,
attr: &'a AttrDetails<'_>,
encoder: AttrDataEncoder<'a, '_, '_>,
) -> Result<(), Error> {
(**self).read(attr, encoder).await
}
async fn write<'a>( impl<T> AsyncHandler for Asyncify<T>
&'a self,
attr: &'a AttrDetails<'_>,
data: AttrData<'a>,
) -> Result<(), Error> {
(**self).write(attr, data).await
}
async fn invoke<'a>(
&'a self,
exchange: &'a Exchange<'_>,
cmd: &'a CmdDetails<'_>,
data: &'a TLVElement<'_>,
encoder: CmdDataEncoder<'a, '_, '_>,
) -> Result<(), Error> {
(**self).invoke(exchange, cmd, data, encoder).await
}
}
impl<M, H> AsyncHandler for (M, H)
where
H: AsyncHandler,
{
async fn read<'a>(
&'a self,
attr: &'a AttrDetails<'_>,
encoder: AttrDataEncoder<'a, '_, '_>,
) -> Result<(), Error> {
self.1.read(attr, encoder).await
}
async fn write<'a>(
&'a self,
attr: &'a AttrDetails<'_>,
data: AttrData<'a>,
) -> Result<(), Error> {
self.1.write(attr, data).await
}
async fn invoke<'a>(
&'a self,
exchange: &'a Exchange<'_>,
cmd: &'a CmdDetails<'_>,
data: &'a TLVElement<'_>,
encoder: CmdDataEncoder<'a, '_, '_>,
) -> Result<(), Error> {
self.1.invoke(exchange, cmd, data, encoder).await
}
}
impl<T> AsyncHandler for HandlerCompat<T>
where where
T: NonBlockingHandler, T: NonBlockingHandler,
{ {
@ -437,21 +285,21 @@ mod asynch {
} }
async fn write<'a>( async fn write<'a>(
&'a self, &'a mut self,
attr: &'a AttrDetails<'_>, attr: &'a AttrDetails<'_>,
data: AttrData<'a>, data: AttrData<'a>,
) -> Result<(), Error> { ) -> Result<(), Error> {
Handler::write(&self.0, attr, data) Handler::write(&mut self.0, attr, data)
} }
async fn invoke<'a>( async fn invoke<'a>(
&'a self, &'a mut self,
exchange: &'a Exchange<'_>, transaction: &'a mut Transaction<'_, '_>,
cmd: &'a CmdDetails<'_>, cmd: &'a CmdDetails<'_>,
data: &'a TLVElement<'_>, data: &'a TLVElement<'_>,
encoder: CmdDataEncoder<'a, '_, '_>, encoder: CmdDataEncoder<'a, '_, '_>,
) -> Result<(), Error> { ) -> Result<(), Error> {
Handler::invoke(&self.0, exchange, cmd, data, encoder) Handler::invoke(&mut self.0, transaction, cmd, data, encoder)
} }
} }
@ -484,7 +332,7 @@ mod asynch {
} }
async fn write<'a>( async fn write<'a>(
&'a self, &'a mut self,
attr: &'a AttrDetails<'_>, attr: &'a AttrDetails<'_>,
data: AttrData<'a>, data: AttrData<'a>,
) -> Result<(), Error> { ) -> Result<(), Error> {
@ -497,16 +345,16 @@ mod asynch {
} }
async fn invoke<'a>( async fn invoke<'a>(
&'a self, &'a mut self,
exchange: &'a Exchange<'_>, transaction: &'a mut Transaction<'_, '_>,
cmd: &'a CmdDetails<'_>, cmd: &'a CmdDetails<'_>,
data: &'a TLVElement<'_>, data: &'a TLVElement<'_>,
encoder: CmdDataEncoder<'a, '_, '_>, encoder: CmdDataEncoder<'a, '_, '_>,
) -> Result<(), Error> { ) -> Result<(), Error> {
if self.handler_endpoint == cmd.endpoint_id && self.handler_cluster == cmd.cluster_id { if self.handler_endpoint == cmd.endpoint_id && self.handler_cluster == cmd.cluster_id {
self.handler.invoke(exchange, cmd, data, encoder).await self.handler.invoke(transaction, cmd, data, encoder).await
} else { } else {
self.next.invoke(exchange, cmd, data, encoder).await self.next.invoke(transaction, cmd, data, encoder).await
} }
} }
} }

View file

@ -41,9 +41,6 @@ pub use handler::*;
mod dataver; mod dataver;
pub use dataver::*; pub use dataver::*;
mod metadata;
pub use metadata::*;
pub type EndptId = u16; pub type EndptId = u16;
pub type ClusterId = u32; pub type ClusterId = u32;
pub type AttrId = u16; pub type AttrId = u16;

View file

@ -17,10 +17,9 @@
use crate::{ use crate::{
acl::Accessor, acl::Accessor,
alloc,
data_model::objects::Endpoint, data_model::objects::Endpoint,
interaction_model::{ interaction_model::{
core::IMStatusCode, core::{IMStatusCode, ResumeReadReq, ResumeSubscribeReq},
messages::{ messages::{
ib::{AttrPath, AttrStatus, CmdStatus, DataVersionFilter}, ib::{AttrPath, AttrStatus, CmdStatus, DataVersionFilter},
msg::{InvReq, ReadReq, SubscribeReq, WriteReq}, msg::{InvReq, ReadReq, SubscribeReq, WriteReq},
@ -28,7 +27,7 @@ use crate::{
}, },
}, },
// TODO: This layer shouldn't really depend on the TLV layer, should create an abstraction layer // TODO: This layer shouldn't really depend on the TLV layer, should create an abstraction layer
tlv::{TLVArray, TLVElement}, tlv::{TLVArray, TLVArrayIter, TLVElement},
}; };
use core::{ use core::{
fmt, fmt,
@ -58,6 +57,41 @@ where
} }
} }
pub trait Iterable {
type Item;
type Iterator<'a>: Iterator<Item = Self::Item>
where
Self: 'a;
fn iter(&self) -> Self::Iterator<'_>;
}
impl<'a> Iterable for Option<&'a TLVArray<'a, DataVersionFilter>> {
type Item = DataVersionFilter;
type Iterator<'i> = WildcardIter<TLVArrayIter<'i, DataVersionFilter>, DataVersionFilter> where Self: 'i;
fn iter(&self) -> Self::Iterator<'_> {
if let Some(filters) = self {
WildcardIter::Wildcard(filters.iter())
} else {
WildcardIter::None
}
}
}
impl<'a> Iterable for &'a [DataVersionFilter] {
type Item = DataVersionFilter;
type Iterator<'i> = core::iter::Cloned<core::slice::Iter<'i, DataVersionFilter>> where Self: 'i;
fn iter(&self) -> Self::Iterator<'_> {
let slice: &[DataVersionFilter] = self;
slice.iter().cloned()
}
}
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct Node<'a> { pub struct Node<'a> {
pub id: u16, pub id: u16,
@ -68,7 +102,6 @@ impl<'a> Node<'a> {
pub fn read<'s, 'm>( pub fn read<'s, 'm>(
&'s self, &'s self,
req: &'m ReadReq, req: &'m ReadReq,
from: Option<GenericPath>,
accessor: &'m Accessor<'m>, accessor: &'m Accessor<'m>,
) -> impl Iterator<Item = Result<AttrDetails, AttrStatus>> + 'm ) -> impl Iterator<Item = Result<AttrDetails, AttrStatus>> + 'm
where where
@ -81,14 +114,30 @@ impl<'a> Node<'a> {
req.dataver_filters.as_ref(), req.dataver_filters.as_ref(),
req.fabric_filtered, req.fabric_filtered,
accessor, accessor,
from, None,
)
}
pub fn resume_read<'s, 'm>(
&'s self,
req: &'m ResumeReadReq,
accessor: &'m Accessor<'m>,
) -> impl Iterator<Item = Result<AttrDetails, AttrStatus>> + 'm
where
's: 'm,
{
self.read_attr_requests(
req.paths.iter().cloned(),
req.filters.as_slice(),
req.fabric_filtered,
accessor,
Some(req.resume_path.clone()),
) )
} }
pub fn subscribing_read<'s, 'm>( pub fn subscribing_read<'s, 'm>(
&'s self, &'s self,
req: &'m SubscribeReq, req: &'m SubscribeReq,
from: Option<GenericPath>,
accessor: &'m Accessor<'m>, accessor: &'m Accessor<'m>,
) -> impl Iterator<Item = Result<AttrDetails, AttrStatus>> + 'm ) -> impl Iterator<Item = Result<AttrDetails, AttrStatus>> + 'm
where where
@ -101,14 +150,31 @@ impl<'a> Node<'a> {
req.dataver_filters.as_ref(), req.dataver_filters.as_ref(),
req.fabric_filtered, req.fabric_filtered,
accessor, accessor,
from, None,
) )
} }
fn read_attr_requests<'s, 'm, P>( pub fn resume_subscribing_read<'s, 'm>(
&'s self,
req: &'m ResumeSubscribeReq,
accessor: &'m Accessor<'m>,
) -> impl Iterator<Item = Result<AttrDetails, AttrStatus>> + 'm
where
's: 'm,
{
self.read_attr_requests(
req.paths.iter().cloned(),
req.filters.as_slice(),
req.fabric_filtered,
accessor,
Some(req.resume_path.clone().unwrap()),
)
}
fn read_attr_requests<'s, 'm, P, D>(
&'s self, &'s self,
attr_requests: P, attr_requests: P,
dataver_filters: Option<&'m TLVArray<DataVersionFilter>>, dataver_filters: D,
fabric_filtered: bool, fabric_filtered: bool,
accessor: &'m Accessor<'m>, accessor: &'m Accessor<'m>,
from: Option<GenericPath>, from: Option<GenericPath>,
@ -116,9 +182,11 @@ impl<'a> Node<'a> {
where where
's: 'm, 's: 'm,
P: Iterator<Item = AttrPath> + 'm, P: Iterator<Item = AttrPath> + 'm,
D: Iterable<Item = DataVersionFilter> + Clone + 'm,
{ {
alloc!(attr_requests.flat_map(move |path| { attr_requests.flat_map(move |path| {
if path.to_gp().is_wildcard() { if path.to_gp().is_wildcard() {
let dataver_filters = dataver_filters.clone();
let from = from.clone(); let from = from.clone();
let iter = self let iter = self
@ -136,14 +204,10 @@ impl<'a> Node<'a> {
.is_ok() .is_ok()
}) })
.map(move |(ep, cl, attr)| { .map(move |(ep, cl, attr)| {
let dataver = if let Some(dataver_filters) = dataver_filters { let dataver = dataver_filters.iter().find_map(|filter| {
dataver_filters.iter().find_map(|filter| { (filter.path.endpoint == ep.id && filter.path.cluster == cl.id)
(filter.path.endpoint == ep.id && filter.path.cluster == cl.id) .then_some(filter.data_ver)
.then_some(filter.data_ver) });
})
} else {
None
};
Ok(AttrDetails { Ok(AttrDetails {
node: self, node: self,
@ -166,14 +230,10 @@ impl<'a> Node<'a> {
let result = match self.check_attribute(accessor, ep, cl, attr, false) { let result = match self.check_attribute(accessor, ep, cl, attr, false) {
Ok(()) => { Ok(()) => {
let dataver = if let Some(dataver_filters) = dataver_filters { let dataver = dataver_filters.iter().find_map(|filter| {
dataver_filters.iter().find_map(|filter| { (filter.path.endpoint == ep && filter.path.cluster == cl)
(filter.path.endpoint == ep && filter.path.cluster == cl) .then_some(filter.data_ver)
.then_some(filter.data_ver) });
})
} else {
None
};
Ok(AttrDetails { Ok(AttrDetails {
node: self, node: self,
@ -192,7 +252,7 @@ impl<'a> Node<'a> {
WildcardIter::Single(once(result)) WildcardIter::Single(once(result))
} }
})) })
} }
pub fn write<'m>( pub fn write<'m>(
@ -200,7 +260,7 @@ impl<'a> Node<'a> {
req: &'m WriteReq, req: &'m WriteReq,
accessor: &'m Accessor<'m>, accessor: &'m Accessor<'m>,
) -> impl Iterator<Item = Result<(AttrDetails, TLVElement<'m>), AttrStatus>> + 'm { ) -> impl Iterator<Item = Result<(AttrDetails, TLVElement<'m>), AttrStatus>> + 'm {
alloc!(req.write_requests.iter().flat_map(move |attr_data| { req.write_requests.iter().flat_map(move |attr_data| {
if attr_data.path.cluster.is_none() { if attr_data.path.cluster.is_none() {
WildcardIter::Single(once(Err(AttrStatus::new( WildcardIter::Single(once(Err(AttrStatus::new(
&attr_data.path.to_gp(), &attr_data.path.to_gp(),
@ -272,7 +332,7 @@ impl<'a> Node<'a> {
WildcardIter::Single(once(result)) WildcardIter::Single(once(result))
} }
})) })
} }
pub fn invoke<'m>( pub fn invoke<'m>(
@ -280,8 +340,7 @@ impl<'a> Node<'a> {
req: &'m InvReq, req: &'m InvReq,
accessor: &'m Accessor<'m>, accessor: &'m Accessor<'m>,
) -> impl Iterator<Item = Result<(CmdDetails, TLVElement<'m>), CmdStatus>> + 'm { ) -> impl Iterator<Item = Result<(CmdDetails, TLVElement<'m>), CmdStatus>> + 'm {
alloc!(req req.inv_requests
.inv_requests
.iter() .iter()
.flat_map(|inv_requests| inv_requests.iter()) .flat_map(|inv_requests| inv_requests.iter())
.flat_map(move |cmd_data| { .flat_map(move |cmd_data| {
@ -334,7 +393,7 @@ impl<'a> Node<'a> {
WildcardIter::Single(once(result)) WildcardIter::Single(once(result))
} }
})) })
} }
fn matches(path: Option<&GenericPath>, ep: EndptId, cl: ClusterId, leaf: u32) -> bool { fn matches(path: Option<&GenericPath>, ep: EndptId, cl: ClusterId, leaf: u32) -> bool {

View file

@ -15,12 +15,8 @@ use super::{
sdm::{ sdm::{
admin_commissioning::{self, AdminCommCluster}, admin_commissioning::{self, AdminCommCluster},
dev_att::DevAttDataFetcher, dev_att::DevAttDataFetcher,
ethernet_nw_diagnostics::{self, EthNwDiagCluster},
failsafe::FailSafe, failsafe::FailSafe,
general_commissioning::{self, GenCommCluster}, general_commissioning::{self, GenCommCluster},
general_diagnostics::{self, GenDiagCluster},
group_key_management,
group_key_management::GrpKeyMgmtCluster,
noc::{self, NocCluster}, noc::{self, NocCluster},
nw_commissioning::{self, NwCommCluster}, nw_commissioning::{self, NwCommCluster},
}, },
@ -37,13 +33,10 @@ pub type RootEndpointHandler<'a> = handler_chain_type!(
NwCommCluster, NwCommCluster,
AdminCommCluster<'a>, AdminCommCluster<'a>,
NocCluster<'a>, NocCluster<'a>,
AccessControlCluster<'a>, AccessControlCluster<'a>
GenDiagCluster,
EthNwDiagCluster,
GrpKeyMgmtCluster
); );
pub const CLUSTERS: [Cluster<'static>; 10] = [ pub const CLUSTERS: [Cluster<'static>; 7] = [
descriptor::CLUSTER, descriptor::CLUSTER,
cluster_basic_information::CLUSTER, cluster_basic_information::CLUSTER,
general_commissioning::CLUSTER, general_commissioning::CLUSTER,
@ -51,12 +44,9 @@ pub const CLUSTERS: [Cluster<'static>; 10] = [
admin_commissioning::CLUSTER, admin_commissioning::CLUSTER,
noc::CLUSTER, noc::CLUSTER,
access_control::CLUSTER, access_control::CLUSTER,
general_diagnostics::CLUSTER,
ethernet_nw_diagnostics::CLUSTER,
group_key_management::CLUSTER,
]; ];
pub const fn endpoint(id: EndptId) -> Endpoint<'static> { pub fn endpoint(id: EndptId) -> Endpoint<'static> {
Endpoint { Endpoint {
id, id,
device_type: super::device_types::DEV_TYPE_ROOT_NODE, device_type: super::device_types::DEV_TYPE_ROOT_NODE,
@ -105,21 +95,6 @@ pub fn wrap<'a>(
rand: Rand, rand: Rand,
) -> RootEndpointHandler<'a> { ) -> RootEndpointHandler<'a> {
EmptyHandler EmptyHandler
.chain(
endpoint_id,
group_key_management::ID,
GrpKeyMgmtCluster::new(rand),
)
.chain(
endpoint_id,
ethernet_nw_diagnostics::ID,
EthNwDiagCluster::new(rand),
)
.chain(
endpoint_id,
general_diagnostics::ID,
GenDiagCluster::new(rand),
)
.chain( .chain(
endpoint_id, endpoint_id,
access_control::ID, access_control::ID,

View file

@ -19,11 +19,11 @@ use core::cell::RefCell;
use core::convert::TryInto; use core::convert::TryInto;
use crate::data_model::objects::*; use crate::data_model::objects::*;
use crate::interaction_model::core::Transaction;
use crate::mdns::Mdns; use crate::mdns::Mdns;
use crate::secure_channel::pake::PaseMgr; use crate::secure_channel::pake::PaseMgr;
use crate::secure_channel::spake2p::VerifierData; use crate::secure_channel::spake2p::VerifierData;
use crate::tlv::{FromTLV, Nullable, OctetStr, TLVElement}; use crate::tlv::{FromTLV, Nullable, OctetStr, TLVElement};
use crate::transport::exchange::Exchange;
use crate::utils::rand::Rand; use crate::utils::rand::Rand;
use crate::{attribute_enum, cmd_enter}; use crate::{attribute_enum, cmd_enter};
use crate::{command_enum, error::*}; use crate::{command_enum, error::*};
@ -84,8 +84,8 @@ pub const CLUSTER: Cluster<'static> = Cluster {
], ],
commands: &[ commands: &[
Commands::OpenCommWindow as _, Commands::OpenCommWindow as _,
// Commands::OpenBasicCommWindow as _, Commands::OpenBasicCommWindow as _,
// Commands::RevokeComm as _, Commands::RevokeComm as _,
], ],
}; };
@ -133,7 +133,7 @@ impl<'a> AdminCommCluster<'a> {
} }
pub fn invoke( pub fn invoke(
&self, &mut self,
cmd: &CmdDetails, cmd: &CmdDetails,
data: &TLVElement, data: &TLVElement,
_encoder: CmdDataEncoder, _encoder: CmdDataEncoder,
@ -148,7 +148,7 @@ impl<'a> AdminCommCluster<'a> {
Ok(()) Ok(())
} }
fn handle_command_opencomm_win(&self, data: &TLVElement) -> Result<(), Error> { fn handle_command_opencomm_win(&mut self, data: &TLVElement) -> Result<(), Error> {
cmd_enter!("Open Commissioning Window"); cmd_enter!("Open Commissioning Window");
let req = OpenCommWindowReq::from_tlv(data)?; let req = OpenCommWindowReq::from_tlv(data)?;
let verifier = VerifierData::new(req.verifier.0, req.iterations, req.salt.0); let verifier = VerifierData::new(req.verifier.0, req.iterations, req.salt.0);
@ -166,8 +166,8 @@ impl<'a> Handler for AdminCommCluster<'a> {
} }
fn invoke( fn invoke(
&self, &mut self,
_exchange: &Exchange, _transaction: &mut Transaction,
cmd: &CmdDetails, cmd: &CmdDetails,
data: &TLVElement, data: &TLVElement,
encoder: CmdDataEncoder, encoder: CmdDataEncoder,

View file

@ -34,7 +34,7 @@ enum NocState {
#[derive(PartialEq)] #[derive(PartialEq)]
pub struct ArmedCtx { pub struct ArmedCtx {
session_mode: SessionMode, session_mode: SessionMode,
timeout: u16, timeout: u8,
noc_state: NocState, noc_state: NocState,
} }
@ -54,7 +54,7 @@ impl FailSafe {
Self { state: State::Idle } Self { state: State::Idle }
} }
pub fn arm(&mut self, timeout: u16, session_mode: SessionMode) -> Result<(), Error> { pub fn arm(&mut self, timeout: u8, session_mode: SessionMode) -> Result<(), Error> {
match &mut self.state { match &mut self.state {
State::Idle => { State::Idle => {
self.state = State::Armed(ArmedCtx { self.state = State::Armed(ArmedCtx {

View file

@ -20,8 +20,8 @@ use core::convert::TryInto;
use crate::data_model::objects::*; use crate::data_model::objects::*;
use crate::data_model::sdm::failsafe::FailSafe; use crate::data_model::sdm::failsafe::FailSafe;
use crate::interaction_model::core::Transaction;
use crate::tlv::{FromTLV, TLVElement, TLVWriter, TagType, ToTLV, UtfStr}; use crate::tlv::{FromTLV, TLVElement, TLVWriter, TagType, ToTLV, UtfStr};
use crate::transport::exchange::Exchange;
use crate::utils::rand::Rand; use crate::utils::rand::Rand;
use crate::{attribute_enum, cmd_enter}; use crate::{attribute_enum, cmd_enter};
use crate::{command_enum, error::*}; use crate::{command_enum, error::*};
@ -117,19 +117,13 @@ pub const CLUSTER: Cluster<'static> = Cluster {
#[derive(FromTLV, ToTLV)] #[derive(FromTLV, ToTLV)]
struct FailSafeParams { struct FailSafeParams {
expiry_len: u16, expiry_len: u8,
bread_crumb: u64, bread_crumb: u8,
}
#[derive(ToTLV)]
struct BasicCommissioningInfo {
expiry_len: u16,
max_cmltv_failsafe_secs: u16,
} }
pub struct GenCommCluster<'a> { pub struct GenCommCluster<'a> {
data_ver: Dataver, data_ver: Dataver,
basic_comm_info: BasicCommissioningInfo, expiry_len: u16,
failsafe: &'a RefCell<FailSafe>, failsafe: &'a RefCell<FailSafe>,
} }
@ -139,10 +133,7 @@ impl<'a> GenCommCluster<'a> {
data_ver: Dataver::new(rand), data_ver: Dataver::new(rand),
failsafe, failsafe,
// TODO: Arch-Specific // TODO: Arch-Specific
basic_comm_info: BasicCommissioningInfo { expiry_len: 120,
expiry_len: 120,
max_cmltv_failsafe_secs: 120,
},
} }
} }
@ -166,8 +157,10 @@ impl<'a> GenCommCluster<'a> {
codec.encode(writer, RegLocationType::IndoorOutdoor as _) codec.encode(writer, RegLocationType::IndoorOutdoor as _)
} }
Attributes::BasicCommissioningInfo(_) => { Attributes::BasicCommissioningInfo(_) => {
self.basic_comm_info writer.start_struct(AttrDataWriter::TAG)?;
.to_tlv(&mut writer, AttrDataWriter::TAG)?; writer.u16(TagType::Context(0), self.expiry_len)?;
writer.end_container()?;
writer.complete() writer.complete()
} }
} }
@ -178,19 +171,19 @@ impl<'a> GenCommCluster<'a> {
} }
pub fn invoke( pub fn invoke(
&self, &mut self,
exchange: &Exchange, transaction: &mut Transaction,
cmd: &CmdDetails, cmd: &CmdDetails,
data: &TLVElement, data: &TLVElement,
encoder: CmdDataEncoder, encoder: CmdDataEncoder,
) -> Result<(), Error> { ) -> Result<(), Error> {
match cmd.cmd_id.try_into()? { match cmd.cmd_id.try_into()? {
Commands::ArmFailsafe => self.handle_command_armfailsafe(exchange, data, encoder)?, Commands::ArmFailsafe => self.handle_command_armfailsafe(transaction, data, encoder)?,
Commands::SetRegulatoryConfig => { Commands::SetRegulatoryConfig => {
self.handle_command_setregulatoryconfig(exchange, data, encoder)? self.handle_command_setregulatoryconfig(transaction, data, encoder)?
} }
Commands::CommissioningComplete => { Commands::CommissioningComplete => {
self.handle_command_commissioningcomplete(exchange, encoder)?; self.handle_command_commissioningcomplete(transaction, encoder)?;
} }
} }
@ -200,8 +193,8 @@ impl<'a> GenCommCluster<'a> {
} }
fn handle_command_armfailsafe( fn handle_command_armfailsafe(
&self, &mut self,
exchange: &Exchange, transaction: &mut Transaction,
data: &TLVElement, data: &TLVElement,
encoder: CmdDataEncoder, encoder: CmdDataEncoder,
) -> Result<(), Error> { ) -> Result<(), Error> {
@ -214,7 +207,7 @@ impl<'a> GenCommCluster<'a> {
.borrow_mut() .borrow_mut()
.arm( .arm(
p.expiry_len, p.expiry_len,
exchange.with_session(|sess| Ok(sess.get_session_mode().clone()))?, transaction.session().get_session_mode().clone(),
) )
.is_err() .is_err()
{ {
@ -232,12 +225,13 @@ impl<'a> GenCommCluster<'a> {
.with_command(RespCommands::ArmFailsafeResp as _)? .with_command(RespCommands::ArmFailsafeResp as _)?
.set(cmd_data)?; .set(cmd_data)?;
transaction.complete();
Ok(()) Ok(())
} }
fn handle_command_setregulatoryconfig( fn handle_command_setregulatoryconfig(
&self, &mut self,
_exchange: &Exchange, transaction: &mut Transaction,
data: &TLVElement, data: &TLVElement,
encoder: CmdDataEncoder, encoder: CmdDataEncoder,
) -> Result<(), Error> { ) -> Result<(), Error> {
@ -258,22 +252,20 @@ impl<'a> GenCommCluster<'a> {
.with_command(RespCommands::SetRegulatoryConfigResp as _)? .with_command(RespCommands::SetRegulatoryConfigResp as _)?
.set(cmd_data)?; .set(cmd_data)?;
transaction.complete();
Ok(()) Ok(())
} }
fn handle_command_commissioningcomplete( fn handle_command_commissioningcomplete(
&self, &mut self,
exchange: &Exchange, transaction: &mut Transaction,
encoder: CmdDataEncoder, encoder: CmdDataEncoder,
) -> Result<(), Error> { ) -> Result<(), Error> {
cmd_enter!("Commissioning Complete"); cmd_enter!("Commissioning Complete");
let mut status: u8 = CommissioningError::Ok as u8; let mut status: u8 = CommissioningError::Ok as u8;
// Has to be a Case Session // Has to be a Case Session
if exchange if transaction.session().get_local_fabric_idx().is_none() {
.with_session(|sess| Ok(sess.get_local_fabric_idx()))?
.is_none()
{
status = CommissioningError::ErrInvalidAuth as u8; status = CommissioningError::ErrInvalidAuth as u8;
} }
@ -282,7 +274,7 @@ impl<'a> GenCommCluster<'a> {
if self if self
.failsafe .failsafe
.borrow_mut() .borrow_mut()
.disarm(exchange.with_session(|sess| Ok(sess.get_session_mode().clone()))?) .disarm(transaction.session().get_session_mode().clone())
.is_err() .is_err()
{ {
status = CommissioningError::ErrInvalidAuth as u8; status = CommissioningError::ErrInvalidAuth as u8;
@ -297,6 +289,7 @@ impl<'a> GenCommCluster<'a> {
.with_command(RespCommands::CommissioningCompleteResp as _)? .with_command(RespCommands::CommissioningCompleteResp as _)?
.set(cmd_data)?; .set(cmd_data)?;
transaction.complete();
Ok(()) Ok(())
} }
} }
@ -307,13 +300,13 @@ impl<'a> Handler for GenCommCluster<'a> {
} }
fn invoke( fn invoke(
&self, &mut self,
exchange: &Exchange, transaction: &mut Transaction,
cmd: &CmdDetails, cmd: &CmdDetails,
data: &TLVElement, data: &TLVElement,
encoder: CmdDataEncoder, encoder: CmdDataEncoder,
) -> Result<(), Error> { ) -> Result<(), Error> {
GenCommCluster::invoke(self, exchange, cmd, data, encoder) GenCommCluster::invoke(self, transaction, cmd, data, encoder)
} }
} }

View file

@ -17,10 +17,7 @@
pub mod admin_commissioning; pub mod admin_commissioning;
pub mod dev_att; pub mod dev_att;
pub mod ethernet_nw_diagnostics;
pub mod failsafe; pub mod failsafe;
pub mod general_commissioning; pub mod general_commissioning;
pub mod general_diagnostics;
pub mod group_key_management;
pub mod noc; pub mod noc;
pub mod nw_commissioning; pub mod nw_commissioning;

View file

@ -24,9 +24,9 @@ use crate::crypto::{self, KeyPair};
use crate::data_model::objects::*; use crate::data_model::objects::*;
use crate::data_model::sdm::dev_att; use crate::data_model::sdm::dev_att;
use crate::fabric::{Fabric, FabricMgr, MAX_SUPPORTED_FABRICS}; use crate::fabric::{Fabric, FabricMgr, MAX_SUPPORTED_FABRICS};
use crate::interaction_model::core::Transaction;
use crate::mdns::Mdns; use crate::mdns::Mdns;
use crate::tlv::{FromTLV, OctetStr, TLVElement, TLVWriter, TagType, ToTLV, UtfStr}; use crate::tlv::{FromTLV, OctetStr, TLVElement, TLVWriter, TagType, ToTLV, UtfStr};
use crate::transport::exchange::Exchange;
use crate::transport::session::SessionMode; use crate::transport::session::SessionMode;
use crate::utils::epoch::Epoch; use crate::utils::epoch::Epoch;
use crate::utils::rand::Rand; use crate::utils::rand::Rand;
@ -186,7 +186,7 @@ struct NocResp<'a> {
#[tlvargs(lifetime = "'a")] #[tlvargs(lifetime = "'a")]
struct AddNocReq<'a> { struct AddNocReq<'a> {
noc_value: OctetStr<'a>, noc_value: OctetStr<'a>,
icac_value: Option<OctetStr<'a>>, icac_value: OctetStr<'a>,
ipk_value: OctetStr<'a>, ipk_value: OctetStr<'a>,
case_admin_subject: u64, case_admin_subject: u64,
vendor_id: u16, vendor_id: u16,
@ -289,26 +289,26 @@ impl<'a> NocCluster<'a> {
} }
pub fn invoke( pub fn invoke(
&self, &mut self,
exchange: &Exchange, transaction: &mut Transaction,
cmd: &CmdDetails, cmd: &CmdDetails,
data: &TLVElement, data: &TLVElement,
encoder: CmdDataEncoder, encoder: CmdDataEncoder,
) -> Result<(), Error> { ) -> Result<(), Error> {
match cmd.cmd_id.try_into()? { match cmd.cmd_id.try_into()? {
Commands::AddNOC => self.handle_command_addnoc(exchange, data, encoder)?, Commands::AddNOC => self.handle_command_addnoc(transaction, data, encoder)?,
Commands::CSRReq => self.handle_command_csrrequest(exchange, data, encoder)?, Commands::CSRReq => self.handle_command_csrrequest(transaction, data, encoder)?,
Commands::AddTrustedRootCert => { Commands::AddTrustedRootCert => {
self.handle_command_addtrustedrootcert(exchange, data)? self.handle_command_addtrustedrootcert(transaction, data)?
} }
Commands::AttReq => self.handle_command_attrequest(exchange, data, encoder)?, Commands::AttReq => self.handle_command_attrequest(transaction, data, encoder)?,
Commands::CertChainReq => { Commands::CertChainReq => {
self.handle_command_certchainrequest(exchange, data, encoder)? self.handle_command_certchainrequest(transaction, data, encoder)?
} }
Commands::UpdateFabricLabel => { Commands::UpdateFabricLabel => {
self.handle_command_updatefablabel(exchange, data, encoder)?; self.handle_command_updatefablabel(transaction, data, encoder)?;
} }
Commands::RemoveFabric => self.handle_command_rmfabric(exchange, data, encoder)?, Commands::RemoveFabric => self.handle_command_rmfabric(transaction, data, encoder)?,
} }
self.data_ver.changed(); self.data_ver.changed();
@ -323,12 +323,13 @@ impl<'a> NocCluster<'a> {
} }
fn _handle_command_addnoc( fn _handle_command_addnoc(
&self, &mut self,
exchange: &Exchange, transaction: &mut Transaction,
data: &TLVElement, data: &TLVElement,
) -> Result<u8, NocError> { ) -> Result<u8, NocError> {
let noc_data = exchange let noc_data = transaction
.with_session_mut(|sess| Ok(sess.take_noc_data()))? .session_mut()
.take_noc_data()
.ok_or(NocStatus::MissingCsr)?; .ok_or(NocStatus::MissingCsr)?;
if !self if !self
@ -358,17 +359,13 @@ impl<'a> NocCluster<'a> {
let noc = heapless::Vec::from_slice(r.noc_value.0).map_err(|_| NocStatus::InvalidNOC)?; let noc = heapless::Vec::from_slice(r.noc_value.0).map_err(|_| NocStatus::InvalidNOC)?;
let icac = if let Some(icac_value) = r.icac_value { let icac = if !r.icac_value.0.is_empty() {
if !icac_value.0.is_empty() { let icac_cert = Cert::new(r.icac_value.0).map_err(|_| NocStatus::InvalidNOC)?;
let icac_cert = Cert::new(icac_value.0).map_err(|_| NocStatus::InvalidNOC)?; info!("Received ICAC as: {}", icac_cert);
info!("Received ICAC as: {}", icac_cert);
let icac = let icac =
heapless::Vec::from_slice(icac_value.0).map_err(|_| NocStatus::InvalidNOC)?; heapless::Vec::from_slice(r.icac_value.0).map_err(|_| NocStatus::InvalidNOC)?;
Some(icac) Some(icac)
} else {
None
}
} else { } else {
None None
}; };
@ -414,42 +411,42 @@ impl<'a> NocCluster<'a> {
} }
fn handle_command_updatefablabel( fn handle_command_updatefablabel(
&self, &mut self,
exchange: &Exchange, transaction: &mut Transaction,
data: &TLVElement, data: &TLVElement,
encoder: CmdDataEncoder, encoder: CmdDataEncoder,
) -> Result<(), Error> { ) -> Result<(), Error> {
cmd_enter!("Update Fabric Label"); cmd_enter!("Update Fabric Label");
let req = UpdateFabricLabelReq::from_tlv(data).map_err(Error::map_invalid_data_type)?; let req = UpdateFabricLabelReq::from_tlv(data).map_err(Error::map_invalid_data_type)?;
let (result, fab_idx) = if let SessionMode::Case(c) = let (result, fab_idx) =
exchange.with_session(|sess| Ok(sess.get_session_mode().clone()))? if let SessionMode::Case(c) = transaction.session().get_session_mode() {
{ if self
if self .fabric_mgr
.fabric_mgr .borrow_mut()
.borrow_mut() .set_label(
.set_label( c.fab_idx,
c.fab_idx, req.label.as_str().map_err(Error::map_invalid_data_type)?,
req.label.as_str().map_err(Error::map_invalid_data_type)?, )
) .is_err()
.is_err() {
{ (NocStatus::LabelConflict, c.fab_idx)
(NocStatus::LabelConflict, c.fab_idx) } else {
(NocStatus::Ok, c.fab_idx)
}
} else { } else {
(NocStatus::Ok, c.fab_idx) // Update Fabric Label not allowed
} (NocStatus::InvalidFabricIndex, 0)
} else { };
// Update Fabric Label not allowed
(NocStatus::InvalidFabricIndex, 0)
};
Self::create_nocresponse(encoder, result, fab_idx, "")?; Self::create_nocresponse(encoder, result, fab_idx, "")?;
transaction.complete();
Ok(()) Ok(())
} }
fn handle_command_rmfabric( fn handle_command_rmfabric(
&self, &mut self,
_exchange: &Exchange, transaction: &mut Transaction,
data: &TLVElement, data: &TLVElement,
encoder: CmdDataEncoder, encoder: CmdDataEncoder,
) -> Result<(), Error> { ) -> Result<(), Error> {
@ -462,7 +459,7 @@ impl<'a> NocCluster<'a> {
.is_ok() .is_ok()
{ {
let _ = self.acl_mgr.borrow_mut().delete_for_fabric(req.fab_idx); let _ = self.acl_mgr.borrow_mut().delete_for_fabric(req.fab_idx);
// TODO: transaction.terminate(); transaction.terminate();
Ok(()) Ok(())
} else { } else {
Self::create_nocresponse(encoder, NocStatus::InvalidFabricIndex, req.fab_idx, "") Self::create_nocresponse(encoder, NocStatus::InvalidFabricIndex, req.fab_idx, "")
@ -470,27 +467,28 @@ impl<'a> NocCluster<'a> {
} }
fn handle_command_addnoc( fn handle_command_addnoc(
&self, &mut self,
exchange: &Exchange, transaction: &mut Transaction,
data: &TLVElement, data: &TLVElement,
encoder: CmdDataEncoder, encoder: CmdDataEncoder,
) -> Result<(), Error> { ) -> Result<(), Error> {
cmd_enter!("AddNOC"); cmd_enter!("AddNOC");
let (status, fab_idx) = match self._handle_command_addnoc(exchange, data) { let (status, fab_idx) = match self._handle_command_addnoc(transaction, data) {
Ok(fab_idx) => (NocStatus::Ok, fab_idx), Ok(fab_idx) => (NocStatus::Ok, fab_idx),
Err(NocError::Status(status)) => (status, 0), Err(NocError::Status(status)) => (status, 0),
Err(NocError::Error(error)) => Err(error)?, Err(NocError::Error(error)) => Err(error)?,
}; };
Self::create_nocresponse(encoder, status, fab_idx, "")?; Self::create_nocresponse(encoder, status, fab_idx, "")?;
transaction.complete();
Ok(()) Ok(())
} }
fn handle_command_attrequest( fn handle_command_attrequest(
&self, &mut self,
exchange: &Exchange, transaction: &mut Transaction,
data: &TLVElement, data: &TLVElement,
encoder: CmdDataEncoder, encoder: CmdDataEncoder,
) -> Result<(), Error> { ) -> Result<(), Error> {
@ -500,10 +498,7 @@ impl<'a> NocCluster<'a> {
info!("Received Attestation Nonce:{:?}", req.str); info!("Received Attestation Nonce:{:?}", req.str);
let mut attest_challenge = [0u8; crypto::SYMM_KEY_LEN_BYTES]; let mut attest_challenge = [0u8; crypto::SYMM_KEY_LEN_BYTES];
exchange.with_session(|sess| { attest_challenge.copy_from_slice(transaction.session().get_att_challenge());
attest_challenge.copy_from_slice(sess.get_att_challenge());
Ok(())
})?;
let mut writer = encoder.with_command(RespCommands::AttReqResp as _)?; let mut writer = encoder.with_command(RespCommands::AttReqResp as _)?;
@ -527,12 +522,13 @@ impl<'a> NocCluster<'a> {
writer.complete()?; writer.complete()?;
transaction.complete();
Ok(()) Ok(())
} }
fn handle_command_certchainrequest( fn handle_command_certchainrequest(
&self, &mut self,
_exchange: &Exchange, transaction: &mut Transaction,
data: &TLVElement, data: &TLVElement,
encoder: CmdDataEncoder, encoder: CmdDataEncoder,
) -> Result<(), Error> { ) -> Result<(), Error> {
@ -553,12 +549,13 @@ impl<'a> NocCluster<'a> {
.with_command(RespCommands::CertChainResp as _)? .with_command(RespCommands::CertChainResp as _)?
.set(cmd_data)?; .set(cmd_data)?;
transaction.complete();
Ok(()) Ok(())
} }
fn handle_command_csrrequest( fn handle_command_csrrequest(
&self, &mut self,
exchange: &Exchange, transaction: &mut Transaction,
data: &TLVElement, data: &TLVElement,
encoder: CmdDataEncoder, encoder: CmdDataEncoder,
) -> Result<(), Error> { ) -> Result<(), Error> {
@ -573,10 +570,7 @@ impl<'a> NocCluster<'a> {
let noc_keypair = KeyPair::new(self.rand)?; let noc_keypair = KeyPair::new(self.rand)?;
let mut attest_challenge = [0u8; crypto::SYMM_KEY_LEN_BYTES]; let mut attest_challenge = [0u8; crypto::SYMM_KEY_LEN_BYTES];
exchange.with_session(|sess| { attest_challenge.copy_from_slice(transaction.session().get_att_challenge());
attest_challenge.copy_from_slice(sess.get_att_challenge());
Ok(())
})?;
let mut writer = encoder.with_command(RespCommands::CSRResp as _)?; let mut writer = encoder.with_command(RespCommands::CSRResp as _)?;
@ -597,31 +591,15 @@ impl<'a> NocCluster<'a> {
let noc_data = NocData::new(noc_keypair); let noc_data = NocData::new(noc_keypair);
// Store this in the session data instead of cluster data, so it gets cleared // Store this in the session data instead of cluster data, so it gets cleared
// if the session goes away for some reason // if the session goes away for some reason
exchange.with_session_mut(|sess| { transaction.session_mut().set_noc_data(noc_data);
sess.set_noc_data(noc_data);
Ok(())
})?;
transaction.complete();
Ok(()) Ok(())
} }
fn add_rca_to_session_noc_data(exchange: &Exchange, data: &TLVElement) -> Result<(), Error> {
exchange.with_session_mut(|sess| {
let noc_data = sess.get_noc_data().ok_or(ErrorCode::NoSession)?;
let req = CommonReq::from_tlv(data).map_err(Error::map_invalid_command)?;
info!("Received Trusted Cert:{:x?}", req.str);
noc_data.root_ca =
heapless::Vec::from_slice(req.str.0).map_err(|_| ErrorCode::BufferTooSmall)?;
Ok(())
})
}
fn handle_command_addtrustedrootcert( fn handle_command_addtrustedrootcert(
&self, &mut self,
exchange: &Exchange, transaction: &mut Transaction,
data: &TLVElement, data: &TLVElement,
) -> Result<(), Error> { ) -> Result<(), Error> {
cmd_enter!("AddTrustedRootCert"); cmd_enter!("AddTrustedRootCert");
@ -630,17 +608,25 @@ impl<'a> NocCluster<'a> {
} }
// This may happen on CASE or PASE. For PASE, the existence of NOC Data is necessary // This may happen on CASE or PASE. For PASE, the existence of NOC Data is necessary
match exchange.with_session(|sess| Ok(sess.get_session_mode().clone()))? { match transaction.session().get_session_mode() {
SessionMode::Case(_) => { SessionMode::Case(_) => error!("CASE: AddTrustedRootCert handling pending"), // For a CASE Session, we just return success for now,
// TODO - Updating the Trusted RCA of an existing Fabric
Self::add_rca_to_session_noc_data(exchange, data)?;
}
SessionMode::Pase => { SessionMode::Pase => {
Self::add_rca_to_session_noc_data(exchange, data)?; let noc_data = transaction
.session_mut()
.get_noc_data()
.ok_or(ErrorCode::NoSession)?;
let req = CommonReq::from_tlv(data).map_err(Error::map_invalid_command)?;
info!("Received Trusted Cert:{:x?}", req.str);
noc_data.root_ca =
heapless::Vec::from_slice(req.str.0).map_err(|_| ErrorCode::BufferTooSmall)?;
// TODO
} }
_ => (), _ => (),
} }
transaction.complete();
Ok(()) Ok(())
} }
} }
@ -651,13 +637,13 @@ impl<'a> Handler for NocCluster<'a> {
} }
fn invoke( fn invoke(
&self, &mut self,
exchange: &Exchange, transaction: &mut Transaction,
cmd: &CmdDetails, cmd: &CmdDetails,
data: &TLVElement, data: &TLVElement,
encoder: CmdDataEncoder, encoder: CmdDataEncoder,
) -> Result<(), Error> { ) -> Result<(), Error> {
NocCluster::invoke(self, exchange, cmd, data, encoder) NocCluster::invoke(self, transaction, cmd, data, encoder)
} }
} }

View file

@ -0,0 +1,74 @@
/*
*
* Copyright (c) 2020-2022 Project CHIP Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
use crate::{
data_model::objects::{
AttrDataEncoder, AttrDetails, ChangeNotifier, Cluster, Dataver, Handler,
NonBlockingHandler, ATTRIBUTE_LIST, FEATURE_MAP,
},
error::{Error, ErrorCode},
utils::rand::Rand,
};
pub const ID: u32 = 0x0031;
enum FeatureMap {
_Wifi = 0x01,
_Thread = 0x02,
Ethernet = 0x04,
}
pub const CLUSTER: Cluster<'static> = Cluster {
id: ID as _,
feature_map: FeatureMap::Ethernet as _,
attributes: &[FEATURE_MAP, ATTRIBUTE_LIST],
commands: &[],
};
pub struct NwCommCluster {
data_ver: Dataver,
}
impl NwCommCluster {
pub fn new(rand: Rand) -> Self {
Self {
data_ver: Dataver::new(rand),
}
}
}
impl Handler for NwCommCluster {
fn read(&self, attr: &AttrDetails, encoder: AttrDataEncoder) -> Result<(), Error> {
if let Some(writer) = encoder.with_dataver(self.data_ver.get())? {
if attr.is_system() {
CLUSTER.read(attr.attr_id, writer)
} else {
Err(ErrorCode::AttributeNotFound.into())
}
} else {
Ok(())
}
}
}
impl NonBlockingHandler for NwCommCluster {}
impl ChangeNotifier<()> for NwCommCluster {
fn consume_change(&mut self) -> Option<()> {
self.data_ver.consume_change(())
}
}

View file

@ -132,7 +132,7 @@ impl<'a> AccessControlCluster<'a> {
} }
} }
pub fn write(&self, attr: &AttrDetails, data: AttrData) -> Result<(), Error> { pub fn write(&mut self, attr: &AttrDetails, data: AttrData) -> Result<(), Error> {
match attr.attr_id.try_into()? { match attr.attr_id.try_into()? {
Attributes::Acl(_) => { Attributes::Acl(_) => {
attr_list_write(attr, data.with_dataver(self.data_ver.get())?, |op, data| { attr_list_write(attr, data.with_dataver(self.data_ver.get())?, |op, data| {
@ -151,7 +151,7 @@ impl<'a> AccessControlCluster<'a> {
/// This takes care of 4 things, add item, edit item, delete item, delete list. /// This takes care of 4 things, add item, edit item, delete item, delete list.
/// Care about fabric-scoped behaviour is taken /// Care about fabric-scoped behaviour is taken
fn write_acl_attr( fn write_acl_attr(
&self, &mut self,
op: &ListOperation, op: &ListOperation,
data: &TLVElement, data: &TLVElement,
fab_idx: u8, fab_idx: u8,
@ -185,7 +185,7 @@ impl<'a> Handler for AccessControlCluster<'a> {
AccessControlCluster::read(self, attr, encoder) AccessControlCluster::read(self, attr, encoder)
} }
fn write(&self, attr: &AttrDetails, data: AttrData) -> Result<(), Error> { fn write(&mut self, attr: &AttrDetails, data: AttrData) -> Result<(), Error> {
AccessControlCluster::write(self, attr, data) AccessControlCluster::write(self, attr, data)
} }
} }
@ -220,7 +220,7 @@ mod tests {
let mut tw = TLVWriter::new(&mut writebuf); let mut tw = TLVWriter::new(&mut writebuf);
let acl_mgr = RefCell::new(AclMgr::new()); let acl_mgr = RefCell::new(AclMgr::new());
let acl = AccessControlCluster::new(&acl_mgr, dummy_rand); let mut acl = AccessControlCluster::new(&acl_mgr, dummy_rand);
let new = AclEntry::new(2, Privilege::VIEW, AuthMode::Case); let new = AclEntry::new(2, Privilege::VIEW, AuthMode::Case);
new.to_tlv(&mut tw, TagType::Anonymous).unwrap(); new.to_tlv(&mut tw, TagType::Anonymous).unwrap();
@ -258,7 +258,7 @@ mod tests {
for i in &verifier { for i in &verifier {
acl_mgr.borrow_mut().add(i.clone()).unwrap(); acl_mgr.borrow_mut().add(i.clone()).unwrap();
} }
let acl = AccessControlCluster::new(&acl_mgr, dummy_rand); let mut acl = AccessControlCluster::new(&acl_mgr, dummy_rand);
let new = AclEntry::new(2, Privilege::VIEW, AuthMode::Case); let new = AclEntry::new(2, Privilege::VIEW, AuthMode::Case);
new.to_tlv(&mut tw, TagType::Anonymous).unwrap(); new.to_tlv(&mut tw, TagType::Anonymous).unwrap();
@ -295,7 +295,7 @@ mod tests {
for i in &input { for i in &input {
acl_mgr.borrow_mut().add(i.clone()).unwrap(); acl_mgr.borrow_mut().add(i.clone()).unwrap();
} }
let acl = AccessControlCluster::new(&acl_mgr, dummy_rand); let mut acl = AccessControlCluster::new(&acl_mgr, dummy_rand);
// data is don't-care actually // data is don't-care actually
let data = TLVElement::new(TagType::Anonymous, ElementType::True); let data = TLVElement::new(TagType::Anonymous, ElementType::True);

View file

@ -47,8 +47,6 @@ pub enum ErrorCode {
NoMemory, NoMemory,
NoSession, NoSession,
NoSpace, NoSpace,
NoSpaceExchanges,
NoSpaceSessions,
NoSpaceAckTable, NoSpaceAckTable,
NoSpaceRetransTable, NoSpaceRetransTable,
NoTagFound, NoTagFound,
@ -85,8 +83,6 @@ pub struct Error {
code: ErrorCode, code: ErrorCode,
#[cfg(all(feature = "std", feature = "backtrace"))] #[cfg(all(feature = "std", feature = "backtrace"))]
backtrace: std::backtrace::Backtrace, backtrace: std::backtrace::Backtrace,
#[cfg(all(feature = "std", feature = "backtrace"))]
inner: Option<Box<dyn std::error::Error + Send>>,
} }
impl Error { impl Error {
@ -95,22 +91,6 @@ impl Error {
code, code,
#[cfg(all(feature = "std", feature = "backtrace"))] #[cfg(all(feature = "std", feature = "backtrace"))]
backtrace: std::backtrace::Backtrace::capture(), backtrace: std::backtrace::Backtrace::capture(),
#[cfg(all(feature = "std", feature = "backtrace"))]
inner: None,
}
}
#[cfg(all(feature = "std", feature = "backtrace"))]
pub fn new_with_details(
code: ErrorCode,
detailed_err: Box<dyn std::error::Error + Send>,
) -> Self {
Self {
code,
#[cfg(all(feature = "std", feature = "backtrace"))]
backtrace: std::backtrace::Backtrace::capture(),
#[cfg(all(feature = "std", feature = "backtrace"))]
inner: Some(detailed_err),
} }
} }
@ -123,11 +103,6 @@ impl Error {
&self.backtrace &self.backtrace
} }
#[cfg(all(feature = "std", feature = "backtrace"))]
pub fn details(&self) -> Option<&(dyn std::error::Error + Send)> {
self.inner.as_ref().map(|err| err.as_ref())
}
pub fn remap<F>(self, matcher: F, to: Self) -> Self pub fn remap<F>(self, matcher: F, to: Self) -> Self
where where
F: FnOnce(&Self) -> bool, F: FnOnce(&Self) -> bool,
@ -159,16 +134,10 @@ impl Error {
} }
} }
#[cfg(all(feature = "std", feature = "backtrace"))] #[cfg(feature = "std")]
impl From<std::io::Error> for Error {
fn from(e: std::io::Error) -> Self {
Self::new_with_details(ErrorCode::StdIoError, Box::new(e))
}
}
#[cfg(all(feature = "std", not(feature = "backtrace")))]
impl From<std::io::Error> for Error { impl From<std::io::Error> for Error {
fn from(_e: std::io::Error) -> Self { fn from(_e: std::io::Error) -> Self {
// Keep things simple for now
Self::new(ErrorCode::StdIoError) Self::new(ErrorCode::StdIoError)
} }
} }
@ -180,7 +149,7 @@ impl<T> From<std::sync::PoisonError<T>> for Error {
} }
} }
#[cfg(feature = "openssl")] #[cfg(feature = "crypto_openssl")]
impl From<openssl::error::ErrorStack> for Error { impl From<openssl::error::ErrorStack> for Error {
fn from(e: openssl::error::ErrorStack) -> Self { fn from(e: openssl::error::ErrorStack) -> Self {
::log::error!("Error in TLS: {}", e); ::log::error!("Error in TLS: {}", e);
@ -188,7 +157,7 @@ impl From<openssl::error::ErrorStack> for Error {
} }
} }
#[cfg(all(feature = "mbedtls", not(target_os = "espidf")))] #[cfg(all(feature = "crypto_mbedtls", not(target_os = "espidf")))]
impl From<mbedtls::Error> for Error { impl From<mbedtls::Error> for Error {
fn from(e: mbedtls::Error) -> Self { fn from(e: mbedtls::Error) -> Self {
::log::error!("Error in TLS: {}", e); ::log::error!("Error in TLS: {}", e);
@ -204,7 +173,7 @@ impl From<esp_idf_sys::EspError> for Error {
} }
} }
#[cfg(feature = "rustcrypto")] #[cfg(feature = "crypto_rustcrypto")]
impl From<ccm::aead::Error> for Error { impl From<ccm::aead::Error> for Error {
fn from(_e: ccm::aead::Error) -> Self { fn from(_e: ccm::aead::Error) -> Self {
Self::new(ErrorCode::Crypto) Self::new(ErrorCode::Crypto)
@ -250,21 +219,7 @@ impl fmt::Debug for Error {
impl fmt::Display for Error { impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
#[cfg(all(feature = "std", feature = "backtrace"))] write!(f, "{:?}", self.code())
{
write!(
f,
"{:?}: {}",
self.code(),
self.inner
.as_ref()
.map_or(String::new(), |err| { err.to_string() })
)
}
#[cfg(not(all(feature = "std", feature = "backtrace")))]
{
write!(f, "{:?}", self.code())
}
} }
} }

View file

@ -33,6 +33,7 @@ use crate::{
const COMPRESSED_FABRIC_ID_LEN: usize = 8; const COMPRESSED_FABRIC_ID_LEN: usize = 8;
#[allow(dead_code)]
#[derive(Debug, ToTLV)] #[derive(Debug, ToTLV)]
#[tlvargs(lifetime = "'a", start = 1)] #[tlvargs(lifetime = "'a", start = 1)]
pub struct FabricDescriptor<'a> { pub struct FabricDescriptor<'a> {

View file

@ -0,0 +1,846 @@
/*
*
* Copyright (c) 2020-2022 Project CHIP Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
use core::sync::atomic::{AtomicU32, Ordering};
use core::time::Duration;
use crate::{
data_model::core::DataHandler,
error::*,
tlv::{get_root_node_struct, print_tlv_list, FromTLV, TLVElement, TLVWriter, TagType, ToTLV},
transport::{
exchange::{Exchange, ExchangeCtx},
packet::Packet,
proto_ctx::ProtoCtx,
session::Session,
},
};
use log::{error, info};
use num;
use num_derive::FromPrimitive;
use owo_colors::OwoColorize;
use super::messages::{
ib::{AttrPath, DataVersionFilter},
msg::{self, InvReq, ReadReq, StatusResp, SubscribeReq, SubscribeResp, TimedReq, WriteReq},
GenericPath,
};
#[macro_export]
macro_rules! cmd_enter {
($e:expr) => {{
use owo_colors::OwoColorize;
info! {"{} {}", "Handling Command".cyan(), $e.cyan()}
}};
}
#[derive(FromPrimitive, Debug, Clone, Copy, PartialEq)]
pub enum IMStatusCode {
Success = 0,
Failure = 1,
InvalidSubscription = 0x7D,
UnsupportedAccess = 0x7E,
UnsupportedEndpoint = 0x7F,
InvalidAction = 0x80,
UnsupportedCommand = 0x81,
InvalidCommand = 0x85,
UnsupportedAttribute = 0x86,
ConstraintError = 0x87,
UnsupportedWrite = 0x88,
ResourceExhausted = 0x89,
NotFound = 0x8b,
UnreportableAttribute = 0x8c,
InvalidDataType = 0x8d,
UnsupportedRead = 0x8f,
DataVersionMismatch = 0x92,
Timeout = 0x94,
Busy = 0x9c,
UnsupportedCluster = 0xc3,
NoUpstreamSubscription = 0xc5,
NeedsTimedInteraction = 0xc6,
UnsupportedEvent = 0xc7,
PathsExhausted = 0xc8,
TimedRequestMisMatch = 0xc9,
FailSafeRequired = 0xca,
}
impl From<ErrorCode> for IMStatusCode {
fn from(e: ErrorCode) -> Self {
match e {
ErrorCode::EndpointNotFound => IMStatusCode::UnsupportedEndpoint,
ErrorCode::ClusterNotFound => IMStatusCode::UnsupportedCluster,
ErrorCode::AttributeNotFound => IMStatusCode::UnsupportedAttribute,
ErrorCode::CommandNotFound => IMStatusCode::UnsupportedCommand,
ErrorCode::InvalidAction => IMStatusCode::InvalidAction,
ErrorCode::InvalidCommand => IMStatusCode::InvalidCommand,
ErrorCode::UnsupportedAccess => IMStatusCode::UnsupportedAccess,
ErrorCode::Busy => IMStatusCode::Busy,
ErrorCode::DataVersionMismatch => IMStatusCode::DataVersionMismatch,
ErrorCode::ResourceExhausted => IMStatusCode::ResourceExhausted,
_ => IMStatusCode::Failure,
}
}
}
impl From<Error> for IMStatusCode {
fn from(value: Error) -> Self {
Self::from(value.code())
}
}
impl FromTLV<'_> for IMStatusCode {
fn from_tlv(t: &TLVElement) -> Result<Self, Error> {
num::FromPrimitive::from_u16(t.u16()?).ok_or_else(|| ErrorCode::Invalid.into())
}
}
impl ToTLV for IMStatusCode {
fn to_tlv(&self, tw: &mut TLVWriter, tag_type: TagType) -> Result<(), Error> {
tw.u16(tag_type, *self as u16)
}
}
#[derive(FromPrimitive, Debug, Copy, Clone, PartialEq)]
pub enum OpCode {
Reserved = 0,
StatusResponse = 1,
ReadRequest = 2,
SubscribeRequest = 3,
SubscribeResponse = 4,
ReportData = 5,
WriteRequest = 6,
WriteResponse = 7,
InvokeRequest = 8,
InvokeResponse = 9,
TimedRequest = 10,
}
#[derive(PartialEq)]
pub enum TransactionState {
Ongoing,
Complete,
Terminate,
}
pub struct Transaction<'a, 'b> {
state: TransactionState,
ctx: &'a mut ExchangeCtx<'b>,
}
impl<'a, 'b> Transaction<'a, 'b> {
pub fn new(ctx: &'a mut ExchangeCtx<'b>) -> Self {
Self {
state: TransactionState::Ongoing,
ctx,
}
}
pub fn exch(&self) -> &Exchange {
self.ctx.exch
}
pub fn exch_mut(&mut self) -> &mut Exchange {
self.ctx.exch
}
pub fn session(&self) -> &Session {
self.ctx.sess.session()
}
pub fn session_mut(&mut self) -> &mut Session {
self.ctx.sess.session_mut()
}
/// Terminates the transaction, no communication (even ACKs) happens hence forth
pub fn terminate(&mut self) {
self.state = TransactionState::Terminate
}
pub fn is_terminate(&self) -> bool {
self.state == TransactionState::Terminate
}
/// Marks the transaction as completed from the application's perspective
pub fn complete(&mut self) {
self.state = TransactionState::Complete
}
pub fn is_complete(&self) -> bool {
self.state == TransactionState::Complete
}
pub fn set_timeout(&mut self, timeout: u64) {
let now = (self.ctx.epoch)();
self.ctx
.exch
.set_data_time(now.checked_add(Duration::from_millis(timeout)));
}
pub fn get_timeout(&mut self) -> Option<Duration> {
self.ctx.exch.get_data_time()
}
pub fn has_timed_out(&self) -> bool {
if let Some(timeout) = self.ctx.exch.get_data_time() {
if (self.ctx.epoch)() > timeout {
return true;
}
}
false
}
}
/* Interaction Model ID as per the Matter Spec */
pub const PROTO_ID_INTERACTION_MODEL: u16 = 0x01;
const MAX_RESUME_PATHS: usize = 32;
const MAX_RESUME_DATAVER_FILTERS: usize = 32;
// This is the amount of space we reserve for other things to be attached towards
// the end of long reads.
const LONG_READS_TLV_RESERVE_SIZE: usize = 24;
// TODO: For now...
static SUBS_ID: AtomicU32 = AtomicU32::new(1);
pub enum Interaction<'a> {
Read(ReadReq<'a>),
Write(WriteReq<'a>),
Invoke(InvReq<'a>),
Subscribe(SubscribeReq<'a>),
Timed(TimedReq),
ResumeRead(ResumeReadReq),
ResumeSubscribe(ResumeSubscribeReq),
}
impl<'a> Interaction<'a> {
fn new(rx: &'a Packet, transaction: &mut Transaction) -> Result<Option<Self>, Error> {
let opcode: OpCode = rx.get_proto_opcode()?;
let rx_data = rx.as_slice();
info!("{} {:?}", "Received command".cyan(), opcode);
print_tlv_list(rx_data);
match opcode {
OpCode::ReadRequest => Ok(Some(Self::Read(ReadReq::from_tlv(&get_root_node_struct(
rx_data,
)?)?))),
OpCode::WriteRequest => Ok(Some(Self::Write(WriteReq::from_tlv(
&get_root_node_struct(rx_data)?,
)?))),
OpCode::InvokeRequest => Ok(Some(Self::Invoke(InvReq::from_tlv(
&get_root_node_struct(rx_data)?,
)?))),
OpCode::SubscribeRequest => Ok(Some(Self::Subscribe(SubscribeReq::from_tlv(
&get_root_node_struct(rx_data)?,
)?))),
OpCode::StatusResponse => {
let resp = StatusResp::from_tlv(&get_root_node_struct(rx_data)?)?;
if resp.status == IMStatusCode::Success {
if let Some(req) = transaction.exch_mut().take_suspended_read_req() {
Ok(Some(Self::ResumeRead(req)))
} else if let Some(req) = transaction.exch_mut().take_suspended_subscribe_req()
{
Ok(Some(Self::ResumeSubscribe(req)))
} else {
Ok(None)
}
} else {
Ok(None)
}
}
OpCode::TimedRequest => Ok(Some(Self::Timed(TimedReq::from_tlv(
&get_root_node_struct(rx_data)?,
)?))),
_ => {
error!("Opcode not handled: {:?}", opcode);
Err(ErrorCode::InvalidOpcode.into())
}
}
}
pub fn initiate(
rx: &'a Packet,
tx: &mut Packet,
transaction: &mut Transaction,
) -> Result<Option<Self>, Error> {
if let Some(interaction) = Self::new(rx, transaction)? {
tx.reset();
let initiated = match &interaction {
Interaction::Read(req) => req.initiate(tx, transaction)?,
Interaction::Write(req) => req.initiate(tx, transaction)?,
Interaction::Invoke(req) => req.initiate(tx, transaction)?,
Interaction::Subscribe(req) => req.initiate(tx, transaction)?,
Interaction::Timed(req) => {
req.process(tx, transaction)?;
false
}
Interaction::ResumeRead(req) => req.initiate(tx, transaction)?,
Interaction::ResumeSubscribe(req) => req.initiate(tx, transaction)?,
};
Ok(initiated.then_some(interaction))
} else {
Ok(None)
}
}
fn create_status_response(tx: &mut Packet, status: IMStatusCode) -> Result<(), Error> {
tx.set_proto_id(PROTO_ID_INTERACTION_MODEL);
tx.set_proto_opcode(OpCode::StatusResponse as u8);
let mut tw = TLVWriter::new(tx.get_writebuf()?);
let status = StatusResp { status };
status.to_tlv(&mut tw, TagType::Anonymous)
}
}
impl<'a> ReadReq<'a> {
fn suspend(self, resume_path: GenericPath) -> ResumeReadReq {
ResumeReadReq {
paths: self
.attr_requests
.iter()
.flat_map(|attr_requests| attr_requests.iter())
.collect(),
filters: self
.dataver_filters
.iter()
.flat_map(|filters| filters.iter())
.collect(),
fabric_filtered: self.fabric_filtered,
resume_path,
}
}
fn initiate(&self, tx: &mut Packet, _transaction: &mut Transaction) -> Result<bool, Error> {
tx.set_proto_id(PROTO_ID_INTERACTION_MODEL);
tx.set_proto_opcode(OpCode::ReportData as u8);
let mut tw = Self::reserve_long_read_space(tx)?;
tw.start_struct(TagType::Anonymous)?;
if self.attr_requests.is_some() {
tw.start_array(TagType::Context(msg::ReportDataTag::AttributeReports as u8))?;
}
Ok(true)
}
pub fn complete(
self,
tx: &mut Packet,
transaction: &mut Transaction,
resume_path: Option<GenericPath>,
) -> Result<bool, Error> {
let mut tw = Self::restore_long_read_space(tx)?;
if self.attr_requests.is_some() {
tw.end_container()?;
}
let more_chunks = if let Some(resume_path) = resume_path {
tw.bool(
TagType::Context(msg::ReportDataTag::MoreChunkedMsgs as u8),
true,
)?;
transaction
.exch_mut()
.set_suspended_read_req(self.suspend(resume_path));
true
} else {
false
};
tw.bool(
TagType::Context(msg::ReportDataTag::SupressResponse as u8),
!more_chunks,
)?;
tw.end_container()?;
if !more_chunks {
transaction.complete();
}
Ok(true)
}
fn reserve_long_read_space<'p, 'b>(tx: &'p mut Packet<'b>) -> Result<TLVWriter<'p, 'b>, Error> {
let wb = tx.get_writebuf()?;
wb.shrink(LONG_READS_TLV_RESERVE_SIZE)?;
Ok(TLVWriter::new(wb))
}
fn restore_long_read_space<'p, 'b>(tx: &'p mut Packet<'b>) -> Result<TLVWriter<'p, 'b>, Error> {
let wb = tx.get_writebuf()?;
wb.expand(LONG_READS_TLV_RESERVE_SIZE)?;
Ok(TLVWriter::new(wb))
}
}
impl<'a> WriteReq<'a> {
fn initiate(&self, tx: &mut Packet, transaction: &mut Transaction) -> Result<bool, Error> {
if transaction.has_timed_out() {
Interaction::create_status_response(tx, IMStatusCode::Timeout)?;
transaction.complete();
Ok(false)
} else {
tx.set_proto_id(PROTO_ID_INTERACTION_MODEL);
tx.set_proto_opcode(OpCode::WriteResponse as u8);
let mut tw = TLVWriter::new(tx.get_writebuf()?);
tw.start_struct(TagType::Anonymous)?;
tw.start_array(TagType::Context(msg::WriteRespTag::WriteResponses as u8))?;
Ok(true)
}
}
pub fn complete(self, tx: &mut Packet, transaction: &mut Transaction) -> Result<bool, Error> {
let suppress = self.supress_response.unwrap_or_default();
let mut tw = TLVWriter::new(tx.get_writebuf()?);
tw.end_container()?;
tw.end_container()?;
transaction.complete();
Ok(if suppress {
error!("Supress response is set, is this the expected handling?");
false
} else {
true
})
}
}
impl<'a> InvReq<'a> {
fn initiate(&self, tx: &mut Packet, transaction: &mut Transaction) -> Result<bool, Error> {
if transaction.has_timed_out() {
Interaction::create_status_response(tx, IMStatusCode::Timeout)?;
transaction.complete();
Ok(false)
} else {
let timed_tx = transaction.get_timeout().map(|_| true);
let timed_request = self.timed_request.filter(|a| *a);
// Either both should be None, or both should be Some(true)
if timed_tx != timed_request {
Interaction::create_status_response(tx, IMStatusCode::TimedRequestMisMatch)?;
Ok(false)
} else {
tx.set_proto_id(PROTO_ID_INTERACTION_MODEL);
tx.set_proto_opcode(OpCode::InvokeResponse as u8);
let mut tw = TLVWriter::new(tx.get_writebuf()?);
tw.start_struct(TagType::Anonymous)?;
// Suppress Response -> TODO: Need to revisit this for cases where we send a command back
tw.bool(
TagType::Context(msg::InvRespTag::SupressResponse as u8),
false,
)?;
if self.inv_requests.is_some() {
tw.start_array(TagType::Context(msg::InvRespTag::InvokeResponses as u8))?;
}
Ok(true)
}
}
}
pub fn complete(self, tx: &mut Packet, _transaction: &mut Transaction) -> Result<bool, Error> {
let suppress = self.suppress_response.unwrap_or_default();
let mut tw = TLVWriter::new(tx.get_writebuf()?);
if self.inv_requests.is_some() {
tw.end_container()?;
}
tw.end_container()?;
Ok(if suppress {
error!("Supress response is set, is this the expected handling?");
false
} else {
true
})
}
}
impl TimedReq {
pub fn process(&self, tx: &mut Packet, transaction: &mut Transaction) -> Result<(), Error> {
tx.set_proto_id(PROTO_ID_INTERACTION_MODEL);
tx.set_proto_opcode(OpCode::StatusResponse as u8);
let mut tw = TLVWriter::new(tx.get_writebuf()?);
transaction.set_timeout(self.timeout.into());
let status = StatusResp {
status: IMStatusCode::Success,
};
status.to_tlv(&mut tw, TagType::Anonymous)?;
Ok(())
}
}
impl<'a> SubscribeReq<'a> {
fn suspend(
&self,
resume_path: Option<GenericPath>,
subscription_id: u32,
) -> ResumeSubscribeReq {
ResumeSubscribeReq {
subscription_id,
paths: self
.attr_requests
.iter()
.flat_map(|attr_requests| attr_requests.iter())
.collect(),
filters: self
.dataver_filters
.iter()
.flat_map(|filters| filters.iter())
.collect(),
fabric_filtered: self.fabric_filtered,
resume_path,
keep_subs: self.keep_subs,
min_int_floor: self.min_int_floor,
max_int_ceil: self.max_int_ceil,
}
}
fn initiate(&self, tx: &mut Packet, transaction: &mut Transaction) -> Result<bool, Error> {
tx.set_proto_id(PROTO_ID_INTERACTION_MODEL);
tx.set_proto_opcode(OpCode::ReportData as u8);
let mut tw = ReadReq::reserve_long_read_space(tx)?;
tw.start_struct(TagType::Anonymous)?;
let subscription_id = SUBS_ID.fetch_add(1, Ordering::SeqCst);
transaction.exch_mut().set_subscription_id(subscription_id);
tw.u32(
TagType::Context(msg::ReportDataTag::SubscriptionId as u8),
subscription_id,
)?;
if self.attr_requests.is_some() {
tw.start_array(TagType::Context(msg::ReportDataTag::AttributeReports as u8))?;
}
Ok(true)
}
pub fn complete(
self,
tx: &mut Packet,
transaction: &mut Transaction,
resume_path: Option<GenericPath>,
) -> Result<bool, Error> {
let mut tw = ReadReq::restore_long_read_space(tx)?;
if self.attr_requests.is_some() {
tw.end_container()?;
}
if resume_path.is_some() {
tw.bool(
TagType::Context(msg::ReportDataTag::MoreChunkedMsgs as u8),
true,
)?;
}
let subscription_id = transaction.exch_mut().take_subscription_id().unwrap();
transaction
.exch_mut()
.set_suspended_subscribe_req(self.suspend(resume_path, subscription_id));
tw.bool(
TagType::Context(msg::ReportDataTag::SupressResponse as u8),
false,
)?;
tw.end_container()?;
Ok(true)
}
}
#[derive(Debug)]
pub struct ResumeReadReq {
pub paths: heapless::Vec<AttrPath, MAX_RESUME_PATHS>,
pub filters: heapless::Vec<DataVersionFilter, MAX_RESUME_DATAVER_FILTERS>,
pub fabric_filtered: bool,
pub resume_path: GenericPath,
}
impl ResumeReadReq {
fn initiate(&self, tx: &mut Packet, _transaction: &mut Transaction) -> Result<bool, Error> {
tx.set_proto_id(PROTO_ID_INTERACTION_MODEL);
tx.set_proto_opcode(OpCode::ReportData as u8);
let mut tw = ReadReq::reserve_long_read_space(tx)?;
tw.start_struct(TagType::Anonymous)?;
tw.start_array(TagType::Context(msg::ReportDataTag::AttributeReports as u8))?;
Ok(true)
}
pub fn complete(
mut self,
tx: &mut Packet,
transaction: &mut Transaction,
resume_path: Option<GenericPath>,
) -> Result<bool, Error> {
let mut tw = ReadReq::restore_long_read_space(tx)?;
tw.end_container()?;
let continue_interaction = if let Some(resume_path) = resume_path {
tw.bool(
TagType::Context(msg::ReportDataTag::MoreChunkedMsgs as u8),
true,
)?;
self.resume_path = resume_path;
transaction.exch_mut().set_suspended_read_req(self);
true
} else {
false
};
tw.bool(
TagType::Context(msg::ReportDataTag::SupressResponse as u8),
!continue_interaction,
)?;
tw.end_container()?;
if !continue_interaction {
transaction.complete();
}
Ok(true)
}
}
#[derive(Debug)]
pub struct ResumeSubscribeReq {
pub subscription_id: u32,
pub paths: heapless::Vec<AttrPath, MAX_RESUME_PATHS>,
pub filters: heapless::Vec<DataVersionFilter, MAX_RESUME_DATAVER_FILTERS>,
pub fabric_filtered: bool,
pub resume_path: Option<GenericPath>,
pub keep_subs: bool,
pub min_int_floor: u16,
pub max_int_ceil: u16,
}
impl ResumeSubscribeReq {
fn initiate(&self, tx: &mut Packet, _transaction: &mut Transaction) -> Result<bool, Error> {
tx.set_proto_id(PROTO_ID_INTERACTION_MODEL);
if self.resume_path.is_some() {
tx.set_proto_opcode(OpCode::ReportData as u8);
let mut tw = ReadReq::reserve_long_read_space(tx)?;
tw.start_struct(TagType::Anonymous)?;
tw.u32(
TagType::Context(msg::ReportDataTag::SubscriptionId as u8),
self.subscription_id,
)?;
tw.start_array(TagType::Context(msg::ReportDataTag::AttributeReports as u8))?;
Ok(true)
} else {
tx.set_proto_opcode(OpCode::SubscribeResponse as u8);
let mut tw = TLVWriter::new(tx.get_writebuf()?);
let resp = SubscribeResp::new(self.subscription_id, 40);
resp.to_tlv(&mut tw, TagType::Anonymous)?;
Ok(false)
}
}
pub fn complete(
mut self,
tx: &mut Packet,
transaction: &mut Transaction,
resume_path: Option<GenericPath>,
) -> Result<bool, Error> {
if self.resume_path.is_none() {
// Should not get here as initiate() should've sent the subscribe response already
panic!("Subscription was already processed");
}
// Completing a ReportData message
let mut tw = ReadReq::restore_long_read_space(tx)?;
tw.end_container()?;
if resume_path.is_some() {
tw.bool(
TagType::Context(msg::ReportDataTag::MoreChunkedMsgs as u8),
true,
)?;
}
tw.bool(
TagType::Context(msg::ReportDataTag::SupressResponse as u8),
false,
)?;
tw.end_container()?;
self.resume_path = resume_path;
transaction.exch_mut().set_suspended_subscribe_req(self);
Ok(true)
}
}
pub trait InteractionHandler {
fn handle(&mut self, ctx: &mut ProtoCtx) -> Result<bool, Error>;
}
impl<T> InteractionHandler for &mut T
where
T: InteractionHandler,
{
fn handle(&mut self, ctx: &mut ProtoCtx) -> Result<bool, Error> {
(**self).handle(ctx)
}
}
pub struct InteractionModel<T>(pub T);
impl<T> InteractionModel<T>
where
T: DataHandler,
{
pub fn handle(&mut self, ctx: &mut ProtoCtx) -> Result<bool, Error> {
let mut transaction = Transaction::new(&mut ctx.exch_ctx);
let reply =
if let Some(interaction) = Interaction::initiate(ctx.rx, ctx.tx, &mut transaction)? {
self.0.handle(interaction, ctx.tx, &mut transaction)?
} else {
true
};
if transaction.is_complete() {
transaction.exch_mut().close();
}
Ok(reply)
}
}
#[cfg(feature = "nightly")]
impl<T> InteractionModel<T>
where
T: crate::data_model::core::asynch::AsyncDataHandler,
{
pub async fn handle_async<'a>(&mut self, ctx: &mut ProtoCtx<'_, '_>) -> Result<bool, Error> {
let mut transaction = Transaction::new(&mut ctx.exch_ctx);
let reply =
if let Some(interaction) = Interaction::initiate(ctx.rx, ctx.tx, &mut transaction)? {
self.0.handle(interaction, ctx.tx, &mut transaction).await?
} else {
true
};
if transaction.is_complete() {
transaction.exch_mut().close();
}
Ok(reply)
}
}
impl<T> InteractionHandler for InteractionModel<T>
where
T: DataHandler,
{
fn handle(&mut self, ctx: &mut ProtoCtx) -> Result<bool, Error> {
InteractionModel::handle(self, ctx)
}
}
#[cfg(feature = "nightly")]
pub mod asynch {
use crate::{
data_model::core::asynch::AsyncDataHandler, error::Error, transport::proto_ctx::ProtoCtx,
};
use super::InteractionModel;
pub trait AsyncInteractionHandler {
async fn handle(&mut self, ctx: &mut ProtoCtx<'_, '_>) -> Result<bool, Error>;
}
impl<T> AsyncInteractionHandler for &mut T
where
T: AsyncInteractionHandler,
{
async fn handle(&mut self, ctx: &mut ProtoCtx<'_, '_>) -> Result<bool, Error> {
(**self).handle(ctx).await
}
}
impl<T> AsyncInteractionHandler for InteractionModel<T>
where
T: AsyncDataHandler,
{
async fn handle(&mut self, ctx: &mut ProtoCtx<'_, '_>) -> Result<bool, Error> {
InteractionModel::handle_async(self, ctx).await
}
}
}

View file

@ -23,15 +23,14 @@
//! Currently Ethernet based transport is supported. //! Currently Ethernet based transport is supported.
//! //!
//! # Examples //! # Examples
//! ```ignore //! TODO: Fix once new API has stabilized a bit
//! /// TODO: Fix once new API has stabilized a bit //! use matter::{Matter, CommissioningData};
//! use rs_matter::{Matter, CommissioningData}; //! use matter::data_model::device_types::device_type_add_on_off_light;
//! use rs_matter::data_model::device_types::device_type_add_on_off_light; //! use matter::data_model::cluster_basic_information::BasicInfoConfig;
//! use rs_matter::data_model::cluster_basic_information::BasicInfoConfig; //! use matter::secure_channel::spake2p::VerifierData;
//! use rs_matter::secure_channel::spake2p::VerifierData;
//! //!
//! # use rs_matter::data_model::sdm::dev_att::{DataType, DevAttDataFetcher}; //! # use matter::data_model::sdm::dev_att::{DataType, DevAttDataFetcher};
//! # use rs_matter::error::Error; //! # use matter::error::Error;
//! # pub struct DevAtt{} //! # pub struct DevAtt{}
//! # impl DevAttDataFetcher for DevAtt{ //! # impl DevAttDataFetcher for DevAtt{
//! # fn get_devatt_data(&self, data_type: DataType, data: &mut [u8]) -> Result<usize, Error> { Ok(0) } //! # fn get_devatt_data(&self, data_type: DataType, data: &mut [u8]) -> Result<usize, Error> { Ok(0) }
@ -66,15 +65,11 @@
//! } //! }
//! // Start the Matter Daemon //! // Start the Matter Daemon
//! // matter.start_daemon().unwrap(); //! // matter.start_daemon().unwrap();
//! ```
//! //!
//! Start off exploring by going to the [Matter] object. //! Start off exploring by going to the [Matter] object.
#![cfg_attr(not(feature = "std"), no_std)] #![cfg_attr(not(feature = "std"), no_std)]
#![allow(stable_features)]
#![allow(unknown_lints)]
#![cfg_attr(feature = "nightly", feature(async_fn_in_trait))] #![cfg_attr(feature = "nightly", feature(async_fn_in_trait))]
#![cfg_attr(feature = "nightly", allow(async_fn_in_trait))] #![cfg_attr(feature = "nightly", allow(incomplete_features))]
#![cfg_attr(feature = "nightly", feature(impl_trait_projections))]
pub mod acl; pub mod acl;
pub mod cert; pub mod cert;
@ -95,22 +90,3 @@ pub mod transport;
pub mod utils; pub mod utils;
pub use crate::core::*; pub use crate::core::*;
#[cfg(feature = "alloc")]
extern crate alloc;
#[cfg(feature = "alloc")]
#[macro_export]
macro_rules! alloc {
($val:expr) => {
alloc::boxed::Box::new($val)
};
}
#[cfg(not(feature = "alloc"))]
#[macro_export]
macro_rules! alloc {
($val:expr) => {
$val
};
}

View file

@ -23,8 +23,6 @@ use crate::{data_model::cluster_basic_information::BasicInfoConfig, error::Error
pub mod astro; pub mod astro;
pub mod builtin; pub mod builtin;
pub mod proto; pub mod proto;
#[cfg(all(feature = "std", feature = "zeroconf", target_os = "linux"))]
pub mod zeroconf;
pub trait Mdns { pub trait Mdns {
fn add(&self, service: &str, mode: ServiceMode) -> Result<(), Error>; fn add(&self, service: &str, mode: ServiceMode) -> Result<(), Error>;
@ -58,14 +56,16 @@ where
} }
#[cfg(all(feature = "std", target_os = "macos"))] #[cfg(all(feature = "std", target_os = "macos"))]
pub use astro::MdnsService; pub type DefaultMdns<'a> = astro::Mdns<'a>;
#[cfg(all(feature = "std", target_os = "macos"))]
pub use astro::MdnsUdpBuffers; #[cfg(all(feature = "std", target_os = "macos"))]
pub type DefaultMdnsRunner<'a> = astro::MdnsRunner<'a>;
#[cfg(any(feature = "std", feature = "embassy-net"))]
pub use builtin::MdnsRunBuffers;
#[cfg(not(all(feature = "std", target_os = "macos")))] #[cfg(not(all(feature = "std", target_os = "macos")))]
pub use builtin::MdnsService; pub type DefaultMdns<'a> = builtin::Mdns<'a>;
#[cfg(not(all(feature = "std", target_os = "macos")))]
pub type DefaultMdnsRunner<'a> = builtin::MdnsRunner<'a>;
pub struct DummyMdns; pub struct DummyMdns;

View file

@ -9,32 +9,21 @@ use crate::{
use astro_dnssd::{DNSServiceBuilder, RegisteredDnsService}; use astro_dnssd::{DNSServiceBuilder, RegisteredDnsService};
use log::info; use log::info;
use super::{MdnsRunBuffers, ServiceMode}; use super::ServiceMode;
/// Only for API-compatibility with builtin::MdnsRunner pub struct Mdns<'a> {
pub struct MdnsUdpBuffers(());
/// Only for API-compatibility with builtin::MdnsRunner
impl MdnsUdpBuffers {
#[inline(always)]
pub const fn new() -> Self {
Self(())
}
}
pub struct MdnsService<'a> {
dev_det: &'a BasicInfoConfig<'a>, dev_det: &'a BasicInfoConfig<'a>,
matter_port: u16, matter_port: u16,
services: RefCell<HashMap<String, RegisteredDnsService>>, services: RefCell<HashMap<String, RegisteredDnsService>>,
} }
impl<'a> MdnsService<'a> { impl<'a> Mdns<'a> {
/// This constructor takes extra parameters for API-compatibility with builtin::MdnsRunner
pub fn new( pub fn new(
_id: u16, _id: u16,
_hostname: &str, _hostname: &str,
_ip: [u8; 4], _ip: [u8; 4],
_ipv6: Option<([u8; 16], u32)>, _ipv6: Option<[u8; 16]>,
_interface: u32,
dev_det: &'a BasicInfoConfig<'a>, dev_det: &'a BasicInfoConfig<'a>,
matter_port: u16, matter_port: u16,
) -> Self { ) -> Self {
@ -89,35 +78,30 @@ impl<'a> MdnsService<'a> {
Ok(()) Ok(())
} }
}
/// Only for API-compatibility with builtin::MdnsRunner pub struct MdnsRunner<'a>(&'a Mdns<'a>);
pub async fn run_piped(
&mut self, impl<'a> MdnsRunner<'a> {
_tx_pipe: &Pipe<'_>, pub const fn new(mdns: &'a Mdns<'a>) -> Self {
_rx_pipe: &Pipe<'_>, Self(mdns)
) -> Result<(), Error> { }
pub async fn run_udp(&mut self) -> Result<(), Error> {
core::future::pending::<Result<(), Error>>().await core::future::pending::<Result<(), Error>>().await
} }
/// Only for API-compatibility with builtin::MdnsRunner pub async fn run(&self, _tx_pipe: &Pipe<'_>, _rx_pipe: &Pipe<'_>) -> Result<(), Error> {
pub async fn run<D>(
&self,
_stack: &crate::transport::network::NetworkStack<D>,
_buffers: &mut MdnsRunBuffers,
) -> Result<(), Error>
where
D: crate::transport::network::NetworkStackDriver,
{
core::future::pending::<Result<(), Error>>().await core::future::pending::<Result<(), Error>>().await
} }
} }
impl<'a> super::Mdns for MdnsService<'a> { impl<'a> super::Mdns for Mdns<'a> {
fn add(&self, service: &str, mode: ServiceMode) -> Result<(), Error> { fn add(&self, service: &str, mode: ServiceMode) -> Result<(), Error> {
MdnsService::add(self, service, mode) Mdns::add(self, service, mode)
} }
fn remove(&self, service: &str) -> Result<(), Error> { fn remove(&self, service: &str) -> Result<(), Error> {
MdnsService::remove(self, service) Mdns::remove(self, service)
} }
} }

View file

@ -1,19 +1,17 @@
use core::{cell::RefCell, pin::pin}; use core::{cell::RefCell, mem::MaybeUninit, pin::pin};
use domain::base::name::FromStrError; use domain::base::name::FromStrError;
use domain::base::{octets::ParseError, ShortBuf}; use domain::base::{octets::ParseError, ShortBuf};
use embassy_futures::select::select; use embassy_futures::select::{select, select3};
use embassy_time::{Duration, Timer}; use embassy_time::{Duration, Timer};
use log::info; use log::info;
use crate::data_model::cluster_basic_information::BasicInfoConfig; use crate::data_model::cluster_basic_information::BasicInfoConfig;
use crate::error::{Error, ErrorCode}; use crate::error::{Error, ErrorCode};
#[cfg(any(feature = "std", feature = "embassy-net"))] use crate::transport::network::{Address, IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr};
use crate::transport::network::IpAddr; use crate::transport::packet::{MAX_RX_BUF_SIZE, MAX_TX_BUF_SIZE};
use crate::transport::network::{
Address, Ipv4Addr, Ipv6Addr, SocketAddr, SocketAddrV4, SocketAddrV6,
};
use crate::transport::pipe::{Chunk, Pipe}; use crate::transport::pipe::{Chunk, Pipe};
use crate::transport::udp::UdpListener;
use crate::utils::select::{EitherUnwrap, Notification}; use crate::utils::select::{EitherUnwrap, Notification};
use super::{ use super::{
@ -21,47 +19,33 @@ use super::{
Service, ServiceMode, Service, ServiceMode,
}; };
const IP_BIND_ADDR: IpAddr = IpAddr::V6(Ipv6Addr::UNSPECIFIED);
const IP_BROADCAST_ADDR: Ipv4Addr = Ipv4Addr::new(224, 0, 0, 251); const IP_BROADCAST_ADDR: Ipv4Addr = Ipv4Addr::new(224, 0, 0, 251);
const IPV6_BROADCAST_ADDR: Ipv6Addr = Ipv6Addr::new(0xff02, 0, 0, 0, 0, 0, 0, 0x00fb); const IPV6_BROADCAST_ADDR: Ipv6Addr = Ipv6Addr::new(0xff02, 0, 0, 0, 0, 0, 0, 0x00fb);
const PORT: u16 = 5353; const PORT: u16 = 5353;
#[cfg(any(feature = "std", feature = "embassy-net"))] type MdnsTxBuf = MaybeUninit<[u8; MAX_TX_BUF_SIZE]>;
pub struct MdnsRunBuffers { type MdnsRxBuf = MaybeUninit<[u8; MAX_RX_BUF_SIZE]>;
udp: crate::transport::udp::UdpBuffers,
tx_buf: core::mem::MaybeUninit<[u8; crate::transport::packet::MAX_TX_BUF_SIZE]>,
rx_buf: core::mem::MaybeUninit<[u8; crate::transport::packet::MAX_RX_BUF_SIZE]>,
}
#[cfg(any(feature = "std", feature = "embassy-net"))] pub struct Mdns<'a> {
impl MdnsRunBuffers {
#[inline(always)]
pub const fn new() -> Self {
Self {
udp: crate::transport::udp::UdpBuffers::new(),
tx_buf: core::mem::MaybeUninit::uninit(),
rx_buf: core::mem::MaybeUninit::uninit(),
}
}
}
pub struct MdnsService<'a> {
host: Host<'a>, host: Host<'a>,
#[allow(unused)] interface: u32,
interface: Option<u32>,
dev_det: &'a BasicInfoConfig<'a>, dev_det: &'a BasicInfoConfig<'a>,
matter_port: u16, matter_port: u16,
services: RefCell<heapless::Vec<(heapless::String<40>, ServiceMode), 4>>, services: RefCell<heapless::Vec<(heapless::String<40>, ServiceMode), 4>>,
notification: Notification, notification: Notification,
} }
impl<'a> MdnsService<'a> { impl<'a> Mdns<'a> {
#[inline(always)] #[inline(always)]
pub const fn new( pub const fn new(
id: u16, id: u16,
hostname: &'a str, hostname: &'a str,
ip: [u8; 4], ip: [u8; 4],
ipv6: Option<([u8; 16], u32)>, ipv6: Option<[u8; 16]>,
interface: u32,
dev_det: &'a BasicInfoConfig<'a>, dev_det: &'a BasicInfoConfig<'a>,
matter_port: u16, matter_port: u16,
) -> Self { ) -> Self {
@ -70,17 +54,9 @@ impl<'a> MdnsService<'a> {
id, id,
hostname, hostname,
ip, ip,
ipv6: if let Some((ipv6, _)) = ipv6 { ipv6,
Some(ipv6)
} else {
None
},
},
interface: if let Some((_, interface)) = ipv6 {
Some(interface)
} else {
None
}, },
interface,
dev_det, dev_det,
matter_port, matter_port,
services: RefCell::new(heapless::Vec::new()), services: RefCell::new(heapless::Vec::new()),
@ -123,41 +99,33 @@ impl<'a> MdnsService<'a> {
Ok(()) Ok(())
} }
}
#[cfg(any(feature = "std", feature = "embassy-net"))] pub struct MdnsRunner<'a>(&'a Mdns<'a>);
pub async fn run<D>(
&self,
stack: &crate::transport::network::NetworkStack<D>,
buffers: &mut MdnsRunBuffers,
) -> Result<(), Error>
where
D: crate::transport::network::NetworkStackDriver,
{
let mut udp = crate::transport::udp::UdpListener::new(
stack,
crate::transport::network::SocketAddr::new(IpAddr::V6(Ipv6Addr::UNSPECIFIED), PORT),
&mut buffers.udp,
)
.await?;
// V6 multicast does not work with smoltcp yet (see https://github.com/smoltcp-rs/smoltcp/pull/602) impl<'a> MdnsRunner<'a> {
#[cfg(not(feature = "embassy-net"))] pub const fn new(mdns: &'a Mdns<'a>) -> Self {
if let Some(interface) = self.interface { Self(mdns)
udp.join_multicast_v6(IPV6_BROADCAST_ADDR, interface) }
.await?;
}
udp.join_multicast_v4( pub async fn run_udp(&mut self) -> Result<(), Error> {
IP_BROADCAST_ADDR, let mut tx_buf = MdnsTxBuf::uninit();
crate::transport::network::Ipv4Addr::from(self.host.ip), let mut rx_buf = MdnsRxBuf::uninit();
)
.await?;
let tx_pipe = Pipe::new(unsafe { buffers.tx_buf.assume_init_mut() }); let tx_buf = &mut tx_buf;
let rx_pipe = Pipe::new(unsafe { buffers.rx_buf.assume_init_mut() }); let rx_buf = &mut rx_buf;
let tx_pipe = Pipe::new(unsafe { tx_buf.assume_init_mut() });
let rx_pipe = Pipe::new(unsafe { rx_buf.assume_init_mut() });
let tx_pipe = &tx_pipe; let tx_pipe = &tx_pipe;
let rx_pipe = &rx_pipe; let rx_pipe = &rx_pipe;
let mut udp = UdpListener::new(SocketAddr::new(IP_BIND_ADDR, PORT)).await?;
udp.join_multicast_v6(IPV6_BROADCAST_ADDR, self.0.interface)?;
udp.join_multicast_v4(IP_BROADCAST_ADDR, Ipv4Addr::from(self.0.host.ip))?;
let udp = &udp; let udp = &udp;
let mut tx = pin!(async move { let mut tx = pin!(async move {
@ -198,51 +166,45 @@ impl<'a> MdnsService<'a> {
} }
}); });
let mut run = pin!(async move { self.run_piped(tx_pipe, rx_pipe).await }); let mut run = pin!(async move { self.run(tx_pipe, rx_pipe).await });
embassy_futures::select::select3(&mut tx, &mut rx, &mut run) select3(&mut tx, &mut rx, &mut run).await.unwrap()
.await
.unwrap()
} }
pub async fn run_piped(&self, tx_pipe: &Pipe<'_>, rx_pipe: &Pipe<'_>) -> Result<(), Error> { pub async fn run(&self, tx_pipe: &Pipe<'_>, rx_pipe: &Pipe<'_>) -> Result<(), Error> {
let mut broadcast = pin!(self.broadcast(tx_pipe)); let mut broadcast = pin!(self.broadcast(tx_pipe));
let mut respond = pin!(self.respond(rx_pipe, tx_pipe)); let mut respond = pin!(self.respond(rx_pipe, tx_pipe));
select(&mut broadcast, &mut respond).await.unwrap() select(&mut broadcast, &mut respond).await.unwrap()
} }
#[allow(clippy::await_holding_refcell_ref)]
async fn broadcast(&self, tx_pipe: &Pipe<'_>) -> Result<(), Error> { async fn broadcast(&self, tx_pipe: &Pipe<'_>) -> Result<(), Error> {
loop { loop {
select( select(
self.notification.wait(), self.0.notification.wait(),
Timer::after(Duration::from_secs(30)), Timer::after(Duration::from_secs(30)),
) )
.await; .await;
for addr in [ for addr in [
Some(SocketAddr::V4(SocketAddrV4::new(IP_BROADCAST_ADDR, PORT))), IpAddr::V4(IP_BROADCAST_ADDR),
self.interface.map(|interface| { IpAddr::V6(IPV6_BROADCAST_ADDR),
SocketAddr::V6(SocketAddrV6::new(IPV6_BROADCAST_ADDR, PORT, 0, interface)) ] {
}),
]
.into_iter()
.flatten()
{
loop { loop {
let sent = { let sent = {
let mut data = tx_pipe.data.lock().await; let mut data = tx_pipe.data.lock().await;
if data.chunk.is_none() { if data.chunk.is_none() {
let len = self.host.broadcast(self, data.buf, 60)?; let len = self.0.host.broadcast(&self.0, data.buf, 60)?;
if len > 0 { if len > 0 {
info!("Broadcasting mDNS entry to {addr}"); info!("Broadasting mDNS entry to {}:{}", addr, PORT);
data.chunk = Some(Chunk { data.chunk = Some(Chunk {
start: 0, start: 0,
end: len, end: len,
addr: Address::Udp(addr), addr: Address::Udp(SocketAddr::new(addr, PORT)),
}); });
tx_pipe.data_supplied_notification.signal(()); tx_pipe.data_supplied_notification.signal(());
@ -264,6 +226,7 @@ impl<'a> MdnsService<'a> {
} }
} }
#[allow(clippy::await_holding_refcell_ref)]
async fn respond(&self, rx_pipe: &Pipe<'_>, tx_pipe: &Pipe<'_>) -> Result<(), Error> { async fn respond(&self, rx_pipe: &Pipe<'_>, tx_pipe: &Pipe<'_>) -> Result<(), Error> {
loop { loop {
{ {
@ -277,7 +240,7 @@ impl<'a> MdnsService<'a> {
let mut tx_data = tx_pipe.data.lock().await; let mut tx_data = tx_pipe.data.lock().await;
if tx_data.chunk.is_none() { if tx_data.chunk.is_none() {
let len = self.host.respond(self, data, tx_data.buf, 60)?; let len = self.0.host.respond(&self.0, data, tx_data.buf, 60)?;
if len > 0 { if len > 0 {
info!("Replying to mDNS query from {}", rx_chunk.addr); info!("Replying to mDNS query from {}", rx_chunk.addr);
@ -316,24 +279,24 @@ impl<'a> MdnsService<'a> {
} }
} }
impl<'a> Services for MdnsService<'a> { impl<'a> super::Mdns for Mdns<'a> {
fn add(&self, service: &str, mode: ServiceMode) -> Result<(), Error> {
Mdns::add(self, service, mode)
}
fn remove(&self, service: &str) -> Result<(), Error> {
Mdns::remove(self, service)
}
}
impl<'a> Services for Mdns<'a> {
type Error = crate::error::Error; type Error = crate::error::Error;
fn for_each<F>(&self, callback: F) -> Result<(), Error> fn for_each<F>(&self, callback: F) -> Result<(), Error>
where where
F: FnMut(&Service) -> Result<(), Error>, F: FnMut(&Service) -> Result<(), Error>,
{ {
MdnsService::for_each(self, callback) Mdns::for_each(self, callback)
}
}
impl<'a> super::Mdns for MdnsService<'a> {
fn add(&self, service: &str, mode: ServiceMode) -> Result<(), Error> {
MdnsService::add(self, service, mode)
}
fn remove(&self, service: &str) -> Result<(), Error> {
MdnsService::remove(self, service)
} }
} }

View file

@ -31,7 +31,7 @@ use crate::{
use self::{ use self::{
code::{compute_pairing_code, pretty_print_pairing_code}, code::{compute_pairing_code, pretty_print_pairing_code},
qr::{compute_qr_code_text, print_qr_code}, qr::{compute_qr_code, print_qr_code},
}; };
pub struct DiscoveryCapabilities { pub struct DiscoveryCapabilities {
@ -88,15 +88,15 @@ pub fn print_pairing_code_and_qr(
buf: &mut [u8], buf: &mut [u8],
) -> Result<(), Error> { ) -> Result<(), Error> {
let pairing_code = compute_pairing_code(comm_data); let pairing_code = compute_pairing_code(comm_data);
let qr_code = compute_qr_code_text(dev_det, comm_data, discovery_capabilities, buf)?; let qr_code = compute_qr_code(dev_det, comm_data, discovery_capabilities, buf)?;
pretty_print_pairing_code(&pairing_code); pretty_print_pairing_code(&pairing_code);
print_qr_code(qr_code)?; print_qr_code(qr_code);
Ok(()) Ok(())
} }
fn passwd_from_comm_data(comm_data: &CommissioningData) -> u32 { pub(self) fn passwd_from_comm_data(comm_data: &CommissioningData) -> u32 {
// todo: should this be part of the comm_data implementation? // todo: should this be part of the comm_data implementation?
match comm_data.verifier.data { match comm_data.verifier.data {
VerifierOption::Password(pwd) => pwd, VerifierOption::Password(pwd) => pwd,

View file

@ -15,10 +15,6 @@
* limitations under the License. * limitations under the License.
*/ */
use core::mem::MaybeUninit;
use qrcodegen_no_heap::{QrCode, QrCodeEcc, Version};
use crate::{ use crate::{
error::ErrorCode, error::ErrorCode,
tlv::{TLVWriter, TagType}, tlv::{TLVWriter, TagType},
@ -323,153 +319,28 @@ fn estimate_struct_overhead(first_field_size: usize) -> usize {
first_field_size + 4 + 2 first_field_size + 4 + 2
} }
pub(crate) fn print_qr_code(qr_code_text: &str) -> Result<(), Error> { pub(super) fn print_qr_code(qr_code: &str) {
info!("QR Code Text: {}", qr_code_text); info!("QR Code: {}", qr_code);
let mut tmp_buf = MaybeUninit::<[u8; Version::MAX.buffer_len()]>::uninit(); #[cfg(feature = "std")]
let mut out_buf = MaybeUninit::<[u8; 7000]>::uninit(); {
use qrcode::{render::unicode, QrCode, Version};
let tmp_buf = unsafe { tmp_buf.assume_init_mut() }; let needed_version = compute_qr_version(qr_code);
let out_buf = unsafe { out_buf.assume_init_mut() }; let code =
QrCode::with_version(qr_code, Version::Normal(needed_version), qrcode::EcLevel::M)
.unwrap();
let image = code
.render::<unicode::Dense1x2>()
.dark_color(unicode::Dense1x2::Light)
.light_color(unicode::Dense1x2::Dark)
.build();
let qr_code = compute_qr_code(qr_code_text, out_buf, tmp_buf)?; info!("\n{}", image);
info!(
"\n{}",
TextImage::Unicode.render(&qr_code, 4, false, out_buf)?
);
Ok(())
}
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
pub enum TextImage {
Ascii,
Ansi,
Unicode,
}
impl TextImage {
pub fn render<'a>(
&self,
qr_code: &QrCode,
border: u8,
invert: bool,
out_buf: &'a mut [u8],
) -> Result<&'a str, Error> {
let mut offset = 0;
for c in self.render_iter(qr_code, border, invert) {
let mut dst = [0; 4];
let bytes = c.encode_utf8(&mut dst).as_bytes();
if offset + bytes.len() > out_buf.len() {
return Err(ErrorCode::BufferTooSmall)?;
} else {
out_buf[offset..offset + bytes.len()].copy_from_slice(bytes);
offset += bytes.len();
}
}
Ok(unsafe { core::str::from_utf8_unchecked(&out_buf[..offset]) })
}
pub fn render_iter<'a>(
&self,
qr_code: &'a QrCode<'a>,
border: u8,
invert: bool,
) -> impl Iterator<Item = char> + 'a {
let border: i32 = border as _;
let console_type = *self;
(-border..qr_code.size() + border)
.filter(move |y| console_type != Self::Unicode || (y - -border) % 2 == 0)
.flat_map(move |y| (-border..qr_code.size() + border + 1).map(move |x| (x, y)))
.map(move |(x, y)| {
if x < qr_code.size() + border {
let white = !qr_code.get_module(x, y) ^ invert;
match console_type {
Self::Ascii => {
if white {
"#"
} else {
" "
}
}
Self::Ansi => {
let prev_white = if x > -border {
Some(qr_code.get_module(x - 1, y))
} else {
None
}
.map(|prev_white| !prev_white ^ invert);
if prev_white != Some(white) {
if white {
"\x1b[47m "
} else {
"\x1b[40m "
}
} else {
" "
}
}
Self::Unicode => {
if white == !qr_code.get_module(x, y + 1) ^ invert {
if white {
"\u{2588}"
} else {
" "
}
} else if white {
"\u{2580}"
} else {
"\u{2584}"
}
}
}
} else {
"\x1b[0m\n"
}
})
.flat_map(str::chars)
} }
} }
pub fn compute_qr_code<'a>( pub fn compute_qr_code<'a>(
qr_code_text: &str,
tmp_buf: &mut [u8],
out_buf: &'a mut [u8],
) -> Result<QrCode<'a>, Error> {
let needed_version = compute_qr_code_version(qr_code_text);
let code = QrCode::encode_text(
qr_code_text,
tmp_buf,
out_buf,
QrCodeEcc::Medium,
Version::new(needed_version),
Version::new(needed_version),
None,
false,
)
.map_err(|_| ErrorCode::BufferTooSmall)?;
Ok(code)
}
pub fn compute_qr_code_version(qr_code_text: &str) -> u8 {
match qr_code_text.len() {
0..=38 => 2,
39..=61 => 3,
62..=90 => 4,
_ => 5,
}
}
pub fn compute_qr_code_text<'a>(
dev_det: &BasicInfoConfig, dev_det: &BasicInfoConfig,
comm_data: &CommissioningData, comm_data: &CommissioningData,
discovery_capabilities: DiscoveryCapabilities, discovery_capabilities: DiscoveryCapabilities,
@ -479,6 +350,16 @@ pub fn compute_qr_code_text<'a>(
payload_base38_representation(&qr_code_data, buf) payload_base38_representation(&qr_code_data, buf)
} }
#[cfg(feature = "std")]
fn compute_qr_version(qr_data: &str) -> i16 {
match qr_data.len() {
0..=38 => 2,
39..=61 => 3,
62..=90 => 4,
_ => 5,
}
}
fn populate_bits( fn populate_bits(
bits: &mut [u8], bits: &mut [u8],
offset: &mut usize, offset: &mut usize,

View file

@ -15,63 +15,31 @@
* limitations under the License. * limitations under the License.
*/ */
#[cfg(feature = "std")] #[cfg(feature = "std")]
pub use fileio::*; pub use file_psm::*;
#[cfg(feature = "std")] #[cfg(feature = "std")]
pub mod fileio { mod file_psm {
use std::fs; use std::fs;
use std::io::{Read, Write}; use std::io::{Read, Write};
use std::path::{Path, PathBuf}; use std::path::PathBuf;
use log::info; use log::info;
use crate::error::{Error, ErrorCode}; use crate::error::{Error, ErrorCode};
use crate::Matter;
pub struct Psm<'a> { pub struct FilePsm {
matter: &'a Matter<'a>,
dir: PathBuf, dir: PathBuf,
buf: [u8; 4096],
} }
impl<'a> Psm<'a> { impl FilePsm {
#[inline(always)] pub fn new(dir: PathBuf) -> Result<Self, Error> {
pub fn new(matter: &'a Matter<'a>, dir: PathBuf) -> Result<Self, Error> {
fs::create_dir_all(&dir)?; fs::create_dir_all(&dir)?;
info!("Persisting from/to {}", dir.display()); Ok(Self { dir })
let mut buf = [0; 4096];
if let Some(data) = Self::load(&dir, "acls", &mut buf)? {
matter.load_acls(data)?;
}
if let Some(data) = Self::load(&dir, "fabrics", &mut buf)? {
matter.load_fabrics(data)?;
}
Ok(Self { matter, dir, buf })
} }
pub async fn run(&mut self) -> Result<(), Error> { pub fn load<'a>(&self, key: &str, buf: &'a mut [u8]) -> Result<Option<&'a [u8]>, Error> {
loop { let path = self.dir.join(key);
self.matter.wait_changed().await;
if self.matter.is_changed() {
if let Some(data) = self.matter.store_acls(&mut self.buf)? {
Self::store(&self.dir, "acls", data)?;
}
if let Some(data) = self.matter.store_fabrics(&mut self.buf)? {
Self::store(&self.dir, "fabrics", data)?;
}
}
}
}
fn load<'b>(dir: &Path, key: &str, buf: &'b mut [u8]) -> Result<Option<&'b [u8]>, Error> {
let path = dir.join(key);
match fs::File::open(path) { match fs::File::open(path) {
Ok(mut file) => { Ok(mut file) => {
@ -101,8 +69,8 @@ pub mod fileio {
} }
} }
fn store(dir: &Path, key: &str, data: &[u8]) -> Result<(), Error> { pub fn store(&self, key: &str, data: &[u8]) -> Result<(), Error> {
let path = dir.join(key); let path = self.dir.join(key);
let mut file = fs::File::create(path)?; let mut file = fs::File::create(path)?;

View file

@ -20,25 +20,30 @@ use core::cell::RefCell;
use log::{error, trace}; use log::{error, trace};
use crate::{ use crate::{
alloc,
cert::Cert, cert::Cert,
crypto::{self, KeyPair, Sha256}, crypto::{self, KeyPair, Sha256},
error::{Error, ErrorCode}, error::{Error, ErrorCode},
fabric::{Fabric, FabricMgr}, fabric::{Fabric, FabricMgr},
secure_channel::common::{self, OpCode, PROTO_ID_SECURE_CHANNEL}, secure_channel::common::SCStatusCodes,
secure_channel::common::{complete_with_status, SCStatusCodes}, secure_channel::common::{self, OpCode},
tlv::{get_root_node_struct, FromTLV, OctetStr, TLVWriter, TagType}, tlv::{get_root_node_struct, FromTLV, OctetStr, TLVWriter, TagType},
transport::{ transport::{
exchange::Exchange,
network::Address, network::Address,
packet::Packet, proto_ctx::ProtoCtx,
session::{CaseDetails, CloneData, NocCatIds, SessionMode}, session::{CaseDetails, CloneData, NocCatIds, SessionMode},
}, },
utils::{rand::Rand, writebuf::WriteBuf}, utils::{rand::Rand, writebuf::WriteBuf},
}; };
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
enum State {
Sigma1Rx,
Sigma3Rx,
}
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
struct CaseSession { pub struct CaseSession {
state: State,
peer_sessid: u16, peer_sessid: u16,
local_sessid: u16, local_sessid: u16,
tt_hash: Sha256, tt_hash: Sha256,
@ -49,11 +54,11 @@ struct CaseSession {
} }
impl CaseSession { impl CaseSession {
#[inline(always)] pub fn new(peer_sessid: u16, local_sessid: u16) -> Result<Self, Error> {
pub fn new() -> Result<Self, Error> {
Ok(Self { Ok(Self {
peer_sessid: 0, state: State::Sigma1Rx,
local_sessid: 0, peer_sessid,
local_sessid,
tt_hash: Sha256::new()?, tt_hash: Sha256::new()?,
shared_secret: [0; crypto::ECDH_SHARED_SECRET_LEN_BYTES], shared_secret: [0; crypto::ECDH_SHARED_SECRET_LEN_BYTES],
our_pub_key: [0; crypto::EC_POINT_LEN_BYTES], our_pub_key: [0; crypto::EC_POINT_LEN_BYTES],
@ -74,116 +79,102 @@ impl<'a> Case<'a> {
Self { fabric_mgr, rand } Self { fabric_mgr, rand }
} }
pub async fn handle( pub fn casesigma3_handler(
&mut self, &mut self,
exchange: &mut Exchange<'_>, ctx: &mut ProtoCtx,
rx: &mut Packet<'_>, ) -> Result<(bool, Option<CloneData>), Error> {
tx: &mut Packet<'_>, let mut case_session = ctx
) -> Result<(), Error> { .exch_ctx
let mut session = alloc!(CaseSession::new()?); .exch
.take_case_session()
.ok_or(ErrorCode::InvalidState)?;
if case_session.state != State::Sigma1Rx {
Err(ErrorCode::Invalid)?;
}
case_session.state = State::Sigma3Rx;
self.handle_casesigma1(exchange, rx, tx, &mut session) let fabric_mgr = self.fabric_mgr.borrow();
.await?;
self.handle_casesigma3(exchange, rx, tx, &mut session).await let fabric = fabric_mgr.get_fabric(case_session.local_fabric_idx)?;
if fabric.is_none() {
common::create_sc_status_report(
ctx.tx,
common::SCStatusCodes::NoSharedTrustRoots,
None,
)?;
ctx.exch_ctx.exch.close();
return Ok((true, None));
}
// Safe to unwrap here
let fabric = fabric.unwrap();
let root = get_root_node_struct(ctx.rx.as_slice())?;
let encrypted = root.find_tag(1)?.slice()?;
let mut decrypted: [u8; 800] = [0; 800];
if encrypted.len() > decrypted.len() {
error!("Data too large");
Err(ErrorCode::NoSpace)?;
}
let decrypted = &mut decrypted[..encrypted.len()];
decrypted.copy_from_slice(encrypted);
let len = Case::get_sigma3_decryption(fabric.ipk.op_key(), &case_session, decrypted)?;
let decrypted = &decrypted[..len];
let root = get_root_node_struct(decrypted)?;
let d = Sigma3Decrypt::from_tlv(&root)?;
let initiator_noc = Cert::new(d.initiator_noc.0)?;
let mut initiator_icac = None;
if let Some(icac) = d.initiator_icac {
initiator_icac = Some(Cert::new(icac.0)?);
}
if let Err(e) = Case::validate_certs(fabric, &initiator_noc, &initiator_icac) {
error!("Certificate Chain doesn't match: {}", e);
common::create_sc_status_report(ctx.tx, common::SCStatusCodes::InvalidParameter, None)?;
ctx.exch_ctx.exch.close();
return Ok((true, None));
}
if Case::validate_sigma3_sign(
d.initiator_noc.0,
d.initiator_icac.map(|a| a.0),
&initiator_noc,
d.signature.0,
&case_session,
)
.is_err()
{
error!("Sigma3 Signature doesn't match");
common::create_sc_status_report(ctx.tx, common::SCStatusCodes::InvalidParameter, None)?;
ctx.exch_ctx.exch.close();
return Ok((true, None));
}
// Only now do we add this message to the TT Hash
let mut peer_catids: NocCatIds = Default::default();
initiator_noc.get_cat_ids(&mut peer_catids);
case_session.tt_hash.update(ctx.rx.as_slice())?;
let clone_data = Case::get_session_clone_data(
fabric.ipk.op_key(),
fabric.get_node_id(),
initiator_noc.get_node_id()?,
ctx.exch_ctx.sess.get_peer_addr(),
&case_session,
&peer_catids,
)?;
common::create_sc_status_report(ctx.tx, SCStatusCodes::SessionEstablishmentSuccess, None)?;
ctx.exch_ctx.exch.clear_data();
ctx.exch_ctx.exch.close();
Ok((true, Some(clone_data)))
} }
async fn handle_casesigma3( pub fn casesigma1_handler(&mut self, ctx: &mut ProtoCtx) -> Result<bool, Error> {
&mut self, ctx.tx.set_proto_opcode(OpCode::CASESigma2 as u8);
exchange: &mut Exchange<'_>,
rx: &Packet<'_>,
tx: &mut Packet<'_>,
case_session: &mut CaseSession,
) -> Result<(), Error> {
rx.check_proto_opcode(OpCode::CASESigma3 as _)?;
let result = { let rx_buf = ctx.rx.as_slice();
let fabric_mgr = self.fabric_mgr.borrow();
let fabric = fabric_mgr.get_fabric(case_session.local_fabric_idx)?;
if let Some(fabric) = fabric {
let root = get_root_node_struct(rx.as_slice())?;
let encrypted = root.find_tag(1)?.slice()?;
let mut decrypted = alloc!([0; 800]);
if encrypted.len() > decrypted.len() {
error!("Data too large");
Err(ErrorCode::NoSpace)?;
}
let decrypted = &mut decrypted[..encrypted.len()];
decrypted.copy_from_slice(encrypted);
let len =
Case::get_sigma3_decryption(fabric.ipk.op_key(), case_session, decrypted)?;
let decrypted = &decrypted[..len];
let root = get_root_node_struct(decrypted)?;
let d = Sigma3Decrypt::from_tlv(&root)?;
let initiator_noc = alloc!(Cert::new(d.initiator_noc.0)?);
let mut initiator_icac = None;
if let Some(icac) = d.initiator_icac {
initiator_icac = Some(alloc!(Cert::new(icac.0)?));
}
#[cfg(feature = "alloc")]
let initiator_icac_mut = initiator_icac.as_deref();
#[cfg(not(feature = "alloc"))]
let initiator_icac_mut = initiator_icac.as_ref();
if let Err(e) = Case::validate_certs(fabric, &initiator_noc, initiator_icac_mut) {
error!("Certificate Chain doesn't match: {}", e);
Err(SCStatusCodes::InvalidParameter)
} else if let Err(e) = Case::validate_sigma3_sign(
d.initiator_noc.0,
d.initiator_icac.map(|a| a.0),
&initiator_noc,
d.signature.0,
case_session,
) {
error!("Sigma3 Signature doesn't match: {}", e);
Err(SCStatusCodes::InvalidParameter)
} else {
// Only now do we add this message to the TT Hash
let mut peer_catids: NocCatIds = Default::default();
initiator_noc.get_cat_ids(&mut peer_catids);
case_session.tt_hash.update(rx.as_slice())?;
Ok(Case::get_session_clone_data(
fabric.ipk.op_key(),
fabric.get_node_id(),
initiator_noc.get_node_id()?,
exchange.with_session(|sess| Ok(sess.get_peer_addr()))?,
case_session,
&peer_catids,
)?)
}
} else {
Err(SCStatusCodes::NoSharedTrustRoots)
}
};
let status = match result {
Ok(clone_data) => {
exchange.clone_session(tx, &clone_data).await?;
SCStatusCodes::SessionEstablishmentSuccess
}
Err(status) => status,
};
complete_with_status(exchange, tx, status, None).await
}
async fn handle_casesigma1(
&mut self,
exchange: &mut Exchange<'_>,
rx: &mut Packet<'_>,
tx: &mut Packet<'_>,
case_session: &mut CaseSession,
) -> Result<(), Error> {
rx.check_proto_opcode(OpCode::CASESigma1 as _)?;
let rx_buf = rx.as_slice();
let root = get_root_node_struct(rx_buf)?; let root = get_root_node_struct(rx_buf)?;
let r = Sigma1Req::from_tlv(&root)?; let r = Sigma1Req::from_tlv(&root)?;
@ -193,20 +184,17 @@ impl<'a> Case<'a> {
.match_dest_id(r.initiator_random.0, r.dest_id.0); .match_dest_id(r.initiator_random.0, r.dest_id.0);
if local_fabric_idx.is_err() { if local_fabric_idx.is_err() {
error!("Fabric Index mismatch"); error!("Fabric Index mismatch");
complete_with_status( common::create_sc_status_report(
exchange, ctx.tx,
tx,
common::SCStatusCodes::NoSharedTrustRoots, common::SCStatusCodes::NoSharedTrustRoots,
None, None,
) )?;
.await?; ctx.exch_ctx.exch.close();
return Ok(true);
return Ok(());
} }
let local_sessid = exchange.get_next_sess_id(); let local_sessid = ctx.exch_ctx.sess.reserve_new_sess_id();
case_session.peer_sessid = r.initiator_sessid; let mut case_session = CaseSession::new(r.initiator_sessid, local_sessid)?;
case_session.local_sessid = local_sessid;
case_session.tt_hash.update(rx_buf)?; case_session.tt_hash.update(rx_buf)?;
case_session.local_fabric_idx = local_fabric_idx?; case_session.local_fabric_idx = local_fabric_idx?;
if r.peer_pub_key.0.len() != crypto::EC_POINT_LEN_BYTES { if r.peer_pub_key.0.len() != crypto::EC_POINT_LEN_BYTES {
@ -237,77 +225,52 @@ impl<'a> Case<'a> {
// Derive the Encrypted Part // Derive the Encrypted Part
const MAX_ENCRYPTED_SIZE: usize = 800; const MAX_ENCRYPTED_SIZE: usize = 800;
let mut encrypted = alloc!([0; MAX_ENCRYPTED_SIZE]); let mut encrypted: [u8; MAX_ENCRYPTED_SIZE] = [0; MAX_ENCRYPTED_SIZE];
let mut signature = alloc!([0u8; crypto::EC_SIGNATURE_LEN_BYTES]); let encrypted_len = {
let mut signature = [0u8; crypto::EC_SIGNATURE_LEN_BYTES];
let fabric_found = {
let fabric_mgr = self.fabric_mgr.borrow(); let fabric_mgr = self.fabric_mgr.borrow();
let fabric = fabric_mgr.get_fabric(case_session.local_fabric_idx)?; let fabric = fabric_mgr.get_fabric(case_session.local_fabric_idx)?;
if let Some(fabric) = fabric { if fabric.is_none() {
#[cfg(feature = "alloc")] common::create_sc_status_report(
let signature_mut = &mut *signature; ctx.tx,
common::SCStatusCodes::NoSharedTrustRoots,
#[cfg(not(feature = "alloc"))] None,
let signature_mut = &mut signature;
let sign_len = Case::get_sigma2_sign(
fabric,
&case_session.our_pub_key,
&case_session.peer_pub_key,
signature_mut,
)?; )?;
let signature = &signature[..sign_len]; ctx.exch_ctx.exch.close();
return Ok(true);
#[cfg(feature = "alloc")]
let encrypted_mut = &mut *encrypted;
#[cfg(not(feature = "alloc"))]
let encrypted_mut = &mut encrypted;
let encrypted_len = Case::get_sigma2_encryption(
fabric,
self.rand,
&our_random,
case_session,
signature,
encrypted_mut,
)?;
let encrypted = &encrypted[0..encrypted_len];
// Generate our Response Body
tx.reset();
tx.set_proto_id(PROTO_ID_SECURE_CHANNEL);
tx.set_proto_opcode(OpCode::CASESigma2 as u8);
let mut tw = TLVWriter::new(tx.get_writebuf()?);
tw.start_struct(TagType::Anonymous)?;
tw.str8(TagType::Context(1), &our_random)?;
tw.u16(TagType::Context(2), local_sessid)?;
tw.str8(TagType::Context(3), &case_session.our_pub_key)?;
tw.str16(TagType::Context(4), encrypted)?;
tw.end_container()?;
case_session.tt_hash.update(tx.as_mut_slice())?;
true
} else {
false
} }
};
if fabric_found { let sign_len = Case::get_sigma2_sign(
exchange.exchange(tx, rx).await fabric.unwrap(),
} else { &case_session.our_pub_key,
complete_with_status( &case_session.peer_pub_key,
exchange, &mut signature,
tx, )?;
common::SCStatusCodes::NoSharedTrustRoots, let signature = &signature[..sign_len];
None,
) Case::get_sigma2_encryption(
.await fabric.unwrap(),
} self.rand,
&our_random,
&mut case_session,
signature,
&mut encrypted,
)?
};
let encrypted = &encrypted[0..encrypted_len];
// Generate our Response Body
let mut tw = TLVWriter::new(ctx.tx.get_writebuf()?);
tw.start_struct(TagType::Anonymous)?;
tw.str8(TagType::Context(1), &our_random)?;
tw.u16(TagType::Context(2), local_sessid)?;
tw.str8(TagType::Context(3), &case_session.our_pub_key)?;
tw.str16(TagType::Context(4), encrypted)?;
tw.end_container()?;
case_session.tt_hash.update(ctx.tx.as_mut_slice())?;
ctx.exch_ctx.exch.set_case_session(case_session);
Ok(true)
} }
fn get_session_clone_data( fn get_session_clone_data(
@ -371,7 +334,7 @@ impl<'a> Case<'a> {
Ok(()) Ok(())
} }
fn validate_certs(fabric: &Fabric, noc: &Cert, icac: Option<&Cert>) -> Result<(), Error> { fn validate_certs(fabric: &Fabric, noc: &Cert, icac: &Option<Cert>) -> Result<(), Error> {
let mut verifier = noc.verify_chain_start(); let mut verifier = noc.verify_chain_start();
if fabric.get_fabric_id() != noc.get_fabric_id()? { if fabric.get_fabric_id() != noc.get_fabric_id()? {
@ -474,7 +437,7 @@ impl<'a> Case<'a> {
fn get_sigma2_key( fn get_sigma2_key(
ipk: &[u8], ipk: &[u8],
our_random: &[u8], our_random: &[u8],
case_session: &CaseSession, case_session: &mut CaseSession,
key: &mut [u8], key: &mut [u8],
) -> Result<(), Error> { ) -> Result<(), Error> {
const S2K_INFO: [u8; 6] = [0x53, 0x69, 0x67, 0x6d, 0x61, 0x32]; const S2K_INFO: [u8; 6] = [0x53, 0x69, 0x67, 0x6d, 0x61, 0x32];
@ -504,7 +467,7 @@ impl<'a> Case<'a> {
fabric: &Fabric, fabric: &Fabric,
rand: Rand, rand: Rand,
our_random: &[u8], our_random: &[u8],
case_session: &CaseSession, case_session: &mut CaseSession,
signature: &[u8], signature: &[u8],
out: &mut [u8], out: &mut [u8],
) -> Result<usize, Error> { ) -> Result<usize, Error> {

View file

@ -17,10 +17,7 @@
use num_derive::FromPrimitive; use num_derive::FromPrimitive;
use crate::{ use crate::{error::Error, transport::packet::Packet};
error::Error,
transport::{exchange::Exchange, packet::Packet},
};
use super::status_report::{create_status_report, GeneralCode}; use super::status_report::{create_status_report, GeneralCode};
@ -54,17 +51,6 @@ pub enum SCStatusCodes {
SessionNotFound = 5, SessionNotFound = 5,
} }
pub async fn complete_with_status(
exchange: &mut Exchange<'_>,
tx: &mut Packet<'_>,
status_code: SCStatusCodes,
proto_data: Option<&[u8]>,
) -> Result<(), Error> {
create_sc_status_report(tx, status_code, proto_data)?;
exchange.send_complete(tx).await
}
pub fn create_sc_status_report( pub fn create_sc_status_report(
proto_tx: &mut Packet, proto_tx: &mut Packet,
status_code: SCStatusCodes, status_code: SCStatusCodes,
@ -78,8 +64,8 @@ pub fn create_sc_status_report(
// the session will be closed soon // the session will be closed soon
GeneralCode::Success GeneralCode::Success
} }
SCStatusCodes::Busy => GeneralCode::Busy, SCStatusCodes::Busy
SCStatusCodes::InvalidParameter | SCStatusCodes::InvalidParameter
| SCStatusCodes::NoSharedTrustRoots | SCStatusCodes::NoSharedTrustRoots
| SCStatusCodes::SessionNotFound => GeneralCode::Failure, | SCStatusCodes::SessionNotFound => GeneralCode::Failure,
}; };

View file

@ -15,19 +15,18 @@
* limitations under the License. * limitations under the License.
*/ */
use core::borrow::Borrow; use core::{borrow::Borrow, cell::RefCell};
use core::cell::RefCell;
use log::error;
use crate::{ use crate::{
error::*, error::*,
fabric::FabricMgr, fabric::FabricMgr,
mdns::Mdns, mdns::Mdns,
secure_channel::{common::*, pake::Pake}, secure_channel::common::*,
transport::{exchange::Exchange, packet::Packet}, tlv,
transport::{proto_ctx::ProtoCtx, session::CloneData},
utils::{epoch::Epoch, rand::Rand}, utils::{epoch::Epoch, rand::Rand},
}; };
use log::{error, info};
use super::{case::Case, pake::PaseMgr}; use super::{case::Case, pake::PaseMgr};
@ -35,10 +34,9 @@ use super::{case::Case, pake::PaseMgr};
*/ */
pub struct SecureChannel<'a> { pub struct SecureChannel<'a> {
case: Case<'a>,
pase: &'a RefCell<PaseMgr>, pase: &'a RefCell<PaseMgr>,
fabric: &'a RefCell<FabricMgr>,
mdns: &'a dyn Mdns, mdns: &'a dyn Mdns,
rand: Rand,
} }
impl<'a> SecureChannel<'a> { impl<'a> SecureChannel<'a> {
@ -68,34 +66,45 @@ impl<'a> SecureChannel<'a> {
rand: Rand, rand: Rand,
) -> Self { ) -> Self {
Self { Self {
fabric, case: Case::new(fabric, rand),
pase, pase,
mdns, mdns,
rand,
} }
} }
pub async fn handle( pub fn handle(&mut self, ctx: &mut ProtoCtx) -> Result<(bool, Option<CloneData>), Error> {
&self, let proto_opcode: OpCode = ctx.rx.get_proto_opcode()?;
exchange: &mut Exchange<'_>,
rx: &mut Packet<'_>, ctx.tx.set_proto_id(PROTO_ID_SECURE_CHANNEL);
tx: &mut Packet<'_>, info!("Received Opcode: {:?}", proto_opcode);
) -> Result<(), Error> { info!("Received Data:");
match rx.get_proto_opcode()? { tlv::print_tlv_list(ctx.rx.as_slice());
OpCode::PBKDFParamRequest => { let (reply, clone_data) = match proto_opcode {
Pake::new(self.pase) OpCode::MRPStandAloneAck => Ok((false, None)),
.handle(exchange, rx, tx, self.mdns) OpCode::PBKDFParamRequest => self
.await .pase
} .borrow_mut()
OpCode::CASESigma1 => { .pbkdfparamreq_handler(ctx)
Case::new(self.fabric, self.rand) .map(|reply| (reply, None)),
.handle(exchange, rx, tx) OpCode::PASEPake1 => self
.await .pase
} .borrow_mut()
proto_opcode => { .pasepake1_handler(ctx)
error!("OpCode not handled: {:?}", proto_opcode); .map(|reply| (reply, None)),
OpCode::PASEPake3 => self.pase.borrow_mut().pasepake3_handler(ctx, self.mdns),
OpCode::CASESigma1 => self.case.casesigma1_handler(ctx).map(|reply| (reply, None)),
OpCode::CASESigma3 => self.case.casesigma3_handler(ctx),
_ => {
error!("OpCode Not Handled: {:?}", proto_opcode);
Err(ErrorCode::InvalidOpcode.into()) Err(ErrorCode::InvalidOpcode.into())
} }
}?;
if reply {
info!("Sending response");
tlv::print_tlv_list(ctx.tx.as_mut_slice());
} }
Ok((reply, clone_data))
} }
} }

View file

@ -15,13 +15,17 @@
* limitations under the License. * limitations under the License.
*/ */
#[cfg(not(any(feature = "openssl", feature = "mbedtls", feature = "rustcrypto")))] #[cfg(not(any(
feature = "crypto_openssl",
feature = "crypto_mbedtls",
feature = "crypto_rustcrypto"
)))]
pub use super::crypto_dummy::CryptoSpake2; pub use super::crypto_dummy::CryptoSpake2;
#[cfg(all(feature = "mbedtls", target_os = "espidf"))] #[cfg(all(feature = "crypto_mbedtls", target_os = "espidf"))]
pub use super::crypto_esp_mbedtls::CryptoSpake2; pub use super::crypto_esp_mbedtls::CryptoSpake2;
#[cfg(all(feature = "mbedtls", not(target_os = "espidf")))] #[cfg(all(feature = "crypto_mbedtls", not(target_os = "espidf")))]
pub use super::crypto_mbedtls::CryptoSpake2; pub use super::crypto_mbedtls::CryptoSpake2;
#[cfg(feature = "openssl")] #[cfg(feature = "crypto_openssl")]
pub use super::crypto_openssl::CryptoSpake2; pub use super::crypto_openssl::CryptoSpake2;
#[cfg(feature = "rustcrypto")] #[cfg(feature = "crypto_rustcrypto")]
pub use super::crypto_rustcrypto::CryptoSpake2; pub use super::crypto_rustcrypto::CryptoSpake2;

View file

@ -186,7 +186,7 @@ impl CryptoSpake2 {
let (Z, V) = Self::get_ZV_as_verifier( let (Z, V) = Self::get_ZV_as_verifier(
&self.w0, &self.w0,
&self.L, &self.L,
&self.M, &mut self.M,
&X, &X,
&self.xy, &self.xy,
&self.order, &self.order,
@ -228,7 +228,7 @@ impl CryptoSpake2 {
fn get_ZV_as_prover( fn get_ZV_as_prover(
w0: &Mpi, w0: &Mpi,
w1: &Mpi, w1: &Mpi,
N: &EcPoint, N: &mut EcPoint,
Y: &EcPoint, Y: &EcPoint,
x: &Mpi, x: &Mpi,
order: &Mpi, order: &Mpi,
@ -264,7 +264,7 @@ impl CryptoSpake2 {
fn get_ZV_as_verifier( fn get_ZV_as_verifier(
w0: &Mpi, w0: &Mpi,
L: &EcPoint, L: &EcPoint,
M: &EcPoint, M: &mut EcPoint,
X: &EcPoint, X: &EcPoint,
y: &Mpi, y: &Mpi,
order: &Mpi, order: &Mpi,
@ -292,7 +292,7 @@ impl CryptoSpake2 {
Ok((Z, V)) Ok((Z, V))
} }
fn invert(group: &EcGroup, num: &EcPoint) -> Result<EcPoint, mbedtls::Error> { fn invert(group: &mut EcGroup, num: &EcPoint) -> Result<EcPoint, mbedtls::Error> {
let p = group.p()?; let p = group.p()?;
let num_y = num.y()?; let num_y = num.y()?;
let inverted_num_y = p.sub(&num_y)?; let inverted_num_y = p.sub(&num_y)?;

View file

@ -235,7 +235,7 @@ impl CryptoSpake2 {
let mut len_buf: [u8; 8] = [0; 8]; let mut len_buf: [u8; 8] = [0; 8];
LittleEndian::write_u64(&mut len_buf, buf.len() as u64); LittleEndian::write_u64(&mut len_buf, buf.len() as u64);
tt.update(&len_buf)?; tt.update(&len_buf)?;
if !buf.is_empty() { if buf.len() > 0 {
tt.update(buf)?; tt.update(buf)?;
} }
Ok(()) Ok(())
@ -263,7 +263,6 @@ impl CryptoSpake2 {
#[inline(always)] #[inline(always)]
#[allow(non_snake_case)] #[allow(non_snake_case)]
#[allow(dead_code)] #[allow(dead_code)]
#[allow(clippy::too_many_arguments)]
fn get_ZV_as_prover( fn get_ZV_as_prover(
w0: &BigNum, w0: &BigNum,
w1: &BigNum, w1: &BigNum,
@ -299,7 +298,6 @@ impl CryptoSpake2 {
#[inline(always)] #[inline(always)]
#[allow(non_snake_case)] #[allow(non_snake_case)]
#[allow(dead_code)] #[allow(dead_code)]
#[allow(clippy::too_many_arguments)]
fn get_ZV_as_verifier( fn get_ZV_as_verifier(
w0: &BigNum, w0: &BigNum,
L: &EcPoint, L: &EcPoint,

View file

@ -17,15 +17,19 @@
pub mod case; pub mod case;
pub mod common; pub mod common;
#[cfg(not(any(feature = "openssl", feature = "mbedtls", feature = "rustcrypto")))] #[cfg(not(any(
feature = "crypto_openssl",
feature = "crypto_mbedtls",
feature = "crypto_rustcrypto"
)))]
mod crypto_dummy; mod crypto_dummy;
#[cfg(all(feature = "mbedtls", target_os = "espidf"))] #[cfg(all(feature = "crypto_mbedtls", target_os = "espidf"))]
mod crypto_esp_mbedtls; mod crypto_esp_mbedtls;
#[cfg(all(feature = "mbedtls", not(target_os = "espidf")))] #[cfg(all(feature = "crypto_mbedtls", not(target_os = "espidf")))]
mod crypto_mbedtls; mod crypto_mbedtls;
#[cfg(feature = "openssl")] #[cfg(feature = "crypto_openssl")]
pub mod crypto_openssl; pub mod crypto_openssl;
#[cfg(feature = "rustcrypto")] #[cfg(feature = "crypto_rustcrypto")]
pub mod crypto_rustcrypto; pub mod crypto_rustcrypto;
pub mod core; pub mod core;

View file

@ -0,0 +1,390 @@
/*
*
* Copyright (c) 2020-2022 Project CHIP Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
use core::{fmt::Write, time::Duration};
use super::{
common::{create_sc_status_report, SCStatusCodes},
spake2p::{Spake2P, VerifierData},
};
use crate::{
crypto,
error::{Error, ErrorCode},
mdns::{Mdns, ServiceMode},
secure_channel::common::OpCode,
tlv::{self, get_root_node_struct, FromTLV, OctetStr, TLVWriter, TagType, ToTLV},
transport::{
exchange::ExchangeCtx,
network::Address,
proto_ctx::ProtoCtx,
session::{CloneData, SessionMode},
},
utils::{epoch::Epoch, rand::Rand},
};
use log::{error, info};
#[allow(clippy::large_enum_variant)]
enum PaseMgrState {
Enabled(Pake, heapless::String<16>),
Disabled,
}
pub struct PaseMgr {
state: PaseMgrState,
epoch: Epoch,
rand: Rand,
}
impl PaseMgr {
#[inline(always)]
pub fn new(epoch: Epoch, rand: Rand) -> Self {
Self {
state: PaseMgrState::Disabled,
epoch,
rand,
}
}
pub fn is_pase_session_enabled(&self) -> bool {
matches!(&self.state, PaseMgrState::Enabled(_, _))
}
pub fn enable_pase_session(
&mut self,
verifier: VerifierData,
discriminator: u16,
mdns: &dyn Mdns,
) -> Result<(), Error> {
let mut buf = [0; 8];
(self.rand)(&mut buf);
let num = u64::from_be_bytes(buf);
let mut mdns_service_name = heapless::String::<16>::new();
write!(&mut mdns_service_name, "{:016X}", num).unwrap();
mdns.add(
&mdns_service_name,
ServiceMode::Commissionable(discriminator),
)?;
self.state = PaseMgrState::Enabled(
Pake::new(verifier, self.epoch, self.rand),
mdns_service_name,
);
Ok(())
}
pub fn disable_pase_session(&mut self, mdns: &dyn Mdns) -> Result<(), Error> {
if let PaseMgrState::Enabled(_, mdns_service_name) = &self.state {
mdns.remove(mdns_service_name)?;
}
self.state = PaseMgrState::Disabled;
Ok(())
}
/// If the PASE Session is enabled, execute the closure,
/// if not enabled, generate SC Status Report
fn if_enabled<F, T>(&mut self, ctx: &mut ProtoCtx, f: F) -> Result<Option<T>, Error>
where
F: FnOnce(&mut Pake, &mut ProtoCtx) -> Result<T, Error>,
{
if let PaseMgrState::Enabled(pake, _) = &mut self.state {
let data = f(pake, ctx)?;
Ok(Some(data))
} else {
error!("PASE Not enabled");
create_sc_status_report(ctx.tx, SCStatusCodes::InvalidParameter, None)?;
Ok(None)
}
}
pub fn pbkdfparamreq_handler(&mut self, ctx: &mut ProtoCtx) -> Result<bool, Error> {
ctx.tx.set_proto_opcode(OpCode::PBKDFParamResponse as u8);
self.if_enabled(ctx, |pake, ctx| pake.handle_pbkdfparamrequest(ctx))?;
Ok(true)
}
pub fn pasepake1_handler(&mut self, ctx: &mut ProtoCtx) -> Result<bool, Error> {
ctx.tx.set_proto_opcode(OpCode::PASEPake2 as u8);
self.if_enabled(ctx, |pake, ctx| pake.handle_pasepake1(ctx))?;
Ok(true)
}
pub fn pasepake3_handler(
&mut self,
ctx: &mut ProtoCtx,
mdns: &dyn Mdns,
) -> Result<(bool, Option<CloneData>), Error> {
let clone_data = self.if_enabled(ctx, |pake, ctx| pake.handle_pasepake3(ctx))?;
self.disable_pase_session(mdns)?;
Ok((true, clone_data.flatten()))
}
}
// This file basically deals with the handlers for the PASE secure channel protocol
// TLV extraction and encoding is done in this file.
// We create a Spake2p object and set it up in the exchange-data. This object then
// handles Spake2+ specific stuff.
const PASE_DISCARD_TIMEOUT_SECS: Duration = Duration::from_secs(60);
const SPAKE2_SESSION_KEYS_INFO: [u8; 11] = *b"SessionKeys";
struct SessionData {
start_time: Duration,
exch_id: u16,
peer_addr: Address,
spake2p: Spake2P,
}
impl SessionData {
fn is_sess_expired(&self, epoch: Epoch) -> Result<bool, Error> {
Ok(epoch() - self.start_time > PASE_DISCARD_TIMEOUT_SECS)
}
}
#[allow(clippy::large_enum_variant)]
enum PakeState {
Idle,
InProgress(SessionData),
}
impl PakeState {
const fn new() -> Self {
Self::Idle
}
fn take(&mut self) -> Result<SessionData, Error> {
let new = core::mem::replace(self, PakeState::Idle);
if let PakeState::InProgress(s) = new {
Ok(s)
} else {
Err(ErrorCode::InvalidSignature.into())
}
}
fn is_idle(&self) -> bool {
core::mem::discriminant(self) == core::mem::discriminant(&PakeState::Idle)
}
fn take_sess_data(&mut self, exch_ctx: &ExchangeCtx) -> Result<SessionData, Error> {
let sd = self.take()?;
if sd.exch_id != exch_ctx.exch.get_id() || sd.peer_addr != exch_ctx.sess.get_peer_addr() {
Err(ErrorCode::InvalidState.into())
} else {
Ok(sd)
}
}
fn make_in_progress(&mut self, epoch: Epoch, spake2p: Spake2P, exch_ctx: &ExchangeCtx) {
*self = PakeState::InProgress(SessionData {
start_time: epoch(),
spake2p,
exch_id: exch_ctx.exch.get_id(),
peer_addr: exch_ctx.sess.get_peer_addr(),
});
}
fn set_sess_data(&mut self, sd: SessionData) {
*self = PakeState::InProgress(sd);
}
}
impl Default for PakeState {
fn default() -> Self {
Self::new()
}
}
struct Pake {
verifier: VerifierData,
state: PakeState,
epoch: Epoch,
rand: Rand,
}
impl Pake {
pub fn new(verifier: VerifierData, epoch: Epoch, rand: Rand) -> Self {
// TODO: Can any PBKDF2 calculation be pre-computed here
Self {
verifier,
state: PakeState::new(),
epoch,
rand,
}
}
#[allow(non_snake_case)]
pub fn handle_pasepake3(&mut self, ctx: &mut ProtoCtx) -> Result<Option<CloneData>, Error> {
let mut sd = self.state.take_sess_data(&ctx.exch_ctx)?;
let cA = extract_pasepake_1_or_3_params(ctx.rx.as_slice())?;
let (status_code, ke) = sd.spake2p.handle_cA(cA);
let clone_data = if status_code == SCStatusCodes::SessionEstablishmentSuccess {
// Get the keys
let ke = ke.ok_or(ErrorCode::Invalid)?;
let mut session_keys: [u8; 48] = [0; 48];
crypto::hkdf_sha256(&[], ke, &SPAKE2_SESSION_KEYS_INFO, &mut session_keys)
.map_err(|_x| ErrorCode::NoSpace)?;
// Create a session
let data = sd.spake2p.get_app_data();
let peer_sessid: u16 = (data & 0xffff) as u16;
let local_sessid: u16 = ((data >> 16) & 0xffff) as u16;
let mut clone_data = CloneData::new(
0,
0,
peer_sessid,
local_sessid,
ctx.exch_ctx.sess.get_peer_addr(),
SessionMode::Pase,
);
clone_data.dec_key.copy_from_slice(&session_keys[0..16]);
clone_data.enc_key.copy_from_slice(&session_keys[16..32]);
clone_data
.att_challenge
.copy_from_slice(&session_keys[32..48]);
// Queue a transport mgr request to add a new session
Some(clone_data)
} else {
None
};
create_sc_status_report(ctx.tx, status_code, None)?;
ctx.exch_ctx.exch.close();
Ok(clone_data)
}
#[allow(non_snake_case)]
pub fn handle_pasepake1(&mut self, ctx: &mut ProtoCtx) -> Result<(), Error> {
let mut sd = self.state.take_sess_data(&ctx.exch_ctx)?;
let pA = extract_pasepake_1_or_3_params(ctx.rx.as_slice())?;
let mut pB: [u8; 65] = [0; 65];
let mut cB: [u8; 32] = [0; 32];
sd.spake2p.start_verifier(&self.verifier)?;
sd.spake2p.handle_pA(pA, &mut pB, &mut cB, self.rand)?;
let mut tw = TLVWriter::new(ctx.tx.get_writebuf()?);
let resp = Pake1Resp {
pb: OctetStr(&pB),
cb: OctetStr(&cB),
};
resp.to_tlv(&mut tw, TagType::Anonymous)?;
self.state.set_sess_data(sd);
Ok(())
}
pub fn handle_pbkdfparamrequest(&mut self, ctx: &mut ProtoCtx) -> Result<(), Error> {
if !self.state.is_idle() {
let sd = self.state.take()?;
if sd.is_sess_expired(self.epoch)? {
info!("Previous session expired, clearing it");
self.state = PakeState::Idle;
} else {
info!("Previous session in-progress, denying new request");
// little-endian timeout (here we've hardcoded 500ms)
create_sc_status_report(ctx.tx, SCStatusCodes::Busy, Some(&[0xf4, 0x01]))?;
return Ok(());
}
}
let root = tlv::get_root_node(ctx.rx.as_slice())?;
let a = PBKDFParamReq::from_tlv(&root)?;
if a.passcode_id != 0 {
error!("Can't yet handle passcode_id != 0");
Err(ErrorCode::Invalid)?;
}
let mut our_random: [u8; 32] = [0; 32];
(self.rand)(&mut our_random);
let local_sessid = ctx.exch_ctx.sess.reserve_new_sess_id();
let spake2p_data: u32 = ((local_sessid as u32) << 16) | a.initiator_ssid as u32;
let mut spake2p = Spake2P::new();
spake2p.set_app_data(spake2p_data);
// Generate response
let mut tw = TLVWriter::new(ctx.tx.get_writebuf()?);
let mut resp = PBKDFParamResp {
init_random: a.initiator_random,
our_random: OctetStr(&our_random),
local_sessid,
params: None,
};
if !a.has_params {
let params_resp = PBKDFParamRespParams {
count: self.verifier.count,
salt: OctetStr(&self.verifier.salt),
};
resp.params = Some(params_resp);
}
resp.to_tlv(&mut tw, TagType::Anonymous)?;
spake2p.set_context(ctx.rx.as_slice(), ctx.tx.as_mut_slice())?;
self.state
.make_in_progress(self.epoch, spake2p, &ctx.exch_ctx);
Ok(())
}
}
#[derive(ToTLV)]
#[tlvargs(start = 1)]
struct Pake1Resp<'a> {
pb: OctetStr<'a>,
cb: OctetStr<'a>,
}
#[derive(ToTLV)]
#[tlvargs(start = 1)]
struct PBKDFParamRespParams<'a> {
count: u32,
salt: OctetStr<'a>,
}
#[derive(ToTLV)]
#[tlvargs(start = 1)]
struct PBKDFParamResp<'a> {
init_random: OctetStr<'a>,
our_random: OctetStr<'a>,
local_sessid: u16,
params: Option<PBKDFParamRespParams<'a>>,
}
#[allow(non_snake_case)]
fn extract_pasepake_1_or_3_params(buf: &[u8]) -> Result<&[u8], Error> {
let root = get_root_node_struct(buf)?;
let pA = root.find_tag(1)?.slice()?;
Ok(pA)
}
#[derive(FromTLV)]
#[tlvargs(lifetime = "'a", start = 1)]
struct PBKDFParamReq<'a> {
initiator_random: OctetStr<'a>,
initiator_ssid: u16,
passcode_id: u16,
has_params: bool,
}

View file

@ -47,7 +47,7 @@ mod parser;
mod traits; mod traits;
mod writer; mod writer;
pub use matter_macro_derive::{FromTLV, ToTLV};
pub use parser::*; pub use parser::*;
pub use rs_matter_macros::{FromTLV, ToTLV};
pub use traits::*; pub use traits::*;
pub use writer::*; pub use writer::*;

View file

@ -265,20 +265,6 @@ pub enum Nullable<T> {
} }
impl<T> Nullable<T> { impl<T> Nullable<T> {
pub fn as_mut(&mut self) -> Nullable<&mut T> {
match self {
Nullable::Null => Nullable::Null,
Nullable::NotNull(t) => Nullable::NotNull(t),
}
}
pub fn as_ref(&self) -> Nullable<&T> {
match self {
Nullable::Null => Nullable::Null,
Nullable::NotNull(t) => Nullable::NotNull(t),
}
}
pub fn is_null(&self) -> bool { pub fn is_null(&self) -> bool {
match self { match self {
Nullable::Null => true, Nullable::Null => true,
@ -286,7 +272,7 @@ impl<T> Nullable<T> {
} }
} }
pub fn notnull(self) -> Option<T> { pub fn unwrap_notnull(self) -> Option<T> {
match self { match self {
Nullable::Null => None, Nullable::Null => None,
Nullable::NotNull(t) => Some(t), Nullable::NotNull(t) => Some(t),
@ -371,28 +357,6 @@ impl<'a, T: FromTLV<'a> + Clone> Iterator for TLVArrayIter<'a, T> {
} }
} }
impl<'a, 'b, T> PartialEq<TLVArray<'b, T>> for TLVArray<'a, T>
where
T: ToTLV + FromTLV<'a> + Clone + PartialEq,
'b: 'a,
{
fn eq(&self, other: &TLVArray<'b, T>) -> bool {
let mut iter1 = self.iter();
let mut iter2 = other.iter();
loop {
match (iter1.next(), iter2.next()) {
(None, None) => return true,
(Some(x), Some(y)) => {
if x != y {
return false;
}
}
_ => return false,
}
}
}
}
impl<'a, T> PartialEq<&[T]> for TLVArray<'a, T> impl<'a, T> PartialEq<&[T]> for TLVArray<'a, T>
where where
T: ToTLV + FromTLV<'a> + Clone + PartialEq, T: ToTLV + FromTLV<'a> + Clone + PartialEq,
@ -486,7 +450,7 @@ impl<'a> ToTLV for TLVElement<'a> {
mod tests { mod tests {
use super::{FromTLV, OctetStr, TLVWriter, TagType, ToTLV}; use super::{FromTLV, OctetStr, TLVWriter, TagType, ToTLV};
use crate::{error::Error, tlv::TLVList, utils::writebuf::WriteBuf}; use crate::{error::Error, tlv::TLVList, utils::writebuf::WriteBuf};
use rs_matter_macros::{FromTLV, ToTLV}; use matter_macro_derive::{FromTLV, ToTLV};
#[derive(ToTLV)] #[derive(ToTLV)]
struct TestDerive { struct TestDerive {

View file

@ -0,0 +1,251 @@
/*
*
* Copyright (c) 2020-2022 Project CHIP Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
use log::info;
use crate::{error::*, CommissioningData, Matter};
use crate::secure_channel::common::PROTO_ID_SECURE_CHANNEL;
use crate::secure_channel::core::SecureChannel;
use crate::transport::mrp::ReliableMessage;
use crate::transport::{exchange, network::Address, packet::Packet};
use super::proto_ctx::ProtoCtx;
use super::session::CloneData;
enum RecvState {
New,
OpenExchange,
AddSession(CloneData),
EvictSession,
EvictSession2(CloneData),
Ack,
}
pub enum RecvAction<'r, 'p> {
Send(Address, &'r [u8]),
Interact(ProtoCtx<'r, 'p>),
}
pub struct RecvCompletion<'r, 'a> {
transport: &'r mut Transport<'a>,
rx: Packet<'r>,
tx: Packet<'r>,
state: RecvState,
}
impl<'r, 'a> RecvCompletion<'r, 'a> {
pub fn next_action(&mut self) -> Result<Option<RecvAction<'_, 'r>>, Error> {
loop {
// Polonius will remove the need for unsafe one day
let this = unsafe { (self as *mut RecvCompletion).as_mut().unwrap() };
if let Some(action) = this.maybe_next_action()? {
return Ok(action);
}
}
}
fn maybe_next_action(&mut self) -> Result<Option<Option<RecvAction<'_, 'r>>>, Error> {
self.transport.exch_mgr.purge();
self.tx.reset();
let (state, next) = match core::mem::replace(&mut self.state, RecvState::New) {
RecvState::New => {
self.rx.plain_hdr_decode()?;
(RecvState::OpenExchange, None)
}
RecvState::OpenExchange => match self.transport.exch_mgr.recv(&mut self.rx) {
Ok(Some(exch_ctx)) => {
if self.rx.get_proto_id() == PROTO_ID_SECURE_CHANNEL {
let mut proto_ctx = ProtoCtx::new(exch_ctx, &self.rx, &mut self.tx);
let mut secure_channel = SecureChannel::new(self.transport.matter);
let (reply, clone_data) = secure_channel.handle(&mut proto_ctx)?;
let state = if let Some(clone_data) = clone_data {
RecvState::AddSession(clone_data)
} else {
RecvState::Ack
};
if reply {
if proto_ctx.send()? {
(
state,
Some(Some(RecvAction::Send(self.tx.peer, self.tx.as_slice()))),
)
} else {
(state, None)
}
} else {
(state, None)
}
} else {
let proto_ctx = ProtoCtx::new(exch_ctx, &self.rx, &mut self.tx);
(RecvState::Ack, Some(Some(RecvAction::Interact(proto_ctx))))
}
}
Ok(None) => (RecvState::Ack, None),
Err(e) => match e.code() {
ErrorCode::Duplicate => (RecvState::Ack, None),
ErrorCode::NoSpace => (RecvState::EvictSession, None),
_ => Err(e)?,
},
},
RecvState::AddSession(clone_data) => {
match self.transport.exch_mgr.add_session(&clone_data) {
Ok(_) => (RecvState::Ack, None),
Err(e) => match e.code() {
ErrorCode::NoSpace => (RecvState::EvictSession2(clone_data), None),
_ => Err(e)?,
},
}
}
RecvState::EvictSession => {
if self.transport.exch_mgr.evict_session(&mut self.tx)? {
(
RecvState::OpenExchange,
Some(Some(RecvAction::Send(self.tx.peer, self.tx.as_slice()))),
)
} else {
(RecvState::EvictSession, None)
}
}
RecvState::EvictSession2(clone_data) => {
if self.transport.exch_mgr.evict_session(&mut self.tx)? {
(
RecvState::AddSession(clone_data),
Some(Some(RecvAction::Send(self.tx.peer, self.tx.as_slice()))),
)
} else {
(RecvState::EvictSession2(clone_data), None)
}
}
RecvState::Ack => {
if let Some(exch_id) = self.transport.exch_mgr.pending_ack() {
info!("Sending MRP Standalone ACK for exch {}", exch_id);
ReliableMessage::prepare_ack(exch_id, &mut self.tx);
if self.transport.exch_mgr.send(exch_id, &mut self.tx)? {
(
RecvState::Ack,
Some(Some(RecvAction::Send(self.tx.peer, self.tx.as_slice()))),
)
} else {
(RecvState::Ack, None)
}
} else {
(RecvState::Ack, Some(None))
}
}
};
self.state = state;
Ok(next)
}
}
enum NotifyState {}
pub enum NotifyAction<'r, 'p> {
Send(&'r [u8]),
Notify(ProtoCtx<'r, 'p>),
}
pub struct NotifyCompletion<'r, 'a> {
// TODO
_transport: &'r mut Transport<'a>,
_rx: Packet<'r>,
_tx: Packet<'r>,
_state: NotifyState,
}
impl<'r, 'a> NotifyCompletion<'r, 'a> {
pub fn next_action(&mut self) -> Result<Option<NotifyAction<'_, 'r>>, Error> {
loop {
// Polonius will remove the need for unsafe one day
let this = unsafe { (self as *mut NotifyCompletion).as_mut().unwrap() };
if let Some(action) = this.maybe_next_action()? {
return Ok(action);
}
}
}
fn maybe_next_action(&mut self) -> Result<Option<Option<NotifyAction<'_, 'r>>>, Error> {
Ok(Some(None)) // TODO: Future
}
}
pub struct Transport<'a> {
matter: &'a Matter<'a>,
exch_mgr: exchange::ExchangeMgr,
}
impl<'a> Transport<'a> {
#[inline(always)]
pub fn new(matter: &'a Matter<'a>) -> Self {
let epoch = matter.epoch;
let rand = matter.rand;
Self {
matter,
exch_mgr: exchange::ExchangeMgr::new(epoch, rand),
}
}
pub fn matter(&self) -> &Matter<'a> {
self.matter
}
pub fn start(&mut self, dev_comm: CommissioningData, buf: &mut [u8]) -> Result<(), Error> {
info!("Starting Matter transport");
if self.matter().start_comissioning(dev_comm, buf)? {
info!("Comissioning started");
}
Ok(())
}
pub fn recv<'r>(
&'r mut self,
addr: Address,
rx_buf: &'r mut [u8],
tx_buf: &'r mut [u8],
) -> RecvCompletion<'r, 'a> {
let mut rx = Packet::new_rx(rx_buf);
let tx = Packet::new_tx(tx_buf);
rx.peer = addr;
RecvCompletion {
transport: self,
rx,
tx,
state: RecvState::New,
}
}
pub fn notify(&mut self, _tx: &mut Packet) -> Result<bool, Error> {
Ok(false)
}
}

View file

@ -0,0 +1,625 @@
/*
*
* Copyright (c) 2020-2022 Project CHIP Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
use core::fmt;
use core::time::Duration;
use log::{error, info, trace};
use owo_colors::OwoColorize;
use crate::error::{Error, ErrorCode};
use crate::interaction_model::core::{ResumeReadReq, ResumeSubscribeReq};
use crate::secure_channel;
use crate::secure_channel::case::CaseSession;
use crate::utils::epoch::Epoch;
use crate::utils::rand::Rand;
use heapless::LinearMap;
use super::session::CloneData;
use super::{mrp::ReliableMessage, packet::Packet, session::SessionHandle, session::SessionMgr};
pub struct ExchangeCtx<'a> {
pub exch: &'a mut Exchange,
pub sess: SessionHandle<'a>,
pub epoch: Epoch,
}
impl<'a> ExchangeCtx<'a> {
pub fn send(&mut self, tx: &mut Packet) -> Result<bool, Error> {
self.exch.send(tx, &mut self.sess)
}
}
#[derive(Debug, PartialEq, Eq, Copy, Clone, Default)]
pub enum Role {
#[default]
Initiator = 0,
Responder = 1,
}
#[derive(Debug, PartialEq, Default)]
enum State {
/// The exchange is open and active
#[default]
Open,
/// The exchange is closed, but keys are active since retransmissions/acks may be pending
Close,
/// The exchange is terminated, keys are destroyed, no communication can happen
Terminate,
}
// Instead of just doing an Option<>, we create some special handling
// where the commonly used higher layer data store does't have to do a Box
#[derive(Default)]
pub enum DataOption {
CaseSession(CaseSession),
Time(Duration),
SuspendedReadReq(ResumeReadReq),
SubscriptionId(u32),
SuspendedSubscibeReq(ResumeSubscribeReq),
#[default]
None,
}
#[derive(Default)]
pub struct Exchange {
id: u16,
sess_idx: usize,
role: Role,
state: State,
mrp: ReliableMessage,
// Currently I see this primarily used in PASE and CASE. If that is the limited use
// of this, we might move this into a separate data structure, so as not to burden
// all 'exchanges'.
data: DataOption,
}
impl Exchange {
pub fn new(id: u16, sess_idx: usize, role: Role) -> Exchange {
Exchange {
id,
sess_idx,
role,
state: State::Open,
mrp: ReliableMessage::new(),
..Default::default()
}
}
pub fn terminate(&mut self) {
self.data = DataOption::None;
self.state = State::Terminate;
}
pub fn close(&mut self) {
self.data = DataOption::None;
self.state = State::Close;
}
pub fn is_state_open(&self) -> bool {
self.state == State::Open
}
pub fn is_purgeable(&self) -> bool {
// No Users, No pending ACKs/Retrans
self.state == State::Terminate || (self.state == State::Close && self.mrp.is_empty())
}
pub fn get_id(&self) -> u16 {
self.id
}
pub fn get_role(&self) -> Role {
self.role
}
pub fn clear_data(&mut self) {
self.data = DataOption::None;
}
pub fn set_case_session(&mut self, session: CaseSession) {
self.data = DataOption::CaseSession(session);
}
pub fn get_case_session(&mut self) -> Option<&mut CaseSession> {
if let DataOption::CaseSession(session) = &mut self.data {
Some(session)
} else {
None
}
}
pub fn take_case_session(&mut self) -> Option<CaseSession> {
let old = core::mem::replace(&mut self.data, DataOption::None);
if let DataOption::CaseSession(session) = old {
Some(session)
} else {
self.data = old;
None
}
}
pub fn set_suspended_read_req(&mut self, req: ResumeReadReq) {
self.data = DataOption::SuspendedReadReq(req);
}
pub fn take_suspended_read_req(&mut self) -> Option<ResumeReadReq> {
let old = core::mem::replace(&mut self.data, DataOption::None);
if let DataOption::SuspendedReadReq(req) = old {
Some(req)
} else {
self.data = old;
None
}
}
pub fn set_subscription_id(&mut self, id: u32) {
self.data = DataOption::SubscriptionId(id);
}
pub fn take_subscription_id(&mut self) -> Option<u32> {
let old = core::mem::replace(&mut self.data, DataOption::None);
if let DataOption::SubscriptionId(id) = old {
Some(id)
} else {
self.data = old;
None
}
}
pub fn set_suspended_subscribe_req(&mut self, req: ResumeSubscribeReq) {
self.data = DataOption::SuspendedSubscibeReq(req);
}
pub fn take_suspended_subscribe_req(&mut self) -> Option<ResumeSubscribeReq> {
let old = core::mem::replace(&mut self.data, DataOption::None);
if let DataOption::SuspendedSubscibeReq(req) = old {
Some(req)
} else {
self.data = old;
None
}
}
pub fn set_data_time(&mut self, expiry_ts: Option<Duration>) {
if let Some(t) = expiry_ts {
self.data = DataOption::Time(t);
}
}
pub fn get_data_time(&self) -> Option<Duration> {
match self.data {
DataOption::Time(t) => Some(t),
_ => None,
}
}
pub(crate) fn send(
&mut self,
tx: &mut Packet,
session: &mut SessionHandle,
) -> Result<bool, Error> {
if self.state == State::Terminate {
info!("Skipping tx for terminated exchange {}", self.id);
return Ok(false);
}
trace!("payload: {:x?}", tx.as_slice());
info!(
"{} with proto id: {} opcode: {}, tlv:\n",
"Sending".blue(),
tx.get_proto_id(),
tx.get_proto_raw_opcode(),
);
//print_tlv_list(tx.as_slice());
tx.proto.exch_id = self.id;
if self.role == Role::Initiator {
tx.proto.set_initiator();
}
session.pre_send(tx)?;
self.mrp.pre_send(tx)?;
session.send(tx)?;
Ok(true)
}
}
impl fmt::Display for Exchange {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"exch_id: {:?}, sess_index: {}, role: {:?}, mrp: {:?}, state: {:?}",
self.id, self.sess_idx, self.role, self.mrp, self.state
)
}
}
pub fn get_role(is_initiator: bool) -> Role {
if is_initiator {
Role::Initiator
} else {
Role::Responder
}
}
pub fn get_complementary_role(is_initiator: bool) -> Role {
if is_initiator {
Role::Responder
} else {
Role::Initiator
}
}
const MAX_EXCHANGES: usize = 8;
pub struct ExchangeMgr {
// keys: exch-id
exchanges: LinearMap<u16, Exchange, MAX_EXCHANGES>,
sess_mgr: SessionMgr,
epoch: Epoch,
}
pub const MAX_MRP_ENTRIES: usize = 4;
impl ExchangeMgr {
#[inline(always)]
pub fn new(epoch: Epoch, rand: Rand) -> Self {
Self {
sess_mgr: SessionMgr::new(epoch, rand),
exchanges: LinearMap::new(),
epoch,
}
}
pub fn get_sess_mgr(&mut self) -> &mut SessionMgr {
&mut self.sess_mgr
}
pub fn _get_with_id(
exchanges: &mut LinearMap<u16, Exchange, MAX_EXCHANGES>,
exch_id: u16,
) -> Option<&mut Exchange> {
exchanges.get_mut(&exch_id)
}
pub fn get_with_id(&mut self, exch_id: u16) -> Option<&mut Exchange> {
ExchangeMgr::_get_with_id(&mut self.exchanges, exch_id)
}
fn _get(
exchanges: &mut LinearMap<u16, Exchange, MAX_EXCHANGES>,
sess_idx: usize,
id: u16,
role: Role,
create_new: bool,
) -> Result<&mut Exchange, Error> {
// I don't prefer that we scan the list twice here (once for contains_key and other)
if !exchanges.contains_key(&(id)) {
if create_new {
// If an exchange doesn't exist, create a new one
info!("Creating new exchange");
let e = Exchange::new(id, sess_idx, role);
if exchanges.insert(id, e).is_err() {
Err(ErrorCode::NoSpace)?;
}
} else {
Err(ErrorCode::NoSpace)?;
}
}
// At this point, we would either have inserted the record if 'create_new' was set
// or it existed already
if let Some(result) = exchanges.get_mut(&id) {
if result.get_role() == role && sess_idx == result.sess_idx {
Ok(result)
} else {
Err(ErrorCode::NoExchange.into())
}
} else {
error!("This should never happen");
Err(ErrorCode::NoSpace.into())
}
}
/// The Exchange Mgr receive is like a big processing function
pub fn recv(&mut self, rx: &mut Packet) -> Result<Option<ExchangeCtx>, Error> {
// Get the session
let index = self.sess_mgr.post_recv(rx)?;
let mut session = self.sess_mgr.get_session_handle(index);
// Decrypt the message
session.recv(self.epoch, rx)?;
// Get the exchange
let exch = ExchangeMgr::_get(
&mut self.exchanges,
index,
rx.proto.exch_id,
get_complementary_role(rx.proto.is_initiator()),
// We create a new exchange, only if the peer is the initiator
rx.proto.is_initiator(),
)?;
// Message Reliability Protocol
exch.mrp.recv(rx, self.epoch)?;
if exch.is_state_open() {
Ok(Some(ExchangeCtx {
exch,
sess: session,
epoch: self.epoch,
}))
} else {
// Instead of an error, we send None here, because it is likely that
// we just processed an acknowledgement that cleared the exchange
Ok(None)
}
}
pub fn send(&mut self, exch_id: u16, tx: &mut Packet) -> Result<bool, Error> {
let exchange =
ExchangeMgr::_get_with_id(&mut self.exchanges, exch_id).ok_or(ErrorCode::NoExchange)?;
let mut session = self.sess_mgr.get_session_handle(exchange.sess_idx);
exchange.send(tx, &mut session)
}
pub fn purge(&mut self) {
let mut to_purge: LinearMap<u16, (), MAX_EXCHANGES> = LinearMap::new();
for (exch_id, exchange) in self.exchanges.iter() {
if exchange.is_purgeable() {
let _ = to_purge.insert(*exch_id, ());
}
}
for (exch_id, _) in to_purge.iter() {
self.exchanges.remove(exch_id);
}
}
pub fn pending_ack(&mut self) -> Option<u16> {
self.exchanges
.iter()
.find(|(_, exchange)| exchange.mrp.is_ack_ready(self.epoch))
.map(|(exch_id, _)| *exch_id)
}
pub fn evict_session(&mut self, tx: &mut Packet) -> Result<bool, Error> {
if let Some(index) = self.sess_mgr.get_session_for_eviction() {
info!("Sessions full, vacating session with index: {}", index);
// If we enter here, we have an LRU session that needs to be reclaimed
// As per the spec, we need to send a CLOSE here
let mut session = self.sess_mgr.get_session_handle(index);
secure_channel::common::create_sc_status_report(
tx,
secure_channel::common::SCStatusCodes::CloseSession,
None,
)?;
if let Some((_, exchange)) =
self.exchanges.iter_mut().find(|(_, e)| e.sess_idx == index)
{
// Send Close_session on this exchange, and then close the session
// Should this be done for all exchanges?
error!("Sending Close Session");
exchange.send(tx, &mut session)?;
// TODO: This wouldn't actually send it out, because 'transport' isn't owned yet.
}
let remove_exchanges: heapless::Vec<u16, MAX_EXCHANGES> = self
.exchanges
.iter()
.filter_map(|(eid, e)| {
if e.sess_idx == index {
Some(*eid)
} else {
None
}
})
.collect();
info!(
"Terminating the following exchanges: {:?}",
remove_exchanges
);
for exch_id in remove_exchanges {
// Remove from exchange list
self.exchanges.remove(&exch_id);
}
self.sess_mgr.remove(index);
Ok(true)
} else {
Ok(false)
}
}
pub fn add_session(&mut self, clone_data: &CloneData) -> Result<SessionHandle, Error> {
let sess_idx = self.sess_mgr.clone_session(clone_data)?;
Ok(self.sess_mgr.get_session_handle(sess_idx))
}
}
impl fmt::Display for ExchangeMgr {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
writeln!(f, "{{ Session Mgr: {},", self.sess_mgr)?;
writeln!(f, " Exchanges: [")?;
for s in &self.exchanges {
writeln!(f, "{{ {}, }},", s.1)?;
}
writeln!(f, " ]")?;
write!(f, "}}")
}
}
#[cfg(test)]
#[allow(clippy::bool_assert_comparison)]
mod tests {
use crate::{
error::ErrorCode,
transport::{
network::Address,
session::{CloneData, SessionMode},
},
utils::{epoch::dummy_epoch, rand::dummy_rand},
};
use super::{ExchangeMgr, Role};
#[test]
fn test_purge() {
let mut mgr = ExchangeMgr::new(dummy_epoch, dummy_rand);
let _ = ExchangeMgr::_get(&mut mgr.exchanges, 1, 2, Role::Responder, true).unwrap();
let _ = ExchangeMgr::_get(&mut mgr.exchanges, 1, 3, Role::Responder, true).unwrap();
mgr.purge();
assert_eq!(
ExchangeMgr::_get(&mut mgr.exchanges, 1, 2, Role::Responder, false).is_ok(),
true
);
assert_eq!(
ExchangeMgr::_get(&mut mgr.exchanges, 1, 3, Role::Responder, false).is_ok(),
true
);
// Close e1
let e1 = ExchangeMgr::_get(&mut mgr.exchanges, 1, 2, Role::Responder, false).unwrap();
e1.close();
mgr.purge();
assert_eq!(
ExchangeMgr::_get(&mut mgr.exchanges, 1, 2, Role::Responder, false).is_ok(),
false
);
assert_eq!(
ExchangeMgr::_get(&mut mgr.exchanges, 1, 3, Role::Responder, false).is_ok(),
true
);
}
fn get_clone_data(peer_sess_id: u16, local_sess_id: u16) -> CloneData {
CloneData::new(
12341234,
43211234,
peer_sess_id,
local_sess_id,
Address::default(),
SessionMode::Pase,
)
}
fn fill_sessions(mgr: &mut ExchangeMgr, count: usize) {
let mut local_sess_id = 1;
let mut peer_sess_id = 100;
for _ in 1..count {
let clone_data = get_clone_data(peer_sess_id, local_sess_id);
match mgr.add_session(&clone_data) {
Ok(s) => assert_eq!(peer_sess_id, s.get_peer_sess_id()),
Err(e) => {
if e.code() == ErrorCode::NoSpace {
break;
} else {
panic!("Could not create sessions");
}
}
}
local_sess_id += 1;
peer_sess_id += 1;
}
}
#[cfg(feature = "std")]
#[test]
/// We purposefuly overflow the sessions
/// and when the overflow happens, we confirm that
/// - The sessions are evicted in LRU
/// - The exchanges associated with those sessions are evicted too
fn test_sess_evict() {
use crate::transport::packet::{Packet, MAX_TX_BUF_SIZE};
use crate::transport::session::MAX_SESSIONS;
let mut mgr = ExchangeMgr::new(crate::utils::epoch::sys_epoch, dummy_rand);
fill_sessions(&mut mgr, MAX_SESSIONS + 1);
// Sessions are now full from local session id 1 to 16
// Create exchanges for sessions 2 (i.e. session index 1) and 3 (session index 2)
// Exchange IDs are 20 and 30 respectively
let _ = ExchangeMgr::_get(&mut mgr.exchanges, 1, 20, Role::Responder, true).unwrap();
let _ = ExchangeMgr::_get(&mut mgr.exchanges, 2, 30, Role::Responder, true).unwrap();
// Confirm that session ids 1 to MAX_SESSIONS exists
for i in 1..(MAX_SESSIONS + 1) {
assert_eq!(mgr.sess_mgr.get_with_id(i as u16).is_none(), false);
}
// Confirm that the exchanges are around
assert_eq!(mgr.get_with_id(20).is_none(), false);
assert_eq!(mgr.get_with_id(30).is_none(), false);
let mut old_local_sess_id = 1;
let mut new_local_sess_id = 100;
let mut new_peer_sess_id = 200;
for i in 1..(MAX_SESSIONS + 1) {
// Now purposefully overflow the sessions by adding another session
let result = mgr.add_session(&get_clone_data(new_peer_sess_id, new_local_sess_id));
assert!(matches!(
result.map_err(|e| e.code()),
Err(ErrorCode::NoSpace)
));
let mut buf = [0; MAX_TX_BUF_SIZE];
let tx = &mut Packet::new_tx(&mut buf);
let evicted = mgr.evict_session(tx).unwrap();
assert!(evicted);
let session = mgr
.add_session(&get_clone_data(new_peer_sess_id, new_local_sess_id))
.unwrap();
assert_eq!(session.get_peer_sess_id(), new_peer_sess_id);
// This should have evicted session with local sess_id
assert_eq!(mgr.sess_mgr.get_with_id(old_local_sess_id).is_none(), true);
new_local_sess_id += 1;
new_peer_sess_id += 1;
old_local_sess_id += 1;
match i {
1 => {
// Both exchanges should exist
assert_eq!(mgr.get_with_id(20).is_none(), false);
assert_eq!(mgr.get_with_id(30).is_none(), false);
}
2 => {
// Exchange 20 would have been evicted
assert_eq!(mgr.get_with_id(20).is_none(), true);
assert_eq!(mgr.get_with_id(30).is_none(), false);
}
3 => {
// Exchange 20 and 30 would have been evicted
assert_eq!(mgr.get_with_id(20).is_none(), true);
assert_eq!(mgr.get_with_id(30).is_none(), true);
}
_ => {}
}
}
// println!("Session mgr {}", mgr.sess_mgr);
}
}

View file

@ -23,6 +23,7 @@ pub mod network;
pub mod packet; pub mod packet;
pub mod pipe; pub mod pipe;
pub mod plain_hdr; pub mod plain_hdr;
pub mod proto_ctx;
pub mod proto_hdr; pub mod proto_hdr;
pub mod session; pub mod session;
pub mod udp; pub mod udp;

Some files were not shown because too many files have changed in this diff Show more