Compare commits

..

152 commits

Author SHA1 Message Date
Kedar Sovani
97c9619dba
Merge pull request #94 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 #112 from ivmarkov/ci
Fix nightly CI
2023-10-06 22:33:14 +05:30
Kedar Sovani
7b55e7fbfb
Merge pull request #110 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 #100

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 #104 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 #93 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 #97 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 #99 from ivmarkov/main
#98 with signed CLA
2023-09-25 12:19:02 +05:30
ivmarkov
793870c6cc #98 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 #95 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 #88 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 #92 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 #91 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 #87 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 #85 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 #84 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 #81 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 #80 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 #82 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 #79 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 #78 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 #77 from thekuwayama/fix__quality
modify: Quality bits into separated flags
2023-07-25 09:10:02 +05:30
Kedar Sovani
1fccb18464
Merge pull request #75 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 #76 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 #74 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 #72 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 #60 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 #70 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 #64 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 #57 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 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,
);
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,
CommissioningData {
// TODO: Hard-coded for now
verifier: VerifierData::new_with_pw(123456, *matter.borrow()),
discriminator: 250,
},
&handler,
));
info!(
"Matter transport runner memory: {}",
core::mem::size_of_val(&runner)
);
// 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());
let mut runner = pin!(runner);
let mut save = pin!(save(matter, &psm));
select3(&mut transport, &mut mdns, &mut save).await.unwrap()
});
#[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,12 +372,17 @@ 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() {
entries_exist = true;
if (t.endpoint.is_none() || t.endpoint == object.path.endpoint)
&& (t.cluster.is_none() || t.cluster == object.path.cluster)
{
allow = true
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)
{
allow = true
}
}
}
}
if !entries_exist {

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
expiry_len: 120,
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,13 +358,17 @@ 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)?;
info!("Received ICAC as: {}", icac_cert);
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)?;
Some(icac)
let icac =
heapless::Vec::from_slice(icac_value.0).map_err(|_| NocStatus::InvalidNOC)?;
Some(icac)
} else {
None
}
} else {
None
};
@ -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,7 +250,21 @@ impl fmt::Debug for Error {
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{:?}", self.code())
#[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())
}
}
}

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)?;
}
if let Some(data) = Self::load(&dir, "fabrics", &mut buf)? {
matter.load_fabrics(data)?;
}
Ok(Self { matter, dir, buf })
}
pub fn load<'a>(&self, key: &str, buf: &'a mut [u8]) -> Result<Option<&'a [u8]>, Error> {
let path = self.dir.join(key);
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,110 +87,93 @@ 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 fabric_mgr = self.fabric_mgr.borrow();
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();
let fabric = fabric_mgr.get_fabric(case_session.local_fabric_idx)?;
if let Some(fabric) = fabric {
let root = get_root_node_struct(rx.as_slice())?;
let encrypted = root.find_tag(1)?.slice()?;
let root = get_root_node_struct(rx.as_slice())?;
let encrypted = root.find_tag(1)?.slice()?;
let mut decrypted = alloc!([0; 800]);
if encrypted.len() > decrypted.len() {
error!("Data too large");
Err(ErrorCode::NoSpace)?;
}
let decrypted = &mut decrypted[..encrypted.len()];
decrypted.copy_from_slice(encrypted);
let mut decrypted = alloc!([0; 800]);
if encrypted.len() > decrypted.len() {
error!("Data too large");
Err(ErrorCode::NoSpace)?;
}
let decrypted = &mut decrypted[..encrypted.len()];
decrypted.copy_from_slice(encrypted);
let len =
Case::get_sigma3_decryption(fabric.ipk.op_key(), case_session, decrypted)?;
let decrypted = &decrypted[..len];
let len = Case::get_sigma3_decryption(fabric.ipk.op_key(), case_session, decrypted)?;
let decrypted = &decrypted[..len];
let root = get_root_node_struct(decrypted)?;
let d = Sigma3Decrypt::from_tlv(&root)?;
let root = get_root_node_struct(decrypted)?;
let d = Sigma3Decrypt::from_tlv(&root)?;
let initiator_noc = alloc!(Cert::new(d.initiator_noc.0)?);
let mut initiator_icac = None;
if let Some(icac) = d.initiator_icac {
initiator_icac = Some(alloc!(Cert::new(icac.0)?));
}
let initiator_noc = alloc!(Cert::new(d.initiator_noc.0)?);
let mut initiator_icac = None;
if let Some(icac) = d.initiator_icac {
initiator_icac = Some(alloc!(Cert::new(icac.0)?));
}
#[cfg(feature = "alloc")]
let initiator_icac_mut = initiator_icac.as_deref();
#[cfg(feature = "alloc")]
let initiator_icac_mut = initiator_icac.as_deref();
#[cfg(not(feature = "alloc"))]
let initiator_icac_mut = initiator_icac.as_ref();
#[cfg(not(feature = "alloc"))]
let initiator_icac_mut = initiator_icac.as_ref();
if let Err(e) = Case::validate_certs(fabric, &initiator_noc, initiator_icac_mut) {
error!("Certificate Chain doesn't match: {}", e);
Err(SCStatusCodes::InvalidParameter)
} else if let Err(e) = Case::validate_sigma3_sign(
d.initiator_noc.0,
d.initiator_icac.map(|a| a.0),
&initiator_noc,
d.signature.0,
case_session,
) {
error!("Sigma3 Signature doesn't match: {}", e);
Err(SCStatusCodes::InvalidParameter)
} else {
// Only now do we add this message to the TT Hash
let mut peer_catids: NocCatIds = Default::default();
initiator_noc.get_cat_ids(&mut peer_catids);
case_session.tt_hash.update(rx.as_slice())?;
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(());
}
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)
}
};
if Case::validate_sigma3_sign(
d.initiator_noc.0,
d.initiator_icac.map(|a| a.0),
&initiator_noc,
d.signature.0,
case_session,
)
.is_err()
{
error!("Sigma3 Signature doesn't match");
complete_with_status(exchange, tx, common::SCStatusCodes::InvalidParameter, None)
.await?;
return Ok(());
}
let status = match result {
Ok(clone_data) => {
exchange.clone_session(tx, &clone_data).await?;
SCStatusCodes::SessionEstablishmentSuccess
}
Err(status) => status,
};
// 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(
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,
)?;
// TODO: Handle NoSpace
exchange.with_session_mgr_mut(|sess_mgr| sess_mgr.clone_session(&clone_data))?;
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,70 +238,76 @@ 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 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;
#[cfg(not(feature = "alloc"))]
let signature_mut = &mut signature;
let sign_len = Case::get_sigma2_sign(
fabric,
&case_session.our_pub_key,
&case_session.peer_pub_key,
signature_mut,
)?;
let signature = &signature[..sign_len];
#[cfg(feature = "alloc")]
let encrypted_mut = &mut *encrypted;
#[cfg(not(feature = "alloc"))]
let encrypted_mut = &mut encrypted;
let encrypted_len = Case::get_sigma2_encryption(
fabric,
self.rand,
&our_random,
case_session,
signature,
encrypted_mut,
)?;
let encrypted = &encrypted[0..encrypted_len];
// Generate our Response Body
tx.reset();
tx.set_proto_id(PROTO_ID_SECURE_CHANNEL);
tx.set_proto_opcode(OpCode::CASESigma2 as u8);
let mut tw = TLVWriter::new(tx.get_writebuf()?);
tw.start_struct(TagType::Anonymous)?;
tw.str8(TagType::Context(1), &our_random)?;
tw.u16(TagType::Context(2), local_sessid)?;
tw.str8(TagType::Context(3), &case_session.our_pub_key)?;
tw.str16(TagType::Context(4), encrypted)?;
tw.end_container()?;
case_session.tt_hash.update(tx.as_mut_slice())?;
true
} else {
false
}
#[cfg(feature = "alloc")]
let signature_mut = &mut *signature;
#[cfg(not(feature = "alloc"))]
let signature_mut = &mut signature;
let sign_len = Case::get_sigma2_sign(
fabric.unwrap(),
&case_session.our_pub_key,
&case_session.peer_pub_key,
signature_mut,
)?;
let signature = &signature[..sign_len];
#[cfg(feature = "alloc")]
let encrypted_mut = &mut *encrypted;
#[cfg(not(feature = "alloc"))]
let encrypted_mut = &mut encrypted;
Case::get_sigma2_encryption(
fabric.unwrap(),
self.rand,
&our_random,
case_session,
signature,
encrypted_mut,
)?
};
let encrypted = &encrypted[0..encrypted_len];
// Generate our Response Body
tx.reset();
tx.set_proto_id(PROTO_ID_SECURE_CHANNEL);
tx.set_proto_opcode(OpCode::CASESigma2 as u8);
let mut tw = TLVWriter::new(tx.get_writebuf()?);
tw.start_struct(TagType::Anonymous)?;
tw.str8(TagType::Context(1), &our_random)?;
tw.u16(TagType::Context(2), local_sessid)?;
tw.str8(TagType::Context(3), &case_session.our_pub_key)?;
tw.str16(TagType::Context(4), encrypted)?;
tw.end_container()?;
case_session.tt_hash.update(tx.as_mut_slice())?;
exchange.exchange(tx, rx).await
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