Compare commits
152 commits
Author | SHA1 | Date | |
---|---|---|---|
|
97c9619dba | ||
|
89f2bc4d98 | ||
|
7b55e7fbfb | ||
|
152419472b | ||
|
8faa31a63a | ||
|
d84402f571 | ||
|
e39fd18b73 | ||
|
29a5c14590 | ||
|
0fe4ae906f | ||
|
7ef08ad559 | ||
|
d02f0ba834 | ||
|
793870c6cc | ||
|
7caf1febe3 | ||
|
c4dcfa540a | ||
|
53b8c9ffd7 | ||
|
f8cd402153 | ||
|
5cda85898b | ||
|
629feea4ec | ||
|
ed227f77cd | ||
|
21536dd10e | ||
|
5e81647291 | ||
|
c064fb12a4 | ||
|
8b73cbd71a | ||
|
c74c46d3ed | ||
|
b93875658b | ||
|
65661f13db | ||
|
320d1ec989 | ||
|
c443a72b42 | ||
|
188fe1b5af | ||
|
addd38c4a6 | ||
|
3f48e2d9c9 | ||
|
b89539c8c6 | ||
|
e171e33510 | ||
|
4c347c0c0b | ||
|
817138b355 | ||
|
d49288d437 | ||
|
bd166e4597 | ||
|
e305ec1d89 | ||
|
0319ece0ab | ||
|
46ef8ef596 | ||
|
18979feeca | ||
|
4bb0831168 | ||
|
ce3bf6b6fb | ||
|
7a601b191e | ||
|
227bb77ba1 | ||
|
21f0bb4e3a | ||
|
faf3c60946 | ||
|
96ceaa4263 | ||
|
eceef8b207 | ||
|
dfd2f7e56e | ||
|
6bec796bad | ||
|
e02b316030 | ||
|
50f18dbbee | ||
|
f53f3b789d | ||
|
7f8ea83403 | ||
|
54e64014a5 | ||
|
ede024cf71 | ||
|
29127e7e07 | ||
|
062f669369 | ||
|
13c2504d4c | ||
|
1fccb18464 | ||
|
2b00a886a7 | ||
|
ae72d1cd31 | ||
|
ded50dd780 | ||
|
b3224d2b40 | ||
|
5a25904a07 | ||
|
d2d5571755 | ||
|
50b2433fb5 | ||
|
92b24920ce | ||
|
b73c65d8b6 | ||
|
bafedb022b | ||
|
6bbac0b6e9 | ||
|
91e13292da | ||
|
916f2148f8 | ||
|
71b9a578d0 | ||
|
263279e714 | ||
|
aa2d5dfe20 | ||
|
24cdf079a6 | ||
|
0d73ba74ee | ||
|
0eecce5f8d | ||
|
762438ca8e | ||
|
9576fd8d9a | ||
|
7f9ccbc38d | ||
|
44e01a5881 | ||
|
831853630b | ||
|
879f816438 | ||
|
5b9fd502c7 | ||
|
62aa69202f | ||
|
e8babedd87 | ||
|
488ef5b9f0 | ||
|
b882aad1ff | ||
|
c0d1b85d9d | ||
|
de3d3de004 | ||
|
1b879f1a5b | ||
|
8e9d8887da | ||
|
b94484b67e | ||
|
2cde37899d | ||
|
443324a764 | ||
|
931e30601e | ||
|
357eb73c6f | ||
|
1e6cd69de8 | ||
|
bd61c95c7d | ||
|
870ae6f21c | ||
|
592d1ee028 | ||
|
a4b8b53014 | ||
|
9d59c79674 | ||
|
e741cab89d | ||
|
695869f13a | ||
|
06b0fcd6f5 | ||
|
89014ed7f2 | ||
|
974ac4d1d8 | ||
|
3dccc0d710 | ||
|
934ecb9165 | ||
|
86e01a0a1b | ||
|
4b39884f6f | ||
|
e8e847cea6 | ||
|
076ba06e07 | ||
|
635be2c35a | ||
|
2a57ecbd87 | ||
|
09a523fc50 | ||
|
2fc4e6ddcf | ||
|
9964466138 | ||
|
f804c21c0b | ||
|
f9536be1e3 | ||
|
b2805570ea | ||
|
9a23a2af2d | ||
|
7ef7e93eb4 | ||
|
8b3bb9527c | ||
|
36011c2e3c | ||
|
eb3c9cdfb1 | ||
|
2ea31432d5 | ||
|
faf5af3e1f | ||
|
d558c73f8d | ||
|
d934912007 | ||
|
688d7ea8d5 | ||
|
505fa39e82 | ||
|
d9c99d73ee | ||
|
bd87ac4ab3 | ||
|
b4b549bb10 | ||
|
bcbac965cd | ||
|
89aab6f444 | ||
|
fcc87bfaf4 | ||
|
817d55aecc | ||
|
40f353c92e | ||
|
4b85887f33 | ||
|
15497a611a | ||
|
a8dce54478 | ||
|
2fe66318ca | ||
|
63cf24e38b | ||
|
1de83701d8 | ||
|
41d4802e42 | ||
|
9ab2098309 |
152 changed files with 6946 additions and 5054 deletions
26
.github/workflows/build-tlv-tool.yml
vendored
26
.github/workflows/build-tlv-tool.yml
vendored
|
@ -1,26 +0,0 @@
|
|||
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
|
||||
|
39
.github/workflows/ci-tlv-tool.yml
vendored
Normal file
39
.github/workflows/ci-tlv-tool.yml
vendored
Normal file
|
@ -0,0 +1,39 @@
|
|||
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
|
48
.github/workflows/ci.yml
vendored
Normal file
48
.github/workflows/ci.yml
vendored
Normal file
|
@ -0,0 +1,48 @@
|
|||
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
|
17
.github/workflows/publish-dry-run.yml
vendored
Normal file
17
.github/workflows/publish-dry-run.yml
vendored
Normal file
|
@ -0,0 +1,17 @@
|
|||
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
|
14
.github/workflows/publish-macros-dry-run.yml
vendored
Normal file
14
.github/workflows/publish-macros-dry-run.yml
vendored
Normal file
|
@ -0,0 +1,14 @@
|
|||
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
|
17
.github/workflows/publish-macros.yml
vendored
Normal file
17
.github/workflows/publish-macros.yml
vendored
Normal file
|
@ -0,0 +1,17 @@
|
|||
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
|
32
.github/workflows/publish.yml
vendored
Normal file
32
.github/workflows/publish.yml
vendored
Normal file
|
@ -0,0 +1,32 @@
|
|||
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}}"
|
22
.github/workflows/test-linux-mbedtls.yml
vendored
22
.github/workflows/test-linux-mbedtls.yml
vendored
|
@ -1,22 +0,0 @@
|
|||
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
|
22
.github/workflows/test-linux-openssl.yml
vendored
22
.github/workflows/test-linux-openssl.yml
vendored
|
@ -1,22 +0,0 @@
|
|||
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
|
22
.github/workflows/test-linux-rustcrypto.yml
vendored
22
.github/workflows/test-linux-rustcrypto.yml
vendored
|
@ -1,22 +0,0 @@
|
|||
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
|
|
@ -1,10 +1,11 @@
|
|||
[workspace]
|
||||
members = ["matter", "matter_macro_derive"]
|
||||
exclude = ["examples/*", "tools/tlv_tool"]
|
||||
resolver = "2"
|
||||
members = ["rs-matter", "rs-matter-macros"]
|
||||
|
||||
exclude = ["examples/*", "tools/tlv"]
|
||||
|
||||
# For compatibility with ESP IDF
|
||||
[patch.crates-io]
|
||||
smol = { git = "https://github.com/esp-rs-compat/smol" }
|
||||
polling = { git = "https://github.com/esp-rs-compat/polling" }
|
||||
socket2 = { git = "https://github.com/esp-rs-compat/socket2" }
|
||||
|
||||
|
|
27
README.md
27
README.md
|
@ -1,29 +1,32 @@
|
|||
# matter-rs: The Rust Implementation of Matter
|
||||
# rs-matter: The Rust Implementation of Matter
|
||||
|
||||
 [](https://raw.githubusercontent.com/project-chip/matter-rs/main/LICENSE)
|
||||
|
||||
[](https://github.com/project-chip/matter-rs/actions/workflows/test-linux-openssl.yml)
|
||||
[](https://github.com/project-chip/matter-rs/actions/workflows/test-linux-mbedtls.yml)
|
||||

|
||||
[](https://raw.githubusercontent.com/project-chip/matter-rs/main/LICENSE)
|
||||
[](https://github.com/project-chip/matter-rs/actions/workflows/ci.yml)
|
||||
[](https://github.com/project-chip/matter-rs/actions/workflows/ci-tlv-tool.yml)
|
||||
[](https://crates.io/crates/rs-matter)
|
||||
[](https://matrix.to/#/#matter-rs:matrix.org)
|
||||
|
||||
## Build
|
||||
|
||||
Building the library:
|
||||
### Building the library
|
||||
|
||||
```
|
||||
$ cargo build
|
||||
```
|
||||
|
||||
Building and running the example (Linux, MacOS X):
|
||||
### Building and running the example (Linux, MacOS X)
|
||||
|
||||
```
|
||||
$ 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)
|
||||
* 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 std,crypto_rustcrypto --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 esp-idf --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:
|
||||
* esp32 / xtensa-esp32-espidf
|
||||
|
@ -31,10 +34,14 @@ export MCU=esp32; export CARGO_TARGET_XTENSA_ESP32_ESPIDF_LINKER=ldproxy; export
|
|||
* esp32s3 / xtensa-esp32s3-espidf
|
||||
* esp32c3 / riscv32imc-esp-espidf
|
||||
* esp32c5 / riscv32imc-esp-espidf
|
||||
* esp32c6 / risxcv32imac-esp-espidf
|
||||
* esp32c6 / riscv32imac-esp-espidf
|
||||
* 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
|
||||
|
||||
### Building the example (ESP32-XX baremetal or RP2040)
|
||||
|
||||
Coming soon!
|
||||
|
||||
## Test
|
||||
|
||||
With the `chip-tool` (the current tool for testing Matter) use the Ethernet commissioning mechanism:
|
||||
|
|
|
@ -15,8 +15,8 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
use matter::data_model::sdm::dev_att::{DataType, DevAttDataFetcher};
|
||||
use matter::error::{Error, ErrorCode};
|
||||
use rs_matter::data_model::sdm::dev_att::{DataType, DevAttDataFetcher};
|
||||
use rs_matter::error::{Error, ErrorCode};
|
||||
|
||||
pub struct HardCodedDevAtt {}
|
||||
|
||||
|
|
|
@ -1,18 +0,0 @@
|
|||
/*
|
||||
*
|
||||
* 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;
|
|
@ -18,33 +18,28 @@
|
|||
use core::borrow::Borrow;
|
||||
use core::pin::pin;
|
||||
|
||||
use embassy_futures::select::select;
|
||||
use embassy_futures::select::select3;
|
||||
use log::info;
|
||||
use matter::core::{CommissioningData, Matter};
|
||||
use matter::data_model::cluster_basic_information::BasicInfoConfig;
|
||||
use matter::data_model::cluster_on_off;
|
||||
use matter::data_model::core::DataModel;
|
||||
use matter::data_model::device_types::DEV_TYPE_ON_OFF_LIGHT;
|
||||
use matter::data_model::objects::*;
|
||||
use matter::data_model::root_endpoint;
|
||||
use matter::data_model::system_model::descriptor;
|
||||
use matter::error::Error;
|
||||
use matter::interaction_model::core::InteractionModel;
|
||||
use matter::mdns::{DefaultMdns, DefaultMdnsRunner};
|
||||
use matter::secure_channel::spake2p::VerifierData;
|
||||
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;
|
||||
use rs_matter::core::{CommissioningData, Matter};
|
||||
use rs_matter::data_model::cluster_basic_information::BasicInfoConfig;
|
||||
use rs_matter::data_model::cluster_on_off;
|
||||
use rs_matter::data_model::device_types::DEV_TYPE_ON_OFF_LIGHT;
|
||||
use rs_matter::data_model::objects::*;
|
||||
use rs_matter::data_model::root_endpoint;
|
||||
use rs_matter::data_model::system_model::descriptor;
|
||||
use rs_matter::error::Error;
|
||||
use rs_matter::mdns::{MdnsRunBuffers, MdnsService};
|
||||
use rs_matter::secure_channel::spake2p::VerifierData;
|
||||
use rs_matter::transport::core::RunBuffers;
|
||||
use rs_matter::transport::network::{Ipv4Addr, Ipv6Addr, NetworkStack};
|
||||
use rs_matter::utils::select::EitherUnwrap;
|
||||
|
||||
mod dev_att;
|
||||
|
||||
#[cfg(feature = "std")]
|
||||
fn main() -> Result<(), Error> {
|
||||
let thread = std::thread::Builder::new()
|
||||
.stack_size(120 * 1024)
|
||||
.stack_size(160 * 1024)
|
||||
.spawn(run)
|
||||
.unwrap();
|
||||
|
||||
|
@ -62,10 +57,11 @@ fn run() -> Result<(), Error> {
|
|||
initialize_logger();
|
||||
|
||||
info!(
|
||||
"Matter memory: mDNS={}, Matter={}, Transport={}",
|
||||
core::mem::size_of::<DefaultMdns>(),
|
||||
"Matter memory: mDNS={}, Matter={}, MdnsBuffers={}, RunBuffers={}",
|
||||
core::mem::size_of::<MdnsService>(),
|
||||
core::mem::size_of::<Matter>(),
|
||||
core::mem::size_of::<Transport>(),
|
||||
core::mem::size_of::<MdnsRunBuffers>(),
|
||||
core::mem::size_of::<RunBuffers>(),
|
||||
);
|
||||
|
||||
let dev_det = BasicInfoConfig {
|
||||
|
@ -76,37 +72,38 @@ fn run() -> Result<(), Error> {
|
|||
sw_ver_str: "1",
|
||||
serial_no: "aabbccdd",
|
||||
device_name: "OnOff Light",
|
||||
product_name: "Light123",
|
||||
vendor_name: "Vendor PQR",
|
||||
};
|
||||
|
||||
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();
|
||||
|
||||
#[cfg(feature = "std")]
|
||||
let epoch = matter::utils::epoch::sys_epoch;
|
||||
let epoch = rs_matter::utils::epoch::sys_epoch;
|
||||
|
||||
#[cfg(feature = "std")]
|
||||
let rand = matter::utils::rand::sys_rand;
|
||||
let rand = rs_matter::utils::rand::sys_rand;
|
||||
|
||||
// NOTE (no_std): For no_std, provide your own function here
|
||||
#[cfg(not(feature = "std"))]
|
||||
let epoch = matter::utils::epoch::dummy_epoch;
|
||||
let epoch = rs_matter::utils::epoch::dummy_epoch;
|
||||
|
||||
// NOTE (no_std): For no_std, provide your own function here
|
||||
#[cfg(not(feature = "std"))]
|
||||
let rand = matter::utils::rand::dummy_rand;
|
||||
let rand = rs_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(
|
||||
// vid/pid should match those in the DAC
|
||||
|
@ -115,41 +112,67 @@ fn run() -> Result<(), Error> {
|
|||
&mdns,
|
||||
epoch,
|
||||
rand,
|
||||
matter::MATTER_PORT,
|
||||
rs_matter::MATTER_PORT,
|
||||
);
|
||||
|
||||
let psm_path = std::env::temp_dir().join("matter-iot");
|
||||
info!("Persisting from/to {}", psm_path.display());
|
||||
info!("Matter initialized");
|
||||
|
||||
#[cfg(all(feature = "std", not(target_os = "espidf")))]
|
||||
let psm = matter::persist::FilePsm::new(psm_path)?;
|
||||
let mut psm = rs_matter::persist::Psm::new(&matter, std::env::temp_dir().join("rs-matter"))?;
|
||||
|
||||
let mut buf = [0; 4096];
|
||||
let buf = &mut buf;
|
||||
let handler = HandlerCompat(handler(&matter));
|
||||
|
||||
#[cfg(all(feature = "std", not(target_os = "espidf")))]
|
||||
{
|
||||
if let Some(data) = psm.load("acls", buf)? {
|
||||
matter.load_acls(data)?;
|
||||
}
|
||||
// When using a custom UDP stack, remove the network stack initialization below
|
||||
// and call `Matter::run_piped()` instead, by utilizing the TX & RX `Pipe` structs
|
||||
// to push/pull your UDP packets from/to the Matter stack.
|
||||
// Ditto for `MdnsService`.
|
||||
//
|
||||
// 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();
|
||||
|
||||
if let Some(data) = psm.load("fabrics", buf)? {
|
||||
matter.load_fabrics(data)?;
|
||||
}
|
||||
}
|
||||
let mut mdns_buffers = MdnsRunBuffers::new();
|
||||
let mut mdns_runner = pin!(mdns.run(&stack, &mut mdns_buffers));
|
||||
|
||||
let mut transport = Transport::new(&matter);
|
||||
|
||||
transport.start(
|
||||
let mut buffers = RunBuffers::new();
|
||||
let runner = matter.run(
|
||||
&stack,
|
||||
&mut buffers,
|
||||
CommissioningData {
|
||||
// TODO: Hard-coded for now
|
||||
verifier: VerifierData::new_with_pw(123456, *matter.borrow()),
|
||||
discriminator: 250,
|
||||
},
|
||||
buf,
|
||||
)?;
|
||||
&handler,
|
||||
);
|
||||
|
||||
let node = Node {
|
||||
info!(
|
||||
"Matter transport runner memory: {}",
|
||||
core::mem::size_of_val(&runner)
|
||||
);
|
||||
|
||||
let mut runner = pin!(runner);
|
||||
|
||||
#[cfg(all(feature = "std", not(target_os = "espidf")))]
|
||||
let mut psm_runner = pin!(psm.run());
|
||||
|
||||
#[cfg(not(all(feature = "std", not(target_os = "espidf"))))]
|
||||
let mut psm_runner = pin!(core::future::pending());
|
||||
|
||||
let runner = select3(&mut runner, &mut mdns_runner, &mut psm_runner);
|
||||
|
||||
#[cfg(feature = "std")]
|
||||
async_io::block_on(runner).unwrap()?;
|
||||
|
||||
// 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
|
||||
#[cfg(not(feature = "std"))]
|
||||
embassy_futures::block_on(&mut runner).unwrap()?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
const NODE: Node<'static> = Node {
|
||||
id: 0,
|
||||
endpoints: &[
|
||||
root_endpoint::endpoint(0),
|
||||
|
@ -159,83 +182,11 @@ fn run() -> Result<(), Error> {
|
|||
clusters: &[descriptor::CLUSTER, cluster_on_off::CLUSTER],
|
||||
},
|
||||
],
|
||||
};
|
||||
};
|
||||
|
||||
let mut handler = handler(&matter);
|
||||
|
||||
let mut im = InteractionModel(DataModel::new(matter.borrow(), &node, &mut handler));
|
||||
|
||||
let mut rx_buf = [0; MAX_RX_BUF_SIZE];
|
||||
let mut tx_buf = [0; MAX_TX_BUF_SIZE];
|
||||
|
||||
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")]
|
||||
smol::block_on(&mut fut)?;
|
||||
|
||||
// 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
|
||||
#[cfg(not(feature = "std"))]
|
||||
embassy_futures::block_on(&mut fut)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn handler<'a>(matter: &'a Matter<'a>) -> impl Handler + 'a {
|
||||
fn handler<'a>(matter: &'a Matter<'a>) -> impl Metadata + NonBlockingHandler + 'a {
|
||||
(
|
||||
NODE,
|
||||
root_endpoint::handler(0, matter)
|
||||
.chain(
|
||||
1,
|
||||
|
@ -246,6 +197,7 @@ fn handler<'a>(matter: &'a Matter<'a>) -> impl Handler + 'a {
|
|||
1,
|
||||
cluster_on_off::ID,
|
||||
cluster_on_off::OnOffCluster::new(*matter.borrow()),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -273,8 +225,8 @@ fn initialize_logger() {
|
|||
#[inline(never)]
|
||||
fn initialize_network() -> Result<(Ipv4Addr, Ipv6Addr, u32), Error> {
|
||||
use log::error;
|
||||
use matter::error::ErrorCode;
|
||||
use nix::{net::if_::InterfaceFlags, sys::socket::SockaddrIn6};
|
||||
use rs_matter::error::ErrorCode;
|
||||
|
||||
let interfaces = || {
|
||||
nix::ifaddrs::getifaddrs().unwrap().filter(|ia| {
|
||||
|
|
|
@ -15,8 +15,8 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
use matter::data_model::sdm::dev_att::{DataType, DevAttDataFetcher};
|
||||
use matter::error::Error;
|
||||
use rs_matter::data_model::sdm::dev_att::{DataType, DevAttDataFetcher};
|
||||
use rs_matter::error::Error;
|
||||
|
||||
pub struct HardCodedDevAtt {}
|
||||
|
||||
|
|
|
@ -1,18 +0,0 @@
|
|||
/*
|
||||
*
|
||||
* 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;
|
|
@ -17,11 +17,11 @@
|
|||
|
||||
// TODO
|
||||
// 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;
|
||||
// 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;
|
||||
|
||||
fn main() {
|
||||
// env_logger::init();
|
||||
|
|
|
@ -15,12 +15,13 @@
|
|||
* 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;
|
||||
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() {
|
||||
env_logger::init();
|
||||
|
|
|
@ -1,301 +0,0 @@
|
|||
/*
|
||||
*
|
||||
* 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
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,74 +0,0 @@
|
|||
/*
|
||||
*
|
||||
* 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(())
|
||||
}
|
||||
}
|
|
@ -1,846 +0,0 @@
|
|||
/*
|
||||
*
|
||||
* 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
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,390 +0,0 @@
|
|||
/*
|
||||
*
|
||||
* 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,
|
||||
}
|
|
@ -1,251 +0,0 @@
|
|||
/*
|
||||
*
|
||||
* 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)
|
||||
}
|
||||
}
|
|
@ -1,625 +0,0 @@
|
|||
/*
|
||||
*
|
||||
* 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);
|
||||
}
|
||||
}
|
|
@ -1,41 +0,0 @@
|
|||
/*
|
||||
*
|
||||
* 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::error::Error;
|
||||
|
||||
use super::exchange::ExchangeCtx;
|
||||
use super::packet::Packet;
|
||||
|
||||
/// This is the context in which a receive packet is being processed
|
||||
pub struct ProtoCtx<'a, 'b> {
|
||||
/// This is the exchange context, that includes the exchange and the session
|
||||
pub exch_ctx: ExchangeCtx<'a>,
|
||||
/// This is the received buffer for this transaction
|
||||
pub rx: &'a Packet<'b>,
|
||||
/// This is the transmit buffer for this transaction
|
||||
pub tx: &'a mut Packet<'b>,
|
||||
}
|
||||
|
||||
impl<'a, 'b> ProtoCtx<'a, 'b> {
|
||||
pub fn new(exch_ctx: ExchangeCtx<'a>, rx: &'a Packet<'b>, tx: &'a mut Packet<'b>) -> Self {
|
||||
Self { exch_ctx, rx, tx }
|
||||
}
|
||||
|
||||
pub fn send(&mut self) -> Result<bool, Error> {
|
||||
self.exch_ctx.exch.send(self.tx, &mut self.exch_ctx.sess)
|
||||
}
|
||||
}
|
|
@ -1,210 +0,0 @@
|
|||
/*
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#[cfg(feature = "std")]
|
||||
pub use smol_udp::*;
|
||||
|
||||
#[cfg(not(feature = "std"))]
|
||||
pub use dummy_udp::*;
|
||||
|
||||
#[cfg(feature = "std")]
|
||||
mod smol_udp {
|
||||
use crate::error::*;
|
||||
use log::{debug, info, warn};
|
||||
use smol::net::UdpSocket;
|
||||
|
||||
use crate::transport::network::{Ipv4Addr, Ipv6Addr, SocketAddr};
|
||||
|
||||
pub struct UdpListener {
|
||||
socket: UdpSocket,
|
||||
}
|
||||
|
||||
impl UdpListener {
|
||||
pub async fn new(addr: SocketAddr) -> Result<UdpListener, Error> {
|
||||
let listener = UdpListener {
|
||||
socket: UdpSocket::bind((addr.ip(), addr.port())).await?,
|
||||
};
|
||||
|
||||
info!("Listening on {:?}", addr);
|
||||
|
||||
Ok(listener)
|
||||
}
|
||||
|
||||
pub fn join_multicast_v6(
|
||||
&mut self,
|
||||
multiaddr: Ipv6Addr,
|
||||
interface: u32,
|
||||
) -> Result<(), Error> {
|
||||
self.socket.join_multicast_v6(&multiaddr, interface)?;
|
||||
|
||||
info!("Joined IPV6 multicast {}/{}", multiaddr, interface);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn join_multicast_v4(
|
||||
&mut self,
|
||||
multiaddr: Ipv4Addr,
|
||||
interface: Ipv4Addr,
|
||||
) -> Result<(), Error> {
|
||||
#[cfg(not(target_os = "espidf"))]
|
||||
self.socket.join_multicast_v4(multiaddr, interface)?;
|
||||
|
||||
// join_multicast_v4() is broken for ESP-IDF, most likely due to wrong `ip_mreq` signature in the `libc` crate
|
||||
// Note that also most *_multicast_v4 and *_multicast_v6 methods are broken as well in Rust STD for the ESP-IDF
|
||||
// due to mismatch w.r.t. sizes (u8 expected but u32 passed to setsockopt() and sometimes the other way around)
|
||||
#[cfg(target_os = "espidf")]
|
||||
{
|
||||
fn esp_setsockopt<T>(
|
||||
socket: &mut UdpSocket,
|
||||
proto: u32,
|
||||
option: u32,
|
||||
value: T,
|
||||
) -> Result<(), Error> {
|
||||
use std::os::fd::AsRawFd;
|
||||
|
||||
esp_idf_sys::esp!(unsafe {
|
||||
esp_idf_sys::lwip_setsockopt(
|
||||
socket.as_raw_fd(),
|
||||
proto as _,
|
||||
option as _,
|
||||
&value as *const _ as *const _,
|
||||
core::mem::size_of::<T>() as _,
|
||||
)
|
||||
})
|
||||
.map_err(|_| ErrorCode::StdIoError)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
let mreq = esp_idf_sys::ip_mreq {
|
||||
imr_multiaddr: esp_idf_sys::in_addr {
|
||||
s_addr: u32::from_ne_bytes(multiaddr.octets()),
|
||||
},
|
||||
imr_interface: esp_idf_sys::in_addr {
|
||||
s_addr: u32::from_ne_bytes(interface.octets()),
|
||||
},
|
||||
};
|
||||
|
||||
esp_setsockopt(
|
||||
&mut self.socket,
|
||||
esp_idf_sys::IPPROTO_IP,
|
||||
esp_idf_sys::IP_ADD_MEMBERSHIP,
|
||||
mreq,
|
||||
)?;
|
||||
}
|
||||
|
||||
info!("Joined IP multicast {}/{}", multiaddr, interface);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn recv(&self, in_buf: &mut [u8]) -> Result<(usize, SocketAddr), Error> {
|
||||
let (size, addr) = self.socket.recv_from(in_buf).await.map_err(|e| {
|
||||
warn!("Error on the network: {:?}", e);
|
||||
ErrorCode::Network
|
||||
})?;
|
||||
|
||||
debug!("Got packet {:?} from addr {:?}", &in_buf[..size], addr);
|
||||
|
||||
Ok((size, addr))
|
||||
}
|
||||
|
||||
pub async fn send(&self, addr: SocketAddr, out_buf: &[u8]) -> Result<usize, Error> {
|
||||
let len = self.socket.send_to(out_buf, addr).await.map_err(|e| {
|
||||
warn!("Error on the network: {:?}", e);
|
||||
ErrorCode::Network
|
||||
})?;
|
||||
|
||||
debug!(
|
||||
"Send packet {:?} ({}/{}) to addr {:?}",
|
||||
out_buf,
|
||||
out_buf.len(),
|
||||
len,
|
||||
addr
|
||||
);
|
||||
|
||||
Ok(len)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "std"))]
|
||||
mod dummy_udp {
|
||||
use core::future::pending;
|
||||
|
||||
use crate::error::*;
|
||||
use log::{debug, info};
|
||||
|
||||
use crate::transport::network::{Ipv4Addr, Ipv6Addr, SocketAddr};
|
||||
|
||||
pub struct UdpListener {}
|
||||
|
||||
impl UdpListener {
|
||||
pub async fn new(addr: SocketAddr) -> Result<UdpListener, Error> {
|
||||
let listener = UdpListener {};
|
||||
|
||||
info!("Pretending to listen on {:?}", addr);
|
||||
|
||||
Ok(listener)
|
||||
}
|
||||
|
||||
pub fn join_multicast_v6(
|
||||
&mut self,
|
||||
multiaddr: Ipv6Addr,
|
||||
interface: u32,
|
||||
) -> Result<(), Error> {
|
||||
info!(
|
||||
"Pretending to join IPV6 multicast {}/{}",
|
||||
multiaddr, interface
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn join_multicast_v4(
|
||||
&mut self,
|
||||
multiaddr: Ipv4Addr,
|
||||
interface: Ipv4Addr,
|
||||
) -> Result<(), Error> {
|
||||
info!(
|
||||
"Pretending to join IP multicast {}/{}",
|
||||
multiaddr, interface
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn recv(&self, _in_buf: &mut [u8]) -> Result<(usize, SocketAddr), Error> {
|
||||
info!("Pretending to wait for incoming packets (looping forever)");
|
||||
|
||||
pending().await
|
||||
}
|
||||
|
||||
pub async fn send(&self, addr: SocketAddr, out_buf: &[u8]) -> Result<usize, Error> {
|
||||
debug!(
|
||||
"Send packet {:?} ({}/{}) to addr {:?}",
|
||||
out_buf,
|
||||
out_buf.len(),
|
||||
out_buf.len(),
|
||||
addr
|
||||
);
|
||||
|
||||
Ok(out_buf.len())
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,317 +0,0 @@
|
|||
use core::time;
|
||||
use std::thread;
|
||||
|
||||
use log::{info, warn};
|
||||
use matter::{
|
||||
interaction_model::{
|
||||
core::{IMStatusCode, OpCode},
|
||||
messages::{
|
||||
ib::{AttrData, AttrPath, AttrResp, AttrStatus, CmdData, DataVersionFilter},
|
||||
msg::{
|
||||
self, InvReq, ReadReq, ReportDataMsg, StatusResp, TimedReq, WriteReq, WriteResp,
|
||||
WriteRespTag,
|
||||
},
|
||||
},
|
||||
},
|
||||
tlv::{self, FromTLV, TLVArray, ToTLV},
|
||||
transport::{
|
||||
exchange::{self, Exchange},
|
||||
session::NocCatIds,
|
||||
},
|
||||
Matter,
|
||||
};
|
||||
|
||||
use super::{
|
||||
attributes::assert_attr_report,
|
||||
commands::{assert_inv_response, ExpectedInvResp},
|
||||
im_engine::{ImEngine, ImInput, IM_ENGINE_PEER_ID},
|
||||
};
|
||||
|
||||
pub enum WriteResponse<'a> {
|
||||
TransactionError,
|
||||
TransactionSuccess(&'a [AttrStatus]),
|
||||
}
|
||||
|
||||
pub enum TimedInvResponse<'a> {
|
||||
TransactionError(IMStatusCode),
|
||||
TransactionSuccess(&'a [ExpectedInvResp]),
|
||||
}
|
||||
|
||||
impl<'a> ImEngine<'a> {
|
||||
// Helper for handling Read Req sequences for this file
|
||||
pub fn handle_read_reqs(
|
||||
&mut self,
|
||||
peer_node_id: u64,
|
||||
input: &[AttrPath],
|
||||
expected: &[AttrResp],
|
||||
) {
|
||||
let mut out_buf = [0u8; 400];
|
||||
let received = self.gen_read_reqs_output(peer_node_id, input, None, &mut out_buf);
|
||||
assert_attr_report(&received, expected)
|
||||
}
|
||||
|
||||
pub fn new_with_read_reqs(
|
||||
matter: &'a Matter<'a>,
|
||||
input: &[AttrPath],
|
||||
expected: &[AttrResp],
|
||||
) -> Self {
|
||||
let mut im = Self::new(matter);
|
||||
|
||||
let mut out_buf = [0u8; 400];
|
||||
let received = im.gen_read_reqs_output(IM_ENGINE_PEER_ID, input, None, &mut out_buf);
|
||||
assert_attr_report(&received, expected);
|
||||
|
||||
im
|
||||
}
|
||||
|
||||
pub fn gen_read_reqs_output<'b>(
|
||||
&mut self,
|
||||
peer_node_id: u64,
|
||||
input: &[AttrPath],
|
||||
dataver_filters: Option<TLVArray<'b, DataVersionFilter>>,
|
||||
out_buf: &'b mut [u8],
|
||||
) -> ReportDataMsg<'b> {
|
||||
let mut read_req = ReadReq::new(true).set_attr_requests(input);
|
||||
read_req.dataver_filters = dataver_filters;
|
||||
|
||||
let mut input = ImInput::new(OpCode::ReadRequest, &read_req);
|
||||
input.set_peer_node_id(peer_node_id);
|
||||
|
||||
let (_, out_buf) = self.process(&input, out_buf);
|
||||
|
||||
tlv::print_tlv_list(out_buf);
|
||||
let root = tlv::get_root_node_struct(out_buf).unwrap();
|
||||
ReportDataMsg::from_tlv(&root).unwrap()
|
||||
}
|
||||
|
||||
pub fn handle_write_reqs(
|
||||
&mut self,
|
||||
peer_node_id: u64,
|
||||
peer_cat_ids: Option<&NocCatIds>,
|
||||
input: &[AttrData],
|
||||
expected: &[AttrStatus],
|
||||
) {
|
||||
let mut out_buf = [0u8; 400];
|
||||
let write_req = WriteReq::new(false, input);
|
||||
|
||||
let mut input = ImInput::new(OpCode::WriteRequest, &write_req);
|
||||
input.set_peer_node_id(peer_node_id);
|
||||
if let Some(cat_ids) = peer_cat_ids {
|
||||
input.set_cat_ids(cat_ids);
|
||||
}
|
||||
|
||||
let (_, out_buf) = self.process(&input, &mut out_buf);
|
||||
|
||||
tlv::print_tlv_list(out_buf);
|
||||
let root = tlv::get_root_node_struct(out_buf).unwrap();
|
||||
|
||||
let mut index = 0;
|
||||
let response_iter = root
|
||||
.find_tag(WriteRespTag::WriteResponses as u32)
|
||||
.unwrap()
|
||||
.confirm_array()
|
||||
.unwrap()
|
||||
.enter()
|
||||
.unwrap();
|
||||
|
||||
for response in response_iter {
|
||||
info!("Validating index {}", index);
|
||||
let status = AttrStatus::from_tlv(&response).unwrap();
|
||||
assert_eq!(expected[index], status);
|
||||
info!("Index {} success", index);
|
||||
index += 1;
|
||||
}
|
||||
assert_eq!(index, expected.len());
|
||||
}
|
||||
|
||||
pub fn new_with_write_reqs(
|
||||
matter: &'a Matter<'a>,
|
||||
input: &[AttrData],
|
||||
expected: &[AttrStatus],
|
||||
) -> Self {
|
||||
let mut im = Self::new(matter);
|
||||
|
||||
im.handle_write_reqs(IM_ENGINE_PEER_ID, None, input, expected);
|
||||
|
||||
im
|
||||
}
|
||||
|
||||
// Helper for handling Invoke Command sequences
|
||||
pub fn handle_commands(
|
||||
&mut self,
|
||||
peer_node_id: u64,
|
||||
input: &[CmdData],
|
||||
expected: &[ExpectedInvResp],
|
||||
) {
|
||||
let mut out_buf = [0u8; 400];
|
||||
let req = InvReq {
|
||||
suppress_response: Some(false),
|
||||
timed_request: Some(false),
|
||||
inv_requests: Some(TLVArray::Slice(input)),
|
||||
};
|
||||
|
||||
let mut input = ImInput::new(OpCode::InvokeRequest, &req);
|
||||
input.set_peer_node_id(peer_node_id);
|
||||
|
||||
let (_, out_buf) = self.process(&input, &mut out_buf);
|
||||
tlv::print_tlv_list(out_buf);
|
||||
let root = tlv::get_root_node_struct(out_buf).unwrap();
|
||||
let resp = msg::InvResp::from_tlv(&root).unwrap();
|
||||
assert_inv_response(&resp, expected)
|
||||
}
|
||||
|
||||
pub fn new_with_commands(
|
||||
matter: &'a Matter<'a>,
|
||||
input: &[CmdData],
|
||||
expected: &[ExpectedInvResp],
|
||||
) -> Self {
|
||||
let mut im = ImEngine::new(matter);
|
||||
|
||||
im.handle_commands(IM_ENGINE_PEER_ID, input, expected);
|
||||
|
||||
im
|
||||
}
|
||||
|
||||
fn handle_timed_reqs<'b>(
|
||||
&mut self,
|
||||
opcode: OpCode,
|
||||
request: &dyn ToTLV,
|
||||
timeout: u16,
|
||||
delay: u16,
|
||||
output: &'b mut [u8],
|
||||
) -> (u8, &'b [u8]) {
|
||||
// Use the same exchange for all parts of the transaction
|
||||
self.exch = Some(Exchange::new(1, 0, exchange::Role::Responder));
|
||||
|
||||
if timeout != 0 {
|
||||
// Send Timed Req
|
||||
let mut tmp_buf = [0u8; 400];
|
||||
let timed_req = TimedReq { timeout };
|
||||
let im_input = ImInput::new(OpCode::TimedRequest, &timed_req);
|
||||
let (_, out_buf) = self.process(&im_input, &mut tmp_buf);
|
||||
tlv::print_tlv_list(out_buf);
|
||||
} else {
|
||||
warn!("Skipping timed request");
|
||||
}
|
||||
|
||||
// Process any delays
|
||||
let delay = time::Duration::from_millis(delay.into());
|
||||
thread::sleep(delay);
|
||||
|
||||
// Send Write Req
|
||||
let input = ImInput::new(opcode, request);
|
||||
let (resp_opcode, output) = self.process(&input, output);
|
||||
(resp_opcode, output)
|
||||
}
|
||||
|
||||
// Helper for handling Write Attribute sequences
|
||||
pub fn handle_timed_write_reqs(
|
||||
&mut self,
|
||||
input: &[AttrData],
|
||||
expected: &WriteResponse,
|
||||
timeout: u16,
|
||||
delay: u16,
|
||||
) {
|
||||
let mut out_buf = [0u8; 400];
|
||||
let write_req = WriteReq::new(false, input);
|
||||
|
||||
let (resp_opcode, out_buf) = self.handle_timed_reqs(
|
||||
OpCode::WriteRequest,
|
||||
&write_req,
|
||||
timeout,
|
||||
delay,
|
||||
&mut out_buf,
|
||||
);
|
||||
tlv::print_tlv_list(out_buf);
|
||||
let root = tlv::get_root_node_struct(out_buf).unwrap();
|
||||
|
||||
match expected {
|
||||
WriteResponse::TransactionSuccess(t) => {
|
||||
assert_eq!(
|
||||
num::FromPrimitive::from_u8(resp_opcode),
|
||||
Some(OpCode::WriteResponse)
|
||||
);
|
||||
let resp = WriteResp::from_tlv(&root).unwrap();
|
||||
assert_eq!(resp.write_responses, t);
|
||||
}
|
||||
WriteResponse::TransactionError => {
|
||||
assert_eq!(
|
||||
num::FromPrimitive::from_u8(resp_opcode),
|
||||
Some(OpCode::StatusResponse)
|
||||
);
|
||||
let status_resp = StatusResp::from_tlv(&root).unwrap();
|
||||
assert_eq!(status_resp.status, IMStatusCode::Timeout);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new_with_timed_write_reqs(
|
||||
matter: &'a Matter<'a>,
|
||||
input: &[AttrData],
|
||||
expected: &WriteResponse,
|
||||
timeout: u16,
|
||||
delay: u16,
|
||||
) -> Self {
|
||||
let mut im = ImEngine::new(matter);
|
||||
|
||||
im.handle_timed_write_reqs(input, expected, timeout, delay);
|
||||
|
||||
im
|
||||
}
|
||||
|
||||
// Helper for handling Invoke Command sequences
|
||||
pub fn handle_timed_commands(
|
||||
&mut self,
|
||||
input: &[CmdData],
|
||||
expected: &TimedInvResponse,
|
||||
timeout: u16,
|
||||
delay: u16,
|
||||
set_timed_request: bool,
|
||||
) {
|
||||
let mut out_buf = [0u8; 400];
|
||||
let req = InvReq {
|
||||
suppress_response: Some(false),
|
||||
timed_request: Some(set_timed_request),
|
||||
inv_requests: Some(TLVArray::Slice(input)),
|
||||
};
|
||||
|
||||
let (resp_opcode, out_buf) =
|
||||
self.handle_timed_reqs(OpCode::InvokeRequest, &req, timeout, delay, &mut out_buf);
|
||||
tlv::print_tlv_list(out_buf);
|
||||
let root = tlv::get_root_node_struct(out_buf).unwrap();
|
||||
|
||||
match expected {
|
||||
TimedInvResponse::TransactionSuccess(t) => {
|
||||
assert_eq!(
|
||||
num::FromPrimitive::from_u8(resp_opcode),
|
||||
Some(OpCode::InvokeResponse)
|
||||
);
|
||||
let resp = msg::InvResp::from_tlv(&root).unwrap();
|
||||
assert_inv_response(&resp, t)
|
||||
}
|
||||
TimedInvResponse::TransactionError(e) => {
|
||||
assert_eq!(
|
||||
num::FromPrimitive::from_u8(resp_opcode),
|
||||
Some(OpCode::StatusResponse)
|
||||
);
|
||||
let status_resp = StatusResp::from_tlv(&root).unwrap();
|
||||
assert_eq!(status_resp.status, *e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new_with_timed_commands(
|
||||
matter: &'a Matter<'a>,
|
||||
input: &[CmdData],
|
||||
expected: &TimedInvResponse,
|
||||
timeout: u16,
|
||||
delay: u16,
|
||||
set_timed_request: bool,
|
||||
) -> Self {
|
||||
let mut im = ImEngine::new(matter);
|
||||
|
||||
im.handle_timed_commands(input, expected, timeout, delay, set_timed_request);
|
||||
|
||||
im
|
||||
}
|
||||
}
|
|
@ -1,256 +0,0 @@
|
|||
/*
|
||||
*
|
||||
* 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::common::echo_cluster;
|
||||
use core::borrow::Borrow;
|
||||
use matter::{
|
||||
acl::{AclEntry, AuthMode},
|
||||
data_model::{
|
||||
cluster_basic_information::{self, BasicInfoConfig},
|
||||
cluster_on_off::{self, OnOffCluster},
|
||||
core::DataModel,
|
||||
device_types::{DEV_TYPE_ON_OFF_LIGHT, DEV_TYPE_ROOT_NODE},
|
||||
objects::{Endpoint, Node, Privilege},
|
||||
root_endpoint::{self, RootEndpointHandler},
|
||||
sdm::{
|
||||
admin_commissioning,
|
||||
dev_att::{DataType, DevAttDataFetcher},
|
||||
general_commissioning, noc, nw_commissioning,
|
||||
},
|
||||
system_model::{
|
||||
access_control,
|
||||
descriptor::{self, DescriptorCluster},
|
||||
},
|
||||
},
|
||||
error::Error,
|
||||
handler_chain_type,
|
||||
interaction_model::core::{InteractionModel, OpCode},
|
||||
mdns::Mdns,
|
||||
tlv::{TLVWriter, TagType, ToTLV},
|
||||
transport::packet::Packet,
|
||||
transport::{
|
||||
exchange::{self, Exchange, ExchangeCtx},
|
||||
network::{Address, IpAddr, Ipv4Addr, SocketAddr},
|
||||
packet::MAX_RX_BUF_SIZE,
|
||||
proto_ctx::ProtoCtx,
|
||||
session::{CaseDetails, CloneData, NocCatIds, SessionMgr, SessionMode},
|
||||
},
|
||||
utils::{rand::dummy_rand, writebuf::WriteBuf},
|
||||
Matter,
|
||||
};
|
||||
|
||||
use super::echo_cluster::EchoCluster;
|
||||
|
||||
const BASIC_INFO: BasicInfoConfig<'static> = BasicInfoConfig {
|
||||
vid: 10,
|
||||
pid: 11,
|
||||
hw_ver: 12,
|
||||
sw_ver: 13,
|
||||
sw_ver_str: "13",
|
||||
serial_no: "aabbccdd",
|
||||
device_name: "Test Device",
|
||||
};
|
||||
|
||||
struct DummyDevAtt;
|
||||
|
||||
impl DevAttDataFetcher for DummyDevAtt {
|
||||
fn get_devatt_data(&self, _data_type: DataType, _data: &mut [u8]) -> Result<usize, Error> {
|
||||
Ok(2)
|
||||
}
|
||||
}
|
||||
|
||||
pub const IM_ENGINE_PEER_ID: u64 = 445566;
|
||||
|
||||
pub struct ImInput<'a> {
|
||||
action: OpCode,
|
||||
data: &'a dyn ToTLV,
|
||||
peer_id: u64,
|
||||
cat_ids: NocCatIds,
|
||||
}
|
||||
|
||||
impl<'a> ImInput<'a> {
|
||||
pub fn new(action: OpCode, data: &'a dyn ToTLV) -> Self {
|
||||
Self {
|
||||
action,
|
||||
data,
|
||||
peer_id: IM_ENGINE_PEER_ID,
|
||||
cat_ids: Default::default(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_peer_node_id(&mut self, peer: u64) {
|
||||
self.peer_id = peer;
|
||||
}
|
||||
|
||||
pub fn set_cat_ids(&mut self, cat_ids: &NocCatIds) {
|
||||
self.cat_ids = *cat_ids;
|
||||
}
|
||||
}
|
||||
|
||||
pub type DmHandler<'a> = handler_chain_type!(OnOffCluster, EchoCluster, DescriptorCluster<'a>, EchoCluster | RootEndpointHandler<'a>);
|
||||
|
||||
pub fn matter(mdns: &mut dyn Mdns) -> Matter<'_> {
|
||||
#[cfg(feature = "std")]
|
||||
use matter::utils::epoch::sys_epoch as epoch;
|
||||
|
||||
#[cfg(not(feature = "std"))]
|
||||
use matter::utils::epoch::dummy_epoch as epoch;
|
||||
|
||||
Matter::new(&BASIC_INFO, &DummyDevAtt, mdns, epoch, dummy_rand, 5540)
|
||||
}
|
||||
|
||||
/// An Interaction Model Engine to facilitate easy testing
|
||||
pub struct ImEngine<'a> {
|
||||
pub matter: &'a Matter<'a>,
|
||||
pub im: InteractionModel<DataModel<'a, DmHandler<'a>>>,
|
||||
// By default, a new exchange is created for every run, if you wish to instead using a specific
|
||||
// exchange, set this variable. This is helpful in situations where you have to run multiple
|
||||
// actions in the same transaction (exchange)
|
||||
pub exch: Option<Exchange>,
|
||||
}
|
||||
|
||||
impl<'a> ImEngine<'a> {
|
||||
/// Create the interaction model engine
|
||||
pub fn new(matter: &'a Matter<'a>) -> Self {
|
||||
let mut default_acl = AclEntry::new(1, Privilege::ADMIN, AuthMode::Case);
|
||||
// Only allow the standard peer node id of the IM Engine
|
||||
default_acl.add_subject(IM_ENGINE_PEER_ID).unwrap();
|
||||
matter.acl_mgr.borrow_mut().add(default_acl).unwrap();
|
||||
|
||||
let dm = DataModel::new(
|
||||
matter.borrow(),
|
||||
&Node {
|
||||
id: 0,
|
||||
endpoints: &[
|
||||
Endpoint {
|
||||
id: 0,
|
||||
clusters: &[
|
||||
descriptor::CLUSTER,
|
||||
cluster_basic_information::CLUSTER,
|
||||
general_commissioning::CLUSTER,
|
||||
nw_commissioning::CLUSTER,
|
||||
admin_commissioning::CLUSTER,
|
||||
noc::CLUSTER,
|
||||
access_control::CLUSTER,
|
||||
echo_cluster::CLUSTER,
|
||||
],
|
||||
device_type: DEV_TYPE_ROOT_NODE,
|
||||
},
|
||||
Endpoint {
|
||||
id: 1,
|
||||
clusters: &[
|
||||
descriptor::CLUSTER,
|
||||
cluster_on_off::CLUSTER,
|
||||
echo_cluster::CLUSTER,
|
||||
],
|
||||
device_type: DEV_TYPE_ON_OFF_LIGHT,
|
||||
},
|
||||
],
|
||||
},
|
||||
root_endpoint::handler(0, matter)
|
||||
.chain(0, echo_cluster::ID, EchoCluster::new(2, *matter.borrow()))
|
||||
.chain(1, descriptor::ID, DescriptorCluster::new(*matter.borrow()))
|
||||
.chain(1, echo_cluster::ID, EchoCluster::new(3, *matter.borrow()))
|
||||
.chain(1, cluster_on_off::ID, OnOffCluster::new(*matter.borrow())),
|
||||
);
|
||||
|
||||
Self {
|
||||
matter,
|
||||
im: InteractionModel(dm),
|
||||
exch: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn echo_cluster(&self, endpoint: u16) -> &EchoCluster {
|
||||
match endpoint {
|
||||
0 => &self.im.0.handler.next.next.next.handler,
|
||||
1 => &self.im.0.handler.next.handler,
|
||||
_ => panic!(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Run a transaction through the interaction model engine
|
||||
pub fn process<'b>(&mut self, input: &ImInput, data_out: &'b mut [u8]) -> (u8, &'b [u8]) {
|
||||
let mut new_exch = Exchange::new(1, 0, exchange::Role::Responder);
|
||||
// Choose whether to use a new exchange, or use the one from the ImEngine configuration
|
||||
let exch = self.exch.as_mut().unwrap_or(&mut new_exch);
|
||||
|
||||
let mut sess_mgr = SessionMgr::new(*self.matter.borrow(), *self.matter.borrow());
|
||||
|
||||
let clone_data = CloneData::new(
|
||||
123456,
|
||||
input.peer_id,
|
||||
10,
|
||||
30,
|
||||
Address::Udp(SocketAddr::new(
|
||||
IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)),
|
||||
5542,
|
||||
)),
|
||||
SessionMode::Case(CaseDetails::new(1, &input.cat_ids)),
|
||||
);
|
||||
let sess_idx = sess_mgr.clone_session(&clone_data).unwrap();
|
||||
let sess = sess_mgr.get_session_handle(sess_idx);
|
||||
let exch_ctx = ExchangeCtx {
|
||||
exch,
|
||||
sess,
|
||||
epoch: *self.matter.borrow(),
|
||||
};
|
||||
let mut rx_buf = [0; MAX_RX_BUF_SIZE];
|
||||
let mut tx_buf = [0; 1440]; // For the long read tests to run unchanged
|
||||
let mut rx = Packet::new_rx(&mut rx_buf);
|
||||
let mut tx = Packet::new_tx(&mut tx_buf);
|
||||
// Create fake rx packet
|
||||
rx.set_proto_id(0x01);
|
||||
rx.set_proto_opcode(input.action as u8);
|
||||
rx.peer = Address::default();
|
||||
|
||||
{
|
||||
let mut buf = [0u8; 400];
|
||||
let mut wb = WriteBuf::new(&mut buf);
|
||||
let mut tw = TLVWriter::new(&mut wb);
|
||||
|
||||
input.data.to_tlv(&mut tw, TagType::Anonymous).unwrap();
|
||||
|
||||
let input_data = wb.as_slice();
|
||||
let in_data_len = input_data.len();
|
||||
let rx_buf = rx.as_mut_slice();
|
||||
rx_buf[..in_data_len].copy_from_slice(input_data);
|
||||
rx.get_parsebuf().unwrap().set_len(in_data_len);
|
||||
}
|
||||
|
||||
let mut ctx = ProtoCtx::new(exch_ctx, &rx, &mut tx);
|
||||
self.im.handle(&mut ctx).unwrap();
|
||||
let out_data_len = ctx.tx.as_slice().len();
|
||||
data_out[..out_data_len].copy_from_slice(ctx.tx.as_slice());
|
||||
let response = ctx.tx.get_proto_raw_opcode();
|
||||
(response, &data_out[..out_data_len])
|
||||
}
|
||||
}
|
||||
|
||||
// TODO - Remove?
|
||||
// // Create an Interaction Model, Data Model and run a rx/tx transaction through it
|
||||
// pub fn im_engine<'a>(
|
||||
// matter: &'a Matter,
|
||||
// action: OpCode,
|
||||
// data: &dyn ToTLV,
|
||||
// data_out: &'a mut [u8],
|
||||
// ) -> (DmHandler<'a>, u8, &'a mut [u8]) {
|
||||
// let mut engine = ImEngine::new(matter);
|
||||
// let input = ImInput::new(action, data);
|
||||
// let (response, output) = engine.process(&input, data_out);
|
||||
// (engine.dm.handler, response, output)
|
||||
// }
|
|
@ -1,152 +0,0 @@
|
|||
/*
|
||||
*
|
||||
* 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 matter::data_model::core::DataHandler;
|
||||
use matter::error::Error;
|
||||
use matter::interaction_model::core::Interaction;
|
||||
use matter::interaction_model::core::InteractionModel;
|
||||
use matter::interaction_model::core::OpCode;
|
||||
use matter::interaction_model::core::Transaction;
|
||||
use matter::transport::exchange::Exchange;
|
||||
use matter::transport::exchange::ExchangeCtx;
|
||||
use matter::transport::network::Address;
|
||||
use matter::transport::network::IpAddr;
|
||||
use matter::transport::network::Ipv4Addr;
|
||||
use matter::transport::network::SocketAddr;
|
||||
use matter::transport::packet::Packet;
|
||||
use matter::transport::packet::MAX_RX_BUF_SIZE;
|
||||
use matter::transport::packet::MAX_TX_BUF_SIZE;
|
||||
use matter::transport::proto_ctx::ProtoCtx;
|
||||
use matter::transport::session::SessionMgr;
|
||||
use matter::utils::epoch::dummy_epoch;
|
||||
use matter::utils::rand::dummy_rand;
|
||||
|
||||
struct Node {
|
||||
pub endpoint: u16,
|
||||
pub cluster: u32,
|
||||
pub command: u16,
|
||||
pub variable: u8,
|
||||
}
|
||||
|
||||
struct DataModel {
|
||||
node: Node,
|
||||
}
|
||||
|
||||
impl DataModel {
|
||||
pub fn new(node: Node) -> Self {
|
||||
DataModel { node }
|
||||
}
|
||||
}
|
||||
|
||||
impl DataHandler for DataModel {
|
||||
fn handle(
|
||||
&mut self,
|
||||
interaction: Interaction,
|
||||
_tx: &mut Packet,
|
||||
_transaction: &mut Transaction,
|
||||
) -> Result<bool, Error> {
|
||||
if let Interaction::Invoke(req) = interaction {
|
||||
if let Some(inv_requests) = &req.inv_requests {
|
||||
for i in inv_requests.iter() {
|
||||
let data = if let Some(data) = i.data.unwrap_tlv() {
|
||||
data
|
||||
} else {
|
||||
continue;
|
||||
};
|
||||
let cmd_path_ib = i.path;
|
||||
let common_data = &mut self.node;
|
||||
common_data.endpoint = cmd_path_ib.path.endpoint.unwrap_or(1);
|
||||
common_data.cluster = cmd_path_ib.path.cluster.unwrap_or(0);
|
||||
common_data.command = cmd_path_ib.path.leaf.unwrap_or(0) as u16;
|
||||
data.confirm_struct().unwrap();
|
||||
common_data.variable = data.find_tag(0).unwrap().u8().unwrap();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(false)
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_data(action: OpCode, data_in: &[u8], data_out: &mut [u8]) -> (DataModel, usize) {
|
||||
let data_model = DataModel::new(Node {
|
||||
endpoint: 0,
|
||||
cluster: 0,
|
||||
command: 0,
|
||||
variable: 0,
|
||||
});
|
||||
let mut interaction_model = InteractionModel(data_model);
|
||||
let mut exch: Exchange = Default::default();
|
||||
let mut sess_mgr = SessionMgr::new(dummy_epoch, dummy_rand);
|
||||
let sess_idx = sess_mgr
|
||||
.get_or_add(
|
||||
0,
|
||||
Address::Udp(SocketAddr::new(
|
||||
IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)),
|
||||
5542,
|
||||
)),
|
||||
None,
|
||||
false,
|
||||
)
|
||||
.unwrap();
|
||||
let sess = sess_mgr.get_session_handle(sess_idx);
|
||||
let exch_ctx = ExchangeCtx {
|
||||
exch: &mut exch,
|
||||
sess,
|
||||
epoch: dummy_epoch,
|
||||
};
|
||||
let mut rx_buf = [0; MAX_RX_BUF_SIZE];
|
||||
let mut tx_buf = [0; MAX_TX_BUF_SIZE];
|
||||
let mut rx = Packet::new_rx(&mut rx_buf);
|
||||
let mut tx = Packet::new_tx(&mut tx_buf);
|
||||
// Create fake rx packet
|
||||
rx.set_proto_id(0x01);
|
||||
rx.set_proto_opcode(action as u8);
|
||||
rx.peer = Address::default();
|
||||
let in_data_len = data_in.len();
|
||||
let rx_buf = rx.as_mut_slice();
|
||||
rx_buf[..in_data_len].copy_from_slice(data_in);
|
||||
|
||||
let mut ctx = ProtoCtx::new(exch_ctx, &rx, &mut tx);
|
||||
|
||||
interaction_model.handle(&mut ctx).unwrap();
|
||||
|
||||
let out_len = ctx.tx.as_mut_slice().len();
|
||||
data_out[..out_len].copy_from_slice(ctx.tx.as_mut_slice());
|
||||
(interaction_model.0, out_len)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_valid_invoke_cmd() -> Result<(), Error> {
|
||||
// An invoke command for endpoint 0, cluster 49, command 12 and a u8 variable value of 0x05
|
||||
|
||||
let b = [
|
||||
0x15, 0x28, 0x00, 0x28, 0x01, 0x36, 0x02, 0x15, 0x37, 0x00, 0x25, 0x00, 0x00, 0x00, 0x26,
|
||||
0x01, 0x31, 0x00, 0x00, 0x00, 0x26, 0x02, 0x0c, 0x00, 0x00, 0x00, 0x18, 0x35, 0x01, 0x24,
|
||||
0x00, 0x05, 0x18, 0x18, 0x18, 0x18,
|
||||
];
|
||||
|
||||
let mut out_buf: [u8; 20] = [0; 20];
|
||||
|
||||
let (data_model, _) = handle_data(OpCode::InvokeRequest, &b, &mut out_buf);
|
||||
let data = &data_model.node;
|
||||
assert_eq!(data.endpoint, 0);
|
||||
assert_eq!(data.cluster, 49);
|
||||
assert_eq!(data.command, 12);
|
||||
assert_eq!(data.variable, 5);
|
||||
Ok(())
|
||||
}
|
|
@ -1,14 +0,0 @@
|
|||
[package]
|
||||
name = "matter_macro_derive"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
[lib]
|
||||
proc-macro = true
|
||||
|
||||
[dependencies]
|
||||
syn = { version = "1", features = ["extra-traits"]}
|
||||
quote = "1"
|
||||
proc-macro2 = "1"
|
||||
proc-macro-crate = "1.3"
|
21
rs-matter-macros/Cargo.toml
Normal file
21
rs-matter-macros/Cargo.toml
Normal file
|
@ -0,0 +1,21 @@
|
|||
[package]
|
||||
name = "rs-matter-macros"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
authors = ["Kedar Sovani <kedars@gmail.com>", "Ivan Markov", "Project CHIP Authors"]
|
||||
description = "Native Rust implementation of the Matter (Smart-Home) ecosystem - Proc-macros"
|
||||
repository = "https://github.com/project-chip/matter-rs"
|
||||
readme = "README.md"
|
||||
keywords = ["matter", "smart", "smart-home", "IoT", "ESP32"]
|
||||
categories = ["embedded", "network-programming"]
|
||||
license = "Apache-2.0"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
[lib]
|
||||
proc-macro = true
|
||||
|
||||
[dependencies]
|
||||
syn = { version = "1", features = ["extra-traits"]}
|
||||
quote = "1"
|
||||
proc-macro2 = "1"
|
||||
proc-macro-crate = "1.3"
|
5
rs-matter-macros/README.md
Normal file
5
rs-matter-macros/README.md
Normal file
|
@ -0,0 +1,5 @@
|
|||
# rs-matter-macros: The Rust Implementation of Matter Library - Proc-macros
|
||||
|
||||
Proc-macros for implementing the `ToTLV` and `FromTLV` traits.
|
||||
|
||||
NOTE: The macros are re-exported by the `rs-matter` crate which should be used instead of adding a direct dependency on the `rs-matter-macros` crate.
|
|
@ -107,7 +107,7 @@ fn parse_tag_val(field: &syn::Field) -> Option<u8> {
|
|||
}
|
||||
|
||||
fn get_crate_name() -> String {
|
||||
let found_crate = proc_macro_crate::crate_name("matter-iot").unwrap_or_else(|err| {
|
||||
let found_crate = proc_macro_crate::crate_name("rs-matter").unwrap_or_else(|err| {
|
||||
eprintln!("Warning: defaulting to `crate` {err}");
|
||||
proc_macro_crate::FoundCrate::Itself
|
||||
});
|
|
@ -1,32 +1,31 @@
|
|||
[package]
|
||||
name = "matter-iot"
|
||||
version = "0.1.0"
|
||||
edition = "2018"
|
||||
authors = ["Kedar Sovani <kedars@gmail.com>"]
|
||||
description = "Native RUST implementation of the Matter (Smart-Home) ecosystem"
|
||||
repository = "https://github.com/kedars/matter-rs"
|
||||
name = "rs-matter"
|
||||
version = "0.1.1"
|
||||
edition = "2021"
|
||||
authors = ["Kedar Sovani <kedars@gmail.com>", "Ivan Markov", "Project CHIP Authors"]
|
||||
description = "Native Rust implementation of the Matter (Smart-Home) ecosystem"
|
||||
repository = "https://github.com/project-chip/matter-rs"
|
||||
readme = "README.md"
|
||||
keywords = ["matter", "smart", "smart-home", "IoT", "ESP32"]
|
||||
categories = ["embedded", "network-programming"]
|
||||
license = "MIT"
|
||||
|
||||
[lib]
|
||||
name = "matter"
|
||||
path = "src/lib.rs"
|
||||
license = "Apache-2.0"
|
||||
|
||||
[features]
|
||||
default = ["os", "crypto_rustcrypto"]
|
||||
default = ["os", "mbedtls"]
|
||||
os = ["std", "backtrace", "env_logger", "nix", "critical-section/std", "embassy-sync/std", "embassy-time/std"]
|
||||
std = ["alloc", "rand", "qrcode", "async-io", "smol", "esp-idf-sys/std"]
|
||||
esp-idf = ["std", "rustcrypto", "esp-idf-sys"]
|
||||
std = ["alloc", "rand", "async-io", "esp-idf-sys?/std", "embassy-time/generic-queue-16"]
|
||||
backtrace = []
|
||||
alloc = []
|
||||
nightly = []
|
||||
crypto_openssl = ["alloc", "openssl", "foreign-types", "hmac", "sha2"]
|
||||
crypto_mbedtls = ["alloc", "mbedtls"]
|
||||
crypto_rustcrypto = ["alloc", "sha2", "hmac", "pbkdf2", "hkdf", "aes", "ccm", "p256", "elliptic-curve", "crypto-bigint", "x509-cert", "rand_core"]
|
||||
openssl = ["alloc", "dep:openssl", "foreign-types", "hmac", "sha2"]
|
||||
mbedtls = ["alloc", "dep:mbedtls"]
|
||||
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]
|
||||
matter_macro_derive = { path = "../matter_macro_derive" }
|
||||
rs-matter-macros = { version = "0.1", path = "../rs-matter-macros" }
|
||||
bitflags = { version = "1.3", default-features = false }
|
||||
byteorder = { version = "1.4.3", default-features = false }
|
||||
heapless = "0.7.16"
|
||||
|
@ -42,19 +41,24 @@ owo-colors = "3"
|
|||
time = { version = "0.3", default-features = false }
|
||||
verhoeff = { version = "1", default-features = false }
|
||||
embassy-futures = "0.1"
|
||||
embassy-time = { version = "0.1.1", features = ["generic-queue-8"] }
|
||||
embassy-time = "0.1.1"
|
||||
embassy-sync = "0.2"
|
||||
critical-section = "1.1.1"
|
||||
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
|
||||
rand = { version = "0.8.5", optional = true }
|
||||
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
|
||||
async-io = { version = "=1.12", optional = true } # =1.12 for compatibility with ESP IDF
|
||||
|
||||
# crypto
|
||||
openssl = { git = "https://github.com/sfackler/rust-openssl", optional = true }
|
||||
openssl = { version = "0.10.55", optional = true }
|
||||
foreign-types = { version = "0.3.2", optional = true }
|
||||
|
||||
# rust-crypto
|
||||
|
@ -73,20 +77,26 @@ x509-cert = { version = "0.2.0", default-features = false, features = ["pem"], o
|
|||
[target.'cfg(target_os = "macos")'.dependencies]
|
||||
astro-dnssd = { version = "0.3" }
|
||||
|
||||
[target.'cfg(target_os = "linux")'.dependencies]
|
||||
zeroconf = { version = "0.12", optional = true }
|
||||
|
||||
[target.'cfg(not(target_os = "espidf"))'.dependencies]
|
||||
mbedtls = { git = "https://github.com/fortanix/rust-mbedtls", optional = true }
|
||||
mbedtls = { version = "0.9", optional = true }
|
||||
env_logger = { version = "0.10.0", optional = true }
|
||||
nix = { version = "0.26", features = ["net"], optional = true }
|
||||
|
||||
[target.'cfg(target_os = "espidf")'.dependencies]
|
||||
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"
|
||||
esp-idf-sys = { version = "0.33", optional = true, default-features = false, features = ["native"] }
|
||||
|
||||
[build-dependencies]
|
||||
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]]
|
||||
name = "onoff_light"
|
||||
path = "../examples/onoff_light/src/main.rs"
|
3
rs-matter/README.md
Normal file
3
rs-matter/README.md
Normal file
|
@ -0,0 +1,3 @@
|
|||
# rs-matter: The Rust Implementation of Matter Library
|
||||
|
||||
This is the actual `rs-matter` library crate. See [the main README file](../README.md) for more information.
|
|
@ -22,7 +22,7 @@ use crate::{
|
|||
error::{Error, ErrorCode},
|
||||
fabric,
|
||||
interaction_model::messages::GenericPath,
|
||||
tlv::{self, FromTLV, TLVElement, TLVList, TLVWriter, TagType, ToTLV},
|
||||
tlv::{self, FromTLV, Nullable, TLVElement, TLVList, TLVWriter, TagType, ToTLV},
|
||||
transport::session::{Session, SessionMode, MAX_CAT_IDS_PER_NOC},
|
||||
utils::writebuf::WriteBuf,
|
||||
};
|
||||
|
@ -282,7 +282,15 @@ impl Target {
|
|||
}
|
||||
|
||||
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)]
|
||||
#[tlvargs(start = 1)]
|
||||
pub struct AclEntry {
|
||||
|
@ -298,14 +306,12 @@ pub struct AclEntry {
|
|||
impl AclEntry {
|
||||
pub fn new(fab_idx: u8, privilege: Privilege, auth_mode: AuthMode) -> Self {
|
||||
const INIT_SUBJECTS: Option<u64> = None;
|
||||
const INIT_TARGETS: Option<Target> = None;
|
||||
let privilege = privilege;
|
||||
Self {
|
||||
fab_idx: Some(fab_idx),
|
||||
privilege,
|
||||
auth_mode,
|
||||
subjects: [INIT_SUBJECTS; SUBJECTS_PER_ENTRY],
|
||||
targets: [INIT_TARGETS; TARGETS_PER_ENTRY],
|
||||
targets: Targets::init_notnull(),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -324,12 +330,20 @@ impl AclEntry {
|
|||
}
|
||||
|
||||
pub fn add_target(&mut self, target: Target) -> Result<(), Error> {
|
||||
if self.targets.is_null() {
|
||||
self.targets = Targets::init_notnull();
|
||||
}
|
||||
let index = self
|
||||
.targets
|
||||
.as_ref()
|
||||
.notnull()
|
||||
.unwrap()
|
||||
.iter()
|
||||
.position(|s| s.is_none())
|
||||
.ok_or(ErrorCode::NoSpace)?;
|
||||
self.targets[index] = Some(target);
|
||||
|
||||
self.targets.as_mut().notnull().unwrap()[index] = Some(target);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
@ -358,7 +372,10 @@ impl AclEntry {
|
|||
fn match_access_desc(&self, object: &AccessDesc) -> bool {
|
||||
let mut allow = false;
|
||||
let mut entries_exist = false;
|
||||
for t in self.targets.iter().flatten() {
|
||||
match self.targets.as_ref().notnull() {
|
||||
None => allow = true, // Allow if targets are NULL
|
||||
Some(targets) => {
|
||||
for t in targets.iter().flatten() {
|
||||
entries_exist = true;
|
||||
if (t.endpoint.is_none() || t.endpoint == object.path.endpoint)
|
||||
&& (t.cluster.is_none() || t.cluster == object.path.cluster)
|
||||
|
@ -366,6 +383,8 @@ impl AclEntry {
|
|||
allow = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if !entries_exist {
|
||||
// Targets array empty implies allow for all targets
|
||||
allow = true;
|
|
@ -264,8 +264,8 @@ impl<'a> CertConsumer for ASN1Writer<'a> {
|
|||
self.write_str(0x06, oid)
|
||||
}
|
||||
|
||||
fn utctime(&mut self, _tag: &str, epoch: u32) -> Result<(), Error> {
|
||||
let matter_epoch = MATTER_EPOCH_SECS + epoch as u64;
|
||||
fn utctime(&mut self, _tag: &str, epoch: u64) -> Result<(), Error> {
|
||||
let matter_epoch = MATTER_EPOCH_SECS + epoch;
|
||||
|
||||
let dt = OffsetDateTime::from_unix_timestamp(matter_epoch as _).unwrap();
|
||||
|
|
@ -21,7 +21,7 @@ use crate::{
|
|||
crypto::KeyPair,
|
||||
error::{Error, ErrorCode},
|
||||
tlv::{self, FromTLV, OctetStr, TLVArray, TLVElement, TLVWriter, TagType, ToTLV},
|
||||
utils::writebuf::WriteBuf,
|
||||
utils::{epoch::MATTER_CERT_DOESNT_EXPIRE, writebuf::WriteBuf},
|
||||
};
|
||||
use log::error;
|
||||
use num_derive::FromPrimitive;
|
||||
|
@ -175,7 +175,7 @@ fn encode_extended_key_usage(
|
|||
w.end_seq()
|
||||
}
|
||||
|
||||
#[derive(FromTLV, ToTLV, Default, Debug)]
|
||||
#[derive(FromTLV, ToTLV, Default, Debug, PartialEq)]
|
||||
#[tlvargs(start = 1)]
|
||||
struct BasicConstraints {
|
||||
is_ca: bool,
|
||||
|
@ -215,8 +215,8 @@ fn encode_extension_end(w: &mut dyn CertConsumer) -> Result<(), Error> {
|
|||
w.end_seq()
|
||||
}
|
||||
|
||||
#[derive(FromTLV, ToTLV, Default, Debug)]
|
||||
#[tlvargs(lifetime = "'a", start = 1, datatype = "list")]
|
||||
#[derive(FromTLV, ToTLV, Default, Debug, PartialEq)]
|
||||
#[tlvargs(lifetime = "'a", start = 1, datatype = "list", unordered)]
|
||||
struct Extensions<'a> {
|
||||
basic_const: Option<BasicConstraints>,
|
||||
key_usage: Option<u16>,
|
||||
|
@ -298,7 +298,7 @@ enum DnTags {
|
|||
NocCat = 22,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
#[derive(Debug, PartialEq)]
|
||||
enum DistNameValue<'a> {
|
||||
Uint(u64),
|
||||
Utf8Str(&'a [u8]),
|
||||
|
@ -307,7 +307,7 @@ enum DistNameValue<'a> {
|
|||
|
||||
const MAX_DN_ENTRIES: usize = 5;
|
||||
|
||||
#[derive(Default, Debug)]
|
||||
#[derive(Default, Debug, PartialEq)]
|
||||
struct DistNames<'a> {
|
||||
// The order in which the DNs arrive is important, as the signing
|
||||
// requires that the ASN1 notation retains the same order
|
||||
|
@ -545,7 +545,7 @@ fn encode_dn_value(
|
|||
w.end_set()
|
||||
}
|
||||
|
||||
#[derive(FromTLV, ToTLV, Default, Debug)]
|
||||
#[derive(FromTLV, ToTLV, Default, Debug, PartialEq)]
|
||||
#[tlvargs(lifetime = "'a", start = 1)]
|
||||
pub struct Cert<'a> {
|
||||
serial_no: OctetStr<'a>,
|
||||
|
@ -650,8 +650,14 @@ impl<'a> Cert<'a> {
|
|||
self.issuer.encode("Issuer:", w)?;
|
||||
|
||||
w.start_seq("Validity:")?;
|
||||
w.utctime("Not Before:", self.not_before)?;
|
||||
w.utctime("Not After:", self.not_after)?;
|
||||
w.utctime("Not Before:", self.not_before.into())?;
|
||||
if self.not_after == 0 {
|
||||
// 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()?;
|
||||
|
||||
self.subject.encode("Subject:", w)?;
|
||||
|
@ -710,8 +716,9 @@ impl<'a> CertVerifier<'a> {
|
|||
let k = KeyPair::new_from_public(parent.get_pubkey())?;
|
||||
k.verify_msg(asn1, self.cert.get_signature()).map_err(|e| {
|
||||
error!(
|
||||
"Error in signature verification of certificate: {:x?}",
|
||||
self.cert.get_subject_key_id()
|
||||
"Error in signature verification of certificate: {:x?} by {:x?}",
|
||||
self.cert.get_subject_key_id(),
|
||||
parent.get_subject_key_id()
|
||||
);
|
||||
e
|
||||
})?;
|
||||
|
@ -744,7 +751,7 @@ pub trait CertConsumer {
|
|||
fn start_ctx(&mut self, tag: &str, id: u8) -> Result<(), Error>;
|
||||
fn end_ctx(&mut self) -> Result<(), Error>;
|
||||
fn oid(&mut self, tag: &str, oid: &[u8]) -> Result<(), Error>;
|
||||
fn utctime(&mut self, tag: &str, epoch: u32) -> Result<(), Error>;
|
||||
fn utctime(&mut self, tag: &str, epoch: u64) -> Result<(), Error>;
|
||||
}
|
||||
|
||||
const MAX_DEPTH: usize = 10;
|
||||
|
@ -826,6 +833,16 @@ 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]
|
||||
fn test_cert_corrupted() {
|
||||
use crate::error::ErrorCode;
|
||||
|
@ -841,9 +858,10 @@ mod tests {
|
|||
|
||||
#[test]
|
||||
fn test_tlv_conversions() {
|
||||
let test_input: [&[u8]; 3] = [
|
||||
let test_input: [&[u8]; 4] = [
|
||||
&test_vectors::NOC1_SUCCESS,
|
||||
&test_vectors::ICAC1_SUCCESS,
|
||||
&test_vectors::ICAC2_SUCCESS,
|
||||
&test_vectors::RCA1_SUCCESS,
|
||||
];
|
||||
|
||||
|
@ -855,7 +873,10 @@ mod tests {
|
|||
let mut wb = WriteBuf::new(&mut buf);
|
||||
let mut tw = TLVWriter::new(&mut wb);
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -894,6 +915,23 @@ mod tests {
|
|||
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,
|
||||
];
|
||||
// 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
|
||||
pub const NOC1_AUTH_KEY_FAIL: [u8; 247] = [
|
||||
0x15, 0x30, 0x1, 0x1, 0x1, 0x24, 0x2, 0x1, 0x37, 0x3, 0x24, 0x13, 0x1, 0x24, 0x15, 0x1,
|
||||
|
@ -1112,5 +1150,47 @@ mod tests {
|
|||
0x16, 0x80, 0x14, 0x72, 0xc2, 0x01, 0xf7, 0x57, 0x19, 0x13, 0xb3, 0x48, 0xca, 0x00,
|
||||
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,
|
||||
];
|
||||
}
|
||||
}
|
|
@ -122,8 +122,8 @@ impl<'a, 'b> CertConsumer for CertPrinter<'a, 'b> {
|
|||
}
|
||||
Ok(())
|
||||
}
|
||||
fn utctime(&mut self, tag: &str, epoch: u32) -> Result<(), Error> {
|
||||
let matter_epoch = MATTER_EPOCH_SECS + epoch as u64;
|
||||
fn utctime(&mut self, tag: &str, epoch: u64) -> Result<(), Error> {
|
||||
let matter_epoch = MATTER_EPOCH_SECS + epoch;
|
||||
|
||||
let dt = OffsetDateTime::from_unix_timestamp(matter_epoch as _).unwrap();
|
||||
|
|
@ -17,6 +17,8 @@
|
|||
|
||||
use core::{borrow::Borrow, cell::RefCell};
|
||||
|
||||
use embassy_sync::{blocking_mutex::raw::NoopRawMutex, mutex::Mutex};
|
||||
|
||||
use crate::{
|
||||
acl::AclMgr,
|
||||
data_model::{
|
||||
|
@ -28,7 +30,11 @@ use crate::{
|
|||
mdns::Mdns,
|
||||
pairing::{print_pairing_code_and_qr, DiscoveryCapabilities},
|
||||
secure_channel::{pake::PaseMgr, spake2p::VerifierData},
|
||||
utils::{epoch::Epoch, rand::Rand},
|
||||
transport::{
|
||||
exchange::{ExchangeCtx, MAX_EXCHANGES},
|
||||
session::SessionMgr,
|
||||
},
|
||||
utils::{epoch::Epoch, rand::Rand, select::Notification},
|
||||
};
|
||||
|
||||
/* The Matter Port */
|
||||
|
@ -44,16 +50,22 @@ pub struct CommissioningData {
|
|||
|
||||
/// The primary Matter Object
|
||||
pub struct Matter<'a> {
|
||||
pub fabric_mgr: RefCell<FabricMgr>,
|
||||
pub acl_mgr: RefCell<AclMgr>,
|
||||
pub pase_mgr: RefCell<PaseMgr>,
|
||||
pub failsafe: RefCell<FailSafe>,
|
||||
pub mdns: &'a dyn Mdns,
|
||||
pub epoch: Epoch,
|
||||
pub rand: Rand,
|
||||
pub dev_det: &'a BasicInfoConfig<'a>,
|
||||
pub dev_att: &'a dyn DevAttDataFetcher,
|
||||
pub port: u16,
|
||||
fabric_mgr: RefCell<FabricMgr>,
|
||||
pub acl_mgr: RefCell<AclMgr>, // Public for tests
|
||||
pase_mgr: RefCell<PaseMgr>,
|
||||
failsafe: RefCell<FailSafe>,
|
||||
persist_notification: Notification,
|
||||
pub(crate) send_notification: Notification,
|
||||
mdns: &'a dyn Mdns,
|
||||
pub(crate) epoch: Epoch,
|
||||
pub(crate) rand: Rand,
|
||||
dev_det: &'a BasicInfoConfig<'a>,
|
||||
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> {
|
||||
|
@ -91,12 +103,18 @@ impl<'a> Matter<'a> {
|
|||
acl_mgr: RefCell::new(AclMgr::new()),
|
||||
pase_mgr: RefCell::new(PaseMgr::new(epoch, rand)),
|
||||
failsafe: RefCell::new(FailSafe::new()),
|
||||
persist_notification: Notification::new(),
|
||||
send_notification: Notification::new(),
|
||||
mdns,
|
||||
epoch,
|
||||
rand,
|
||||
dev_det,
|
||||
dev_att,
|
||||
port,
|
||||
exchanges: RefCell::new(heapless::Vec::new()),
|
||||
ephemeral: RefCell::new(None),
|
||||
ephemeral_mutex: Mutex::new(()),
|
||||
session_mgr: RefCell::new(SessionMgr::new(epoch, rand)),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -157,6 +175,16 @@ impl<'a> Matter<'a> {
|
|||
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> {
|
|
@ -17,6 +17,8 @@
|
|||
|
||||
extern crate alloc;
|
||||
|
||||
use core::fmt::{self, Debug};
|
||||
|
||||
use alloc::sync::Arc;
|
||||
|
||||
use log::{error, info};
|
||||
|
@ -355,3 +357,9 @@ impl Sha256 {
|
|||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl Debug for Sha256 {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
|
||||
write!(f, "Sha256")
|
||||
}
|
||||
}
|
|
@ -15,6 +15,8 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
use core::fmt::{self, Debug};
|
||||
|
||||
use crate::error::{Error, ErrorCode};
|
||||
use crate::utils::rand::Rand;
|
||||
|
||||
|
@ -57,7 +59,8 @@ impl HmacSha256 {
|
|||
}
|
||||
|
||||
pub fn update(&mut self, data: &[u8]) -> Result<(), Error> {
|
||||
Ok(self.ctx.update(data))
|
||||
self.ctx.update(data);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn finish(self, out: &mut [u8]) -> Result<(), Error> {
|
||||
|
@ -116,7 +119,7 @@ impl KeyPair {
|
|||
fn private_key(&self) -> Result<&EcKey<Private>, Error> {
|
||||
match &self.key {
|
||||
KeyType::Public(_) => Err(ErrorCode::Invalid.into()),
|
||||
KeyType::Private(k) => Ok(&k),
|
||||
KeyType::Private(k) => Ok(k),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -225,7 +228,7 @@ impl KeyPair {
|
|||
}
|
||||
|
||||
const P256_KEY_LEN: usize = 256 / 8;
|
||||
pub fn pubkey_from_der<'a>(der: &'a [u8], out_key: &mut [u8]) -> Result<(), Error> {
|
||||
pub fn pubkey_from_der(der: &[u8], out_key: &mut [u8]) -> Result<(), Error> {
|
||||
if out_key.len() != P256_KEY_LEN {
|
||||
error!("Insufficient length");
|
||||
Err(ErrorCode::NoSpace.into())
|
||||
|
@ -284,7 +287,7 @@ pub fn decrypt_in_place(
|
|||
) -> Result<usize, Error> {
|
||||
let tag_start = data.len() - super::AEAD_MIC_LEN_BYTES;
|
||||
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());
|
||||
Ok(result.len())
|
||||
}
|
||||
|
@ -391,3 +394,9 @@ impl Sha256 {
|
|||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl Debug for Sha256 {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
|
||||
write!(f, "Sha256")
|
||||
}
|
||||
}
|
|
@ -37,37 +37,29 @@ pub const ECDH_SHARED_SECRET_LEN_BYTES: usize = 32;
|
|||
|
||||
pub const EC_SIGNATURE_LEN_BYTES: usize = 64;
|
||||
|
||||
#[cfg(all(feature = "crypto_mbedtls", target_os = "espidf"))]
|
||||
#[cfg(all(feature = "mbedtls", target_os = "espidf"))]
|
||||
mod crypto_esp_mbedtls;
|
||||
#[cfg(all(feature = "crypto_mbedtls", target_os = "espidf"))]
|
||||
#[cfg(all(feature = "mbedtls", target_os = "espidf"))]
|
||||
pub use self::crypto_esp_mbedtls::*;
|
||||
|
||||
#[cfg(all(feature = "crypto_mbedtls", not(target_os = "espidf")))]
|
||||
#[cfg(all(feature = "mbedtls", not(target_os = "espidf")))]
|
||||
mod crypto_mbedtls;
|
||||
#[cfg(all(feature = "crypto_mbedtls", not(target_os = "espidf")))]
|
||||
#[cfg(all(feature = "mbedtls", not(target_os = "espidf")))]
|
||||
pub use self::crypto_mbedtls::*;
|
||||
|
||||
#[cfg(feature = "crypto_openssl")]
|
||||
#[cfg(feature = "openssl")]
|
||||
mod crypto_openssl;
|
||||
#[cfg(feature = "crypto_openssl")]
|
||||
#[cfg(feature = "openssl")]
|
||||
pub use self::crypto_openssl::*;
|
||||
|
||||
#[cfg(feature = "crypto_rustcrypto")]
|
||||
#[cfg(feature = "rustcrypto")]
|
||||
mod crypto_rustcrypto;
|
||||
#[cfg(feature = "crypto_rustcrypto")]
|
||||
#[cfg(feature = "rustcrypto")]
|
||||
pub use self::crypto_rustcrypto::*;
|
||||
|
||||
#[cfg(not(any(
|
||||
feature = "crypto_openssl",
|
||||
feature = "crypto_mbedtls",
|
||||
feature = "crypto_rustcrypto"
|
||||
)))]
|
||||
#[cfg(not(any(feature = "openssl", feature = "mbedtls", feature = "rustcrypto")))]
|
||||
pub mod crypto_dummy;
|
||||
#[cfg(not(any(
|
||||
feature = "crypto_openssl",
|
||||
feature = "crypto_mbedtls",
|
||||
feature = "crypto_rustcrypto"
|
||||
)))]
|
||||
#[cfg(not(any(feature = "openssl", feature = "mbedtls", feature = "rustcrypto")))]
|
||||
pub use self::crypto_dummy::*;
|
||||
|
||||
impl<'a> FromTLV<'a> for KeyPair {
|
|
@ -15,10 +15,15 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
use core::convert::TryInto;
|
||||
use core::{cell::RefCell, convert::TryInto};
|
||||
|
||||
use super::objects::*;
|
||||
use crate::{attribute_enum, error::Error, utils::rand::Rand};
|
||||
use crate::{
|
||||
attribute_enum,
|
||||
error::{Error, ErrorCode},
|
||||
utils::rand::Rand,
|
||||
};
|
||||
use heapless::String;
|
||||
use strum::FromRepr;
|
||||
|
||||
pub const ID: u32 = 0x0028;
|
||||
|
@ -27,8 +32,11 @@ pub const ID: u32 = 0x0028;
|
|||
#[repr(u16)]
|
||||
pub enum Attributes {
|
||||
DMRevision(AttrType<u8>) = 0,
|
||||
VendorName(AttrUtfType) = 1,
|
||||
VendorId(AttrType<u16>) = 2,
|
||||
ProductName(AttrUtfType) = 3,
|
||||
ProductId(AttrType<u16>) = 4,
|
||||
NodeLabel(AttrUtfType) = 5,
|
||||
HwVer(AttrType<u16>) = 7,
|
||||
SwVer(AttrType<u32>) = 9,
|
||||
SwVerString(AttrUtfType) = 0xa,
|
||||
|
@ -39,8 +47,11 @@ attribute_enum!(Attributes);
|
|||
|
||||
pub enum AttributesDiscriminants {
|
||||
DMRevision = 0,
|
||||
VendorName = 1,
|
||||
VendorId = 2,
|
||||
ProductName = 3,
|
||||
ProductId = 4,
|
||||
NodeLabel = 5,
|
||||
HwVer = 7,
|
||||
SwVer = 9,
|
||||
SwVerString = 0xa,
|
||||
|
@ -57,6 +68,8 @@ pub struct BasicInfoConfig<'a> {
|
|||
pub serial_no: &'a str,
|
||||
/// Device name; up to 32 characters
|
||||
pub device_name: &'a str,
|
||||
pub vendor_name: &'a str,
|
||||
pub product_name: &'a str,
|
||||
}
|
||||
|
||||
pub const CLUSTER: Cluster<'static> = Cluster {
|
||||
|
@ -70,16 +83,31 @@ pub const CLUSTER: Cluster<'static> = Cluster {
|
|||
Access::RV,
|
||||
Quality::FIXED,
|
||||
),
|
||||
Attribute::new(
|
||||
AttributesDiscriminants::VendorName as u16,
|
||||
Access::RV,
|
||||
Quality::FIXED,
|
||||
),
|
||||
Attribute::new(
|
||||
AttributesDiscriminants::VendorId as u16,
|
||||
Access::RV,
|
||||
Quality::FIXED,
|
||||
),
|
||||
Attribute::new(
|
||||
AttributesDiscriminants::ProductName as u16,
|
||||
Access::RV,
|
||||
Quality::FIXED,
|
||||
),
|
||||
Attribute::new(
|
||||
AttributesDiscriminants::ProductId as u16,
|
||||
Access::RV,
|
||||
Quality::FIXED,
|
||||
),
|
||||
Attribute::new(
|
||||
AttributesDiscriminants::NodeLabel as u16,
|
||||
Access::RWVM,
|
||||
Quality::N,
|
||||
),
|
||||
Attribute::new(
|
||||
AttributesDiscriminants::HwVer as u16,
|
||||
Access::RV,
|
||||
|
@ -107,13 +135,16 @@ pub const CLUSTER: Cluster<'static> = Cluster {
|
|||
pub struct BasicInfoCluster<'a> {
|
||||
data_ver: Dataver,
|
||||
cfg: &'a BasicInfoConfig<'a>,
|
||||
node_label: RefCell<String<32>>, // Max node-label as per the spec
|
||||
}
|
||||
|
||||
impl<'a> BasicInfoCluster<'a> {
|
||||
pub fn new(cfg: &'a BasicInfoConfig<'a>, rand: Rand) -> Self {
|
||||
let node_label = RefCell::new(String::from(""));
|
||||
Self {
|
||||
data_ver: Dataver::new(rand),
|
||||
cfg,
|
||||
node_label,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -124,8 +155,13 @@ impl<'a> BasicInfoCluster<'a> {
|
|||
} else {
|
||||
match attr.attr_id.try_into()? {
|
||||
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::ProductName(codec) => codec.encode(writer, self.cfg.product_name),
|
||||
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::SwVer(codec) => codec.encode(writer, self.cfg.sw_ver),
|
||||
Attributes::SwVerString(codec) => codec.encode(writer, self.cfg.sw_ver_str),
|
||||
|
@ -136,12 +172,35 @@ impl<'a> BasicInfoCluster<'a> {
|
|||
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> {
|
||||
fn read(&self, attr: &AttrDetails, encoder: AttrDataEncoder) -> Result<(), Error> {
|
||||
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> {}
|
|
@ -15,12 +15,12 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
use core::convert::TryInto;
|
||||
use core::{cell::Cell, convert::TryInto};
|
||||
|
||||
use super::objects::*;
|
||||
use crate::{
|
||||
attribute_enum, cmd_enter, command_enum, error::Error, interaction_model::core::Transaction,
|
||||
tlv::TLVElement, utils::rand::Rand,
|
||||
attribute_enum, cmd_enter, command_enum, error::Error, tlv::TLVElement,
|
||||
transport::exchange::Exchange, utils::rand::Rand,
|
||||
};
|
||||
use log::info;
|
||||
use strum::{EnumDiscriminants, FromRepr};
|
||||
|
@ -54,7 +54,7 @@ pub const CLUSTER: Cluster<'static> = Cluster {
|
|||
Attribute::new(
|
||||
AttributesDiscriminants::OnOff as u16,
|
||||
Access::RV,
|
||||
Quality::PERSISTENT,
|
||||
Quality::SN,
|
||||
),
|
||||
],
|
||||
commands: &[
|
||||
|
@ -66,20 +66,20 @@ pub const CLUSTER: Cluster<'static> = Cluster {
|
|||
|
||||
pub struct OnOffCluster {
|
||||
data_ver: Dataver,
|
||||
on: bool,
|
||||
on: Cell<bool>,
|
||||
}
|
||||
|
||||
impl OnOffCluster {
|
||||
pub fn new(rand: Rand) -> Self {
|
||||
Self {
|
||||
data_ver: Dataver::new(rand),
|
||||
on: false,
|
||||
on: Cell::new(false),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set(&mut self, on: bool) {
|
||||
if self.on != on {
|
||||
self.on = on;
|
||||
pub fn set(&self, on: bool) {
|
||||
if self.on.get() != on {
|
||||
self.on.set(on);
|
||||
self.data_ver.changed();
|
||||
}
|
||||
}
|
||||
|
@ -90,7 +90,7 @@ impl OnOffCluster {
|
|||
CLUSTER.read(attr.attr_id, writer)
|
||||
} else {
|
||||
match attr.attr_id.try_into()? {
|
||||
Attributes::OnOff(codec) => codec.encode(writer, self.on),
|
||||
Attributes::OnOff(codec) => codec.encode(writer, self.on.get()),
|
||||
}
|
||||
}
|
||||
} else {
|
||||
|
@ -98,7 +98,7 @@ impl OnOffCluster {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn write(&mut self, attr: &AttrDetails, data: AttrData) -> Result<(), Error> {
|
||||
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()? {
|
||||
|
@ -111,8 +111,8 @@ impl OnOffCluster {
|
|||
}
|
||||
|
||||
pub fn invoke(
|
||||
&mut self,
|
||||
transaction: &mut Transaction,
|
||||
&self,
|
||||
_exchange: &Exchange,
|
||||
cmd: &CmdDetails,
|
||||
_data: &TLVElement,
|
||||
_encoder: CmdDataEncoder,
|
||||
|
@ -128,12 +128,10 @@ impl OnOffCluster {
|
|||
}
|
||||
Commands::Toggle => {
|
||||
cmd_enter!("Toggle");
|
||||
self.set(!self.on);
|
||||
self.set(!self.on.get());
|
||||
}
|
||||
}
|
||||
|
||||
transaction.complete();
|
||||
|
||||
self.data_ver.changed();
|
||||
|
||||
Ok(())
|
||||
|
@ -145,18 +143,18 @@ impl Handler for OnOffCluster {
|
|||
OnOffCluster::read(self, attr, encoder)
|
||||
}
|
||||
|
||||
fn write(&mut self, attr: &AttrDetails, data: AttrData) -> Result<(), Error> {
|
||||
fn write(&self, attr: &AttrDetails, data: AttrData) -> Result<(), Error> {
|
||||
OnOffCluster::write(self, attr, data)
|
||||
}
|
||||
|
||||
fn invoke(
|
||||
&mut self,
|
||||
transaction: &mut Transaction,
|
||||
&self,
|
||||
exchange: &Exchange,
|
||||
cmd: &CmdDetails,
|
||||
data: &TLVElement,
|
||||
encoder: CmdDataEncoder,
|
||||
) -> Result<(), Error> {
|
||||
OnOffCluster::invoke(self, transaction, cmd, data, encoder)
|
||||
OnOffCluster::invoke(self, exchange, cmd, data, encoder)
|
||||
}
|
||||
}
|
||||
|
160
rs-matter/src/data_model/core.rs
Normal file
160
rs-matter/src/data_model/core.rs
Normal file
|
@ -0,0 +1,160 @@
|
|||
/*
|
||||
*
|
||||
* 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 portable_atomic::{AtomicU32, Ordering};
|
||||
|
||||
use super::objects::*;
|
||||
use crate::{
|
||||
alloc,
|
||||
error::*,
|
||||
interaction_model::core::Interaction,
|
||||
transport::{exchange::Exchange, packet::Packet},
|
||||
};
|
||||
|
||||
// TODO: For now...
|
||||
static SUBS_ID: AtomicU32 = AtomicU32::new(1);
|
||||
|
||||
/// The Maximum number of expanded writer request per transaction
|
||||
///
|
||||
/// The write requests are first wildcard-expanded, and these many number of
|
||||
/// write requests per-transaction will be supported.
|
||||
pub const MAX_WRITE_ATTRS_IN_ONE_TRANS: usize = 7;
|
||||
|
||||
pub struct DataModel<T>(T);
|
||||
|
||||
impl<T> DataModel<T> {
|
||||
pub fn new(handler: T) -> Self {
|
||||
Self(handler)
|
||||
}
|
||||
|
||||
pub async fn handle<'r, 'p>(
|
||||
&self,
|
||||
exchange: &'r mut Exchange<'_>,
|
||||
rx: &'r mut Packet<'p>,
|
||||
tx: &'r mut Packet<'p>,
|
||||
rx_status: &'r mut Packet<'p>,
|
||||
) -> Result<(), Error>
|
||||
where
|
||||
T: DataModelHandler,
|
||||
{
|
||||
let timeout = Interaction::timeout(exchange, rx, tx).await?;
|
||||
|
||||
let mut interaction = alloc!(Interaction::new(
|
||||
exchange,
|
||||
rx,
|
||||
tx,
|
||||
rx_status,
|
||||
|| SUBS_ID.fetch_add(1, Ordering::SeqCst),
|
||||
timeout,
|
||||
)?);
|
||||
|
||||
#[cfg(feature = "alloc")]
|
||||
let interaction = &mut *interaction;
|
||||
|
||||
#[cfg(not(feature = "alloc"))]
|
||||
let interaction = &mut interaction;
|
||||
|
||||
#[cfg(feature = "nightly")]
|
||||
let metadata = self.0.lock().await;
|
||||
|
||||
#[cfg(not(feature = "nightly"))]
|
||||
let metadata = self.0.lock();
|
||||
|
||||
if interaction.start().await? {
|
||||
match interaction {
|
||||
Interaction::Read {
|
||||
req,
|
||||
ref mut driver,
|
||||
} => {
|
||||
let accessor = driver.accessor()?;
|
||||
|
||||
'outer: for item in metadata.node().read(req, None, &accessor) {
|
||||
while !AttrDataEncoder::handle_read(&item, &self.0, &mut driver.writer()?)
|
||||
.await?
|
||||
{
|
||||
if !driver.send_chunk(req).await? {
|
||||
break 'outer;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
driver.complete(req).await?;
|
||||
}
|
||||
Interaction::Write {
|
||||
req,
|
||||
ref mut driver,
|
||||
} => {
|
||||
let accessor = driver.accessor()?;
|
||||
// The spec expects that a single write request like DeleteList + AddItem
|
||||
// should cause all ACLs of that fabric to be deleted and the new one to be added (Case 1).
|
||||
//
|
||||
// This is in conflict with the immediate-effect expectation of ACL: an ACL
|
||||
// write should instantaneously update the ACL so that immediate next WriteAttribute
|
||||
// *in the same WriteRequest* should see that effect (Case 2).
|
||||
//
|
||||
// As with the C++ SDK, here we do all the ACLs checks first, before any write begins.
|
||||
// Thus we support the Case1 by doing this. It does come at the cost of maintaining an
|
||||
// additional list of expanded write requests as we start processing those.
|
||||
let node = metadata.node();
|
||||
let write_attrs: heapless::Vec<_, MAX_WRITE_ATTRS_IN_ONE_TRANS> =
|
||||
node.write(req, &accessor).collect();
|
||||
|
||||
for item in write_attrs {
|
||||
AttrDataEncoder::handle_write(&item, &self.0, &mut driver.writer()?)
|
||||
.await?;
|
||||
}
|
||||
|
||||
driver.complete(req).await?;
|
||||
}
|
||||
Interaction::Invoke {
|
||||
req,
|
||||
ref mut driver,
|
||||
} => {
|
||||
let accessor = driver.accessor()?;
|
||||
|
||||
for item in metadata.node().invoke(req, &accessor) {
|
||||
let (mut tw, exchange) = driver.writer_exchange()?;
|
||||
|
||||
CmdDataEncoder::handle(&item, &self.0, &mut tw, exchange).await?;
|
||||
}
|
||||
|
||||
driver.complete(req).await?;
|
||||
}
|
||||
Interaction::Subscribe {
|
||||
req,
|
||||
ref mut driver,
|
||||
} => {
|
||||
let accessor = driver.accessor()?;
|
||||
|
||||
'outer: for item in metadata.node().subscribing_read(req, None, &accessor) {
|
||||
while !AttrDataEncoder::handle_read(&item, &self.0, &mut driver.writer()?)
|
||||
.await?
|
||||
{
|
||||
if !driver.send_chunk(req).await? {
|
||||
break 'outer;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
driver.complete(req).await?;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
|
@ -14,6 +14,7 @@
|
|||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
#![allow(clippy::bad_bit_mask)]
|
||||
|
||||
use crate::data_model::objects::GlobalElements;
|
||||
|
||||
|
@ -39,9 +40,12 @@ bitflags! {
|
|||
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 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 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 RWFVM = Self::READ.bits | Self::WRITE.bits | Self::FAB_SCOPED.bits |Self::NEED_VIEW.bits | Self::NEED_MANAGE.bits;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -72,10 +76,16 @@ bitflags! {
|
|||
#[derive(Default)]
|
||||
pub struct Quality: u8 {
|
||||
const NONE = 0x00;
|
||||
const SCENE = 0x01;
|
||||
const PERSISTENT = 0x02;
|
||||
const FIXED = 0x03;
|
||||
const NULLABLE = 0x04;
|
||||
const SCENE = 0x01; // Short: S
|
||||
const PERSISTENT = 0x02; // Short: N
|
||||
const FIXED = 0x04; // Short: F
|
||||
const NULLABLE = 0x08; // Short: X
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
|
@ -15,11 +15,13 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
use core::cell::Cell;
|
||||
|
||||
use crate::utils::rand::Rand;
|
||||
|
||||
pub struct Dataver {
|
||||
ver: u32,
|
||||
changed: bool,
|
||||
ver: Cell<u32>,
|
||||
changed: Cell<bool>,
|
||||
}
|
||||
|
||||
impl Dataver {
|
||||
|
@ -28,25 +30,25 @@ impl Dataver {
|
|||
rand(&mut buf);
|
||||
|
||||
Self {
|
||||
ver: u32::from_be_bytes(buf),
|
||||
changed: false,
|
||||
ver: Cell::new(u32::from_be_bytes(buf)),
|
||||
changed: Cell::new(false),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get(&self) -> u32 {
|
||||
self.ver
|
||||
self.ver.get()
|
||||
}
|
||||
|
||||
pub fn changed(&mut self) -> u32 {
|
||||
(self.ver, _) = self.ver.overflowing_add(1);
|
||||
self.changed = true;
|
||||
pub fn changed(&self) -> u32 {
|
||||
self.ver.set(self.ver.get().overflowing_add(1).0);
|
||||
self.changed.set(true);
|
||||
|
||||
self.get()
|
||||
}
|
||||
|
||||
pub fn consume_change<T>(&mut self, change: T) -> Option<T> {
|
||||
if self.changed {
|
||||
self.changed = false;
|
||||
pub fn consume_change<T>(&self, change: T) -> Option<T> {
|
||||
if self.changed.get() {
|
||||
self.changed.set(false);
|
||||
Some(change)
|
||||
} else {
|
||||
None
|
|
@ -19,12 +19,12 @@ use core::fmt::{Debug, Formatter};
|
|||
use core::marker::PhantomData;
|
||||
use core::ops::{Deref, DerefMut};
|
||||
|
||||
use crate::interaction_model::core::{IMStatusCode, Transaction};
|
||||
use crate::interaction_model::core::IMStatusCode;
|
||||
use crate::interaction_model::messages::ib::{
|
||||
AttrPath, AttrResp, AttrStatus, CmdDataTag, CmdPath, CmdStatus, InvResp, InvRespTag,
|
||||
};
|
||||
use crate::interaction_model::messages::GenericPath;
|
||||
use crate::tlv::UtfStr;
|
||||
use crate::transport::exchange::Exchange;
|
||||
use crate::{
|
||||
error::{Error, ErrorCode},
|
||||
interaction_model::messages::ib::{AttrDataTag, AttrRespTag},
|
||||
|
@ -32,7 +32,7 @@ use crate::{
|
|||
};
|
||||
use log::error;
|
||||
|
||||
use super::{AttrDetails, CmdDetails, Handler};
|
||||
use super::{AttrDetails, CmdDetails, DataModelHandler};
|
||||
|
||||
// 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
|
||||
|
@ -124,47 +124,73 @@ pub struct AttrDataEncoder<'a, 'b, 'c> {
|
|||
}
|
||||
|
||||
impl<'a, 'b, 'c> AttrDataEncoder<'a, 'b, 'c> {
|
||||
pub fn handle_read<T: Handler>(
|
||||
item: Result<AttrDetails, AttrStatus>,
|
||||
pub async fn handle_read<T: DataModelHandler>(
|
||||
item: &Result<AttrDetails<'_>, AttrStatus>,
|
||||
handler: &T,
|
||||
tw: &mut TLVWriter,
|
||||
) -> Result<Option<GenericPath>, Error> {
|
||||
tw: &mut TLVWriter<'_, '_>,
|
||||
) -> Result<bool, Error> {
|
||||
let status = match item {
|
||||
Ok(attr) => {
|
||||
let encoder = AttrDataEncoder::new(&attr, tw);
|
||||
let encoder = AttrDataEncoder::new(attr, tw);
|
||||
|
||||
match handler.read(&attr, encoder) {
|
||||
let result = {
|
||||
#[cfg(not(feature = "nightly"))]
|
||||
{
|
||||
handler.read(attr, encoder)
|
||||
}
|
||||
|
||||
#[cfg(feature = "nightly")]
|
||||
{
|
||||
handler.read(attr, encoder).await
|
||||
}
|
||||
};
|
||||
|
||||
match result {
|
||||
Ok(()) => None,
|
||||
Err(e) => {
|
||||
if e.code() == ErrorCode::NoSpace {
|
||||
return Ok(Some(attr.path().to_gp()));
|
||||
return Ok(false);
|
||||
} else {
|
||||
attr.status(e.into())?
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(status) => Some(status),
|
||||
Err(status) => Some(status.clone()),
|
||||
};
|
||||
|
||||
if let Some(status) = status {
|
||||
AttrResp::Status(status).to_tlv(tw, TagType::Anonymous)?;
|
||||
}
|
||||
|
||||
Ok(None)
|
||||
Ok(true)
|
||||
}
|
||||
|
||||
pub fn handle_write<T: Handler>(
|
||||
item: Result<(AttrDetails, TLVElement), AttrStatus>,
|
||||
handler: &mut T,
|
||||
tw: &mut TLVWriter,
|
||||
pub async fn handle_write<T: DataModelHandler>(
|
||||
item: &Result<(AttrDetails<'_>, TLVElement<'_>), AttrStatus>,
|
||||
handler: &T,
|
||||
tw: &mut TLVWriter<'_, '_>,
|
||||
) -> Result<(), Error> {
|
||||
let status = match item {
|
||||
Ok((attr, data)) => match handler.write(&attr, AttrData::new(attr.dataver, &data)) {
|
||||
Ok((attr, data)) => {
|
||||
let result = {
|
||||
#[cfg(not(feature = "nightly"))]
|
||||
{
|
||||
handler.write(attr, AttrData::new(attr.dataver, data))
|
||||
}
|
||||
|
||||
#[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),
|
||||
}
|
||||
}
|
||||
Err(status) => Some(status.clone()),
|
||||
};
|
||||
|
||||
if let Some(status) = status {
|
||||
|
@ -174,61 +200,6 @@ impl<'a, 'b, 'c> AttrDataEncoder<'a, 'b, 'c> {
|
|||
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 {
|
||||
Self {
|
||||
dataver_filter: attr.dataver,
|
||||
|
@ -365,18 +336,30 @@ pub struct CmdDataEncoder<'a, 'b, 'c> {
|
|||
}
|
||||
|
||||
impl<'a, 'b, 'c> CmdDataEncoder<'a, 'b, 'c> {
|
||||
pub fn handle<T: Handler>(
|
||||
item: Result<(CmdDetails, TLVElement), CmdStatus>,
|
||||
handler: &mut T,
|
||||
transaction: &mut Transaction,
|
||||
tw: &mut TLVWriter,
|
||||
pub async fn handle<T: DataModelHandler>(
|
||||
item: &Result<(CmdDetails<'_>, TLVElement<'_>), CmdStatus>,
|
||||
handler: &T,
|
||||
tw: &mut TLVWriter<'_, '_>,
|
||||
exchange: &Exchange<'_>,
|
||||
) -> Result<(), Error> {
|
||||
let status = match item {
|
||||
Ok((cmd, data)) => {
|
||||
let mut tracker = CmdDataTracker::new();
|
||||
let encoder = CmdDataEncoder::new(&cmd, &mut tracker, tw);
|
||||
let encoder = CmdDataEncoder::new(cmd, &mut tracker, tw);
|
||||
|
||||
match handler.invoke(transaction, &cmd, &data, encoder) {
|
||||
let result = {
|
||||
#[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),
|
||||
Err(error) => {
|
||||
error!("Error invoking command: {}", error);
|
||||
|
@ -386,7 +369,7 @@ impl<'a, 'b, 'c> CmdDataEncoder<'a, 'b, 'c> {
|
|||
}
|
||||
Err(status) => {
|
||||
error!("Error invoking command: {:?}", status);
|
||||
Some(status)
|
||||
Some(status.clone())
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -397,33 +380,6 @@ impl<'a, 'b, 'c> CmdDataEncoder<'a, 'b, 'c> {
|
|||
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(
|
||||
cmd: &CmdDetails,
|
||||
tracker: &'a mut CmdDataTracker,
|
|
@ -17,12 +17,25 @@
|
|||
|
||||
use crate::{
|
||||
error::{Error, ErrorCode},
|
||||
interaction_model::core::Transaction,
|
||||
tlv::TLVElement,
|
||||
transport::exchange::Exchange,
|
||||
};
|
||||
|
||||
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> {
|
||||
fn consume_change(&mut self) -> Option<T>;
|
||||
}
|
||||
|
@ -30,13 +43,13 @@ pub trait ChangeNotifier<T> {
|
|||
pub trait Handler {
|
||||
fn read(&self, attr: &AttrDetails, encoder: AttrDataEncoder) -> Result<(), Error>;
|
||||
|
||||
fn write(&mut self, _attr: &AttrDetails, _data: AttrData) -> Result<(), Error> {
|
||||
fn write(&self, _attr: &AttrDetails, _data: AttrData) -> Result<(), Error> {
|
||||
Err(ErrorCode::AttributeNotFound.into())
|
||||
}
|
||||
|
||||
fn invoke(
|
||||
&mut self,
|
||||
_transaction: &mut Transaction,
|
||||
&self,
|
||||
_exchange: &Exchange,
|
||||
_cmd: &CmdDetails,
|
||||
_data: &TLVElement,
|
||||
_encoder: CmdDataEncoder,
|
||||
|
@ -45,6 +58,29 @@ 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
|
||||
where
|
||||
T: Handler,
|
||||
|
@ -53,25 +89,52 @@ where
|
|||
(**self).read(attr, encoder)
|
||||
}
|
||||
|
||||
fn write(&mut self, attr: &AttrDetails, data: AttrData) -> Result<(), Error> {
|
||||
fn write(&self, attr: &AttrDetails, data: AttrData) -> Result<(), Error> {
|
||||
(**self).write(attr, data)
|
||||
}
|
||||
|
||||
fn invoke(
|
||||
&mut self,
|
||||
transaction: &mut Transaction,
|
||||
&self,
|
||||
exchange: &Exchange,
|
||||
cmd: &CmdDetails,
|
||||
data: &TLVElement,
|
||||
encoder: CmdDataEncoder,
|
||||
) -> Result<(), Error> {
|
||||
(**self).invoke(transaction, cmd, data, encoder)
|
||||
(**self).invoke(exchange, cmd, data, encoder)
|
||||
}
|
||||
}
|
||||
|
||||
pub trait NonBlockingHandler: Handler {}
|
||||
|
||||
impl<T> NonBlockingHandler for &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;
|
||||
|
||||
impl EmptyHandler {
|
||||
|
@ -140,7 +203,7 @@ where
|
|||
}
|
||||
}
|
||||
|
||||
fn write(&mut self, attr: &AttrDetails, data: AttrData) -> Result<(), Error> {
|
||||
fn write(&self, attr: &AttrDetails, data: AttrData) -> Result<(), Error> {
|
||||
if self.handler_endpoint == attr.endpoint_id && self.handler_cluster == attr.cluster_id {
|
||||
self.handler.write(attr, data)
|
||||
} else {
|
||||
|
@ -149,16 +212,16 @@ where
|
|||
}
|
||||
|
||||
fn invoke(
|
||||
&mut self,
|
||||
transaction: &mut Transaction,
|
||||
&self,
|
||||
exchange: &Exchange,
|
||||
cmd: &CmdDetails,
|
||||
data: &TLVElement,
|
||||
encoder: CmdDataEncoder,
|
||||
) -> Result<(), Error> {
|
||||
if self.handler_endpoint == cmd.endpoint_id && self.handler_cluster == cmd.cluster_id {
|
||||
self.handler.invoke(transaction, cmd, data, encoder)
|
||||
self.handler.invoke(exchange, cmd, data, encoder)
|
||||
} else {
|
||||
self.next.invoke(transaction, cmd, data, encoder)
|
||||
self.next.invoke(exchange, cmd, data, encoder)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -184,6 +247,35 @@ 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)]
|
||||
#[macro_export]
|
||||
macro_rules! handler_chain_type {
|
||||
|
@ -203,15 +295,15 @@ macro_rules! handler_chain_type {
|
|||
}
|
||||
|
||||
#[cfg(feature = "nightly")]
|
||||
pub mod asynch {
|
||||
mod asynch {
|
||||
use crate::{
|
||||
data_model::objects::{AttrData, AttrDataEncoder, AttrDetails, CmdDataEncoder, CmdDetails},
|
||||
error::{Error, ErrorCode},
|
||||
interaction_model::core::Transaction,
|
||||
tlv::TLVElement,
|
||||
transport::exchange::Exchange,
|
||||
};
|
||||
|
||||
use super::{ChainedHandler, EmptyHandler, Handler, NonBlockingHandler};
|
||||
use super::{ChainedHandler, EmptyHandler, Handler, HandlerCompat, NonBlockingHandler};
|
||||
|
||||
pub trait AsyncHandler {
|
||||
async fn read<'a>(
|
||||
|
@ -221,7 +313,7 @@ pub mod asynch {
|
|||
) -> Result<(), Error>;
|
||||
|
||||
async fn write<'a>(
|
||||
&'a mut self,
|
||||
&'a self,
|
||||
_attr: &'a AttrDetails<'_>,
|
||||
_data: AttrData<'a>,
|
||||
) -> Result<(), Error> {
|
||||
|
@ -229,8 +321,8 @@ pub mod asynch {
|
|||
}
|
||||
|
||||
async fn invoke<'a>(
|
||||
&'a mut self,
|
||||
_transaction: &'a mut Transaction<'_, '_>,
|
||||
&'a self,
|
||||
_exchange: &'a Exchange<'_>,
|
||||
_cmd: &'a CmdDetails<'_>,
|
||||
_data: &'a TLVElement<'_>,
|
||||
_encoder: CmdDataEncoder<'a, '_, '_>,
|
||||
|
@ -252,7 +344,7 @@ pub mod asynch {
|
|||
}
|
||||
|
||||
async fn write<'a>(
|
||||
&'a mut self,
|
||||
&'a self,
|
||||
attr: &'a AttrDetails<'_>,
|
||||
data: AttrData<'a>,
|
||||
) -> Result<(), Error> {
|
||||
|
@ -260,19 +352,79 @@ pub mod asynch {
|
|||
}
|
||||
|
||||
async fn invoke<'a>(
|
||||
&'a mut self,
|
||||
transaction: &'a mut Transaction<'_, '_>,
|
||||
&'a self,
|
||||
exchange: &'a Exchange<'_>,
|
||||
cmd: &'a CmdDetails<'_>,
|
||||
data: &'a TLVElement<'_>,
|
||||
encoder: CmdDataEncoder<'a, '_, '_>,
|
||||
) -> Result<(), Error> {
|
||||
(**self).invoke(transaction, cmd, data, encoder).await
|
||||
(**self).invoke(exchange, cmd, data, encoder).await
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Asyncify<T>(pub T);
|
||||
impl<T> AsyncHandler for &T
|
||||
where
|
||||
T: AsyncHandler,
|
||||
{
|
||||
async fn read<'a>(
|
||||
&'a self,
|
||||
attr: &'a AttrDetails<'_>,
|
||||
encoder: AttrDataEncoder<'a, '_, '_>,
|
||||
) -> Result<(), Error> {
|
||||
(**self).read(attr, encoder).await
|
||||
}
|
||||
|
||||
impl<T> AsyncHandler for Asyncify<T>
|
||||
async fn write<'a>(
|
||||
&'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
|
||||
T: NonBlockingHandler,
|
||||
{
|
||||
|
@ -285,21 +437,21 @@ pub mod asynch {
|
|||
}
|
||||
|
||||
async fn write<'a>(
|
||||
&'a mut self,
|
||||
&'a self,
|
||||
attr: &'a AttrDetails<'_>,
|
||||
data: AttrData<'a>,
|
||||
) -> Result<(), Error> {
|
||||
Handler::write(&mut self.0, attr, data)
|
||||
Handler::write(&self.0, attr, data)
|
||||
}
|
||||
|
||||
async fn invoke<'a>(
|
||||
&'a mut self,
|
||||
transaction: &'a mut Transaction<'_, '_>,
|
||||
&'a self,
|
||||
exchange: &'a Exchange<'_>,
|
||||
cmd: &'a CmdDetails<'_>,
|
||||
data: &'a TLVElement<'_>,
|
||||
encoder: CmdDataEncoder<'a, '_, '_>,
|
||||
) -> Result<(), Error> {
|
||||
Handler::invoke(&mut self.0, transaction, cmd, data, encoder)
|
||||
Handler::invoke(&self.0, exchange, cmd, data, encoder)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -332,7 +484,7 @@ pub mod asynch {
|
|||
}
|
||||
|
||||
async fn write<'a>(
|
||||
&'a mut self,
|
||||
&'a self,
|
||||
attr: &'a AttrDetails<'_>,
|
||||
data: AttrData<'a>,
|
||||
) -> Result<(), Error> {
|
||||
|
@ -345,16 +497,16 @@ pub mod asynch {
|
|||
}
|
||||
|
||||
async fn invoke<'a>(
|
||||
&'a mut self,
|
||||
transaction: &'a mut Transaction<'_, '_>,
|
||||
&'a self,
|
||||
exchange: &'a Exchange<'_>,
|
||||
cmd: &'a CmdDetails<'_>,
|
||||
data: &'a TLVElement<'_>,
|
||||
encoder: CmdDataEncoder<'a, '_, '_>,
|
||||
) -> Result<(), Error> {
|
||||
if self.handler_endpoint == cmd.endpoint_id && self.handler_cluster == cmd.cluster_id {
|
||||
self.handler.invoke(transaction, cmd, data, encoder).await
|
||||
self.handler.invoke(exchange, cmd, data, encoder).await
|
||||
} else {
|
||||
self.next.invoke(transaction, cmd, data, encoder).await
|
||||
self.next.invoke(exchange, cmd, data, encoder).await
|
||||
}
|
||||
}
|
||||
}
|
195
rs-matter/src/data_model/objects/metadata.rs
Normal file
195
rs-matter/src/data_model/objects/metadata.rs
Normal file
|
@ -0,0 +1,195 @@
|
|||
/*
|
||||
*
|
||||
* 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::Node;
|
||||
|
||||
#[cfg(feature = "nightly")]
|
||||
pub use asynch::*;
|
||||
|
||||
use super::HandlerCompat;
|
||||
|
||||
pub trait MetadataGuard {
|
||||
fn node(&self) -> Node<'_>;
|
||||
}
|
||||
|
||||
impl<T> MetadataGuard for &T
|
||||
where
|
||||
T: MetadataGuard,
|
||||
{
|
||||
fn node(&self) -> Node<'_> {
|
||||
(**self).node()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> MetadataGuard for &mut T
|
||||
where
|
||||
T: MetadataGuard,
|
||||
{
|
||||
fn node(&self) -> Node<'_> {
|
||||
(**self).node()
|
||||
}
|
||||
}
|
||||
|
||||
pub trait Metadata {
|
||||
type MetadataGuard<'a>: MetadataGuard
|
||||
where
|
||||
Self: 'a;
|
||||
|
||||
fn lock(&self) -> Self::MetadataGuard<'_>;
|
||||
}
|
||||
|
||||
impl<T> Metadata for &T
|
||||
where
|
||||
T: Metadata,
|
||||
{
|
||||
type MetadataGuard<'a> = T::MetadataGuard<'a> where Self: 'a;
|
||||
|
||||
fn lock(&self) -> Self::MetadataGuard<'_> {
|
||||
(**self).lock()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Metadata for &mut T
|
||||
where
|
||||
T: Metadata,
|
||||
{
|
||||
type MetadataGuard<'a> = T::MetadataGuard<'a> where Self: 'a;
|
||||
|
||||
fn lock(&self) -> Self::MetadataGuard<'_> {
|
||||
(**self).lock()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> MetadataGuard for Node<'a> {
|
||||
fn node(&self) -> Node<'_> {
|
||||
Node {
|
||||
id: self.id,
|
||||
endpoints: self.endpoints,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Metadata for Node<'a> {
|
||||
type MetadataGuard<'g> = Node<'g> where Self: 'g;
|
||||
|
||||
fn lock(&self) -> Self::MetadataGuard<'_> {
|
||||
Node {
|
||||
id: self.id,
|
||||
endpoints: self.endpoints,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<M, H> Metadata for (M, H)
|
||||
where
|
||||
M: Metadata,
|
||||
{
|
||||
type MetadataGuard<'a> = M::MetadataGuard<'a>
|
||||
where
|
||||
Self: 'a;
|
||||
|
||||
fn lock(&self) -> Self::MetadataGuard<'_> {
|
||||
self.0.lock()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Metadata for HandlerCompat<T>
|
||||
where
|
||||
T: Metadata,
|
||||
{
|
||||
type MetadataGuard<'a> = T::MetadataGuard<'a>
|
||||
where
|
||||
Self: 'a;
|
||||
|
||||
fn lock(&self) -> Self::MetadataGuard<'_> {
|
||||
self.0.lock()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "nightly")]
|
||||
pub mod asynch {
|
||||
use crate::data_model::objects::{HandlerCompat, Node};
|
||||
|
||||
use super::{Metadata, MetadataGuard};
|
||||
|
||||
pub trait AsyncMetadata {
|
||||
type MetadataGuard<'a>: MetadataGuard
|
||||
where
|
||||
Self: 'a;
|
||||
|
||||
async fn lock(&self) -> Self::MetadataGuard<'_>;
|
||||
}
|
||||
|
||||
impl<T> AsyncMetadata for &T
|
||||
where
|
||||
T: AsyncMetadata,
|
||||
{
|
||||
type MetadataGuard<'a> = T::MetadataGuard<'a> where Self: 'a;
|
||||
|
||||
async fn lock(&self) -> Self::MetadataGuard<'_> {
|
||||
(**self).lock().await
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> AsyncMetadata for &mut T
|
||||
where
|
||||
T: AsyncMetadata,
|
||||
{
|
||||
type MetadataGuard<'a> = T::MetadataGuard<'a> where Self: 'a;
|
||||
|
||||
async fn lock(&self) -> Self::MetadataGuard<'_> {
|
||||
(**self).lock().await
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> AsyncMetadata for Node<'a> {
|
||||
type MetadataGuard<'g> = Node<'g> where Self: 'g;
|
||||
|
||||
async fn lock(&self) -> Self::MetadataGuard<'_> {
|
||||
Node {
|
||||
id: self.id,
|
||||
endpoints: self.endpoints,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<M, H> AsyncMetadata for (M, H)
|
||||
where
|
||||
M: AsyncMetadata,
|
||||
{
|
||||
type MetadataGuard<'a> = M::MetadataGuard<'a>
|
||||
where
|
||||
Self: 'a;
|
||||
|
||||
async fn lock(&self) -> Self::MetadataGuard<'_> {
|
||||
self.0.lock().await
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> AsyncMetadata for HandlerCompat<T>
|
||||
where
|
||||
T: Metadata,
|
||||
{
|
||||
type MetadataGuard<'a> = T::MetadataGuard<'a>
|
||||
where
|
||||
Self: 'a;
|
||||
|
||||
async fn lock(&self) -> Self::MetadataGuard<'_> {
|
||||
self.0.lock()
|
||||
}
|
||||
}
|
||||
}
|
|
@ -41,6 +41,9 @@ pub use handler::*;
|
|||
mod dataver;
|
||||
pub use dataver::*;
|
||||
|
||||
mod metadata;
|
||||
pub use metadata::*;
|
||||
|
||||
pub type EndptId = u16;
|
||||
pub type ClusterId = u32;
|
||||
pub type AttrId = u16;
|
|
@ -17,9 +17,10 @@
|
|||
|
||||
use crate::{
|
||||
acl::Accessor,
|
||||
alloc,
|
||||
data_model::objects::Endpoint,
|
||||
interaction_model::{
|
||||
core::{IMStatusCode, ResumeReadReq, ResumeSubscribeReq},
|
||||
core::IMStatusCode,
|
||||
messages::{
|
||||
ib::{AttrPath, AttrStatus, CmdStatus, DataVersionFilter},
|
||||
msg::{InvReq, ReadReq, SubscribeReq, WriteReq},
|
||||
|
@ -27,7 +28,7 @@ use crate::{
|
|||
},
|
||||
},
|
||||
// TODO: This layer shouldn't really depend on the TLV layer, should create an abstraction layer
|
||||
tlv::{TLVArray, TLVArrayIter, TLVElement},
|
||||
tlv::{TLVArray, TLVElement},
|
||||
};
|
||||
use core::{
|
||||
fmt,
|
||||
|
@ -57,41 +58,6 @@ 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)]
|
||||
pub struct Node<'a> {
|
||||
pub id: u16,
|
||||
|
@ -102,6 +68,7 @@ impl<'a> Node<'a> {
|
|||
pub fn read<'s, 'm>(
|
||||
&'s self,
|
||||
req: &'m ReadReq,
|
||||
from: Option<GenericPath>,
|
||||
accessor: &'m Accessor<'m>,
|
||||
) -> impl Iterator<Item = Result<AttrDetails, AttrStatus>> + 'm
|
||||
where
|
||||
|
@ -114,30 +81,14 @@ impl<'a> Node<'a> {
|
|||
req.dataver_filters.as_ref(),
|
||||
req.fabric_filtered,
|
||||
accessor,
|
||||
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()),
|
||||
from,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn subscribing_read<'s, 'm>(
|
||||
&'s self,
|
||||
req: &'m SubscribeReq,
|
||||
from: Option<GenericPath>,
|
||||
accessor: &'m Accessor<'m>,
|
||||
) -> impl Iterator<Item = Result<AttrDetails, AttrStatus>> + 'm
|
||||
where
|
||||
|
@ -150,31 +101,14 @@ impl<'a> Node<'a> {
|
|||
req.dataver_filters.as_ref(),
|
||||
req.fabric_filtered,
|
||||
accessor,
|
||||
None,
|
||||
from,
|
||||
)
|
||||
}
|
||||
|
||||
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>(
|
||||
fn read_attr_requests<'s, 'm, P>(
|
||||
&'s self,
|
||||
attr_requests: P,
|
||||
dataver_filters: D,
|
||||
dataver_filters: Option<&'m TLVArray<DataVersionFilter>>,
|
||||
fabric_filtered: bool,
|
||||
accessor: &'m Accessor<'m>,
|
||||
from: Option<GenericPath>,
|
||||
|
@ -182,11 +116,9 @@ impl<'a> Node<'a> {
|
|||
where
|
||||
's: 'm,
|
||||
P: Iterator<Item = AttrPath> + 'm,
|
||||
D: Iterable<Item = DataVersionFilter> + Clone + 'm,
|
||||
{
|
||||
attr_requests.flat_map(move |path| {
|
||||
alloc!(attr_requests.flat_map(move |path| {
|
||||
if path.to_gp().is_wildcard() {
|
||||
let dataver_filters = dataver_filters.clone();
|
||||
let from = from.clone();
|
||||
|
||||
let iter = self
|
||||
|
@ -204,10 +136,14 @@ impl<'a> Node<'a> {
|
|||
.is_ok()
|
||||
})
|
||||
.map(move |(ep, cl, attr)| {
|
||||
let dataver = dataver_filters.iter().find_map(|filter| {
|
||||
let dataver = if let Some(dataver_filters) = dataver_filters {
|
||||
dataver_filters.iter().find_map(|filter| {
|
||||
(filter.path.endpoint == ep.id && filter.path.cluster == cl.id)
|
||||
.then_some(filter.data_ver)
|
||||
});
|
||||
})
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
Ok(AttrDetails {
|
||||
node: self,
|
||||
|
@ -230,10 +166,14 @@ impl<'a> Node<'a> {
|
|||
|
||||
let result = match self.check_attribute(accessor, ep, cl, attr, false) {
|
||||
Ok(()) => {
|
||||
let dataver = dataver_filters.iter().find_map(|filter| {
|
||||
let dataver = if let Some(dataver_filters) = dataver_filters {
|
||||
dataver_filters.iter().find_map(|filter| {
|
||||
(filter.path.endpoint == ep && filter.path.cluster == cl)
|
||||
.then_some(filter.data_ver)
|
||||
});
|
||||
})
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
Ok(AttrDetails {
|
||||
node: self,
|
||||
|
@ -252,7 +192,7 @@ impl<'a> Node<'a> {
|
|||
|
||||
WildcardIter::Single(once(result))
|
||||
}
|
||||
})
|
||||
}))
|
||||
}
|
||||
|
||||
pub fn write<'m>(
|
||||
|
@ -260,7 +200,7 @@ impl<'a> Node<'a> {
|
|||
req: &'m WriteReq,
|
||||
accessor: &'m Accessor<'m>,
|
||||
) -> impl Iterator<Item = Result<(AttrDetails, TLVElement<'m>), AttrStatus>> + 'm {
|
||||
req.write_requests.iter().flat_map(move |attr_data| {
|
||||
alloc!(req.write_requests.iter().flat_map(move |attr_data| {
|
||||
if attr_data.path.cluster.is_none() {
|
||||
WildcardIter::Single(once(Err(AttrStatus::new(
|
||||
&attr_data.path.to_gp(),
|
||||
|
@ -332,7 +272,7 @@ impl<'a> Node<'a> {
|
|||
|
||||
WildcardIter::Single(once(result))
|
||||
}
|
||||
})
|
||||
}))
|
||||
}
|
||||
|
||||
pub fn invoke<'m>(
|
||||
|
@ -340,7 +280,8 @@ impl<'a> Node<'a> {
|
|||
req: &'m InvReq,
|
||||
accessor: &'m Accessor<'m>,
|
||||
) -> impl Iterator<Item = Result<(CmdDetails, TLVElement<'m>), CmdStatus>> + 'm {
|
||||
req.inv_requests
|
||||
alloc!(req
|
||||
.inv_requests
|
||||
.iter()
|
||||
.flat_map(|inv_requests| inv_requests.iter())
|
||||
.flat_map(move |cmd_data| {
|
||||
|
@ -393,7 +334,7 @@ impl<'a> Node<'a> {
|
|||
|
||||
WildcardIter::Single(once(result))
|
||||
}
|
||||
})
|
||||
}))
|
||||
}
|
||||
|
||||
fn matches(path: Option<&GenericPath>, ep: EndptId, cl: ClusterId, leaf: u32) -> bool {
|
|
@ -15,8 +15,12 @@ use super::{
|
|||
sdm::{
|
||||
admin_commissioning::{self, AdminCommCluster},
|
||||
dev_att::DevAttDataFetcher,
|
||||
ethernet_nw_diagnostics::{self, EthNwDiagCluster},
|
||||
failsafe::FailSafe,
|
||||
general_commissioning::{self, GenCommCluster},
|
||||
general_diagnostics::{self, GenDiagCluster},
|
||||
group_key_management,
|
||||
group_key_management::GrpKeyMgmtCluster,
|
||||
noc::{self, NocCluster},
|
||||
nw_commissioning::{self, NwCommCluster},
|
||||
},
|
||||
|
@ -33,10 +37,13 @@ pub type RootEndpointHandler<'a> = handler_chain_type!(
|
|||
NwCommCluster,
|
||||
AdminCommCluster<'a>,
|
||||
NocCluster<'a>,
|
||||
AccessControlCluster<'a>
|
||||
AccessControlCluster<'a>,
|
||||
GenDiagCluster,
|
||||
EthNwDiagCluster,
|
||||
GrpKeyMgmtCluster
|
||||
);
|
||||
|
||||
pub const CLUSTERS: [Cluster<'static>; 7] = [
|
||||
pub const CLUSTERS: [Cluster<'static>; 10] = [
|
||||
descriptor::CLUSTER,
|
||||
cluster_basic_information::CLUSTER,
|
||||
general_commissioning::CLUSTER,
|
||||
|
@ -44,9 +51,12 @@ pub const CLUSTERS: [Cluster<'static>; 7] = [
|
|||
admin_commissioning::CLUSTER,
|
||||
noc::CLUSTER,
|
||||
access_control::CLUSTER,
|
||||
general_diagnostics::CLUSTER,
|
||||
ethernet_nw_diagnostics::CLUSTER,
|
||||
group_key_management::CLUSTER,
|
||||
];
|
||||
|
||||
pub fn endpoint(id: EndptId) -> Endpoint<'static> {
|
||||
pub const fn endpoint(id: EndptId) -> Endpoint<'static> {
|
||||
Endpoint {
|
||||
id,
|
||||
device_type: super::device_types::DEV_TYPE_ROOT_NODE,
|
||||
|
@ -95,6 +105,21 @@ pub fn wrap<'a>(
|
|||
rand: Rand,
|
||||
) -> RootEndpointHandler<'a> {
|
||||
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(
|
||||
endpoint_id,
|
||||
access_control::ID,
|
|
@ -19,11 +19,11 @@ use core::cell::RefCell;
|
|||
use core::convert::TryInto;
|
||||
|
||||
use crate::data_model::objects::*;
|
||||
use crate::interaction_model::core::Transaction;
|
||||
use crate::mdns::Mdns;
|
||||
use crate::secure_channel::pake::PaseMgr;
|
||||
use crate::secure_channel::spake2p::VerifierData;
|
||||
use crate::tlv::{FromTLV, Nullable, OctetStr, TLVElement};
|
||||
use crate::transport::exchange::Exchange;
|
||||
use crate::utils::rand::Rand;
|
||||
use crate::{attribute_enum, cmd_enter};
|
||||
use crate::{command_enum, error::*};
|
||||
|
@ -84,8 +84,8 @@ pub const CLUSTER: Cluster<'static> = Cluster {
|
|||
],
|
||||
commands: &[
|
||||
Commands::OpenCommWindow as _,
|
||||
Commands::OpenBasicCommWindow as _,
|
||||
Commands::RevokeComm as _,
|
||||
// Commands::OpenBasicCommWindow as _,
|
||||
// Commands::RevokeComm as _,
|
||||
],
|
||||
};
|
||||
|
||||
|
@ -133,7 +133,7 @@ impl<'a> AdminCommCluster<'a> {
|
|||
}
|
||||
|
||||
pub fn invoke(
|
||||
&mut self,
|
||||
&self,
|
||||
cmd: &CmdDetails,
|
||||
data: &TLVElement,
|
||||
_encoder: CmdDataEncoder,
|
||||
|
@ -148,7 +148,7 @@ impl<'a> AdminCommCluster<'a> {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
fn handle_command_opencomm_win(&mut self, data: &TLVElement) -> Result<(), Error> {
|
||||
fn handle_command_opencomm_win(&self, data: &TLVElement) -> Result<(), Error> {
|
||||
cmd_enter!("Open Commissioning Window");
|
||||
let req = OpenCommWindowReq::from_tlv(data)?;
|
||||
let verifier = VerifierData::new(req.verifier.0, req.iterations, req.salt.0);
|
||||
|
@ -166,8 +166,8 @@ impl<'a> Handler for AdminCommCluster<'a> {
|
|||
}
|
||||
|
||||
fn invoke(
|
||||
&mut self,
|
||||
_transaction: &mut Transaction,
|
||||
&self,
|
||||
_exchange: &Exchange,
|
||||
cmd: &CmdDetails,
|
||||
data: &TLVElement,
|
||||
encoder: CmdDataEncoder,
|
146
rs-matter/src/data_model/sdm/ethernet_nw_diagnostics.rs
Normal file
146
rs-matter/src/data_model/sdm/ethernet_nw_diagnostics.rs
Normal file
|
@ -0,0 +1,146 @@
|
|||
/*
|
||||
*
|
||||
* Copyright (c) 2023 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::convert::TryInto;
|
||||
|
||||
use crate::{
|
||||
attribute_enum, cmd_enter, command_enum, data_model::objects::AttrType, data_model::objects::*,
|
||||
error::Error, tlv::TLVElement, transport::exchange::Exchange, utils::rand::Rand,
|
||||
};
|
||||
use log::info;
|
||||
use strum::{EnumDiscriminants, FromRepr};
|
||||
|
||||
pub const ID: u32 = 0x0037;
|
||||
|
||||
#[derive(FromRepr, EnumDiscriminants)]
|
||||
#[repr(u16)]
|
||||
pub enum Attributes {
|
||||
PacketRxCount(AttrType<u64>) = 0x02,
|
||||
PacketTxCount(AttrType<u64>) = 0x03,
|
||||
}
|
||||
|
||||
attribute_enum!(Attributes);
|
||||
|
||||
#[derive(FromRepr, EnumDiscriminants)]
|
||||
#[repr(u32)]
|
||||
pub enum Commands {
|
||||
ResetCounts = 0x0,
|
||||
}
|
||||
|
||||
command_enum!(Commands);
|
||||
|
||||
pub const CLUSTER: Cluster<'static> = Cluster {
|
||||
id: ID as _,
|
||||
feature_map: 0,
|
||||
attributes: &[
|
||||
FEATURE_MAP,
|
||||
ATTRIBUTE_LIST,
|
||||
Attribute::new(
|
||||
AttributesDiscriminants::PacketRxCount as u16,
|
||||
Access::RV,
|
||||
Quality::NONE,
|
||||
),
|
||||
Attribute::new(
|
||||
AttributesDiscriminants::PacketTxCount as u16,
|
||||
Access::RV,
|
||||
Quality::FIXED,
|
||||
),
|
||||
],
|
||||
commands: &[CommandsDiscriminants::ResetCounts as _],
|
||||
};
|
||||
|
||||
pub struct EthNwDiagCluster {
|
||||
data_ver: Dataver,
|
||||
}
|
||||
|
||||
impl EthNwDiagCluster {
|
||||
pub fn new(rand: Rand) -> Self {
|
||||
Self {
|
||||
data_ver: Dataver::new(rand),
|
||||
}
|
||||
}
|
||||
|
||||
pub 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 {
|
||||
match attr.attr_id.try_into()? {
|
||||
Attributes::PacketRxCount(codec) => codec.encode(writer, 1),
|
||||
Attributes::PacketTxCount(codec) => codec.encode(writer, 1),
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub fn write(&self, _attr: &AttrDetails, data: AttrData) -> Result<(), Error> {
|
||||
let _data = data.with_dataver(self.data_ver.get())?;
|
||||
|
||||
self.data_ver.changed();
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn invoke(
|
||||
&self,
|
||||
_exchange: &Exchange,
|
||||
cmd: &CmdDetails,
|
||||
_data: &TLVElement,
|
||||
_encoder: CmdDataEncoder,
|
||||
) -> Result<(), Error> {
|
||||
match cmd.cmd_id.try_into()? {
|
||||
Commands::ResetCounts => {
|
||||
cmd_enter!("ResetCounts: Not yet supported");
|
||||
}
|
||||
}
|
||||
|
||||
self.data_ver.changed();
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl Handler for EthNwDiagCluster {
|
||||
fn read(&self, attr: &AttrDetails, encoder: AttrDataEncoder) -> Result<(), Error> {
|
||||
EthNwDiagCluster::read(self, attr, encoder)
|
||||
}
|
||||
|
||||
fn write(&self, attr: &AttrDetails, data: AttrData) -> Result<(), Error> {
|
||||
EthNwDiagCluster::write(self, attr, data)
|
||||
}
|
||||
|
||||
fn invoke(
|
||||
&self,
|
||||
exchange: &Exchange,
|
||||
cmd: &CmdDetails,
|
||||
data: &TLVElement,
|
||||
encoder: CmdDataEncoder,
|
||||
) -> Result<(), Error> {
|
||||
EthNwDiagCluster::invoke(self, exchange, cmd, data, encoder)
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Might be removed once the `on` member is externalized
|
||||
impl NonBlockingHandler for EthNwDiagCluster {}
|
||||
|
||||
impl ChangeNotifier<()> for EthNwDiagCluster {
|
||||
fn consume_change(&mut self) -> Option<()> {
|
||||
self.data_ver.consume_change(())
|
||||
}
|
||||
}
|
|
@ -34,7 +34,7 @@ enum NocState {
|
|||
#[derive(PartialEq)]
|
||||
pub struct ArmedCtx {
|
||||
session_mode: SessionMode,
|
||||
timeout: u8,
|
||||
timeout: u16,
|
||||
noc_state: NocState,
|
||||
}
|
||||
|
||||
|
@ -54,7 +54,7 @@ impl FailSafe {
|
|||
Self { state: State::Idle }
|
||||
}
|
||||
|
||||
pub fn arm(&mut self, timeout: u8, session_mode: SessionMode) -> Result<(), Error> {
|
||||
pub fn arm(&mut self, timeout: u16, session_mode: SessionMode) -> Result<(), Error> {
|
||||
match &mut self.state {
|
||||
State::Idle => {
|
||||
self.state = State::Armed(ArmedCtx {
|
|
@ -20,8 +20,8 @@ use core::convert::TryInto;
|
|||
|
||||
use crate::data_model::objects::*;
|
||||
use crate::data_model::sdm::failsafe::FailSafe;
|
||||
use crate::interaction_model::core::Transaction;
|
||||
use crate::tlv::{FromTLV, TLVElement, TLVWriter, TagType, ToTLV, UtfStr};
|
||||
use crate::transport::exchange::Exchange;
|
||||
use crate::utils::rand::Rand;
|
||||
use crate::{attribute_enum, cmd_enter};
|
||||
use crate::{command_enum, error::*};
|
||||
|
@ -117,13 +117,19 @@ pub const CLUSTER: Cluster<'static> = Cluster {
|
|||
|
||||
#[derive(FromTLV, ToTLV)]
|
||||
struct FailSafeParams {
|
||||
expiry_len: u8,
|
||||
bread_crumb: u8,
|
||||
expiry_len: u16,
|
||||
bread_crumb: u64,
|
||||
}
|
||||
|
||||
#[derive(ToTLV)]
|
||||
struct BasicCommissioningInfo {
|
||||
expiry_len: u16,
|
||||
max_cmltv_failsafe_secs: u16,
|
||||
}
|
||||
|
||||
pub struct GenCommCluster<'a> {
|
||||
data_ver: Dataver,
|
||||
expiry_len: u16,
|
||||
basic_comm_info: BasicCommissioningInfo,
|
||||
failsafe: &'a RefCell<FailSafe>,
|
||||
}
|
||||
|
||||
|
@ -133,7 +139,10 @@ impl<'a> GenCommCluster<'a> {
|
|||
data_ver: Dataver::new(rand),
|
||||
failsafe,
|
||||
// TODO: Arch-Specific
|
||||
basic_comm_info: BasicCommissioningInfo {
|
||||
expiry_len: 120,
|
||||
max_cmltv_failsafe_secs: 120,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -157,10 +166,8 @@ impl<'a> GenCommCluster<'a> {
|
|||
codec.encode(writer, RegLocationType::IndoorOutdoor as _)
|
||||
}
|
||||
Attributes::BasicCommissioningInfo(_) => {
|
||||
writer.start_struct(AttrDataWriter::TAG)?;
|
||||
writer.u16(TagType::Context(0), self.expiry_len)?;
|
||||
writer.end_container()?;
|
||||
|
||||
self.basic_comm_info
|
||||
.to_tlv(&mut writer, AttrDataWriter::TAG)?;
|
||||
writer.complete()
|
||||
}
|
||||
}
|
||||
|
@ -171,19 +178,19 @@ impl<'a> GenCommCluster<'a> {
|
|||
}
|
||||
|
||||
pub fn invoke(
|
||||
&mut self,
|
||||
transaction: &mut Transaction,
|
||||
&self,
|
||||
exchange: &Exchange,
|
||||
cmd: &CmdDetails,
|
||||
data: &TLVElement,
|
||||
encoder: CmdDataEncoder,
|
||||
) -> Result<(), Error> {
|
||||
match cmd.cmd_id.try_into()? {
|
||||
Commands::ArmFailsafe => self.handle_command_armfailsafe(transaction, data, encoder)?,
|
||||
Commands::ArmFailsafe => self.handle_command_armfailsafe(exchange, data, encoder)?,
|
||||
Commands::SetRegulatoryConfig => {
|
||||
self.handle_command_setregulatoryconfig(transaction, data, encoder)?
|
||||
self.handle_command_setregulatoryconfig(exchange, data, encoder)?
|
||||
}
|
||||
Commands::CommissioningComplete => {
|
||||
self.handle_command_commissioningcomplete(transaction, encoder)?;
|
||||
self.handle_command_commissioningcomplete(exchange, encoder)?;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -193,8 +200,8 @@ impl<'a> GenCommCluster<'a> {
|
|||
}
|
||||
|
||||
fn handle_command_armfailsafe(
|
||||
&mut self,
|
||||
transaction: &mut Transaction,
|
||||
&self,
|
||||
exchange: &Exchange,
|
||||
data: &TLVElement,
|
||||
encoder: CmdDataEncoder,
|
||||
) -> Result<(), Error> {
|
||||
|
@ -207,7 +214,7 @@ impl<'a> GenCommCluster<'a> {
|
|||
.borrow_mut()
|
||||
.arm(
|
||||
p.expiry_len,
|
||||
transaction.session().get_session_mode().clone(),
|
||||
exchange.with_session(|sess| Ok(sess.get_session_mode().clone()))?,
|
||||
)
|
||||
.is_err()
|
||||
{
|
||||
|
@ -225,13 +232,12 @@ impl<'a> GenCommCluster<'a> {
|
|||
.with_command(RespCommands::ArmFailsafeResp as _)?
|
||||
.set(cmd_data)?;
|
||||
|
||||
transaction.complete();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn handle_command_setregulatoryconfig(
|
||||
&mut self,
|
||||
transaction: &mut Transaction,
|
||||
&self,
|
||||
_exchange: &Exchange,
|
||||
data: &TLVElement,
|
||||
encoder: CmdDataEncoder,
|
||||
) -> Result<(), Error> {
|
||||
|
@ -252,20 +258,22 @@ impl<'a> GenCommCluster<'a> {
|
|||
.with_command(RespCommands::SetRegulatoryConfigResp as _)?
|
||||
.set(cmd_data)?;
|
||||
|
||||
transaction.complete();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn handle_command_commissioningcomplete(
|
||||
&mut self,
|
||||
transaction: &mut Transaction,
|
||||
&self,
|
||||
exchange: &Exchange,
|
||||
encoder: CmdDataEncoder,
|
||||
) -> Result<(), Error> {
|
||||
cmd_enter!("Commissioning Complete");
|
||||
let mut status: u8 = CommissioningError::Ok as u8;
|
||||
|
||||
// Has to be a Case Session
|
||||
if transaction.session().get_local_fabric_idx().is_none() {
|
||||
if exchange
|
||||
.with_session(|sess| Ok(sess.get_local_fabric_idx()))?
|
||||
.is_none()
|
||||
{
|
||||
status = CommissioningError::ErrInvalidAuth as u8;
|
||||
}
|
||||
|
||||
|
@ -274,7 +282,7 @@ impl<'a> GenCommCluster<'a> {
|
|||
if self
|
||||
.failsafe
|
||||
.borrow_mut()
|
||||
.disarm(transaction.session().get_session_mode().clone())
|
||||
.disarm(exchange.with_session(|sess| Ok(sess.get_session_mode().clone()))?)
|
||||
.is_err()
|
||||
{
|
||||
status = CommissioningError::ErrInvalidAuth as u8;
|
||||
|
@ -289,7 +297,6 @@ impl<'a> GenCommCluster<'a> {
|
|||
.with_command(RespCommands::CommissioningCompleteResp as _)?
|
||||
.set(cmd_data)?;
|
||||
|
||||
transaction.complete();
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
@ -300,13 +307,13 @@ impl<'a> Handler for GenCommCluster<'a> {
|
|||
}
|
||||
|
||||
fn invoke(
|
||||
&mut self,
|
||||
transaction: &mut Transaction,
|
||||
&self,
|
||||
exchange: &Exchange,
|
||||
cmd: &CmdDetails,
|
||||
data: &TLVElement,
|
||||
encoder: CmdDataEncoder,
|
||||
) -> Result<(), Error> {
|
||||
GenCommCluster::invoke(self, transaction, cmd, data, encoder)
|
||||
GenCommCluster::invoke(self, exchange, cmd, data, encoder)
|
||||
}
|
||||
}
|
||||
|
157
rs-matter/src/data_model/sdm/general_diagnostics.rs
Normal file
157
rs-matter/src/data_model/sdm/general_diagnostics.rs
Normal file
|
@ -0,0 +1,157 @@
|
|||
/*
|
||||
*
|
||||
* Copyright (c) 2023 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::convert::TryInto;
|
||||
|
||||
use crate::{
|
||||
attribute_enum, cmd_enter, command_enum,
|
||||
data_model::objects::AttrType,
|
||||
data_model::objects::*,
|
||||
error::{Error, ErrorCode},
|
||||
tlv::TLVElement,
|
||||
transport::exchange::Exchange,
|
||||
utils::rand::Rand,
|
||||
};
|
||||
use log::info;
|
||||
use strum::{EnumDiscriminants, FromRepr};
|
||||
|
||||
pub const ID: u32 = 0x0033;
|
||||
|
||||
#[derive(FromRepr, EnumDiscriminants)]
|
||||
#[repr(u16)]
|
||||
pub enum Attributes {
|
||||
NetworkInterfaces(()) = 0x00,
|
||||
RebootCount(AttrType<u16>) = 0x01,
|
||||
TestEventTriggersEnabled(AttrType<bool>) = 0x08,
|
||||
}
|
||||
|
||||
attribute_enum!(Attributes);
|
||||
|
||||
#[derive(FromRepr, EnumDiscriminants)]
|
||||
#[repr(u32)]
|
||||
pub enum Commands {
|
||||
TestEventTrigger = 0x0,
|
||||
}
|
||||
|
||||
command_enum!(Commands);
|
||||
|
||||
pub const CLUSTER: Cluster<'static> = Cluster {
|
||||
id: ID as _,
|
||||
feature_map: 0,
|
||||
attributes: &[
|
||||
FEATURE_MAP,
|
||||
ATTRIBUTE_LIST,
|
||||
Attribute::new(
|
||||
AttributesDiscriminants::NetworkInterfaces as u16,
|
||||
Access::RV,
|
||||
Quality::NONE,
|
||||
),
|
||||
Attribute::new(
|
||||
AttributesDiscriminants::RebootCount as u16,
|
||||
Access::RV,
|
||||
Quality::PERSISTENT,
|
||||
),
|
||||
Attribute::new(
|
||||
AttributesDiscriminants::TestEventTriggersEnabled as u16,
|
||||
Access::RV,
|
||||
Quality::NONE,
|
||||
),
|
||||
],
|
||||
commands: &[CommandsDiscriminants::TestEventTrigger as _],
|
||||
};
|
||||
|
||||
pub struct GenDiagCluster {
|
||||
data_ver: Dataver,
|
||||
}
|
||||
|
||||
impl GenDiagCluster {
|
||||
pub fn new(rand: Rand) -> Self {
|
||||
Self {
|
||||
data_ver: Dataver::new(rand),
|
||||
}
|
||||
}
|
||||
|
||||
pub 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 {
|
||||
match attr.attr_id.try_into()? {
|
||||
Attributes::RebootCount(codec) => codec.encode(writer, 1),
|
||||
_ => Err(ErrorCode::AttributeNotFound.into()),
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub fn write(&self, _attr: &AttrDetails, data: AttrData) -> Result<(), Error> {
|
||||
let _data = data.with_dataver(self.data_ver.get())?;
|
||||
|
||||
self.data_ver.changed();
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn invoke(
|
||||
&self,
|
||||
_exchange: &Exchange,
|
||||
cmd: &CmdDetails,
|
||||
_data: &TLVElement,
|
||||
_encoder: CmdDataEncoder,
|
||||
) -> Result<(), Error> {
|
||||
match cmd.cmd_id.try_into()? {
|
||||
Commands::TestEventTrigger => {
|
||||
cmd_enter!("TestEventTrigger: Not yet supported");
|
||||
}
|
||||
}
|
||||
|
||||
self.data_ver.changed();
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl Handler for GenDiagCluster {
|
||||
fn read(&self, attr: &AttrDetails, encoder: AttrDataEncoder) -> Result<(), Error> {
|
||||
GenDiagCluster::read(self, attr, encoder)
|
||||
}
|
||||
|
||||
fn write(&self, attr: &AttrDetails, data: AttrData) -> Result<(), Error> {
|
||||
GenDiagCluster::write(self, attr, data)
|
||||
}
|
||||
|
||||
fn invoke(
|
||||
&self,
|
||||
exchange: &Exchange,
|
||||
cmd: &CmdDetails,
|
||||
data: &TLVElement,
|
||||
encoder: CmdDataEncoder,
|
||||
) -> Result<(), Error> {
|
||||
GenDiagCluster::invoke(self, exchange, cmd, data, encoder)
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Might be removed once the `on` member is externalized
|
||||
impl NonBlockingHandler for GenDiagCluster {}
|
||||
|
||||
impl ChangeNotifier<()> for GenDiagCluster {
|
||||
fn consume_change(&mut self) -> Option<()> {
|
||||
self.data_ver.consume_change(())
|
||||
}
|
||||
}
|
164
rs-matter/src/data_model/sdm/group_key_management.rs
Normal file
164
rs-matter/src/data_model/sdm/group_key_management.rs
Normal file
|
@ -0,0 +1,164 @@
|
|||
/*
|
||||
*
|
||||
* Copyright (c) 2023 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::convert::TryInto;
|
||||
|
||||
use crate::{
|
||||
attribute_enum, cmd_enter, command_enum,
|
||||
data_model::objects::AttrType,
|
||||
data_model::objects::*,
|
||||
error::{Error, ErrorCode},
|
||||
tlv::TLVElement,
|
||||
transport::exchange::Exchange,
|
||||
utils::rand::Rand,
|
||||
};
|
||||
use log::info;
|
||||
use strum::{EnumDiscriminants, FromRepr};
|
||||
|
||||
pub const ID: u32 = 0x003F;
|
||||
|
||||
#[derive(FromRepr, EnumDiscriminants)]
|
||||
#[repr(u16)]
|
||||
pub enum Attributes {
|
||||
GroupKeyMap(()) = 0x00,
|
||||
GroupTable(()) = 0x01,
|
||||
MaxGroupsPerFabric(AttrType<u16>) = 0x02,
|
||||
MaxGroupKeysPerFabric(AttrType<u16>) = 0x03,
|
||||
}
|
||||
|
||||
attribute_enum!(Attributes);
|
||||
|
||||
#[derive(FromRepr, EnumDiscriminants)]
|
||||
#[repr(u32)]
|
||||
pub enum Commands {
|
||||
KeySetWrite = 0x0,
|
||||
}
|
||||
|
||||
command_enum!(Commands);
|
||||
|
||||
pub const CLUSTER: Cluster<'static> = Cluster {
|
||||
id: ID as _,
|
||||
feature_map: 0,
|
||||
attributes: &[
|
||||
FEATURE_MAP,
|
||||
ATTRIBUTE_LIST,
|
||||
Attribute::new(
|
||||
AttributesDiscriminants::GroupKeyMap as u16,
|
||||
Access::RWFVM,
|
||||
Quality::PERSISTENT,
|
||||
),
|
||||
Attribute::new(
|
||||
AttributesDiscriminants::GroupTable as u16,
|
||||
Access::RF,
|
||||
Quality::NONE,
|
||||
),
|
||||
Attribute::new(
|
||||
AttributesDiscriminants::MaxGroupsPerFabric as u16,
|
||||
Access::READ,
|
||||
Quality::FIXED,
|
||||
),
|
||||
Attribute::new(
|
||||
AttributesDiscriminants::MaxGroupKeysPerFabric as u16,
|
||||
Access::READ,
|
||||
Quality::FIXED,
|
||||
),
|
||||
],
|
||||
commands: &[CommandsDiscriminants::KeySetWrite as _],
|
||||
};
|
||||
|
||||
pub struct GrpKeyMgmtCluster {
|
||||
data_ver: Dataver,
|
||||
}
|
||||
|
||||
impl GrpKeyMgmtCluster {
|
||||
pub fn new(rand: Rand) -> Self {
|
||||
Self {
|
||||
data_ver: Dataver::new(rand),
|
||||
}
|
||||
}
|
||||
|
||||
pub 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 {
|
||||
match attr.attr_id.try_into()? {
|
||||
Attributes::MaxGroupsPerFabric(codec) => codec.encode(writer, 1),
|
||||
Attributes::MaxGroupKeysPerFabric(codec) => codec.encode(writer, 1),
|
||||
_ => Err(ErrorCode::AttributeNotFound.into()),
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub fn write(&self, _attr: &AttrDetails, data: AttrData) -> Result<(), Error> {
|
||||
let _data = data.with_dataver(self.data_ver.get())?;
|
||||
|
||||
self.data_ver.changed();
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn invoke(
|
||||
&self,
|
||||
_exchange: &Exchange,
|
||||
cmd: &CmdDetails,
|
||||
_data: &TLVElement,
|
||||
_encoder: CmdDataEncoder,
|
||||
) -> Result<(), Error> {
|
||||
match cmd.cmd_id.try_into()? {
|
||||
Commands::KeySetWrite => {
|
||||
cmd_enter!("KeySetWrite: Not yet supported");
|
||||
}
|
||||
}
|
||||
|
||||
self.data_ver.changed();
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl Handler for GrpKeyMgmtCluster {
|
||||
fn read(&self, attr: &AttrDetails, encoder: AttrDataEncoder) -> Result<(), Error> {
|
||||
GrpKeyMgmtCluster::read(self, attr, encoder)
|
||||
}
|
||||
|
||||
fn write(&self, attr: &AttrDetails, data: AttrData) -> Result<(), Error> {
|
||||
GrpKeyMgmtCluster::write(self, attr, data)
|
||||
}
|
||||
|
||||
fn invoke(
|
||||
&self,
|
||||
exchange: &Exchange,
|
||||
cmd: &CmdDetails,
|
||||
data: &TLVElement,
|
||||
encoder: CmdDataEncoder,
|
||||
) -> Result<(), Error> {
|
||||
GrpKeyMgmtCluster::invoke(self, exchange, cmd, data, encoder)
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Might be removed once the `on` member is externalized
|
||||
impl NonBlockingHandler for GrpKeyMgmtCluster {}
|
||||
|
||||
impl ChangeNotifier<()> for GrpKeyMgmtCluster {
|
||||
fn consume_change(&mut self) -> Option<()> {
|
||||
self.data_ver.consume_change(())
|
||||
}
|
||||
}
|
|
@ -17,7 +17,10 @@
|
|||
|
||||
pub mod admin_commissioning;
|
||||
pub mod dev_att;
|
||||
pub mod ethernet_nw_diagnostics;
|
||||
pub mod failsafe;
|
||||
pub mod general_commissioning;
|
||||
pub mod general_diagnostics;
|
||||
pub mod group_key_management;
|
||||
pub mod noc;
|
||||
pub mod nw_commissioning;
|
|
@ -24,9 +24,9 @@ use crate::crypto::{self, KeyPair};
|
|||
use crate::data_model::objects::*;
|
||||
use crate::data_model::sdm::dev_att;
|
||||
use crate::fabric::{Fabric, FabricMgr, MAX_SUPPORTED_FABRICS};
|
||||
use crate::interaction_model::core::Transaction;
|
||||
use crate::mdns::Mdns;
|
||||
use crate::tlv::{FromTLV, OctetStr, TLVElement, TLVWriter, TagType, ToTLV, UtfStr};
|
||||
use crate::transport::exchange::Exchange;
|
||||
use crate::transport::session::SessionMode;
|
||||
use crate::utils::epoch::Epoch;
|
||||
use crate::utils::rand::Rand;
|
||||
|
@ -186,7 +186,7 @@ struct NocResp<'a> {
|
|||
#[tlvargs(lifetime = "'a")]
|
||||
struct AddNocReq<'a> {
|
||||
noc_value: OctetStr<'a>,
|
||||
icac_value: OctetStr<'a>,
|
||||
icac_value: Option<OctetStr<'a>>,
|
||||
ipk_value: OctetStr<'a>,
|
||||
case_admin_subject: u64,
|
||||
vendor_id: u16,
|
||||
|
@ -289,26 +289,26 @@ impl<'a> NocCluster<'a> {
|
|||
}
|
||||
|
||||
pub fn invoke(
|
||||
&mut self,
|
||||
transaction: &mut Transaction,
|
||||
&self,
|
||||
exchange: &Exchange,
|
||||
cmd: &CmdDetails,
|
||||
data: &TLVElement,
|
||||
encoder: CmdDataEncoder,
|
||||
) -> Result<(), Error> {
|
||||
match cmd.cmd_id.try_into()? {
|
||||
Commands::AddNOC => self.handle_command_addnoc(transaction, data, encoder)?,
|
||||
Commands::CSRReq => self.handle_command_csrrequest(transaction, data, encoder)?,
|
||||
Commands::AddNOC => self.handle_command_addnoc(exchange, data, encoder)?,
|
||||
Commands::CSRReq => self.handle_command_csrrequest(exchange, data, encoder)?,
|
||||
Commands::AddTrustedRootCert => {
|
||||
self.handle_command_addtrustedrootcert(transaction, data)?
|
||||
self.handle_command_addtrustedrootcert(exchange, data)?
|
||||
}
|
||||
Commands::AttReq => self.handle_command_attrequest(transaction, data, encoder)?,
|
||||
Commands::AttReq => self.handle_command_attrequest(exchange, data, encoder)?,
|
||||
Commands::CertChainReq => {
|
||||
self.handle_command_certchainrequest(transaction, data, encoder)?
|
||||
self.handle_command_certchainrequest(exchange, data, encoder)?
|
||||
}
|
||||
Commands::UpdateFabricLabel => {
|
||||
self.handle_command_updatefablabel(transaction, data, encoder)?;
|
||||
self.handle_command_updatefablabel(exchange, data, encoder)?;
|
||||
}
|
||||
Commands::RemoveFabric => self.handle_command_rmfabric(transaction, data, encoder)?,
|
||||
Commands::RemoveFabric => self.handle_command_rmfabric(exchange, data, encoder)?,
|
||||
}
|
||||
|
||||
self.data_ver.changed();
|
||||
|
@ -323,13 +323,12 @@ impl<'a> NocCluster<'a> {
|
|||
}
|
||||
|
||||
fn _handle_command_addnoc(
|
||||
&mut self,
|
||||
transaction: &mut Transaction,
|
||||
&self,
|
||||
exchange: &Exchange,
|
||||
data: &TLVElement,
|
||||
) -> Result<u8, NocError> {
|
||||
let noc_data = transaction
|
||||
.session_mut()
|
||||
.take_noc_data()
|
||||
let noc_data = exchange
|
||||
.with_session_mut(|sess| Ok(sess.take_noc_data()))?
|
||||
.ok_or(NocStatus::MissingCsr)?;
|
||||
|
||||
if !self
|
||||
|
@ -359,15 +358,19 @@ impl<'a> NocCluster<'a> {
|
|||
|
||||
let noc = heapless::Vec::from_slice(r.noc_value.0).map_err(|_| NocStatus::InvalidNOC)?;
|
||||
|
||||
let icac = if !r.icac_value.0.is_empty() {
|
||||
let icac_cert = Cert::new(r.icac_value.0).map_err(|_| NocStatus::InvalidNOC)?;
|
||||
let icac = if let Some(icac_value) = r.icac_value {
|
||||
if !icac_value.0.is_empty() {
|
||||
let icac_cert = Cert::new(icac_value.0).map_err(|_| NocStatus::InvalidNOC)?;
|
||||
info!("Received ICAC as: {}", icac_cert);
|
||||
|
||||
let icac =
|
||||
heapless::Vec::from_slice(r.icac_value.0).map_err(|_| NocStatus::InvalidNOC)?;
|
||||
heapless::Vec::from_slice(icac_value.0).map_err(|_| NocStatus::InvalidNOC)?;
|
||||
Some(icac)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let fabric = Fabric::new(
|
||||
|
@ -411,15 +414,16 @@ impl<'a> NocCluster<'a> {
|
|||
}
|
||||
|
||||
fn handle_command_updatefablabel(
|
||||
&mut self,
|
||||
transaction: &mut Transaction,
|
||||
&self,
|
||||
exchange: &Exchange,
|
||||
data: &TLVElement,
|
||||
encoder: CmdDataEncoder,
|
||||
) -> Result<(), Error> {
|
||||
cmd_enter!("Update Fabric Label");
|
||||
let req = UpdateFabricLabelReq::from_tlv(data).map_err(Error::map_invalid_data_type)?;
|
||||
let (result, fab_idx) =
|
||||
if let SessionMode::Case(c) = transaction.session().get_session_mode() {
|
||||
let (result, fab_idx) = if let SessionMode::Case(c) =
|
||||
exchange.with_session(|sess| Ok(sess.get_session_mode().clone()))?
|
||||
{
|
||||
if self
|
||||
.fabric_mgr
|
||||
.borrow_mut()
|
||||
|
@ -440,13 +444,12 @@ impl<'a> NocCluster<'a> {
|
|||
|
||||
Self::create_nocresponse(encoder, result, fab_idx, "")?;
|
||||
|
||||
transaction.complete();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn handle_command_rmfabric(
|
||||
&mut self,
|
||||
transaction: &mut Transaction,
|
||||
&self,
|
||||
_exchange: &Exchange,
|
||||
data: &TLVElement,
|
||||
encoder: CmdDataEncoder,
|
||||
) -> Result<(), Error> {
|
||||
|
@ -459,7 +462,7 @@ impl<'a> NocCluster<'a> {
|
|||
.is_ok()
|
||||
{
|
||||
let _ = self.acl_mgr.borrow_mut().delete_for_fabric(req.fab_idx);
|
||||
transaction.terminate();
|
||||
// TODO: transaction.terminate();
|
||||
Ok(())
|
||||
} else {
|
||||
Self::create_nocresponse(encoder, NocStatus::InvalidFabricIndex, req.fab_idx, "")
|
||||
|
@ -467,28 +470,27 @@ impl<'a> NocCluster<'a> {
|
|||
}
|
||||
|
||||
fn handle_command_addnoc(
|
||||
&mut self,
|
||||
transaction: &mut Transaction,
|
||||
&self,
|
||||
exchange: &Exchange,
|
||||
data: &TLVElement,
|
||||
encoder: CmdDataEncoder,
|
||||
) -> Result<(), Error> {
|
||||
cmd_enter!("AddNOC");
|
||||
|
||||
let (status, fab_idx) = match self._handle_command_addnoc(transaction, data) {
|
||||
let (status, fab_idx) = match self._handle_command_addnoc(exchange, data) {
|
||||
Ok(fab_idx) => (NocStatus::Ok, fab_idx),
|
||||
Err(NocError::Status(status)) => (status, 0),
|
||||
Err(NocError::Error(error)) => Err(error)?,
|
||||
};
|
||||
|
||||
Self::create_nocresponse(encoder, status, fab_idx, "")?;
|
||||
transaction.complete();
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn handle_command_attrequest(
|
||||
&mut self,
|
||||
transaction: &mut Transaction,
|
||||
&self,
|
||||
exchange: &Exchange,
|
||||
data: &TLVElement,
|
||||
encoder: CmdDataEncoder,
|
||||
) -> Result<(), Error> {
|
||||
|
@ -498,7 +500,10 @@ impl<'a> NocCluster<'a> {
|
|||
info!("Received Attestation Nonce:{:?}", req.str);
|
||||
|
||||
let mut attest_challenge = [0u8; crypto::SYMM_KEY_LEN_BYTES];
|
||||
attest_challenge.copy_from_slice(transaction.session().get_att_challenge());
|
||||
exchange.with_session(|sess| {
|
||||
attest_challenge.copy_from_slice(sess.get_att_challenge());
|
||||
Ok(())
|
||||
})?;
|
||||
|
||||
let mut writer = encoder.with_command(RespCommands::AttReqResp as _)?;
|
||||
|
||||
|
@ -522,13 +527,12 @@ impl<'a> NocCluster<'a> {
|
|||
|
||||
writer.complete()?;
|
||||
|
||||
transaction.complete();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn handle_command_certchainrequest(
|
||||
&mut self,
|
||||
transaction: &mut Transaction,
|
||||
&self,
|
||||
_exchange: &Exchange,
|
||||
data: &TLVElement,
|
||||
encoder: CmdDataEncoder,
|
||||
) -> Result<(), Error> {
|
||||
|
@ -549,13 +553,12 @@ impl<'a> NocCluster<'a> {
|
|||
.with_command(RespCommands::CertChainResp as _)?
|
||||
.set(cmd_data)?;
|
||||
|
||||
transaction.complete();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn handle_command_csrrequest(
|
||||
&mut self,
|
||||
transaction: &mut Transaction,
|
||||
&self,
|
||||
exchange: &Exchange,
|
||||
data: &TLVElement,
|
||||
encoder: CmdDataEncoder,
|
||||
) -> Result<(), Error> {
|
||||
|
@ -570,7 +573,10 @@ impl<'a> NocCluster<'a> {
|
|||
|
||||
let noc_keypair = KeyPair::new(self.rand)?;
|
||||
let mut attest_challenge = [0u8; crypto::SYMM_KEY_LEN_BYTES];
|
||||
attest_challenge.copy_from_slice(transaction.session().get_att_challenge());
|
||||
exchange.with_session(|sess| {
|
||||
attest_challenge.copy_from_slice(sess.get_att_challenge());
|
||||
Ok(())
|
||||
})?;
|
||||
|
||||
let mut writer = encoder.with_command(RespCommands::CSRResp as _)?;
|
||||
|
||||
|
@ -591,15 +597,31 @@ impl<'a> NocCluster<'a> {
|
|||
let noc_data = NocData::new(noc_keypair);
|
||||
// Store this in the session data instead of cluster data, so it gets cleared
|
||||
// if the session goes away for some reason
|
||||
transaction.session_mut().set_noc_data(noc_data);
|
||||
exchange.with_session_mut(|sess| {
|
||||
sess.set_noc_data(noc_data);
|
||||
Ok(())
|
||||
})?;
|
||||
|
||||
transaction.complete();
|
||||
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(
|
||||
&mut self,
|
||||
transaction: &mut Transaction,
|
||||
&self,
|
||||
exchange: &Exchange,
|
||||
data: &TLVElement,
|
||||
) -> Result<(), Error> {
|
||||
cmd_enter!("AddTrustedRootCert");
|
||||
|
@ -608,25 +630,17 @@ impl<'a> NocCluster<'a> {
|
|||
}
|
||||
|
||||
// This may happen on CASE or PASE. For PASE, the existence of NOC Data is necessary
|
||||
match transaction.session().get_session_mode() {
|
||||
SessionMode::Case(_) => error!("CASE: AddTrustedRootCert handling pending"), // For a CASE Session, we just return success for now,
|
||||
match exchange.with_session(|sess| Ok(sess.get_session_mode().clone()))? {
|
||||
SessionMode::Case(_) => {
|
||||
// TODO - Updating the Trusted RCA of an existing Fabric
|
||||
Self::add_rca_to_session_noc_data(exchange, data)?;
|
||||
}
|
||||
SessionMode::Pase => {
|
||||
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
|
||||
Self::add_rca_to_session_noc_data(exchange, data)?;
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
|
||||
transaction.complete();
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
@ -637,13 +651,13 @@ impl<'a> Handler for NocCluster<'a> {
|
|||
}
|
||||
|
||||
fn invoke(
|
||||
&mut self,
|
||||
transaction: &mut Transaction,
|
||||
&self,
|
||||
exchange: &Exchange,
|
||||
cmd: &CmdDetails,
|
||||
data: &TLVElement,
|
||||
encoder: CmdDataEncoder,
|
||||
) -> Result<(), Error> {
|
||||
NocCluster::invoke(self, transaction, cmd, data, encoder)
|
||||
NocCluster::invoke(self, exchange, cmd, data, encoder)
|
||||
}
|
||||
}
|
||||
|
193
rs-matter/src/data_model/sdm/nw_commissioning.rs
Normal file
193
rs-matter/src/data_model/sdm/nw_commissioning.rs
Normal file
|
@ -0,0 +1,193 @@
|
|||
/*
|
||||
*
|
||||
* 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 strum::FromRepr;
|
||||
|
||||
use crate::{
|
||||
attribute_enum,
|
||||
data_model::objects::{
|
||||
Access, AttrDataEncoder, AttrDataWriter, AttrDetails, AttrType, Attribute, ChangeNotifier,
|
||||
Cluster, Dataver, Handler, NonBlockingHandler, Quality, ATTRIBUTE_LIST, FEATURE_MAP,
|
||||
},
|
||||
error::Error,
|
||||
tlv::{OctetStr, TLVWriter, TagType, ToTLV},
|
||||
utils::rand::Rand,
|
||||
};
|
||||
|
||||
pub const ID: u32 = 0x0031;
|
||||
|
||||
#[derive(FromRepr)]
|
||||
#[repr(u16)]
|
||||
pub enum Attributes {
|
||||
MaxNetworks = 0x00,
|
||||
Networks = 0x01,
|
||||
ConnectMaxTimeSecs = 0x03,
|
||||
InterfaceEnabled = 0x04,
|
||||
LastNetworkingStatus = 0x05,
|
||||
LastNetworkID = 0x06,
|
||||
LastConnectErrorValue = 0x07,
|
||||
}
|
||||
|
||||
attribute_enum!(Attributes);
|
||||
|
||||
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,
|
||||
Attribute::new(Attributes::MaxNetworks as u16, Access::RA, Quality::F),
|
||||
Attribute::new(Attributes::Networks as u16, Access::RA, Quality::NONE),
|
||||
Attribute::new(
|
||||
Attributes::ConnectMaxTimeSecs as u16,
|
||||
Access::RV,
|
||||
Quality::F,
|
||||
),
|
||||
Attribute::new(
|
||||
Attributes::InterfaceEnabled as u16,
|
||||
Access::RWVA,
|
||||
Quality::N,
|
||||
),
|
||||
Attribute::new(
|
||||
Attributes::LastNetworkingStatus as u16,
|
||||
Access::RA,
|
||||
Quality::X,
|
||||
),
|
||||
Attribute::new(Attributes::LastNetworkID as u16, Access::RA, Quality::X),
|
||||
Attribute::new(
|
||||
Attributes::LastConnectErrorValue as u16,
|
||||
Access::RA,
|
||||
Quality::X,
|
||||
),
|
||||
],
|
||||
commands: &[],
|
||||
};
|
||||
|
||||
pub struct NwCommCluster {
|
||||
data_ver: Dataver,
|
||||
}
|
||||
|
||||
impl NwCommCluster {
|
||||
pub fn new(rand: Rand) -> Self {
|
||||
Self {
|
||||
data_ver: Dataver::new(rand),
|
||||
}
|
||||
}
|
||||
|
||||
fn get_network_info(&self) -> NwMetaInfo<'static> {
|
||||
// Only single, Ethernet, supported for now
|
||||
let nw_info = NwInfo {
|
||||
network_id: OctetStr::new(b"en0"),
|
||||
connected: true,
|
||||
};
|
||||
NwMetaInfo {
|
||||
nw_info,
|
||||
connect_max_time_secs: 60,
|
||||
interface_enabled: true,
|
||||
last_nw_status: NetworkCommissioningStatus::Success,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(ToTLV)]
|
||||
struct NwInfo<'a> {
|
||||
network_id: OctetStr<'a>,
|
||||
connected: bool,
|
||||
}
|
||||
|
||||
struct NwMetaInfo<'a> {
|
||||
nw_info: NwInfo<'a>,
|
||||
connect_max_time_secs: u8,
|
||||
interface_enabled: bool,
|
||||
last_nw_status: NetworkCommissioningStatus,
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
enum NetworkCommissioningStatus {
|
||||
Success = 0,
|
||||
OutOfRange = 1,
|
||||
BoundsExceeded = 2,
|
||||
NetworkIDNotFound = 3,
|
||||
DuplicateNetworkID = 4,
|
||||
NetworkNotFound = 5,
|
||||
RegulatoryError = 6,
|
||||
AuthFailure = 7,
|
||||
UnsupportedSecurity = 8,
|
||||
OtherConnectionFailure = 9,
|
||||
IPV6Failed = 10,
|
||||
IPBindFailed = 11,
|
||||
UnknownError = 12,
|
||||
}
|
||||
|
||||
impl Handler for NwCommCluster {
|
||||
fn read(&self, attr: &AttrDetails, encoder: AttrDataEncoder) -> Result<(), Error> {
|
||||
let info = self.get_network_info();
|
||||
if let Some(mut writer) = encoder.with_dataver(self.data_ver.get())? {
|
||||
if attr.is_system() {
|
||||
CLUSTER.read(attr.attr_id, writer)
|
||||
} else {
|
||||
match attr.attr_id.try_into()? {
|
||||
Attributes::MaxNetworks => AttrType::<u8>::new().encode(writer, 1),
|
||||
Attributes::Networks => {
|
||||
writer.start_array(AttrDataWriter::TAG)?;
|
||||
info.nw_info.to_tlv(&mut writer, TagType::Anonymous)?;
|
||||
writer.end_container()?;
|
||||
writer.complete()
|
||||
}
|
||||
Attributes::ConnectMaxTimeSecs => {
|
||||
AttrType::<u8>::new().encode(writer, info.connect_max_time_secs)
|
||||
}
|
||||
|
||||
Attributes::InterfaceEnabled => {
|
||||
AttrType::<bool>::new().encode(writer, info.interface_enabled)
|
||||
}
|
||||
|
||||
Attributes::LastNetworkingStatus => {
|
||||
AttrType::<u8>::new().encode(writer, info.last_nw_status as u8)
|
||||
}
|
||||
|
||||
Attributes::LastNetworkID => {
|
||||
info.nw_info
|
||||
.network_id
|
||||
.to_tlv(&mut writer, AttrDataWriter::TAG)?;
|
||||
writer.complete()
|
||||
}
|
||||
Attributes::LastConnectErrorValue => {
|
||||
writer.null(AttrDataWriter::TAG)?;
|
||||
writer.complete()
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl NonBlockingHandler for NwCommCluster {}
|
||||
|
||||
impl ChangeNotifier<()> for NwCommCluster {
|
||||
fn consume_change(&mut self) -> Option<()> {
|
||||
self.data_ver.consume_change(())
|
||||
}
|
||||
}
|
|
@ -132,7 +132,7 @@ impl<'a> AccessControlCluster<'a> {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn write(&mut self, attr: &AttrDetails, data: AttrData) -> Result<(), Error> {
|
||||
pub fn write(&self, attr: &AttrDetails, data: AttrData) -> Result<(), Error> {
|
||||
match attr.attr_id.try_into()? {
|
||||
Attributes::Acl(_) => {
|
||||
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.
|
||||
/// Care about fabric-scoped behaviour is taken
|
||||
fn write_acl_attr(
|
||||
&mut self,
|
||||
&self,
|
||||
op: &ListOperation,
|
||||
data: &TLVElement,
|
||||
fab_idx: u8,
|
||||
|
@ -185,7 +185,7 @@ impl<'a> Handler for AccessControlCluster<'a> {
|
|||
AccessControlCluster::read(self, attr, encoder)
|
||||
}
|
||||
|
||||
fn write(&mut self, attr: &AttrDetails, data: AttrData) -> Result<(), Error> {
|
||||
fn write(&self, attr: &AttrDetails, data: AttrData) -> Result<(), Error> {
|
||||
AccessControlCluster::write(self, attr, data)
|
||||
}
|
||||
}
|
||||
|
@ -220,7 +220,7 @@ mod tests {
|
|||
let mut tw = TLVWriter::new(&mut writebuf);
|
||||
|
||||
let acl_mgr = RefCell::new(AclMgr::new());
|
||||
let mut acl = AccessControlCluster::new(&acl_mgr, dummy_rand);
|
||||
let acl = AccessControlCluster::new(&acl_mgr, dummy_rand);
|
||||
|
||||
let new = AclEntry::new(2, Privilege::VIEW, AuthMode::Case);
|
||||
new.to_tlv(&mut tw, TagType::Anonymous).unwrap();
|
||||
|
@ -258,7 +258,7 @@ mod tests {
|
|||
for i in &verifier {
|
||||
acl_mgr.borrow_mut().add(i.clone()).unwrap();
|
||||
}
|
||||
let mut acl = AccessControlCluster::new(&acl_mgr, dummy_rand);
|
||||
let acl = AccessControlCluster::new(&acl_mgr, dummy_rand);
|
||||
|
||||
let new = AclEntry::new(2, Privilege::VIEW, AuthMode::Case);
|
||||
new.to_tlv(&mut tw, TagType::Anonymous).unwrap();
|
||||
|
@ -295,7 +295,7 @@ mod tests {
|
|||
for i in &input {
|
||||
acl_mgr.borrow_mut().add(i.clone()).unwrap();
|
||||
}
|
||||
let mut acl = AccessControlCluster::new(&acl_mgr, dummy_rand);
|
||||
let acl = AccessControlCluster::new(&acl_mgr, dummy_rand);
|
||||
// data is don't-care actually
|
||||
let data = TLVElement::new(TagType::Anonymous, ElementType::True);
|
||||
|
|
@ -47,6 +47,8 @@ pub enum ErrorCode {
|
|||
NoMemory,
|
||||
NoSession,
|
||||
NoSpace,
|
||||
NoSpaceExchanges,
|
||||
NoSpaceSessions,
|
||||
NoSpaceAckTable,
|
||||
NoSpaceRetransTable,
|
||||
NoTagFound,
|
||||
|
@ -83,6 +85,8 @@ pub struct Error {
|
|||
code: ErrorCode,
|
||||
#[cfg(all(feature = "std", feature = "backtrace"))]
|
||||
backtrace: std::backtrace::Backtrace,
|
||||
#[cfg(all(feature = "std", feature = "backtrace"))]
|
||||
inner: Option<Box<dyn std::error::Error + Send>>,
|
||||
}
|
||||
|
||||
impl Error {
|
||||
|
@ -91,6 +95,22 @@ impl Error {
|
|||
code,
|
||||
#[cfg(all(feature = "std", feature = "backtrace"))]
|
||||
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),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -103,6 +123,11 @@ impl Error {
|
|||
&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
|
||||
where
|
||||
F: FnOnce(&Self) -> bool,
|
||||
|
@ -134,10 +159,16 @@ impl Error {
|
|||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "std")]
|
||||
#[cfg(all(feature = "std", feature = "backtrace"))]
|
||||
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 {
|
||||
fn from(_e: std::io::Error) -> Self {
|
||||
// Keep things simple for now
|
||||
Self::new(ErrorCode::StdIoError)
|
||||
}
|
||||
}
|
||||
|
@ -149,7 +180,7 @@ impl<T> From<std::sync::PoisonError<T>> for Error {
|
|||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "crypto_openssl")]
|
||||
#[cfg(feature = "openssl")]
|
||||
impl From<openssl::error::ErrorStack> for Error {
|
||||
fn from(e: openssl::error::ErrorStack) -> Self {
|
||||
::log::error!("Error in TLS: {}", e);
|
||||
|
@ -157,7 +188,7 @@ impl From<openssl::error::ErrorStack> for Error {
|
|||
}
|
||||
}
|
||||
|
||||
#[cfg(all(feature = "crypto_mbedtls", not(target_os = "espidf")))]
|
||||
#[cfg(all(feature = "mbedtls", not(target_os = "espidf")))]
|
||||
impl From<mbedtls::Error> for Error {
|
||||
fn from(e: mbedtls::Error) -> Self {
|
||||
::log::error!("Error in TLS: {}", e);
|
||||
|
@ -173,7 +204,7 @@ impl From<esp_idf_sys::EspError> for Error {
|
|||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "crypto_rustcrypto")]
|
||||
#[cfg(feature = "rustcrypto")]
|
||||
impl From<ccm::aead::Error> for Error {
|
||||
fn from(_e: ccm::aead::Error) -> Self {
|
||||
Self::new(ErrorCode::Crypto)
|
||||
|
@ -219,8 +250,22 @@ impl fmt::Debug for Error {
|
|||
|
||||
impl fmt::Display for Error {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
#[cfg(all(feature = "std", feature = "backtrace"))]
|
||||
{
|
||||
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())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "std")]
|
|
@ -33,7 +33,6 @@ use crate::{
|
|||
|
||||
const COMPRESSED_FABRIC_ID_LEN: usize = 8;
|
||||
|
||||
#[allow(dead_code)]
|
||||
#[derive(Debug, ToTLV)]
|
||||
#[tlvargs(lifetime = "'a", start = 1)]
|
||||
pub struct FabricDescriptor<'a> {
|
733
rs-matter/src/interaction_model/core.rs
Normal file
733
rs-matter/src/interaction_model/core.rs
Normal file
|
@ -0,0 +1,733 @@
|
|||
/*
|
||||
*
|
||||
* 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::time::Duration;
|
||||
|
||||
use crate::{
|
||||
acl::Accessor,
|
||||
error::*,
|
||||
tlv::{get_root_node_struct, FromTLV, TLVElement, TLVWriter, TagType, ToTLV},
|
||||
transport::{exchange::Exchange, packet::Packet},
|
||||
utils::epoch::Epoch,
|
||||
};
|
||||
use log::error;
|
||||
use num::{self, FromPrimitive};
|
||||
use num_derive::FromPrimitive;
|
||||
|
||||
use super::messages::msg::{
|
||||
self, InvReq, ReadReq, StatusResp, SubscribeReq, SubscribeResp, TimedReq, WriteReq,
|
||||
};
|
||||
|
||||
#[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> {
|
||||
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, Eq, 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,
|
||||
}
|
||||
|
||||
/* Interaction Model ID as per the Matter Spec */
|
||||
pub const PROTO_ID_INTERACTION_MODEL: u16 = 0x01;
|
||||
|
||||
// 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;
|
||||
|
||||
impl<'a> ReadReq<'a> {
|
||||
pub fn tx_start<'r, 'p>(&self, tx: &'r mut Packet<'p>) -> Result<TLVWriter<'r, 'p>, Error> {
|
||||
tx.reset();
|
||||
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(tw)
|
||||
}
|
||||
|
||||
pub fn tx_finish_chunk(&self, tx: &mut Packet) -> Result<(), Error> {
|
||||
self.complete(tx, true)
|
||||
}
|
||||
|
||||
pub fn tx_finish(&self, tx: &mut Packet) -> Result<(), Error> {
|
||||
self.complete(tx, false)
|
||||
}
|
||||
|
||||
fn complete(&self, tx: &mut Packet<'_>, more_chunks: bool) -> Result<(), Error> {
|
||||
let mut tw = Self::restore_long_read_space(tx)?;
|
||||
|
||||
if self.attr_requests.is_some() {
|
||||
tw.end_container()?;
|
||||
}
|
||||
|
||||
if more_chunks {
|
||||
tw.bool(
|
||||
TagType::Context(msg::ReportDataTag::MoreChunkedMsgs as u8),
|
||||
true,
|
||||
)?;
|
||||
}
|
||||
|
||||
tw.bool(
|
||||
TagType::Context(msg::ReportDataTag::SupressResponse as u8),
|
||||
!more_chunks,
|
||||
)?;
|
||||
|
||||
tw.end_container()
|
||||
}
|
||||
|
||||
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> {
|
||||
pub fn tx_start<'r, 'p>(
|
||||
&self,
|
||||
tx: &'r mut Packet<'p>,
|
||||
epoch: Epoch,
|
||||
timeout: Option<Duration>,
|
||||
) -> Result<Option<TLVWriter<'r, 'p>>, Error> {
|
||||
if has_timed_out(epoch, timeout) {
|
||||
Interaction::status_response(tx, IMStatusCode::Timeout)?;
|
||||
|
||||
Ok(None)
|
||||
} else {
|
||||
tx.reset();
|
||||
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(Some(tw))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn tx_finish(&self, tx: &mut Packet<'_>) -> Result<(), Error> {
|
||||
let mut tw = TLVWriter::new(tx.get_writebuf()?);
|
||||
|
||||
tw.end_container()?;
|
||||
tw.end_container()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> InvReq<'a> {
|
||||
pub fn tx_start<'r, 'p>(
|
||||
&self,
|
||||
tx: &'r mut Packet<'p>,
|
||||
epoch: Epoch,
|
||||
timeout: Option<Duration>,
|
||||
) -> Result<Option<TLVWriter<'r, 'p>>, Error> {
|
||||
if has_timed_out(epoch, timeout) {
|
||||
Interaction::status_response(tx, IMStatusCode::Timeout)?;
|
||||
|
||||
Ok(None)
|
||||
} else {
|
||||
let timed_tx = 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::status_response(tx, IMStatusCode::TimedRequestMisMatch)?;
|
||||
|
||||
Ok(None)
|
||||
} else {
|
||||
tx.reset();
|
||||
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(Some(tw))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn tx_finish(&self, tx: &mut Packet<'_>) -> Result<(), Error> {
|
||||
let mut tw = TLVWriter::new(tx.get_writebuf()?);
|
||||
|
||||
if self.inv_requests.is_some() {
|
||||
tw.end_container()?;
|
||||
}
|
||||
|
||||
tw.end_container()
|
||||
}
|
||||
}
|
||||
|
||||
impl TimedReq {
|
||||
pub fn timeout(&self, epoch: Epoch) -> Duration {
|
||||
epoch()
|
||||
.checked_add(Duration::from_millis(self.timeout as _))
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
pub fn tx_process(self, tx: &mut Packet<'_>, epoch: Epoch) -> Result<Duration, Error> {
|
||||
Interaction::status_response(tx, IMStatusCode::Success)?;
|
||||
|
||||
Ok(epoch()
|
||||
.checked_add(Duration::from_millis(self.timeout as _))
|
||||
.unwrap())
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> SubscribeReq<'a> {
|
||||
pub fn tx_start<'r, 'p>(
|
||||
&self,
|
||||
tx: &'r mut Packet<'p>,
|
||||
subscription_id: u32,
|
||||
) -> Result<TLVWriter<'r, 'p>, Error> {
|
||||
tx.reset();
|
||||
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.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(tw)
|
||||
}
|
||||
|
||||
pub fn tx_finish_chunk(&self, tx: &mut Packet<'_>, more_chunks: bool) -> Result<(), Error> {
|
||||
let mut tw = ReadReq::restore_long_read_space(tx)?;
|
||||
|
||||
if self.attr_requests.is_some() {
|
||||
tw.end_container()?;
|
||||
}
|
||||
|
||||
if more_chunks {
|
||||
tw.bool(
|
||||
TagType::Context(msg::ReportDataTag::MoreChunkedMsgs as u8),
|
||||
true,
|
||||
)?;
|
||||
}
|
||||
|
||||
tw.bool(
|
||||
TagType::Context(msg::ReportDataTag::SupressResponse as u8),
|
||||
false,
|
||||
)?;
|
||||
|
||||
tw.end_container()
|
||||
}
|
||||
|
||||
pub fn tx_process_final(&self, tx: &mut Packet, subscription_id: u32) -> Result<(), Error> {
|
||||
tx.reset();
|
||||
tx.set_proto_id(PROTO_ID_INTERACTION_MODEL);
|
||||
tx.set_proto_opcode(OpCode::SubscribeResponse as u8);
|
||||
|
||||
let mut tw = TLVWriter::new(tx.get_writebuf()?);
|
||||
|
||||
let resp = SubscribeResp::new(subscription_id, 40);
|
||||
resp.to_tlv(&mut tw, TagType::Anonymous)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ReadDriver<'a, 'r, 'p> {
|
||||
exchange: &'r mut Exchange<'a>,
|
||||
tx: &'r mut Packet<'p>,
|
||||
rx: &'r mut Packet<'p>,
|
||||
completed: bool,
|
||||
}
|
||||
|
||||
impl<'a, 'r, 'p> ReadDriver<'a, 'r, 'p> {
|
||||
fn new(exchange: &'r mut Exchange<'a>, tx: &'r mut Packet<'p>, rx: &'r mut Packet<'p>) -> Self {
|
||||
Self {
|
||||
exchange,
|
||||
tx,
|
||||
rx,
|
||||
completed: false,
|
||||
}
|
||||
}
|
||||
|
||||
fn start(&mut self, req: &ReadReq) -> Result<(), Error> {
|
||||
req.tx_start(self.tx)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn accessor(&self) -> Result<Accessor<'a>, Error> {
|
||||
self.exchange.accessor()
|
||||
}
|
||||
|
||||
pub fn writer(&mut self) -> Result<TLVWriter<'_, 'p>, Error> {
|
||||
if self.completed {
|
||||
Err(ErrorCode::Invalid.into()) // TODO
|
||||
} else {
|
||||
Ok(TLVWriter::new(self.tx.get_writebuf()?))
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn send_chunk(&mut self, req: &ReadReq<'_>) -> Result<bool, Error> {
|
||||
req.tx_finish_chunk(self.tx)?;
|
||||
|
||||
if exchange_confirm(self.exchange, self.tx, self.rx).await? != IMStatusCode::Success {
|
||||
self.completed = true;
|
||||
Ok(false)
|
||||
} else {
|
||||
req.tx_start(self.tx)?;
|
||||
|
||||
Ok(true)
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn complete(&mut self, req: &ReadReq<'_>) -> Result<(), Error> {
|
||||
req.tx_finish(self.tx)?;
|
||||
|
||||
self.exchange.send_complete(self.tx).await
|
||||
}
|
||||
}
|
||||
|
||||
pub struct WriteDriver<'a, 'r, 'p> {
|
||||
exchange: &'r mut Exchange<'a>,
|
||||
tx: &'r mut Packet<'p>,
|
||||
epoch: Epoch,
|
||||
timeout: Option<Duration>,
|
||||
}
|
||||
|
||||
impl<'a, 'r, 'p> WriteDriver<'a, 'r, 'p> {
|
||||
fn new(
|
||||
exchange: &'r mut Exchange<'a>,
|
||||
epoch: Epoch,
|
||||
timeout: Option<Duration>,
|
||||
tx: &'r mut Packet<'p>,
|
||||
) -> Self {
|
||||
Self {
|
||||
exchange,
|
||||
tx,
|
||||
epoch,
|
||||
timeout,
|
||||
}
|
||||
}
|
||||
|
||||
async fn start(&mut self, req: &WriteReq<'_>) -> Result<bool, Error> {
|
||||
if req.tx_start(self.tx, self.epoch, self.timeout)?.is_some() {
|
||||
Ok(true)
|
||||
} else {
|
||||
self.exchange.send_complete(self.tx).await?;
|
||||
|
||||
Ok(false)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn accessor(&self) -> Result<Accessor<'a>, Error> {
|
||||
self.exchange.accessor()
|
||||
}
|
||||
|
||||
pub fn writer(&mut self) -> Result<TLVWriter<'_, 'p>, Error> {
|
||||
Ok(TLVWriter::new(self.tx.get_writebuf()?))
|
||||
}
|
||||
|
||||
pub async fn complete(&mut self, req: &WriteReq<'_>) -> Result<(), Error> {
|
||||
if !req.supress_response.unwrap_or_default() {
|
||||
req.tx_finish(self.tx)?;
|
||||
self.exchange.send_complete(self.tx).await?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub struct InvokeDriver<'a, 'r, 'p> {
|
||||
exchange: &'r mut Exchange<'a>,
|
||||
tx: &'r mut Packet<'p>,
|
||||
epoch: Epoch,
|
||||
timeout: Option<Duration>,
|
||||
}
|
||||
|
||||
impl<'a, 'r, 'p> InvokeDriver<'a, 'r, 'p> {
|
||||
fn new(
|
||||
exchange: &'r mut Exchange<'a>,
|
||||
epoch: Epoch,
|
||||
timeout: Option<Duration>,
|
||||
tx: &'r mut Packet<'p>,
|
||||
) -> Self {
|
||||
Self {
|
||||
exchange,
|
||||
tx,
|
||||
epoch,
|
||||
timeout,
|
||||
}
|
||||
}
|
||||
|
||||
async fn start(&mut self, req: &InvReq<'_>) -> Result<bool, Error> {
|
||||
if req.tx_start(self.tx, self.epoch, self.timeout)?.is_some() {
|
||||
Ok(true)
|
||||
} else {
|
||||
self.exchange.send_complete(self.tx).await?;
|
||||
|
||||
Ok(false)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn accessor(&self) -> Result<Accessor<'a>, Error> {
|
||||
self.exchange.accessor()
|
||||
}
|
||||
|
||||
pub fn writer(&mut self) -> Result<TLVWriter<'_, 'p>, Error> {
|
||||
Ok(TLVWriter::new(self.tx.get_writebuf()?))
|
||||
}
|
||||
|
||||
pub fn writer_exchange(&mut self) -> Result<(TLVWriter<'_, 'p>, &Exchange<'a>), Error> {
|
||||
Ok((TLVWriter::new(self.tx.get_writebuf()?), (self.exchange)))
|
||||
}
|
||||
|
||||
pub async fn complete(&mut self, req: &InvReq<'_>) -> Result<(), Error> {
|
||||
if !req.suppress_response.unwrap_or_default() {
|
||||
req.tx_finish(self.tx)?;
|
||||
self.exchange.send_complete(self.tx).await?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub struct SubscribeDriver<'a, 'r, 'p> {
|
||||
exchange: &'r mut Exchange<'a>,
|
||||
tx: &'r mut Packet<'p>,
|
||||
rx: &'r mut Packet<'p>,
|
||||
subscription_id: u32,
|
||||
completed: bool,
|
||||
}
|
||||
|
||||
impl<'a, 'r, 'p> SubscribeDriver<'a, 'r, 'p> {
|
||||
fn new(
|
||||
exchange: &'r mut Exchange<'a>,
|
||||
subscription_id: u32,
|
||||
tx: &'r mut Packet<'p>,
|
||||
rx: &'r mut Packet<'p>,
|
||||
) -> Self {
|
||||
Self {
|
||||
exchange,
|
||||
tx,
|
||||
rx,
|
||||
subscription_id,
|
||||
completed: false,
|
||||
}
|
||||
}
|
||||
|
||||
fn start(&mut self, req: &SubscribeReq) -> Result<(), Error> {
|
||||
req.tx_start(self.tx, self.subscription_id)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn accessor(&self) -> Result<Accessor<'a>, Error> {
|
||||
self.exchange.accessor()
|
||||
}
|
||||
|
||||
pub fn writer(&mut self) -> Result<TLVWriter<'_, 'p>, Error> {
|
||||
if self.completed {
|
||||
Err(ErrorCode::Invalid.into()) // TODO
|
||||
} else {
|
||||
Ok(TLVWriter::new(self.tx.get_writebuf()?))
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn send_chunk(&mut self, req: &SubscribeReq<'_>) -> Result<bool, Error> {
|
||||
req.tx_finish_chunk(self.tx, true)?;
|
||||
|
||||
if exchange_confirm(self.exchange, self.tx, self.rx).await? != IMStatusCode::Success {
|
||||
self.completed = true;
|
||||
Ok(false)
|
||||
} else {
|
||||
req.tx_start(self.tx, self.subscription_id)?;
|
||||
|
||||
Ok(true)
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn complete(&mut self, req: &SubscribeReq<'_>) -> Result<(), Error> {
|
||||
if !self.completed {
|
||||
req.tx_finish_chunk(self.tx, false)?;
|
||||
|
||||
if exchange_confirm(self.exchange, self.tx, self.rx).await? != IMStatusCode::Success {
|
||||
self.completed = true;
|
||||
} else {
|
||||
req.tx_process_final(self.tx, self.subscription_id)?;
|
||||
self.exchange.send_complete(self.tx).await?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub enum Interaction<'a, 'r, 'p> {
|
||||
Read {
|
||||
req: ReadReq<'r>,
|
||||
driver: ReadDriver<'a, 'r, 'p>,
|
||||
},
|
||||
Write {
|
||||
req: WriteReq<'r>,
|
||||
driver: WriteDriver<'a, 'r, 'p>,
|
||||
},
|
||||
Invoke {
|
||||
req: InvReq<'r>,
|
||||
driver: InvokeDriver<'a, 'r, 'p>,
|
||||
},
|
||||
Subscribe {
|
||||
req: SubscribeReq<'r>,
|
||||
driver: SubscribeDriver<'a, 'r, 'p>,
|
||||
},
|
||||
}
|
||||
|
||||
impl<'a, 'r, 'p> Interaction<'a, 'r, 'p> {
|
||||
pub async fn timeout(
|
||||
exchange: &mut Exchange<'_>,
|
||||
rx: &mut Packet<'_>,
|
||||
tx: &mut Packet<'_>,
|
||||
) -> Result<Option<Duration>, Error> {
|
||||
let epoch = exchange.matter.epoch;
|
||||
|
||||
let mut opcode: OpCode = rx.get_proto_opcode()?;
|
||||
|
||||
let mut timeout = None;
|
||||
|
||||
while opcode == OpCode::TimedRequest {
|
||||
let rx_data = rx.as_slice();
|
||||
let req = TimedReq::from_tlv(&get_root_node_struct(rx_data)?)?;
|
||||
|
||||
timeout = Some(req.tx_process(tx, epoch)?);
|
||||
|
||||
exchange.exchange(tx, rx).await?;
|
||||
|
||||
opcode = rx.get_proto_opcode()?;
|
||||
}
|
||||
|
||||
Ok(timeout)
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn new<S>(
|
||||
exchange: &'r mut Exchange<'a>,
|
||||
rx: &'r Packet<'p>,
|
||||
tx: &'r mut Packet<'p>,
|
||||
rx_status: &'r mut Packet<'p>,
|
||||
subscription_id: S,
|
||||
timeout: Option<Duration>,
|
||||
) -> Result<Interaction<'a, 'r, 'p>, Error>
|
||||
where
|
||||
S: FnOnce() -> u32,
|
||||
{
|
||||
let epoch = exchange.matter.epoch;
|
||||
|
||||
let opcode = rx.get_proto_opcode()?;
|
||||
let rx_data = rx.as_slice();
|
||||
|
||||
match opcode {
|
||||
OpCode::ReadRequest => {
|
||||
let req = ReadReq::from_tlv(&get_root_node_struct(rx_data)?)?;
|
||||
let driver = ReadDriver::new(exchange, tx, rx_status);
|
||||
|
||||
Ok(Self::Read { req, driver })
|
||||
}
|
||||
OpCode::WriteRequest => {
|
||||
let req = WriteReq::from_tlv(&get_root_node_struct(rx_data)?)?;
|
||||
let driver = WriteDriver::new(exchange, epoch, timeout, tx);
|
||||
|
||||
Ok(Self::Write { req, driver })
|
||||
}
|
||||
OpCode::InvokeRequest => {
|
||||
let req = InvReq::from_tlv(&get_root_node_struct(rx_data)?)?;
|
||||
let driver = InvokeDriver::new(exchange, epoch, timeout, tx);
|
||||
|
||||
Ok(Self::Invoke { req, driver })
|
||||
}
|
||||
OpCode::SubscribeRequest => {
|
||||
let req = SubscribeReq::from_tlv(&get_root_node_struct(rx_data)?)?;
|
||||
let driver = SubscribeDriver::new(exchange, subscription_id(), tx, rx_status);
|
||||
|
||||
Ok(Self::Subscribe { req, driver })
|
||||
}
|
||||
_ => {
|
||||
error!("Opcode not handled: {:?}", opcode);
|
||||
Err(ErrorCode::InvalidOpcode.into())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn start(&mut self) -> Result<bool, Error> {
|
||||
let started = match self {
|
||||
Self::Read { req, driver } => {
|
||||
driver.start(req)?;
|
||||
true
|
||||
}
|
||||
Self::Write { req, driver } => driver.start(req).await?,
|
||||
Self::Invoke { req, driver } => driver.start(req).await?,
|
||||
Self::Subscribe { req, driver } => {
|
||||
driver.start(req)?;
|
||||
true
|
||||
}
|
||||
};
|
||||
|
||||
Ok(started)
|
||||
}
|
||||
|
||||
fn status_response(tx: &mut Packet, status: IMStatusCode) -> Result<(), Error> {
|
||||
tx.reset();
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
async fn exchange_confirm(
|
||||
exchange: &mut Exchange<'_>,
|
||||
tx: &mut Packet<'_>,
|
||||
rx: &mut Packet<'_>,
|
||||
) -> Result<IMStatusCode, Error> {
|
||||
exchange.exchange(tx, rx).await?;
|
||||
|
||||
let opcode: OpCode = rx.get_proto_opcode()?;
|
||||
|
||||
if opcode == OpCode::StatusResponse {
|
||||
let resp = StatusResp::from_tlv(&get_root_node_struct(rx.as_slice())?)?;
|
||||
Ok(resp.status)
|
||||
} else {
|
||||
Interaction::status_response(tx, IMStatusCode::Busy)?; // TODO
|
||||
|
||||
exchange.send_complete(tx).await?;
|
||||
|
||||
Err(ErrorCode::Invalid.into()) // TODO
|
||||
}
|
||||
}
|
||||
|
||||
fn has_timed_out(epoch: Epoch, timeout: Option<Duration>) -> bool {
|
||||
timeout.map(|timeout| epoch() > timeout).unwrap_or(false)
|
||||
}
|
|
@ -23,14 +23,15 @@
|
|||
//! Currently Ethernet based transport is supported.
|
||||
//!
|
||||
//! # Examples
|
||||
//! TODO: Fix once new API has stabilized a bit
|
||||
//! use matter::{Matter, CommissioningData};
|
||||
//! use matter::data_model::device_types::device_type_add_on_off_light;
|
||||
//! use matter::data_model::cluster_basic_information::BasicInfoConfig;
|
||||
//! use matter::secure_channel::spake2p::VerifierData;
|
||||
//! ```ignore
|
||||
//! /// TODO: Fix once new API has stabilized a bit
|
||||
//! use rs_matter::{Matter, CommissioningData};
|
||||
//! use rs_matter::data_model::device_types::device_type_add_on_off_light;
|
||||
//! use rs_matter::data_model::cluster_basic_information::BasicInfoConfig;
|
||||
//! use rs_matter::secure_channel::spake2p::VerifierData;
|
||||
//!
|
||||
//! # use matter::data_model::sdm::dev_att::{DataType, DevAttDataFetcher};
|
||||
//! # use matter::error::Error;
|
||||
//! # use rs_matter::data_model::sdm::dev_att::{DataType, DevAttDataFetcher};
|
||||
//! # use rs_matter::error::Error;
|
||||
//! # pub struct DevAtt{}
|
||||
//! # impl DevAttDataFetcher for DevAtt{
|
||||
//! # fn get_devatt_data(&self, data_type: DataType, data: &mut [u8]) -> Result<usize, Error> { Ok(0) }
|
||||
|
@ -65,11 +66,15 @@
|
|||
//! }
|
||||
//! // Start the Matter Daemon
|
||||
//! // matter.start_daemon().unwrap();
|
||||
//! ```
|
||||
//!
|
||||
//! Start off exploring by going to the [Matter] object.
|
||||
#![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", allow(incomplete_features))]
|
||||
#![cfg_attr(feature = "nightly", allow(async_fn_in_trait))]
|
||||
#![cfg_attr(feature = "nightly", feature(impl_trait_projections))]
|
||||
|
||||
pub mod acl;
|
||||
pub mod cert;
|
||||
|
@ -90,3 +95,22 @@ pub mod transport;
|
|||
pub mod utils;
|
||||
|
||||
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
|
||||
};
|
||||
}
|
|
@ -23,6 +23,8 @@ use crate::{data_model::cluster_basic_information::BasicInfoConfig, error::Error
|
|||
pub mod astro;
|
||||
pub mod builtin;
|
||||
pub mod proto;
|
||||
#[cfg(all(feature = "std", feature = "zeroconf", target_os = "linux"))]
|
||||
pub mod zeroconf;
|
||||
|
||||
pub trait Mdns {
|
||||
fn add(&self, service: &str, mode: ServiceMode) -> Result<(), Error>;
|
||||
|
@ -56,16 +58,14 @@ where
|
|||
}
|
||||
|
||||
#[cfg(all(feature = "std", target_os = "macos"))]
|
||||
pub type DefaultMdns<'a> = astro::Mdns<'a>;
|
||||
|
||||
pub use astro::MdnsService;
|
||||
#[cfg(all(feature = "std", target_os = "macos"))]
|
||||
pub type DefaultMdnsRunner<'a> = astro::MdnsRunner<'a>;
|
||||
pub use astro::MdnsUdpBuffers;
|
||||
|
||||
#[cfg(any(feature = "std", feature = "embassy-net"))]
|
||||
pub use builtin::MdnsRunBuffers;
|
||||
#[cfg(not(all(feature = "std", target_os = "macos")))]
|
||||
pub type DefaultMdns<'a> = builtin::Mdns<'a>;
|
||||
|
||||
#[cfg(not(all(feature = "std", target_os = "macos")))]
|
||||
pub type DefaultMdnsRunner<'a> = builtin::MdnsRunner<'a>;
|
||||
pub use builtin::MdnsService;
|
||||
|
||||
pub struct DummyMdns;
|
||||
|
|
@ -9,21 +9,32 @@ use crate::{
|
|||
use astro_dnssd::{DNSServiceBuilder, RegisteredDnsService};
|
||||
use log::info;
|
||||
|
||||
use super::ServiceMode;
|
||||
use super::{MdnsRunBuffers, ServiceMode};
|
||||
|
||||
pub struct Mdns<'a> {
|
||||
/// Only for API-compatibility with builtin::MdnsRunner
|
||||
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>,
|
||||
matter_port: u16,
|
||||
services: RefCell<HashMap<String, RegisteredDnsService>>,
|
||||
}
|
||||
|
||||
impl<'a> Mdns<'a> {
|
||||
impl<'a> MdnsService<'a> {
|
||||
/// This constructor takes extra parameters for API-compatibility with builtin::MdnsRunner
|
||||
pub fn new(
|
||||
_id: u16,
|
||||
_hostname: &str,
|
||||
_ip: [u8; 4],
|
||||
_ipv6: Option<[u8; 16]>,
|
||||
_interface: u32,
|
||||
_ipv6: Option<([u8; 16], u32)>,
|
||||
dev_det: &'a BasicInfoConfig<'a>,
|
||||
matter_port: u16,
|
||||
) -> Self {
|
||||
|
@ -78,30 +89,35 @@ impl<'a> Mdns<'a> {
|
|||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub struct MdnsRunner<'a>(&'a Mdns<'a>);
|
||||
|
||||
impl<'a> MdnsRunner<'a> {
|
||||
pub const fn new(mdns: &'a Mdns<'a>) -> Self {
|
||||
Self(mdns)
|
||||
}
|
||||
|
||||
pub async fn run_udp(&mut self) -> Result<(), Error> {
|
||||
/// Only for API-compatibility with builtin::MdnsRunner
|
||||
pub async fn run_piped(
|
||||
&mut self,
|
||||
_tx_pipe: &Pipe<'_>,
|
||||
_rx_pipe: &Pipe<'_>,
|
||||
) -> Result<(), Error> {
|
||||
core::future::pending::<Result<(), Error>>().await
|
||||
}
|
||||
|
||||
pub async fn run(&self, _tx_pipe: &Pipe<'_>, _rx_pipe: &Pipe<'_>) -> Result<(), Error> {
|
||||
/// Only for API-compatibility with builtin::MdnsRunner
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> super::Mdns for Mdns<'a> {
|
||||
impl<'a> super::Mdns for MdnsService<'a> {
|
||||
fn add(&self, service: &str, mode: ServiceMode) -> Result<(), Error> {
|
||||
Mdns::add(self, service, mode)
|
||||
MdnsService::add(self, service, mode)
|
||||
}
|
||||
|
||||
fn remove(&self, service: &str) -> Result<(), Error> {
|
||||
Mdns::remove(self, service)
|
||||
MdnsService::remove(self, service)
|
||||
}
|
||||
}
|
|
@ -1,17 +1,19 @@
|
|||
use core::{cell::RefCell, mem::MaybeUninit, pin::pin};
|
||||
use core::{cell::RefCell, pin::pin};
|
||||
|
||||
use domain::base::name::FromStrError;
|
||||
use domain::base::{octets::ParseError, ShortBuf};
|
||||
use embassy_futures::select::{select, select3};
|
||||
use embassy_futures::select::select;
|
||||
use embassy_time::{Duration, Timer};
|
||||
use log::info;
|
||||
|
||||
use crate::data_model::cluster_basic_information::BasicInfoConfig;
|
||||
use crate::error::{Error, ErrorCode};
|
||||
use crate::transport::network::{Address, IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr};
|
||||
use crate::transport::packet::{MAX_RX_BUF_SIZE, MAX_TX_BUF_SIZE};
|
||||
#[cfg(any(feature = "std", feature = "embassy-net"))]
|
||||
use crate::transport::network::IpAddr;
|
||||
use crate::transport::network::{
|
||||
Address, Ipv4Addr, Ipv6Addr, SocketAddr, SocketAddrV4, SocketAddrV6,
|
||||
};
|
||||
use crate::transport::pipe::{Chunk, Pipe};
|
||||
use crate::transport::udp::UdpListener;
|
||||
use crate::utils::select::{EitherUnwrap, Notification};
|
||||
|
||||
use super::{
|
||||
|
@ -19,33 +21,47 @@ use super::{
|
|||
Service, ServiceMode,
|
||||
};
|
||||
|
||||
const IP_BIND_ADDR: IpAddr = IpAddr::V6(Ipv6Addr::UNSPECIFIED);
|
||||
|
||||
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 PORT: u16 = 5353;
|
||||
|
||||
type MdnsTxBuf = MaybeUninit<[u8; MAX_TX_BUF_SIZE]>;
|
||||
type MdnsRxBuf = MaybeUninit<[u8; MAX_RX_BUF_SIZE]>;
|
||||
#[cfg(any(feature = "std", feature = "embassy-net"))]
|
||||
pub struct MdnsRunBuffers {
|
||||
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]>,
|
||||
}
|
||||
|
||||
pub struct Mdns<'a> {
|
||||
#[cfg(any(feature = "std", feature = "embassy-net"))]
|
||||
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>,
|
||||
interface: u32,
|
||||
#[allow(unused)]
|
||||
interface: Option<u32>,
|
||||
dev_det: &'a BasicInfoConfig<'a>,
|
||||
matter_port: u16,
|
||||
services: RefCell<heapless::Vec<(heapless::String<40>, ServiceMode), 4>>,
|
||||
notification: Notification,
|
||||
}
|
||||
|
||||
impl<'a> Mdns<'a> {
|
||||
impl<'a> MdnsService<'a> {
|
||||
#[inline(always)]
|
||||
pub const fn new(
|
||||
id: u16,
|
||||
hostname: &'a str,
|
||||
ip: [u8; 4],
|
||||
ipv6: Option<[u8; 16]>,
|
||||
interface: u32,
|
||||
ipv6: Option<([u8; 16], u32)>,
|
||||
dev_det: &'a BasicInfoConfig<'a>,
|
||||
matter_port: u16,
|
||||
) -> Self {
|
||||
|
@ -54,9 +70,17 @@ impl<'a> Mdns<'a> {
|
|||
id,
|
||||
hostname,
|
||||
ip,
|
||||
ipv6,
|
||||
ipv6: if let Some((ipv6, _)) = ipv6 {
|
||||
Some(ipv6)
|
||||
} else {
|
||||
None
|
||||
},
|
||||
},
|
||||
interface: if let Some((_, interface)) = ipv6 {
|
||||
Some(interface)
|
||||
} else {
|
||||
None
|
||||
},
|
||||
interface,
|
||||
dev_det,
|
||||
matter_port,
|
||||
services: RefCell::new(heapless::Vec::new()),
|
||||
|
@ -99,33 +123,41 @@ impl<'a> Mdns<'a> {
|
|||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub struct MdnsRunner<'a>(&'a Mdns<'a>);
|
||||
#[cfg(any(feature = "std", feature = "embassy-net"))]
|
||||
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?;
|
||||
|
||||
impl<'a> MdnsRunner<'a> {
|
||||
pub const fn new(mdns: &'a Mdns<'a>) -> Self {
|
||||
Self(mdns)
|
||||
// V6 multicast does not work with smoltcp yet (see https://github.com/smoltcp-rs/smoltcp/pull/602)
|
||||
#[cfg(not(feature = "embassy-net"))]
|
||||
if let Some(interface) = self.interface {
|
||||
udp.join_multicast_v6(IPV6_BROADCAST_ADDR, interface)
|
||||
.await?;
|
||||
}
|
||||
|
||||
pub async fn run_udp(&mut self) -> Result<(), Error> {
|
||||
let mut tx_buf = MdnsTxBuf::uninit();
|
||||
let mut rx_buf = MdnsRxBuf::uninit();
|
||||
udp.join_multicast_v4(
|
||||
IP_BROADCAST_ADDR,
|
||||
crate::transport::network::Ipv4Addr::from(self.host.ip),
|
||||
)
|
||||
.await?;
|
||||
|
||||
let tx_buf = &mut tx_buf;
|
||||
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 = Pipe::new(unsafe { buffers.tx_buf.assume_init_mut() });
|
||||
let rx_pipe = Pipe::new(unsafe { buffers.rx_buf.assume_init_mut() });
|
||||
|
||||
let tx_pipe = &tx_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 mut tx = pin!(async move {
|
||||
|
@ -166,45 +198,51 @@ impl<'a> MdnsRunner<'a> {
|
|||
}
|
||||
});
|
||||
|
||||
let mut run = pin!(async move { self.run(tx_pipe, rx_pipe).await });
|
||||
let mut run = pin!(async move { self.run_piped(tx_pipe, rx_pipe).await });
|
||||
|
||||
select3(&mut tx, &mut rx, &mut run).await.unwrap()
|
||||
embassy_futures::select::select3(&mut tx, &mut rx, &mut run)
|
||||
.await
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
pub async fn run(&self, tx_pipe: &Pipe<'_>, rx_pipe: &Pipe<'_>) -> Result<(), Error> {
|
||||
pub async fn run_piped(&self, tx_pipe: &Pipe<'_>, rx_pipe: &Pipe<'_>) -> Result<(), Error> {
|
||||
let mut broadcast = pin!(self.broadcast(tx_pipe));
|
||||
let mut respond = pin!(self.respond(rx_pipe, tx_pipe));
|
||||
|
||||
select(&mut broadcast, &mut respond).await.unwrap()
|
||||
}
|
||||
|
||||
#[allow(clippy::await_holding_refcell_ref)]
|
||||
async fn broadcast(&self, tx_pipe: &Pipe<'_>) -> Result<(), Error> {
|
||||
loop {
|
||||
select(
|
||||
self.0.notification.wait(),
|
||||
self.notification.wait(),
|
||||
Timer::after(Duration::from_secs(30)),
|
||||
)
|
||||
.await;
|
||||
|
||||
for addr in [
|
||||
IpAddr::V4(IP_BROADCAST_ADDR),
|
||||
IpAddr::V6(IPV6_BROADCAST_ADDR),
|
||||
] {
|
||||
Some(SocketAddr::V4(SocketAddrV4::new(IP_BROADCAST_ADDR, PORT))),
|
||||
self.interface.map(|interface| {
|
||||
SocketAddr::V6(SocketAddrV6::new(IPV6_BROADCAST_ADDR, PORT, 0, interface))
|
||||
}),
|
||||
]
|
||||
.into_iter()
|
||||
.flatten()
|
||||
{
|
||||
loop {
|
||||
let sent = {
|
||||
let mut data = tx_pipe.data.lock().await;
|
||||
|
||||
if data.chunk.is_none() {
|
||||
let len = self.0.host.broadcast(&self.0, data.buf, 60)?;
|
||||
let len = self.host.broadcast(self, data.buf, 60)?;
|
||||
|
||||
if len > 0 {
|
||||
info!("Broadasting mDNS entry to {}:{}", addr, PORT);
|
||||
info!("Broadcasting mDNS entry to {addr}");
|
||||
|
||||
data.chunk = Some(Chunk {
|
||||
start: 0,
|
||||
end: len,
|
||||
addr: Address::Udp(SocketAddr::new(addr, PORT)),
|
||||
addr: Address::Udp(addr),
|
||||
});
|
||||
|
||||
tx_pipe.data_supplied_notification.signal(());
|
||||
|
@ -226,7 +264,6 @@ impl<'a> MdnsRunner<'a> {
|
|||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::await_holding_refcell_ref)]
|
||||
async fn respond(&self, rx_pipe: &Pipe<'_>, tx_pipe: &Pipe<'_>) -> Result<(), Error> {
|
||||
loop {
|
||||
{
|
||||
|
@ -240,7 +277,7 @@ impl<'a> MdnsRunner<'a> {
|
|||
let mut tx_data = tx_pipe.data.lock().await;
|
||||
|
||||
if tx_data.chunk.is_none() {
|
||||
let len = self.0.host.respond(&self.0, data, tx_data.buf, 60)?;
|
||||
let len = self.host.respond(self, data, tx_data.buf, 60)?;
|
||||
|
||||
if len > 0 {
|
||||
info!("Replying to mDNS query from {}", rx_chunk.addr);
|
||||
|
@ -279,24 +316,24 @@ impl<'a> MdnsRunner<'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> {
|
||||
impl<'a> Services for MdnsService<'a> {
|
||||
type Error = crate::error::Error;
|
||||
|
||||
fn for_each<F>(&self, callback: F) -> Result<(), Error>
|
||||
where
|
||||
F: FnMut(&Service) -> Result<(), Error>,
|
||||
{
|
||||
Mdns::for_each(self, callback)
|
||||
MdnsService::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)
|
||||
}
|
||||
}
|
||||
|
176
rs-matter/src/mdns/zeroconf.rs
Normal file
176
rs-matter/src/mdns/zeroconf.rs
Normal file
|
@ -0,0 +1,176 @@
|
|||
use core::cell::RefCell;
|
||||
use std::collections::HashMap;
|
||||
use std::sync::mpsc::{sync_channel, SyncSender};
|
||||
|
||||
use super::{MdnsRunBuffers, ServiceMode};
|
||||
use crate::{
|
||||
data_model::cluster_basic_information::BasicInfoConfig,
|
||||
error::{Error, ErrorCode},
|
||||
transport::pipe::Pipe,
|
||||
};
|
||||
use zeroconf::{prelude::TEventLoop, service::TMdnsService, txt_record::TTxtRecord, ServiceType};
|
||||
|
||||
/// Only for API-compatibility with builtin::MdnsRunner
|
||||
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>,
|
||||
matter_port: u16,
|
||||
services: RefCell<HashMap<String, SyncSender<()>>>,
|
||||
}
|
||||
|
||||
impl<'a> MdnsService<'a> {
|
||||
/// This constructor takes extra parameters for API-compatibility with builtin::MdnsRunner
|
||||
pub fn new(
|
||||
_id: u16,
|
||||
_hostname: &str,
|
||||
_ip: [u8; 4],
|
||||
_ipv6: Option<([u8; 16], u32)>,
|
||||
dev_det: &'a BasicInfoConfig<'a>,
|
||||
matter_port: u16,
|
||||
) -> Self {
|
||||
Self::native_new(dev_det, matter_port)
|
||||
}
|
||||
|
||||
pub fn native_new(dev_det: &'a BasicInfoConfig<'a>, matter_port: u16) -> Self {
|
||||
Self {
|
||||
dev_det,
|
||||
matter_port,
|
||||
services: RefCell::new(HashMap::new()),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn add(&self, name: &str, mode: ServiceMode) -> Result<(), Error> {
|
||||
log::info!("Registering mDNS service {}/{:?}", name, mode);
|
||||
|
||||
let _ = self.remove(name);
|
||||
|
||||
mode.service(self.dev_det, self.matter_port, name, |service| {
|
||||
let service_name = service.service.strip_prefix('_').unwrap_or(service.service);
|
||||
let protocol = service
|
||||
.protocol
|
||||
.strip_prefix('_')
|
||||
.unwrap_or(service.protocol);
|
||||
|
||||
let service_type = if !service.service_subtypes.is_empty() {
|
||||
let subtypes = service
|
||||
.service_subtypes
|
||||
.into_iter()
|
||||
.map(|subtype| subtype.strip_prefix('_').unwrap_or(*subtype))
|
||||
.collect();
|
||||
|
||||
ServiceType::with_sub_types(service_name, protocol, subtypes)
|
||||
} else {
|
||||
ServiceType::new(service_name, protocol)
|
||||
}
|
||||
.map_err(|err| {
|
||||
log::error!(
|
||||
"Encountered error building service type: {}",
|
||||
err.to_string()
|
||||
);
|
||||
ErrorCode::MdnsError
|
||||
})?;
|
||||
|
||||
let (sender, receiver) = sync_channel(1);
|
||||
|
||||
let service_port = service.port;
|
||||
let mut txt_kvs = vec![];
|
||||
for (k, v) in service.txt_kvs {
|
||||
txt_kvs.push((k.to_string(), v.to_string()));
|
||||
}
|
||||
|
||||
let name_copy = name.to_owned();
|
||||
|
||||
std::thread::spawn(move || {
|
||||
let mut mdns_service = zeroconf::MdnsService::new(service_type, service_port);
|
||||
|
||||
let mut txt_record = zeroconf::TxtRecord::new();
|
||||
for (k, v) in txt_kvs {
|
||||
log::info!("mDNS TXT key {k} val {v}");
|
||||
if let Err(err) = txt_record.insert(&k, &v) {
|
||||
log::error!(
|
||||
"Encountered error inserting kv-pair into txt record {}",
|
||||
err.to_string()
|
||||
);
|
||||
}
|
||||
}
|
||||
mdns_service.set_name(&name_copy);
|
||||
mdns_service.set_txt_record(txt_record);
|
||||
mdns_service.set_registered_callback(Box::new(|_, _| {}));
|
||||
|
||||
match mdns_service.register() {
|
||||
Ok(event_loop) => loop {
|
||||
if let Ok(()) = receiver.try_recv() {
|
||||
break;
|
||||
}
|
||||
if let Err(err) = event_loop.poll(std::time::Duration::from_secs(1)) {
|
||||
log::error!(
|
||||
"Failed to poll mDNS service event loop: {}",
|
||||
err.to_string()
|
||||
);
|
||||
break;
|
||||
}
|
||||
},
|
||||
Err(err) => log::error!(
|
||||
"Encountered error registering mDNS service: {}",
|
||||
err.to_string()
|
||||
),
|
||||
}
|
||||
});
|
||||
|
||||
self.services.borrow_mut().insert(name.to_owned(), sender);
|
||||
|
||||
Ok(())
|
||||
})
|
||||
}
|
||||
|
||||
pub fn remove(&self, name: &str) -> Result<(), Error> {
|
||||
if let Some(cancellation_notice) = self.services.borrow_mut().remove(name) {
|
||||
log::info!("Deregistering mDNS service {}", name);
|
||||
cancellation_notice
|
||||
.send(())
|
||||
.map_err(|_| ErrorCode::MdnsError)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Only for API-compatibility with builtin::MdnsRunner
|
||||
pub async fn run_piped(
|
||||
&mut self,
|
||||
_tx_pipe: &Pipe<'_>,
|
||||
_rx_pipe: &Pipe<'_>,
|
||||
) -> Result<(), Error> {
|
||||
core::future::pending::<Result<(), Error>>().await
|
||||
}
|
||||
|
||||
/// Only for API-compatibility with builtin::MdnsRunner
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
|
@ -31,7 +31,7 @@ use crate::{
|
|||
|
||||
use self::{
|
||||
code::{compute_pairing_code, pretty_print_pairing_code},
|
||||
qr::{compute_qr_code, print_qr_code},
|
||||
qr::{compute_qr_code_text, print_qr_code},
|
||||
};
|
||||
|
||||
pub struct DiscoveryCapabilities {
|
||||
|
@ -88,15 +88,15 @@ pub fn print_pairing_code_and_qr(
|
|||
buf: &mut [u8],
|
||||
) -> Result<(), Error> {
|
||||
let pairing_code = compute_pairing_code(comm_data);
|
||||
let qr_code = compute_qr_code(dev_det, comm_data, discovery_capabilities, buf)?;
|
||||
let qr_code = compute_qr_code_text(dev_det, comm_data, discovery_capabilities, buf)?;
|
||||
|
||||
pretty_print_pairing_code(&pairing_code);
|
||||
print_qr_code(qr_code);
|
||||
print_qr_code(qr_code)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(self) fn passwd_from_comm_data(comm_data: &CommissioningData) -> u32 {
|
||||
fn passwd_from_comm_data(comm_data: &CommissioningData) -> u32 {
|
||||
// todo: should this be part of the comm_data implementation?
|
||||
match comm_data.verifier.data {
|
||||
VerifierOption::Password(pwd) => pwd,
|
|
@ -15,6 +15,10 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
use core::mem::MaybeUninit;
|
||||
|
||||
use qrcodegen_no_heap::{QrCode, QrCodeEcc, Version};
|
||||
|
||||
use crate::{
|
||||
error::ErrorCode,
|
||||
tlv::{TLVWriter, TagType},
|
||||
|
@ -319,28 +323,153 @@ fn estimate_struct_overhead(first_field_size: usize) -> usize {
|
|||
first_field_size + 4 + 2
|
||||
}
|
||||
|
||||
pub(super) fn print_qr_code(qr_code: &str) {
|
||||
info!("QR Code: {}", qr_code);
|
||||
pub(crate) fn print_qr_code(qr_code_text: &str) -> Result<(), Error> {
|
||||
info!("QR Code Text: {}", qr_code_text);
|
||||
|
||||
#[cfg(feature = "std")]
|
||||
{
|
||||
use qrcode::{render::unicode, QrCode, Version};
|
||||
let mut tmp_buf = MaybeUninit::<[u8; Version::MAX.buffer_len()]>::uninit();
|
||||
let mut out_buf = MaybeUninit::<[u8; 7000]>::uninit();
|
||||
|
||||
let needed_version = compute_qr_version(qr_code);
|
||||
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 tmp_buf = unsafe { tmp_buf.assume_init_mut() };
|
||||
let out_buf = unsafe { out_buf.assume_init_mut() };
|
||||
|
||||
info!("\n{}", image);
|
||||
let qr_code = compute_qr_code(qr_code_text, out_buf, tmp_buf)?;
|
||||
|
||||
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>(
|
||||
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,
|
||||
comm_data: &CommissioningData,
|
||||
discovery_capabilities: DiscoveryCapabilities,
|
||||
|
@ -350,16 +479,6 @@ pub fn compute_qr_code<'a>(
|
|||
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(
|
||||
bits: &mut [u8],
|
||||
offset: &mut usize,
|
|
@ -15,31 +15,63 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
#[cfg(feature = "std")]
|
||||
pub use file_psm::*;
|
||||
pub use fileio::*;
|
||||
|
||||
#[cfg(feature = "std")]
|
||||
mod file_psm {
|
||||
pub mod fileio {
|
||||
use std::fs;
|
||||
use std::io::{Read, Write};
|
||||
use std::path::PathBuf;
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
use log::info;
|
||||
|
||||
use crate::error::{Error, ErrorCode};
|
||||
use crate::Matter;
|
||||
|
||||
pub struct FilePsm {
|
||||
pub struct Psm<'a> {
|
||||
matter: &'a Matter<'a>,
|
||||
dir: PathBuf,
|
||||
buf: [u8; 4096],
|
||||
}
|
||||
|
||||
impl FilePsm {
|
||||
pub fn new(dir: PathBuf) -> Result<Self, Error> {
|
||||
impl<'a> Psm<'a> {
|
||||
#[inline(always)]
|
||||
pub fn new(matter: &'a Matter<'a>, dir: PathBuf) -> Result<Self, Error> {
|
||||
fs::create_dir_all(&dir)?;
|
||||
|
||||
Ok(Self { dir })
|
||||
info!("Persisting from/to {}", dir.display());
|
||||
|
||||
let mut buf = [0; 4096];
|
||||
|
||||
if let Some(data) = Self::load(&dir, "acls", &mut buf)? {
|
||||
matter.load_acls(data)?;
|
||||
}
|
||||
|
||||
pub fn load<'a>(&self, key: &str, buf: &'a mut [u8]) -> Result<Option<&'a [u8]>, Error> {
|
||||
let path = self.dir.join(key);
|
||||
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> {
|
||||
loop {
|
||||
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) {
|
||||
Ok(mut file) => {
|
||||
|
@ -69,8 +101,8 @@ mod file_psm {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn store(&self, key: &str, data: &[u8]) -> Result<(), Error> {
|
||||
let path = self.dir.join(key);
|
||||
fn store(dir: &Path, key: &str, data: &[u8]) -> Result<(), Error> {
|
||||
let path = dir.join(key);
|
||||
|
||||
let mut file = fs::File::create(path)?;
|
||||
|
|
@ -20,30 +20,25 @@ use core::cell::RefCell;
|
|||
use log::{error, trace};
|
||||
|
||||
use crate::{
|
||||
alloc,
|
||||
cert::Cert,
|
||||
crypto::{self, KeyPair, Sha256},
|
||||
error::{Error, ErrorCode},
|
||||
fabric::{Fabric, FabricMgr},
|
||||
secure_channel::common::SCStatusCodes,
|
||||
secure_channel::common::{self, OpCode},
|
||||
secure_channel::common::{self, OpCode, PROTO_ID_SECURE_CHANNEL},
|
||||
secure_channel::common::{complete_with_status, SCStatusCodes},
|
||||
tlv::{get_root_node_struct, FromTLV, OctetStr, TLVWriter, TagType},
|
||||
transport::{
|
||||
exchange::Exchange,
|
||||
network::Address,
|
||||
proto_ctx::ProtoCtx,
|
||||
packet::Packet,
|
||||
session::{CaseDetails, CloneData, NocCatIds, SessionMode},
|
||||
},
|
||||
utils::{rand::Rand, writebuf::WriteBuf},
|
||||
};
|
||||
|
||||
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
|
||||
enum State {
|
||||
Sigma1Rx,
|
||||
Sigma3Rx,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct CaseSession {
|
||||
state: State,
|
||||
struct CaseSession {
|
||||
peer_sessid: u16,
|
||||
local_sessid: u16,
|
||||
tt_hash: Sha256,
|
||||
|
@ -54,11 +49,11 @@ pub struct CaseSession {
|
|||
}
|
||||
|
||||
impl CaseSession {
|
||||
pub fn new(peer_sessid: u16, local_sessid: u16) -> Result<Self, Error> {
|
||||
#[inline(always)]
|
||||
pub fn new() -> Result<Self, Error> {
|
||||
Ok(Self {
|
||||
state: State::Sigma1Rx,
|
||||
peer_sessid,
|
||||
local_sessid,
|
||||
peer_sessid: 0,
|
||||
local_sessid: 0,
|
||||
tt_hash: Sha256::new()?,
|
||||
shared_secret: [0; crypto::ECDH_SHARED_SECRET_LEN_BYTES],
|
||||
our_pub_key: [0; crypto::EC_POINT_LEN_BYTES],
|
||||
|
@ -79,39 +74,37 @@ impl<'a> Case<'a> {
|
|||
Self { fabric_mgr, rand }
|
||||
}
|
||||
|
||||
pub fn casesigma3_handler(
|
||||
pub async fn handle(
|
||||
&mut self,
|
||||
ctx: &mut ProtoCtx,
|
||||
) -> Result<(bool, Option<CloneData>), Error> {
|
||||
let mut case_session = ctx
|
||||
.exch_ctx
|
||||
.exch
|
||||
.take_case_session()
|
||||
.ok_or(ErrorCode::InvalidState)?;
|
||||
if case_session.state != State::Sigma1Rx {
|
||||
Err(ErrorCode::Invalid)?;
|
||||
}
|
||||
case_session.state = State::Sigma3Rx;
|
||||
exchange: &mut Exchange<'_>,
|
||||
rx: &mut Packet<'_>,
|
||||
tx: &mut Packet<'_>,
|
||||
) -> Result<(), Error> {
|
||||
let mut session = alloc!(CaseSession::new()?);
|
||||
|
||||
self.handle_casesigma1(exchange, rx, tx, &mut session)
|
||||
.await?;
|
||||
self.handle_casesigma3(exchange, rx, tx, &mut session).await
|
||||
}
|
||||
|
||||
async fn handle_casesigma3(
|
||||
&mut self,
|
||||
exchange: &mut Exchange<'_>,
|
||||
rx: &Packet<'_>,
|
||||
tx: &mut Packet<'_>,
|
||||
case_session: &mut CaseSession,
|
||||
) -> Result<(), Error> {
|
||||
rx.check_proto_opcode(OpCode::CASESigma3 as _)?;
|
||||
|
||||
let result = {
|
||||
let fabric_mgr = self.fabric_mgr.borrow();
|
||||
|
||||
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())?;
|
||||
if let Some(fabric) = fabric {
|
||||
let root = get_root_node_struct(rx.as_slice())?;
|
||||
let encrypted = root.find_tag(1)?.slice()?;
|
||||
|
||||
let mut decrypted: [u8; 800] = [0; 800];
|
||||
let mut decrypted = alloc!([0; 800]);
|
||||
if encrypted.len() > decrypted.len() {
|
||||
error!("Data too large");
|
||||
Err(ErrorCode::NoSpace)?;
|
||||
|
@ -119,62 +112,78 @@ impl<'a> Case<'a> {
|
|||
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 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 initiator_noc = alloc!(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));
|
||||
initiator_icac = Some(alloc!(Cert::new(icac.0)?));
|
||||
}
|
||||
|
||||
if Case::validate_sigma3_sign(
|
||||
#[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,
|
||||
)
|
||||
.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));
|
||||
}
|
||||
|
||||
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(ctx.rx.as_slice())?;
|
||||
let clone_data = Case::get_session_clone_data(
|
||||
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()?,
|
||||
ctx.exch_ctx.sess.get_peer_addr(),
|
||||
&case_session,
|
||||
exchange.with_session(|sess| Ok(sess.get_peer_addr()))?,
|
||||
case_session,
|
||||
&peer_catids,
|
||||
)?;
|
||||
)?)
|
||||
}
|
||||
} else {
|
||||
Err(SCStatusCodes::NoSharedTrustRoots)
|
||||
}
|
||||
};
|
||||
|
||||
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)))
|
||||
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
|
||||
}
|
||||
|
||||
pub fn casesigma1_handler(&mut self, ctx: &mut ProtoCtx) -> Result<bool, Error> {
|
||||
ctx.tx.set_proto_opcode(OpCode::CASESigma2 as u8);
|
||||
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 = ctx.rx.as_slice();
|
||||
let rx_buf = rx.as_slice();
|
||||
let root = get_root_node_struct(rx_buf)?;
|
||||
let r = Sigma1Req::from_tlv(&root)?;
|
||||
|
||||
|
@ -184,17 +193,20 @@ impl<'a> Case<'a> {
|
|||
.match_dest_id(r.initiator_random.0, r.dest_id.0);
|
||||
if local_fabric_idx.is_err() {
|
||||
error!("Fabric Index mismatch");
|
||||
common::create_sc_status_report(
|
||||
ctx.tx,
|
||||
complete_with_status(
|
||||
exchange,
|
||||
tx,
|
||||
common::SCStatusCodes::NoSharedTrustRoots,
|
||||
None,
|
||||
)?;
|
||||
ctx.exch_ctx.exch.close();
|
||||
return Ok(true);
|
||||
)
|
||||
.await?;
|
||||
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let local_sessid = ctx.exch_ctx.sess.reserve_new_sess_id();
|
||||
let mut case_session = CaseSession::new(r.initiator_sessid, local_sessid)?;
|
||||
let local_sessid = exchange.get_next_sess_id();
|
||||
case_session.peer_sessid = r.initiator_sessid;
|
||||
case_session.local_sessid = local_sessid;
|
||||
case_session.tt_hash.update(rx_buf)?;
|
||||
case_session.local_fabric_idx = local_fabric_idx?;
|
||||
if r.peer_pub_key.0.len() != crypto::EC_POINT_LEN_BYTES {
|
||||
|
@ -225,52 +237,77 @@ impl<'a> Case<'a> {
|
|||
// Derive the Encrypted Part
|
||||
const MAX_ENCRYPTED_SIZE: usize = 800;
|
||||
|
||||
let mut encrypted: [u8; MAX_ENCRYPTED_SIZE] = [0; MAX_ENCRYPTED_SIZE];
|
||||
let encrypted_len = {
|
||||
let mut signature = [0u8; crypto::EC_SIGNATURE_LEN_BYTES];
|
||||
let mut encrypted = alloc!([0; MAX_ENCRYPTED_SIZE]);
|
||||
let mut signature = alloc!([0u8; crypto::EC_SIGNATURE_LEN_BYTES]);
|
||||
|
||||
let fabric_found = {
|
||||
let fabric_mgr = self.fabric_mgr.borrow();
|
||||
|
||||
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);
|
||||
}
|
||||
if let Some(fabric) = fabric {
|
||||
#[cfg(feature = "alloc")]
|
||||
let signature_mut = &mut *signature;
|
||||
|
||||
#[cfg(not(feature = "alloc"))]
|
||||
let signature_mut = &mut signature;
|
||||
|
||||
let sign_len = Case::get_sigma2_sign(
|
||||
fabric.unwrap(),
|
||||
fabric,
|
||||
&case_session.our_pub_key,
|
||||
&case_session.peer_pub_key,
|
||||
&mut signature,
|
||||
signature_mut,
|
||||
)?;
|
||||
let signature = &signature[..sign_len];
|
||||
|
||||
Case::get_sigma2_encryption(
|
||||
fabric.unwrap(),
|
||||
#[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,
|
||||
&mut case_session,
|
||||
case_session,
|
||||
signature,
|
||||
&mut encrypted,
|
||||
)?
|
||||
};
|
||||
encrypted_mut,
|
||||
)?;
|
||||
|
||||
let encrypted = &encrypted[0..encrypted_len];
|
||||
|
||||
// Generate our Response Body
|
||||
let mut tw = TLVWriter::new(ctx.tx.get_writebuf()?);
|
||||
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(ctx.tx.as_mut_slice())?;
|
||||
ctx.exch_ctx.exch.set_case_session(case_session);
|
||||
Ok(true)
|
||||
|
||||
case_session.tt_hash.update(tx.as_mut_slice())?;
|
||||
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
};
|
||||
|
||||
if fabric_found {
|
||||
exchange.exchange(tx, rx).await
|
||||
} else {
|
||||
complete_with_status(
|
||||
exchange,
|
||||
tx,
|
||||
common::SCStatusCodes::NoSharedTrustRoots,
|
||||
None,
|
||||
)
|
||||
.await
|
||||
}
|
||||
}
|
||||
|
||||
fn get_session_clone_data(
|
||||
|
@ -334,7 +371,7 @@ impl<'a> Case<'a> {
|
|||
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();
|
||||
|
||||
if fabric.get_fabric_id() != noc.get_fabric_id()? {
|
||||
|
@ -437,7 +474,7 @@ impl<'a> Case<'a> {
|
|||
fn get_sigma2_key(
|
||||
ipk: &[u8],
|
||||
our_random: &[u8],
|
||||
case_session: &mut CaseSession,
|
||||
case_session: &CaseSession,
|
||||
key: &mut [u8],
|
||||
) -> Result<(), Error> {
|
||||
const S2K_INFO: [u8; 6] = [0x53, 0x69, 0x67, 0x6d, 0x61, 0x32];
|
||||
|
@ -467,7 +504,7 @@ impl<'a> Case<'a> {
|
|||
fabric: &Fabric,
|
||||
rand: Rand,
|
||||
our_random: &[u8],
|
||||
case_session: &mut CaseSession,
|
||||
case_session: &CaseSession,
|
||||
signature: &[u8],
|
||||
out: &mut [u8],
|
||||
) -> Result<usize, Error> {
|
|
@ -17,7 +17,10 @@
|
|||
|
||||
use num_derive::FromPrimitive;
|
||||
|
||||
use crate::{error::Error, transport::packet::Packet};
|
||||
use crate::{
|
||||
error::Error,
|
||||
transport::{exchange::Exchange, packet::Packet},
|
||||
};
|
||||
|
||||
use super::status_report::{create_status_report, GeneralCode};
|
||||
|
||||
|
@ -51,6 +54,17 @@ pub enum SCStatusCodes {
|
|||
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(
|
||||
proto_tx: &mut Packet,
|
||||
status_code: SCStatusCodes,
|
||||
|
@ -64,8 +78,8 @@ pub fn create_sc_status_report(
|
|||
// the session will be closed soon
|
||||
GeneralCode::Success
|
||||
}
|
||||
SCStatusCodes::Busy
|
||||
| SCStatusCodes::InvalidParameter
|
||||
SCStatusCodes::Busy => GeneralCode::Busy,
|
||||
SCStatusCodes::InvalidParameter
|
||||
| SCStatusCodes::NoSharedTrustRoots
|
||||
| SCStatusCodes::SessionNotFound => GeneralCode::Failure,
|
||||
};
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Reference in a new issue