Compare commits

..

152 commits

Author SHA1 Message Date
Kedar Sovani
97c9619dba
Merge pull request from ssnover/zeroconf
Add feature zeroconf in order to support avahi mDNS registration via zeroconf crate
2023-10-06 22:33:59 +05:30
Kedar Sovani
89f2bc4d98
Merge pull request from ivmarkov/ci
Fix nightly CI
2023-10-06 22:33:14 +05:30
Kedar Sovani
7b55e7fbfb
Merge pull request from jasta/fix-ipv6-mcast
[mdns] Fix multicast routing error on esp32 (and likely other platforms)
2023-10-06 22:32:52 +05:30
ivmarkov
152419472b Fix nightly CI 2023-10-06 14:24:49 +00:00
Josh Guilfoyle
8faa31a63a Manually fix clippy error related to unused import in conditional branch 2023-10-03 20:13:27 -07:00
Josh Guilfoyle
d84402f571 [mdns] Fix multicast routing error on esp32 (and likely other platforms)
According to the RFC
(https://datatracker.ietf.org/doc/html/rfc2553#section-3.3), it is
necessary to disambiguate link-local addresses with the interface index
(in the scope_id field).  Lacking this field, newer versions of lwip that
support proper IPv6 scopes will yield EHOSTUNREACH (Host unreachable).
Other implementations like on Linux and OS X will likely be affected by
the lack of this field for more complex networking setups.

Fixes 

Run cargo fmt again

Run cargo clippy again

Revert "Run cargo clippy again"

This reverts commit e3bba1f6367172d9ecd07c8c8fb7263cda40e8f6.
2023-10-03 20:07:00 -07:00
Kedar Sovani
e39fd18b73
Merge pull request from ivmarkov/main
Fix CI
2023-10-01 22:52:57 +05:30
ivmarkov
29a5c14590 Fix CI 2023-10-01 07:11:15 +00:00
Kedar Sovani
0fe4ae906f
Merge pull request from ssnover/std-box-dyn-error
Add a boxed dyn error to make error info easier to access on hosted systems
2023-09-25 22:35:58 +05:30
Kedar Sovani
7ef08ad559
Merge pull request from ssnover/tlv-parse-cert-extensions
Handle non-consecutive tag numbers in Sigma3 certificate extensions
2023-09-25 12:24:43 +05:30
Kedar Sovani
d02f0ba834
Merge pull request from ivmarkov/main
 with signed CLA
2023-09-25 12:19:02 +05:30
ivmarkov
793870c6cc with signed CLA 2023-09-25 06:17:51 +00:00
Shane Snover
7caf1febe3 Pull in the change for unsoundness in lifetime to get the build working 2023-09-24 20:04:33 -06:00
Shane Snover
c4dcfa540a Merge branch 'main' into zeroconf 2023-09-24 19:42:51 -06:00
Shane Snover
53b8c9ffd7 Bump the zeroconf version 2023-09-24 19:05:23 -06:00
Shane Snover
f8cd402153 Use the unordered parameter to the tlv derive macro 2023-09-19 22:55:34 -06:00
Shane Snover
5cda85898b Add comment explaining the difference between ICAC1_SUCCESS and ICAC2_SUCCESS 2023-09-17 17:49:15 -06:00
Shane Snover
629feea4ec Oops too much of the structure 2023-09-17 17:48:06 -06:00
Shane Snover
ed227f77cd Add ICAC cert test vector which exercises the non-consecutive extensions tag parsing 2023-09-17 17:15:42 -06:00
Shane Snover
21536dd10e Add test to verify the cert can be deserialized out of order, serialized again, and then when deserialized it matches 2023-09-17 17:07:40 -06:00
Shane Snover
5e81647291 Implement FromTLV for Extensions manually 2023-09-17 16:42:37 -06:00
Shane Snover
c064fb12a4 Update with service name 2023-09-17 11:23:44 -06:00
Kedar Sovani
8b73cbd71a
Merge pull request from ssnover/fix-armfailsafe-command-arg-types
Update the types of the arguments for General Commissioning cluster's ArmFailSafe command to match spec
2023-09-15 11:52:58 +05:30
Shane Snover
c74c46d3ed Update the types of the arguments for General Commissioning cluster's ArmFailSafe command to match spec 2023-09-12 18:58:11 -06:00
Shane Snover
b93875658b Update with fork for zeroconf 2023-09-12 18:23:42 -06:00
Shane Snover
65661f13db Implement linux mdns with avahi/zerconf 2023-09-06 23:43:09 -06:00
Kedar Sovani
320d1ec989
Merge pull request from ivmarkov/session-eviction
Handle out of sessions and out of exchanges
2023-08-31 11:45:12 +05:30
ssnover
c443a72b42 Add a boxed dyn error to make error info easier to access on hosted systems 2023-08-29 15:04:58 -06:00
Kedar Sovani
188fe1b5af
Merge pull request from KorvinSzanto/patch-1
Fix typo in target for esp32c6
2023-08-28 07:19:51 +05:30
Korvin Szanto
addd38c4a6
Fix typo in target for esp32c6 2023-08-27 12:36:02 -07:00
Kedar Sovani
3f48e2d9c9
Merge pull request from ivmarkov/qr
no_std QR code rendering
2023-08-24 17:39:01 +05:30
ivmarkov
b89539c8c6 no_std QR code rendering 2023-08-20 18:56:12 +00:00
ivmarkov
e171e33510 Handle out of sessions and out of exchanges 2023-08-17 05:39:56 +00:00
Kedar Sovani
4c347c0c0b
Merge pull request from ssnover/fix-documentation-page-example
Fix the example section of the cargo generated documentation
2023-08-16 11:54:31 +05:30
ssnover
817138b355 Use ignore for now until this example is fixed 2023-08-15 13:41:13 -06:00
ssnover
d49288d437 Put the example code in code tags 2023-08-15 13:28:26 -06:00
Kedar Sovani
bd166e4597
Merge pull request from kedars/bugfix/ios_fixes_part2
Multiple fixes for working with iOS
2023-08-12 19:11:26 +05:30
Kedar Sovani
e305ec1d89 Bump-up crate version 2023-08-09 15:24:33 +05:30
Kedar Sovani
0319ece0ab Bump up the stack utilisation 2023-08-09 13:35:23 +05:30
Kedar Sovani
46ef8ef596 ACL: For Writes, perform all ACL checks before any *write* begins 2023-08-09 12:15:00 +05:30
Kedar Sovani
18979feeca ACL: Targets in ACL entries are NULLable 2023-08-09 12:15:00 +05:30
Kedar Sovani
4bb0831168 BasicInfo: Add ProductName/VendorName/NodeLabel 2023-08-09 12:14:55 +05:30
Kedar Sovani
ce3bf6b6fb
Merge pull request from ivmarkov/main
Fix CI by addressing the Clippy warnings
2023-08-04 06:43:55 +05:30
ivmarkov
7a601b191e Clippy 2023-08-03 14:30:25 +00:00
Kedar Sovani
227bb77ba1
Merge pull request from kedars/bugfix/multiple_ios_fixes
Multiple fixes for iOS support
2023-08-01 17:38:33 +05:30
Kedar Sovani
21f0bb4e3a Fix tests for data model updates 2023-08-01 17:33:19 +05:30
Kedar Sovani
faf3c60946 Placeholder clusters that show in 'server-list' to make iOS happy 2023-08-01 14:01:42 +05:30
Kedar Sovani
96ceaa4263 Weird: Disable #[allow(dead_code)], as it was causing incorrect behaviour
For some reason, this caused the 'start' attribute to be ignored in the
tlvargs. Need to investigate further.
2023-08-01 14:01:42 +05:30
Kedar Sovani
eceef8b207 CASE: Support ICAC Optional in AddNocReq and AddTrusted RCA in CASE 2023-08-01 14:01:42 +05:30
Kedar Sovani
dfd2f7e56e ASN1: Handle special case for Not-After == 0 2023-08-01 14:01:42 +05:30
Kedar Sovani
6bec796bad GenComm: BasicCommInfo Attribute should return the entire structure 2023-08-01 14:01:42 +05:30
Kedar Sovani
e02b316030
Merge pull request from kedars/bugfix/ios_support
Support Attributes of Nw Commissioning Cluster
2023-08-01 14:01:05 +05:30
Kedar Sovani
50f18dbbee
Merge pull request from ivmarkov/main
Do not hold on to RefCell borrows across await points
2023-08-01 14:00:49 +05:30
ivmarkov
f53f3b789d Do not hold on to RefCell borrows across await points 2023-08-01 06:49:42 +00:00
Kedar Sovani
7f8ea83403 NwCommissioning: Include mandatory Attributes of NwCommissioning Cluster 2023-07-29 15:14:35 +05:30
Kedar Sovani
54e64014a5 DataModel: Quality discriminants easier to align with the names in the spec 2023-07-29 14:46:14 +05:30
Kedar Sovani
ede024cf71
Merge pull request from ivmarkov/adjustments
Adjust the authors header
2023-07-28 11:59:32 +05:30
ivmarkov
29127e7e07 Adjust the authors header 2023-07-28 06:22:13 +00:00
Kedar Sovani
062f669369
Merge pull request from cheat-sc/fix-a-typo
mdns: builtin: Fix a typo
2023-07-25 09:10:49 +05:30
Kedar Sovani
13c2504d4c
Merge pull request from thekuwayama/fix__quality
modify: Quality bits into separated flags
2023-07-25 09:10:02 +05:30
Kedar Sovani
1fccb18464
Merge pull request from kedars/bugfix/osx_support
Fix broken build on OS-X
2023-07-25 09:09:48 +05:30
Kedar Sovani
2b00a886a7
Merge pull request from ivmarkov/main
cargo fmt and clippy; build and test most features; publish actions
2023-07-25 09:09:34 +05:30
Shohei Maruyama
ae72d1cd31 mdns: builtin: Fix a typo
This commit fixes just a typo.

Signed-off-by: Shohei Maruyama <cheat.sc.linux@outlook.com>
2023-07-25 03:53:10 +09:00
Kedar Sovani
ded50dd780 Fix broken build on OS-X 2023-07-24 11:54:16 +05:30
thekuwayama
b3224d2b40 add SN Quality 2023-07-23 22:41:53 +09:00
thekuwayama
5a25904a07 modify: Quality bits into separated flags 2023-07-23 18:49:28 +09:00
ivmarkov
d2d5571755 Fix badges 2023-07-23 07:25:38 +00:00
ivmarkov
50b2433fb5 Address review feedback 2023-07-23 07:13:54 +00:00
ivmarkov
92b24920ce cargo fmt and clippy; build and test most features; publish actions 2023-07-22 16:29:50 +00:00
Kedar Sovani
b73c65d8b6
Merge pull request from ivmarkov/main
Rename matter(-iot) to rs-matter
2023-07-22 18:08:56 +05:30
ivmarkov
bafedb022b Rename matter(-iot) to rs-matter; matter_macro_derive to rs-matter-macros; tlv_tool to just tlv 2023-07-22 10:31:29 +00:00
Kedar Sovani
6bbac0b6e9
Merge pull request from ivmarkov/sequential-embassy-net
no_std + async support
2023-07-22 15:11:29 +05:30
ivmarkov
91e13292da Remove the note referring to the no_std and sequential branches 2023-07-22 07:00:53 +00:00
ivmarkov
916f2148f8 Simplify API by combining Matter, Transport and TransportRunner; simplify Mdns and Psm runners 2023-07-22 05:50:02 +00:00
ivmarkov
71b9a578d0 Remove embassy-net features that matter-rs is not using 2023-07-21 12:15:12 +00:00
ivmarkov
263279e714 Make multicast ipv6 optional 2023-07-21 12:15:12 +00:00
ivmarkov
aa2d5dfe20 Compatibility with embassy-net fixed multicast support 2023-07-21 12:15:12 +00:00
ivmarkov
24cdf079a6 New helper APIs in Transport 2023-07-21 12:15:12 +00:00
ivmarkov
0d73ba74ee UDP stack based on embassy-net 2023-07-21 12:15:12 +00:00
ivmarkov
0eecce5f8d UDP stack based on embassy-net 2023-07-21 12:15:12 +00:00
Kedar Sovani
762438ca8e on_off_light: Save ACLs and Fabrics to PSM 2023-07-21 12:15:12 +00:00
ivmarkov
9576fd8d9a Fix 2023-07-21 12:15:12 +00:00
ivmarkov
7f9ccbc38d Sequential Exchange API 2023-07-21 12:15:12 +00:00
ivmarkov
44e01a5881 Configurable parts_list in descriptor 2023-07-21 12:15:12 +00:00
ivmarkov
831853630b Add from/to TLV for i16, i32 and i64 2023-07-21 12:15:12 +00:00
ivmarkov
879f816438 More comments for tailoring the example for no_std 2023-07-21 12:15:12 +00:00
ivmarkov
5b9fd502c7 Fix the no_std build 2023-07-21 12:15:12 +00:00
ivmarkov
62aa69202f Workaround broken join_multicast_v4 on ESP-IDF 2023-07-21 12:15:12 +00:00
ivmarkov
e8babedd87 Support for ESP-IDF build 2023-07-21 12:15:12 +00:00
ivmarkov
488ef5b9f0 Proper mDNS responder 2023-07-21 12:15:12 +00:00
ivmarkov
b882aad1ff Clippy 2023-07-21 12:15:12 +00:00
ivmarkov
c0d1b85d9d Default mDns impl 2023-07-21 12:15:11 +00:00
ivmarkov
de3d3de004 Make Matter covariant over its lifetime 2023-07-21 12:15:11 +00:00
ivmarkov
1b879f1a5b Simplify main user-facing API 2023-07-21 12:15:11 +00:00
ivmarkov
8e9d8887da Fix a bug in mDNS 2023-07-21 12:15:11 +00:00
ivmarkov
b94484b67e Make sure nix is not brought in no-std compiles 2023-07-21 12:15:11 +00:00
ivmarkov
2cde37899d Make the example working again 2023-07-21 12:15:11 +00:00
ivmarkov
443324a764 More inlines 2023-07-21 12:15:11 +00:00
ivmarkov
931e30601e Clippy 2023-07-21 12:15:11 +00:00
ivmarkov
357eb73c6f Control memory by removing implicit copy 2023-07-21 12:15:11 +00:00
ivmarkov
1e6cd69de8 built-in mDNS; memory optimizations 2023-07-21 12:15:11 +00:00
ivmarkov
bd61c95c7d no_std needs default features switched off for several crates 2023-07-21 12:15:11 +00:00
ivmarkov
870ae6f21c Move MATTER_PORT outside of STD-only udp module 2023-07-21 12:15:11 +00:00
ivmarkov
592d1ee028 Just use time-rs in no_std mode 2023-07-21 12:15:11 +00:00
ivmarkov
a4b8b53014 Builds for STD with ESP IDF 2023-07-21 12:15:10 +00:00
ivmarkov
9d59c79674 Colorizing is now no_std compatible 2023-07-21 12:12:55 +00:00
ivmarkov
e741cab89d More crypto fixes 2023-07-21 12:12:55 +00:00
ivmarkov
695869f13a Fix compilation errors in crypto 2023-07-21 12:12:55 +00:00
ivmarkov
06b0fcd6f5 Fix no_std errors 2023-07-21 12:12:55 +00:00
ivmarkov
89014ed7f2 Remove heapless::String from QR API 2023-07-21 12:12:55 +00:00
imarkov
974ac4d1d8 Optional feature to capture stacktrace on error 2023-07-21 12:12:55 +00:00
ivmarkov
3dccc0d710 Persistence - trace info 2023-07-21 12:12:55 +00:00
ivmarkov
934ecb9165 Persistence bugfixing 2023-07-21 12:12:55 +00:00
ivmarkov
86e01a0a1b Simple persistance via TLV 2023-07-21 12:12:55 +00:00
ivmarkov
4b39884f6f Bugfix: unnecessary struct container 2023-07-21 12:12:55 +00:00
ivmarkov
e8e847cea6 Bugfix: subscription_id was not sent 2023-07-21 12:12:55 +00:00
ivmarkov
076ba06e07 Bugfix: missing descriptor cluster 2023-07-21 12:12:55 +00:00
ivmarkov
635be2c35a Error log on arm failure 2023-07-21 12:12:55 +00:00
ivmarkov
2a57ecbd87 Bugfix: only report devtype for the queried endpoint 2023-07-21 12:12:55 +00:00
ivmarkov
09a523fc50 TX packets are reused; need way to reset them 2023-07-21 12:12:55 +00:00
ivmarkov
2fc4e6ddcf Root cert buffer too short 2023-07-21 12:12:55 +00:00
ivmarkov
9964466138 MRP standalone ack messages should not be acknowledged 2023-07-21 12:12:55 +00:00
ivmarkov
f804c21c0b Bugfix: fabric adding wrongly started at index 0 2023-07-21 12:12:55 +00:00
ivmarkov
f9536be1e3 Bugfix: two separate failsafe instances were used 2023-07-21 12:12:55 +00:00
ivmarkov
b2805570ea Restore transaction completion code 2023-07-21 12:12:55 +00:00
ivmarkov
9a23a2af2d Bugfix: arm failsafe was reporting wrong status 2023-07-21 12:12:55 +00:00
ivmarkov
7ef7e93eb4 Heap-allocated packets not necessary; no_std and no-alloc build supported end-to-end 2023-07-21 12:12:55 +00:00
ivmarkov
8b3bb9527c Comm with chip-tool 2023-07-21 12:12:54 +00:00
ivmarkov
36011c2e3c Actually add the bonjour feature 2023-07-21 12:12:54 +00:00
ivmarkov
eb3c9cdfb1 Cleanup a bit the mDns story 2023-07-21 12:12:54 +00:00
ivmarkov
2ea31432d5 On-off example now buildable 2023-07-21 12:12:54 +00:00
ivmarkov
faf5af3e1f no_std printing of QR code (kind of...) 2023-07-21 12:12:54 +00:00
ivmarkov
d558c73f8d Cleanup the dependencies as much as possible 2023-07-21 12:12:53 +00:00
ivmarkov
d934912007 Fix compilation error since the introduction of UtcCalendar 2023-07-21 12:09:56 +00:00
ivmarkov
688d7ea8d5 More ergonomic api when STD is available 2023-07-21 12:09:56 +00:00
ivmarkov
505fa39e82 Create new secure channel sessions without async-channel 2023-07-21 12:09:56 +00:00
ivmarkov
d9c99d73ee Chrono dep made optional 2023-07-21 12:09:56 +00:00
ivmarkov
bd87ac4ab3 Linux & MacOS mDNS services now implement the Mdns trait 2023-07-21 12:09:56 +00:00
ivmarkov
b4b549bb10 Fix several no_std incompatibilities 2023-07-21 12:09:56 +00:00
ivmarkov
bcbac965cd Remove allocations from Cert handling 2023-07-21 12:09:56 +00:00
ivmarkov
89aab6f444 Remove allocations from Base38 and QR calc 2023-07-21 12:09:56 +00:00
ivmarkov
fcc87bfaf4 Long reads and subscriptions reintroduced 2023-07-21 12:09:56 +00:00
ivmarkov
817d55aecc Start reintroducing long reads and subscriptions from mainline 2023-07-21 12:09:56 +00:00
ivmarkov
40f353c92e Support for no_std
Support for no_std

Further no_std compat
2023-07-21 12:09:56 +00:00
Kedar Sovani
4b85887f33
Merge pull request from simlay/refactor-ci-to-use-matrix-for-crypto-backends
Refactored CIs to use a matrix for cryptography backend feature flags.
2023-07-20 17:27:11 +05:30
Sebastian Imlay
15497a611a Renamed Test-Linux-OpenSSL to Test-Linux 2023-07-16 02:22:14 -04:00
Sebastian Imlay
a8dce54478 Refactored CIs to use a matrix for cryptographic backend featuer flags. 2023-07-16 02:16:03 -04:00
Kedar Sovani
2fe66318ca
Merge pull request from thekuwayama/modify__specify_mbedtls_version
modify: specify mbedtls version
2023-07-03 09:29:25 +05:30
thekuwayama
63cf24e38b modify: specify mbedtls version 2023-07-02 15:56:34 +09:00
Kedar Sovani
1de83701d8
Merge pull request from ivmarkov/main
Put a reference and explanation of the new branches
2023-06-25 00:09:08 -04:00
Restyled.io
41d4802e42 Restyled by prettier-markdown 2023-06-23 18:13:32 +00:00
ivmarkov
9ab2098309 Put a reference and explanation of the new branches 2023-06-23 18:04:09 +00:00
144 changed files with 3794 additions and 2103 deletions

View file

@ -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
View 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
View 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
View 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

View 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
View 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
View 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}}"

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -1,11 +1,11 @@
[workspace]
members = ["matter", "matter_macro_derive"]
resolver = "2"
members = ["rs-matter", "rs-matter-macros"]
exclude = ["examples/*"]
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" }

View file

@ -1,29 +1,32 @@
# matter-rs: The Rust Implementation of Matter
# rs-matter: The Rust Implementation of Matter
![experimental](https://img.shields.io/badge/status-Experimental-red) [![license](https://img.shields.io/badge/license-Apache2-green.svg)](https://raw.githubusercontent.com/project-chip/matter-rs/main/LICENSE)
[![Test Linux (OpenSSL)](https://github.com/project-chip/matter-rs/actions/workflows/test-linux-openssl.yml/badge.svg)](https://github.com/project-chip/matter-rs/actions/workflows/test-linux-openssl.yml)
[![Test Linux (mbedTLS)](https://github.com/project-chip/matter-rs/actions/workflows/test-linux-mbedtls.yml/badge.svg)](https://github.com/project-chip/matter-rs/actions/workflows/test-linux-mbedtls.yml)
![experimental](https://img.shields.io/badge/status-Experimental-red)
[![license](https://img.shields.io/badge/license-Apache2-green.svg)](https://raw.githubusercontent.com/project-chip/matter-rs/main/LICENSE)
[![CI](https://github.com/project-chip/matter-rs/actions/workflows/ci.yml/badge.svg)](https://github.com/project-chip/matter-rs/actions/workflows/ci.yml)
[![CI - TLV](https://github.com/project-chip/matter-rs/actions/workflows/ci-tlv-tool.yml/badge.svg)](https://github.com/project-chip/matter-rs/actions/workflows/ci-tlv-tool.yml)
[![crates.io](https://img.shields.io/crates/v/rs-matter.svg)](https://crates.io/crates/rs-matter)
[![Matrix](https://img.shields.io/matrix/matter-rs:matrix.org?label=join%20matrix&color=BEC5C9&logo=matrix)](https://matrix.to/#/#matter-rs:matrix.org)
## Build
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:

View file

@ -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 {}

View file

@ -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;

View file

@ -20,27 +20,26 @@ use core::pin::pin;
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::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::mdns::{DefaultMdns, DefaultMdnsRunner};
use matter::persist::FilePsm;
use matter::secure_channel::spake2p::VerifierData;
use matter::transport::network::{Ipv4Addr, Ipv6Addr};
use matter::transport::runner::{RxBuf, TransportRunner, TxBuf};
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(140 * 1024)
.stack_size(160 * 1024)
.spawn(run)
.unwrap();
@ -58,10 +57,11 @@ fn run() -> Result<(), Error> {
initialize_logger();
info!(
"Matter memory: mDNS={}, Matter={}, TransportRunner={}",
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::<TransportRunner>(),
core::mem::size_of::<MdnsRunBuffers>(),
core::mem::size_of::<RunBuffers>(),
);
let dev_det = BasicInfoConfig {
@ -72,45 +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 psm_path = std::env::temp_dir().join("matter-iot");
info!("Persisting from/to {}", psm_path.display());
#[cfg(all(feature = "std", not(target_os = "espidf")))]
let psm = matter::persist::FilePsm::new(psm_path)?;
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);
info!("mDNS initialized: {:p}, {:p}", &mdns, &mdns_runner);
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
@ -119,87 +112,62 @@ fn run() -> Result<(), Error> {
&mdns,
epoch,
rand,
matter::MATTER_PORT,
rs_matter::MATTER_PORT,
);
info!("Matter initialized: {:p}", &matter);
let mut runner = TransportRunner::new(&matter);
info!("Transport Runner initialized: {:p}", &runner);
let mut tx_buf = TxBuf::uninit();
let mut rx_buf = RxBuf::uninit();
info!("Matter initialized");
#[cfg(all(feature = "std", not(target_os = "espidf")))]
{
let mut buf = [0; 4096];
let buf = &mut buf;
if let Some(data) = psm.load("acls", buf)? {
matter.load_acls(data)?;
}
if let Some(data) = psm.load("fabrics", buf)? {
matter.load_fabrics(data)?;
}
}
let node = Node {
id: 0,
endpoints: &[
root_endpoint::endpoint(0),
Endpoint {
id: 1,
device_type: DEV_TYPE_ON_OFF_LIGHT,
clusters: &[descriptor::CLUSTER, cluster_on_off::CLUSTER],
},
],
};
let mut psm = rs_matter::persist::Psm::new(&matter, std::env::temp_dir().join("rs-matter"))?;
let handler = HandlerCompat(handler(&matter));
let matter = &matter;
let node = &node;
let handler = &handler;
let runner = &mut runner;
let tx_buf = &mut tx_buf;
let rx_buf = &mut rx_buf;
// 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();
info!(
"About to run wth node {:p}, handler {:p}, transport runner {:p}, mdns_runner {:p}",
node, handler, runner, &mdns_runner
);
let mut mdns_buffers = MdnsRunBuffers::new();
let mut mdns_runner = pin!(mdns.run(&stack, &mut mdns_buffers));
let mut fut = pin!(async move {
// 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 transport = pin!(runner.run_udp(
tx_buf,
rx_buf,
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,
},
&handler,
));
);
// 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 = pin!(mdns_runner.run_udp());
info!(
"Matter transport runner memory: {}",
core::mem::size_of_val(&runner)
);
let mut save = pin!(save(matter, &psm));
select3(&mut transport, &mut mdns, &mut save).await.unwrap()
});
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);
// NOTE: For no_std, replace with your own no_std way of polling the future
#[cfg(feature = "std")]
smol::block_on(&mut fut)?;
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 fut)?;
embassy_futures::block_on(&mut runner).unwrap()?;
Ok(())
}
@ -257,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| {
@ -303,26 +271,6 @@ fn initialize_network() -> Result<(Ipv4Addr, Ipv6Addr, u32), Error> {
Ok((ip, ipv6, 0 as _))
}
#[cfg(all(feature = "std", not(target_os = "espidf")))]
#[inline(never)]
async fn save(matter: &Matter<'_>, psm: &FilePsm) -> Result<(), Error> {
let mut buf = [0; 4096];
let buf = &mut buf;
loop {
matter.wait_changed().await;
if matter.is_changed() {
if let Some(data) = matter.store_acls(buf)? {
psm.store("acls", data)?;
}
if let Some(data) = matter.store_fabrics(buf)? {
psm.store("fabrics", data)?;
}
}
}
}
#[cfg(target_os = "espidf")]
#[inline(never)]
fn initialize_logger() {

View file

@ -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 {}

View file

@ -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;

View file

@ -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();

View file

@ -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();

View file

@ -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(())
}
}

View file

@ -1,415 +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::{borrow::Borrow, cell::RefCell};
use crate::{error::ErrorCode, secure_channel::common::OpCode, Matter};
use embassy_futures::select::select;
use embassy_time::{Duration, Timer};
use log::info;
use crate::{
error::Error, secure_channel::common::PROTO_ID_SECURE_CHANNEL, transport::packet::Packet,
};
use super::{
exchange::{
Exchange, ExchangeCtr, ExchangeCtx, ExchangeId, ExchangeState, Notification, Role,
MAX_EXCHANGES,
},
mrp::ReliableMessage,
session::SessionMgr,
};
#[derive(Debug)]
enum OpCodeDescriptor {
SecureChannel(OpCode),
InteractionModel(crate::interaction_model::core::OpCode),
Unknown(u8),
}
impl From<u8> for OpCodeDescriptor {
fn from(value: u8) -> Self {
if let Some(opcode) = num::FromPrimitive::from_u8(value) {
Self::SecureChannel(opcode)
} else if let Some(opcode) = num::FromPrimitive::from_u8(value) {
Self::InteractionModel(opcode)
} else {
Self::Unknown(value)
}
}
}
pub struct Transport<'a> {
matter: &'a Matter<'a>,
pub(crate) exchanges: RefCell<heapless::Vec<ExchangeCtx, MAX_EXCHANGES>>,
pub(crate) send_notification: Notification,
pub session_mgr: RefCell<SessionMgr>,
}
impl<'a> Transport<'a> {
#[inline(always)]
pub fn new(matter: &'a Matter<'a>) -> Self {
let epoch = matter.epoch;
let rand = matter.rand;
Self {
matter,
exchanges: RefCell::new(heapless::Vec::new()),
send_notification: Notification::new(),
session_mgr: RefCell::new(SessionMgr::new(epoch, rand)),
}
}
pub fn matter(&self) -> &'a Matter<'a> {
self.matter
}
pub async fn initiate(&self, _fabric_id: u64, _node_id: u64) -> Result<Exchange<'a>, Error> {
unimplemented!()
}
pub fn process_rx<'r>(
&'r self,
construction_notification: &'r Notification,
src_rx: &mut Packet<'_>,
) -> Result<Option<ExchangeCtr<'r>>, Error> {
self.purge()?;
let mut exchanges = self.exchanges.borrow_mut();
let (ctx, new) = match self.post_recv(&mut exchanges, src_rx) {
Ok((ctx, new)) => (ctx, new),
Err(e) => match e.code() {
ErrorCode::Duplicate => {
self.send_notification.signal(());
return Ok(None);
}
_ => Err(e)?,
},
};
src_rx.log("Got packet");
if src_rx.proto.is_ack() {
if new {
Err(ErrorCode::Invalid)?;
} else {
let state = &mut ctx.state;
match state {
ExchangeState::ExchangeRecv {
tx_acknowledged, ..
} => {
*tx_acknowledged = true;
}
ExchangeState::CompleteAcknowledge { notification, .. } => {
unsafe { notification.as_ref() }.unwrap().signal(());
ctx.state = ExchangeState::Closed;
}
_ => {
// TODO: Error handling
todo!()
}
}
self.matter().notify_changed();
}
}
if new {
let constructor = ExchangeCtr {
exchange: Exchange {
id: ctx.id.clone(),
transport: self,
notification: Notification::new(),
},
construction_notification,
};
self.matter().notify_changed();
Ok(Some(constructor))
} else if src_rx.proto.proto_id == PROTO_ID_SECURE_CHANNEL
&& src_rx.proto.proto_opcode == OpCode::MRPStandAloneAck as u8
{
// Standalone ack, do nothing
Ok(None)
} else {
let state = &mut ctx.state;
match state {
ExchangeState::ExchangeRecv {
rx, notification, ..
} => {
let rx = unsafe { rx.as_mut() }.unwrap();
rx.load(src_rx)?;
unsafe { notification.as_ref() }.unwrap().signal(());
*state = ExchangeState::Active;
}
_ => {
// TODO: Error handling
todo!()
}
}
self.matter().notify_changed();
Ok(None)
}
}
pub async fn wait_construction(
&self,
construction_notification: &Notification,
src_rx: &Packet<'_>,
exchange_id: &ExchangeId,
) -> Result<(), Error> {
construction_notification.wait().await;
let mut exchanges = self.exchanges.borrow_mut();
let ctx = Self::get(&mut exchanges, exchange_id).unwrap();
let state = &mut ctx.state;
match state {
ExchangeState::Construction { rx, notification } => {
let rx = unsafe { rx.as_mut() }.unwrap();
rx.load(src_rx)?;
unsafe { notification.as_ref() }.unwrap().signal(());
*state = ExchangeState::Active;
}
_ => unreachable!(),
}
Ok(())
}
pub async fn wait_tx(&self) -> Result<(), Error> {
select(
self.send_notification.wait(),
Timer::after(Duration::from_millis(100)),
)
.await;
Ok(())
}
pub async fn pull_tx(&self, dest_tx: &mut Packet<'_>) -> Result<bool, Error> {
self.purge()?;
let mut exchanges = self.exchanges.borrow_mut();
let ctx = exchanges.iter_mut().find(|ctx| {
matches!(
&ctx.state,
ExchangeState::Acknowledge { .. }
| ExchangeState::ExchangeSend { .. }
// | ExchangeState::ExchangeRecv {
// tx_acknowledged: false,
// ..
// }
| ExchangeState::Complete { .. } // | ExchangeState::CompleteAcknowledge { .. }
) || ctx.mrp.is_ack_ready(*self.matter.borrow())
});
if let Some(ctx) = ctx {
self.matter().notify_changed();
let state = &mut ctx.state;
let send = match state {
ExchangeState::Acknowledge { notification } => {
ReliableMessage::prepare_ack(ctx.id.id, dest_tx);
unsafe { notification.as_ref() }.unwrap().signal(());
*state = ExchangeState::Active;
true
}
ExchangeState::ExchangeSend {
tx,
rx,
notification,
} => {
let tx = unsafe { tx.as_ref() }.unwrap();
dest_tx.load(tx)?;
*state = ExchangeState::ExchangeRecv {
_tx: tx,
tx_acknowledged: false,
rx: *rx,
notification: *notification,
};
true
}
// ExchangeState::ExchangeRecv { .. } => {
// // TODO: Re-send the tx package if due
// false
// }
ExchangeState::Complete { tx, notification } => {
let tx = unsafe { tx.as_ref() }.unwrap();
dest_tx.load(tx)?;
*state = ExchangeState::CompleteAcknowledge {
_tx: tx as *const _,
notification: *notification,
};
true
}
// ExchangeState::CompleteAcknowledge { .. } => {
// // TODO: Re-send the tx package if due
// false
// }
_ => {
ReliableMessage::prepare_ack(ctx.id.id, dest_tx);
true
}
};
if send {
dest_tx.log("Sending packet");
self.pre_send(ctx, dest_tx)?;
self.matter().notify_changed();
return Ok(true);
}
}
Ok(false)
}
fn purge(&self) -> Result<(), Error> {
loop {
let mut exchanges = self.exchanges.borrow_mut();
if let Some(index) = exchanges.iter_mut().enumerate().find_map(|(index, ctx)| {
matches!(ctx.state, ExchangeState::Closed).then_some(index)
}) {
exchanges.swap_remove(index);
} else {
break;
}
}
Ok(())
}
fn post_recv<'r>(
&self,
exchanges: &'r mut heapless::Vec<ExchangeCtx, MAX_EXCHANGES>,
rx: &mut Packet<'_>,
) -> Result<(&'r mut ExchangeCtx, bool), Error> {
rx.plain_hdr_decode()?;
// Get the session
let mut session_mgr = self.session_mgr.borrow_mut();
let sess_index = session_mgr.post_recv(rx)?;
let session = session_mgr.mut_by_index(sess_index).unwrap();
// Decrypt the message
session.recv(self.matter.epoch, rx)?;
// Get the exchange
// TODO: Handle out of space
let (exch, new) = Self::register(
exchanges,
ExchangeId::load(rx),
Role::complementary(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.matter.epoch)?;
Ok((exch, new))
}
fn pre_send(&self, ctx: &mut ExchangeCtx, tx: &mut Packet) -> Result<(), Error> {
let mut session_mgr = self.session_mgr.borrow_mut();
let sess_index = session_mgr
.get(
ctx.id.session_id.id,
ctx.id.session_id.peer_addr,
ctx.id.session_id.peer_nodeid,
ctx.id.session_id.is_encrypted,
)
.ok_or(ErrorCode::NoSession)?;
let session = session_mgr.mut_by_index(sess_index).unwrap();
tx.proto.exch_id = ctx.id.id;
if ctx.role == Role::Initiator {
tx.proto.set_initiator();
}
session.pre_send(tx)?;
ctx.mrp.pre_send(tx)?;
session_mgr.send(sess_index, tx)
}
fn register(
exchanges: &mut heapless::Vec<ExchangeCtx, MAX_EXCHANGES>,
id: ExchangeId,
role: Role,
create_new: bool,
) -> Result<(&mut ExchangeCtx, bool), Error> {
let exchange_index = exchanges
.iter_mut()
.enumerate()
.find_map(|(index, exchange)| (exchange.id == id).then_some(index));
if let Some(exchange_index) = exchange_index {
let exchange = &mut exchanges[exchange_index];
if exchange.role == role {
Ok((exchange, false))
} else {
Err(ErrorCode::NoExchange.into())
}
} else if create_new {
info!("Creating new exchange: {:?}", id);
let exchange = ExchangeCtx {
id,
role,
mrp: ReliableMessage::new(),
state: ExchangeState::Active,
};
exchanges.push(exchange).map_err(|_| ErrorCode::NoSpace)?;
Ok((exchanges.iter_mut().next_back().unwrap(), true))
} else {
Err(ErrorCode::NoExchange.into())
}
}
pub(crate) fn get<'r>(
exchanges: &'r mut heapless::Vec<ExchangeCtx, MAX_EXCHANGES>,
id: &ExchangeId,
) -> Option<&'r mut ExchangeCtx> {
exchanges.iter_mut().find(|exchange| exchange.id == *id)
}
}

View file

@ -1,392 +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::{mem::MaybeUninit, pin::pin};
use crate::{
alloc,
data_model::{core::DataModel, objects::DataModelHandler},
interaction_model::core::PROTO_ID_INTERACTION_MODEL,
transport::network::{Address, IpAddr, Ipv6Addr, SocketAddr},
CommissioningData, Matter,
};
use embassy_futures::select::{select, select3, select_slice, Either};
use embassy_sync::{blocking_mutex::raw::NoopRawMutex, channel::Channel};
use log::{error, info, warn};
use crate::{
error::Error,
secure_channel::{common::PROTO_ID_SECURE_CHANNEL, core::SecureChannel},
transport::packet::{Packet, MAX_RX_BUF_SIZE},
utils::select::EitherUnwrap,
};
use super::{
core::Transport,
exchange::{ExchangeCtr, Notification, MAX_EXCHANGES},
packet::{MAX_RX_STATUS_BUF_SIZE, MAX_TX_BUF_SIZE},
pipe::{Chunk, Pipe},
udp::UdpListener,
};
pub type TxBuf = MaybeUninit<[u8; MAX_TX_BUF_SIZE]>;
pub type RxBuf = MaybeUninit<[u8; MAX_RX_BUF_SIZE]>;
type SxBuf = MaybeUninit<[u8; MAX_RX_STATUS_BUF_SIZE]>;
struct PacketPools {
tx: [TxBuf; MAX_EXCHANGES],
rx: [RxBuf; MAX_EXCHANGES],
sx: [SxBuf; MAX_EXCHANGES],
}
impl PacketPools {
const TX_ELEM: TxBuf = MaybeUninit::uninit();
const RX_ELEM: RxBuf = MaybeUninit::uninit();
const SX_ELEM: SxBuf = MaybeUninit::uninit();
const TX_INIT: [TxBuf; MAX_EXCHANGES] = [Self::TX_ELEM; MAX_EXCHANGES];
const RX_INIT: [RxBuf; MAX_EXCHANGES] = [Self::RX_ELEM; MAX_EXCHANGES];
const SX_INIT: [SxBuf; MAX_EXCHANGES] = [Self::SX_ELEM; MAX_EXCHANGES];
#[inline(always)]
pub const fn new() -> Self {
Self {
tx: Self::TX_INIT,
rx: Self::RX_INIT,
sx: Self::SX_INIT,
}
}
}
/// This struct implements an executor-agnostic option to run the Matter transport stack end-to-end.
///
/// Since it is not possible to use executor tasks spawning in an executor-agnostic way (yet),
/// the async loops are arranged as one giant future. Therefore, the cost is a slightly slower execution
/// due to the generated future being relatively big and deeply nested.
///
/// Users are free to implement their own async execution loop, by utilizing the `Transport`
/// struct directly with their async executor of choice.
pub struct TransportRunner<'a> {
transport: Transport<'a>,
pools: PacketPools,
}
impl<'a> TransportRunner<'a> {
#[inline(always)]
pub fn new(matter: &'a Matter<'a>) -> Self {
Self::wrap(Transport::new(matter))
}
#[inline(always)]
pub const fn wrap(transport: Transport<'a>) -> Self {
Self {
transport,
pools: PacketPools::new(),
}
}
pub fn transport(&self) -> &Transport {
&self.transport
}
pub async fn run_udp<H>(
&mut self,
tx_buf: &mut TxBuf,
rx_buf: &mut RxBuf,
dev_comm: CommissioningData,
handler: &H,
) -> Result<(), Error>
where
H: DataModelHandler,
{
let udp = UdpListener::new(SocketAddr::new(
IpAddr::V6(Ipv6Addr::UNSPECIFIED),
self.transport.matter().port,
))
.await?;
let tx_pipe = Pipe::new(unsafe { tx_buf.assume_init_mut() });
let rx_pipe = Pipe::new(unsafe { rx_buf.assume_init_mut() });
let tx_pipe = &tx_pipe;
let rx_pipe = &rx_pipe;
let udp = &udp;
let mut tx = pin!(async move {
loop {
{
let mut data = tx_pipe.data.lock().await;
if let Some(chunk) = data.chunk {
udp.send(chunk.addr.unwrap_udp(), &data.buf[chunk.start..chunk.end])
.await?;
data.chunk = None;
tx_pipe.data_consumed_notification.signal(());
}
}
tx_pipe.data_supplied_notification.wait().await;
}
});
let mut rx = pin!(async move {
loop {
{
let mut data = rx_pipe.data.lock().await;
if data.chunk.is_none() {
let (len, addr) = udp.recv(data.buf).await?;
data.chunk = Some(Chunk {
start: 0,
end: len,
addr: Address::Udp(addr),
});
rx_pipe.data_supplied_notification.signal(());
}
}
rx_pipe.data_consumed_notification.wait().await;
}
});
let mut run = pin!(async move { self.run(tx_pipe, rx_pipe, dev_comm, handler).await });
select3(&mut tx, &mut rx, &mut run).await.unwrap()
}
pub async fn run<H>(
&mut self,
tx_pipe: &Pipe<'_>,
rx_pipe: &Pipe<'_>,
dev_comm: CommissioningData,
handler: &H,
) -> Result<(), Error>
where
H: DataModelHandler,
{
info!("Running Matter transport");
let buf = unsafe { self.pools.rx[0].assume_init_mut() };
if self.transport.matter().start_comissioning(dev_comm, buf)? {
info!("Comissioning started");
}
let construction_notification = Notification::new();
let mut rx = pin!(Self::handle_rx(
&self.transport,
&mut self.pools,
rx_pipe,
&construction_notification,
handler
));
let mut tx = pin!(Self::handle_tx(&self.transport, tx_pipe));
select(&mut rx, &mut tx).await.unwrap()
}
async fn handle_rx<H>(
transport: &Transport<'_>,
pools: &mut PacketPools,
rx_pipe: &Pipe<'_>,
construction_notification: &Notification,
handler: &H,
) -> Result<(), Error>
where
H: DataModelHandler,
{
info!("Creating queue for {} exchanges", 1);
let channel = Channel::<NoopRawMutex, _, 1>::new();
info!("Creating {} handlers", MAX_EXCHANGES);
let mut handlers = heapless::Vec::<_, MAX_EXCHANGES>::new();
info!("Handlers size: {}", core::mem::size_of_val(&handlers));
let pools = &mut *pools as *mut _;
for index in 0..MAX_EXCHANGES {
let channel = &channel;
let handler_id = index;
handlers
.push(async move {
loop {
let exchange_ctr: ExchangeCtr<'_> = channel.recv().await;
info!(
"Handler {}: Got exchange {:?}",
handler_id,
exchange_ctr.id()
);
let result = Self::handle_exchange(
transport,
pools,
handler_id,
exchange_ctr,
handler,
)
.await;
if let Err(err) = result {
warn!(
"Handler {}: Exchange closed because of error: {:?}",
handler_id, err
);
} else {
info!("Handler {}: Exchange completed", handler_id);
}
}
})
.map_err(|_| ())
.unwrap();
}
let mut rx = pin!(async {
loop {
info!("Transport: waiting for incoming packets");
{
let mut data = rx_pipe.data.lock().await;
if let Some(chunk) = data.chunk {
let mut rx = alloc!(Packet::new_rx(&mut data.buf[chunk.start..chunk.end]));
rx.peer = chunk.addr;
if let Some(exchange_ctr) =
transport.process_rx(construction_notification, &mut rx)?
{
let exchange_id = exchange_ctr.id().clone();
info!("Transport: got new exchange: {:?}", exchange_id);
channel.send(exchange_ctr).await;
info!("Transport: exchange sent");
transport
.wait_construction(construction_notification, &rx, &exchange_id)
.await?;
info!("Transport: exchange started");
}
data.chunk = None;
rx_pipe.data_consumed_notification.signal(());
}
}
rx_pipe.data_supplied_notification.wait().await
}
#[allow(unreachable_code)]
Ok::<_, Error>(())
});
let result = select(&mut rx, select_slice(&mut handlers)).await;
if let Either::First(result) = result {
if let Err(e) = &result {
error!("Exitting RX loop due to an error: {:?}", e);
}
result?;
}
Ok(())
}
async fn handle_tx(transport: &Transport<'_>, tx_pipe: &Pipe<'_>) -> Result<(), Error> {
loop {
loop {
{
let mut data = tx_pipe.data.lock().await;
if data.chunk.is_none() {
let mut tx = alloc!(Packet::new_tx(data.buf));
if transport.pull_tx(&mut tx).await? {
data.chunk = Some(Chunk {
start: tx.get_writebuf()?.get_start(),
end: tx.get_writebuf()?.get_tail(),
addr: tx.peer,
});
tx_pipe.data_supplied_notification.signal(());
} else {
break;
}
}
}
tx_pipe.data_consumed_notification.wait().await;
}
transport.wait_tx().await?;
}
}
#[cfg_attr(feature = "nightly", allow(clippy::await_holding_refcell_ref))] // Fine because of the async mutex
async fn handle_exchange<H>(
transport: &Transport<'_>,
pools: *mut PacketPools,
handler_id: usize,
exchange_ctr: ExchangeCtr<'_>,
handler: &H,
) -> Result<(), Error>
where
H: DataModelHandler,
{
let pools = unsafe { pools.as_mut() }.unwrap();
let tx_buf = unsafe { pools.tx[handler_id].assume_init_mut() };
let rx_buf = unsafe { pools.rx[handler_id].assume_init_mut() };
let rx_status_buf = unsafe { pools.sx[handler_id].assume_init_mut() };
let mut rx = alloc!(Packet::new_rx(rx_buf.as_mut()));
let mut tx = alloc!(Packet::new_tx(tx_buf.as_mut()));
let mut exchange = alloc!(exchange_ctr.get(&mut rx).await?);
match rx.get_proto_id() {
PROTO_ID_SECURE_CHANNEL => {
let sc = SecureChannel::new(transport.matter());
sc.handle(&mut exchange, &mut rx, &mut tx).await?;
transport.matter().notify_changed();
}
PROTO_ID_INTERACTION_MODEL => {
let dm = DataModel::new(handler);
let mut rx_status = alloc!(Packet::new_rx(rx_status_buf));
dm.handle(&mut exchange, &mut rx, &mut tx, &mut rx_status)
.await?;
transport.matter().notify_changed();
}
other => {
error!("Unknown Proto-ID: {}", other);
}
}
Ok(())
}
}

View file

@ -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())
}
}
}

View file

@ -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"

View 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"

View 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.

View file

@ -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
});

View file

@ -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
View 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.

View file

@ -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;

View file

@ -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();

View file

@ -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,
];
}
}

View file

@ -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();

View file

@ -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,8 +30,11 @@ use crate::{
mdns::Mdns,
pairing::{print_pairing_code_and_qr, DiscoveryCapabilities},
secure_channel::{pake::PaseMgr, spake2p::VerifierData},
transport::exchange::Notification,
utils::{epoch::Epoch, rand::Rand},
transport::{
exchange::{ExchangeCtx, MAX_EXCHANGES},
session::SessionMgr,
},
utils::{epoch::Epoch, rand::Rand, select::Notification},
};
/* The Matter Port */
@ -45,17 +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 persist_notification: Notification,
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> {
@ -94,12 +104,17 @@ impl<'a> Matter<'a> {
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)),
}
}
@ -160,6 +175,7 @@ impl<'a> Matter<'a> {
Ok(false)
}
}
pub fn notify_changed(&self) {
if self.is_changed() {
self.persist_notification.signal(());

View file

@ -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")
}
}

View file

@ -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")
}
}

View file

@ -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 {

View file

@ -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> {}

View file

@ -54,7 +54,7 @@ pub const CLUSTER: Cluster<'static> = Cluster {
Attribute::new(
AttributesDiscriminants::OnOff as u16,
Access::RV,
Quality::PERSISTENT,
Quality::SN,
),
],
commands: &[

View file

@ -15,7 +15,7 @@
* limitations under the License.
*/
use core::sync::atomic::{AtomicU32, Ordering};
use portable_atomic::{AtomicU32, Ordering};
use super::objects::*;
use crate::{
@ -28,6 +28,12 @@ use crate::{
// 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> {
@ -93,8 +99,21 @@ impl<T> DataModel<T> {
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 metadata.node().write(req, &accessor) {
for item in write_attrs {
AttrDataEncoder::handle_write(&item, &self.0, &mut driver.writer()?)
.await?;
}

View file

@ -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;
}
}

View file

@ -141,7 +141,7 @@ impl<'a, 'b, 'c> AttrDataEncoder<'a, 'b, 'c> {
#[cfg(feature = "nightly")]
{
handler.read(&attr, encoder).await
handler.read(attr, encoder).await
}
};
@ -181,9 +181,7 @@ impl<'a, 'b, 'c> AttrDataEncoder<'a, 'b, 'c> {
#[cfg(feature = "nightly")]
{
handler
.write(&attr, AttrData::new(attr.dataver, &data))
.await
handler.write(attr, AttrData::new(attr.dataver, data)).await
}
};
@ -357,7 +355,7 @@ impl<'a, 'b, 'c> CmdDataEncoder<'a, 'b, 'c> {
#[cfg(feature = "nightly")]
{
handler.invoke(exchange, &cmd, &data, encoder).await
handler.invoke(exchange, cmd, data, encoder).await
}
};

View file

@ -1,3 +1,20 @@
/*
*
* 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")]

View file

@ -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,6 +51,9 @@ 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 const fn endpoint(id: EndptId) -> Endpoint<'static> {
@ -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,

View 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(())
}
}

View file

@ -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 {

View file

@ -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()
}
}

View 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(())
}
}

View 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(())
}
}

View file

@ -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;

View file

@ -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,
@ -358,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(
@ -601,6 +605,20 @@ impl<'a> NocCluster<'a> {
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(
&self,
exchange: &Exchange,
@ -613,21 +631,12 @@ impl<'a> NocCluster<'a> {
// This may happen on CASE or PASE. For PASE, the existence of NOC Data is necessary
match exchange.with_session(|sess| Ok(sess.get_session_mode().clone()))? {
SessionMode::Case(_) => error!("CASE: AddTrustedRootCert handling pending"), // For a CASE Session, we just return success for now,
SessionMode::Case(_) => {
// TODO - Updating the Trusted RCA of an existing Fabric
Self::add_rca_to_session_noc_data(exchange, data)?;
}
SessionMode::Pase => {
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(())
})?;
// TODO
Self::add_rca_to_session_noc_data(exchange, data)?;
}
_ => (),
}

View 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(())
}
}

View file

@ -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")]

View file

@ -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> {

View file

@ -609,7 +609,7 @@ impl<'a, 'r, 'p> Interaction<'a, 'r, 'p> {
rx: &mut Packet<'_>,
tx: &mut Packet<'_>,
) -> Result<Option<Duration>, Error> {
let epoch = exchange.transport().matter().epoch;
let epoch = exchange.matter.epoch;
let mut opcode: OpCode = rx.get_proto_opcode()?;
@ -632,7 +632,7 @@ impl<'a, 'r, 'p> Interaction<'a, 'r, 'p> {
#[inline(always)]
pub fn new<S>(
exchange: &'r mut Exchange<'a>,
rx: &'r mut Packet<'p>,
rx: &'r Packet<'p>,
tx: &'r mut Packet<'p>,
rx_status: &'r mut Packet<'p>,
subscription_id: S,
@ -641,7 +641,7 @@ impl<'a, 'r, 'p> Interaction<'a, 'r, 'p> {
where
S: FnOnce() -> u32,
{
let epoch = exchange.transport().matter().epoch;
let epoch = exchange.matter.epoch;
let opcode = rx.get_proto_opcode()?;
let rx_data = rx.as_slice();

View file

@ -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,12 +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(async_fn_in_trait))]
#![cfg_attr(feature = "nightly", feature(impl_trait_projections))]
#![cfg_attr(feature = "nightly", allow(incomplete_features))]
pub mod acl;
pub mod cert;

View file

@ -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;

View file

@ -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)
}
}

View file

@ -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)
}
}

View 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)
}
}

View file

@ -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,

View file

@ -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,

View file

@ -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)?;

View file

@ -87,33 +87,20 @@ impl<'a> Case<'a> {
self.handle_casesigma3(exchange, rx, tx, &mut session).await
}
#[allow(clippy::await_holding_refcell_ref)]
async fn handle_casesigma3(
&mut self,
exchange: &mut Exchange<'_>,
rx: &mut Packet<'_>,
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() {
drop(fabric_mgr);
complete_with_status(
exchange,
tx,
common::SCStatusCodes::NoSharedTrustRoots,
None,
)
.await?;
return Ok(());
}
// Safe to unwrap here
let fabric = fabric.unwrap();
if let Some(fabric) = fabric {
let root = get_root_node_struct(rx.as_slice())?;
let encrypted = root.find_tag(1)?.slice()?;
@ -125,7 +112,8 @@ 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)?;
@ -145,52 +133,47 @@ impl<'a> Case<'a> {
if let Err(e) = Case::validate_certs(fabric, &initiator_noc, initiator_icac_mut) {
error!("Certificate Chain doesn't match: {}", e);
complete_with_status(exchange, tx, common::SCStatusCodes::InvalidParameter, None)
.await?;
return Ok(());
}
if Case::validate_sigma3_sign(
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");
complete_with_status(exchange, tx, common::SCStatusCodes::InvalidParameter, None)
.await?;
return Ok(());
}
) {
error!("Sigma3 Signature doesn't match: {}", e);
Err(SCStatusCodes::InvalidParameter)
} else {
// Only now do we add this message to the TT Hash
let mut peer_catids: NocCatIds = Default::default();
initiator_noc.get_cat_ids(&mut peer_catids);
case_session.tt_hash.update(rx.as_slice())?;
let clone_data = Case::get_session_clone_data(
Ok(Case::get_session_clone_data(
fabric.ipk.op_key(),
fabric.get_node_id(),
initiator_noc.get_node_id()?,
exchange.with_session(|sess| Ok(sess.get_peer_addr()))?,
case_session,
&peer_catids,
)?;
)?)
}
} else {
Err(SCStatusCodes::NoSharedTrustRoots)
}
};
// TODO: Handle NoSpace
exchange.with_session_mgr_mut(|sess_mgr| sess_mgr.clone_session(&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,
SCStatusCodes::SessionEstablishmentSuccess,
None,
)
.await
complete_with_status(exchange, tx, status, None).await
}
#[allow(clippy::await_holding_refcell_ref)]
async fn handle_casesigma1(
&mut self,
exchange: &mut Exchange<'_>,
@ -221,7 +204,7 @@ impl<'a> Case<'a> {
return Ok(());
}
let local_sessid = exchange.with_session_mgr_mut(|mgr| Ok(mgr.get_next_sess_id()))?;
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)?;
@ -255,23 +238,13 @@ impl<'a> Case<'a> {
const MAX_ENCRYPTED_SIZE: usize = 800;
let mut encrypted = alloc!([0; MAX_ENCRYPTED_SIZE]);
let encrypted_len = {
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() {
drop(fabric_mgr);
complete_with_status(
exchange,
tx,
common::SCStatusCodes::NoSharedTrustRoots,
None,
)
.await?;
return Ok(());
}
if let Some(fabric) = fabric {
#[cfg(feature = "alloc")]
let signature_mut = &mut *signature;
@ -279,7 +252,7 @@ impl<'a> Case<'a> {
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,
signature_mut,
@ -292,15 +265,15 @@ impl<'a> Case<'a> {
#[cfg(not(feature = "alloc"))]
let encrypted_mut = &mut encrypted;
Case::get_sigma2_encryption(
fabric.unwrap(),
let encrypted_len = Case::get_sigma2_encryption(
fabric,
self.rand,
&our_random,
case_session,
signature,
encrypted_mut,
)?
};
)?;
let encrypted = &encrypted[0..encrypted_len];
// Generate our Response Body
@ -318,7 +291,23 @@ impl<'a> Case<'a> {
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(
@ -485,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];
@ -515,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> {

View file

@ -78,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,
};

View file

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

View file

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

View file

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

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