Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

New module for integrating Matter into RIOT OS #92

Draft
wants to merge 9 commits into
base: main
Choose a base branch
from
11 changes: 11 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,15 @@ embedded-hal-async = { version = "1", optional = true }

critical-section = { version = "1.0", optional = true }

no-std-net = "0.6.0"

# for with_matter feature
rs-matter = { git = "https://github.com/maikerlab/rs-matter", branch = "feature/RIOT_OS", default-features = false, features = ["riot-os"], optional = true }
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it foreseeable that the adjustments there can be upstreamed? Are there open PRs?

Copy link
Author

@maikerlab maikerlab Apr 25, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, not yet. I will discuss this with rs-matter contributors, bc it's also not ideal if all the possible specific adaptations for different OS etc. are in this repo

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I checked again and I guess we could get rid of all the changes I made in rs-matter.
The only "big" task which needs to be done is implementing embassy-time-driver and embassy-time-queue-driver using ztimer.

embassy-futures = { version = "0.1.1", optional = true }
embassy-sync = { version = "0.5.0", optional = true }
embassy-executor-riot = { git = "https://gitlab.com/etonomy/riot-module-examples", optional = true }
embassy-executor = { version = "0.5", features = ["nightly"], optional = true }

[build-dependencies]
shlex = "0.1.1"
syn = { version = "1.0.107", features = [ "parsing" ] }
Expand Down Expand Up @@ -93,6 +102,8 @@ with_embedded_hal_async = [ "embedded-hal-async" ]
# guarantees.
with_msg_v2 = []

with_matter = ["rs-matter", "with_embedded_nal_async", "with_embedded_hal_async", "embassy-futures", "embassy-sync", "embassy-executor-riot", "embassy-executor"]

# Use actual `!` rather than the stable Never workaround. As far as the
# workaround is understood, this causes no actual change in any types, just in
# the private names that are assigned to them.
Expand Down
4 changes: 4 additions & 0 deletions src/gnrc/ipv6.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
//! Components for interacting with IPv6 messages on GNRC

use core::mem::MaybeUninit;
#[cfg(any(feature = "with_embedded_nal", feature = "with_embedded_nal_async"))]
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@chrysn how about that? embedded-nal also uses the types from no-std-net. I don't know if an Ipv6Addr must be used if none of the embedded-nal features are activated, but as a "fallback" we can use the one from core::net

pub use no_std_net::{Ipv6Addr, SocketAddr};
#[cfg(not(any(feature = "with_embedded_nal", feature = "with_embedded_nal_async")))]
pub use core::net::{Ipv6Addr, SocketAddr};

use riot_sys::{ipv6_addr_from_str, ipv6_addr_t, kernel_pid_t};

Expand Down
16 changes: 14 additions & 2 deletions src/gnrc/netapi.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
use riot_sys::gnrc_nettype_t;

use riot_sys::{gnrc_nettype_t, size_t};
use crate::gnrc::pktbuf::{Pktsnip, Shared};
use crate::thread::KernelPID;
use core::ffi::c_void;
use crate::gnrc::ipv6::Ipv6Addr;
use crate::error::NegativeErrorExt;

/// Dispatch a packet to all listeners of the given nettype and demux context.
///
Expand All @@ -21,3 +24,12 @@ pub fn dispatch_send(
}
subscribers
}

/// Joins an IPV6 multicast group at the provided address
pub fn join_multicast_v6(scope_id: KernelPID, addr: Ipv6Addr) -> Result<(), crate::error::NumericError> {
let octets = addr.octets();
let addr_ptr = octets.as_ptr() as *const c_void;
(unsafe {
riot_sys::inline::gnrc_netapi_set(scope_id.into(), riot_sys::netopt_t_NETOPT_IPV6_GROUP, 0, addr_ptr, octets.len() as size_t)
}).negative_to_error().map(|_| ())
}
7 changes: 7 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -192,3 +192,10 @@ pub mod led;
pub mod auto_init;

mod async_helpers;

#[cfg(all(
riot_module_sock_udp,
riot_module_sock_aux_local,
feature = "with_matter"
))]
pub mod matter;
235 changes: 235 additions & 0 deletions src/matter.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,235 @@
use core::time::Duration;
use crate::mutex::Mutex;
use crate::socket_embedded_nal_async_udp::UnconnectedUdpSocket;
use crate::{vfs, ztimer};
use crate::random::Random;
use crate::error::NumericError;
use riot_sys::{ZTIMER_MSEC, inline::{ztimer_now, ztimer_clock}};
use embedded_nal_async::{UnconnectedUdp, SocketAddr};
use embassy_futures::select::{Either, select};
use embedded_hal_async::delay::DelayNs as _;
use embassy_sync::{
signal::Signal,
blocking_mutex::raw::NoopRawMutex,
};
use rand_core_06::RngCore as _;
use rs_matter::error::{Error, ErrorCode};
use rs_matter::data_model::sdm::dev_att::{DataType, DevAttDataFetcher};
use rs_matter::Matter;
use rs_matter::transport::network::{UdpReceive, UdpSend};

// RIOT_EPOCH(2020) in seconds since UNIX Epoch
const RIOT_EPOCH_SECS: u64 = 1577833200;

pub struct MatterCompatUdpSocket {
local_addr: SocketAddr,
socket: Mutex<UnconnectedUdpSocket>,
release_socket_notification: Notification,
socket_released_notification: Notification,
}

pub type Notification = Signal<NoopRawMutex, ()>;

impl MatterCompatUdpSocket {
pub fn new(local_addr: SocketAddr, socket: UnconnectedUdpSocket) -> Self {
Self {
local_addr,
socket: Mutex::new(socket),
release_socket_notification: Notification::new(),
socket_released_notification: Notification::new(),
}
}
}

impl UdpSend for &MatterCompatUdpSocket {
async fn send_to(&mut self, data: &[u8], addr: SocketAddr) -> Result<(), Error> {
if addr.is_ipv4() {
// IPv4 not supported!
return Ok(());
}
// Tell recv_from to release mutex
self.release_socket_notification.signal(());
ztimer::Delay.delay_ms(10).await;
let mut sock = self.socket.try_lock().expect("receiver should have ensured that this mutex is free");
sock.send(self.local_addr, addr, data)
.await
.map_err(|_| Error::new(ErrorCode::StdIoError))?;
// Release socket and notify recv_from -> sending is finished
drop(sock);
self.socket_released_notification.signal(());
Ok(())
}
}

impl UdpReceive for &MatterCompatUdpSocket {
async fn recv_from(&mut self, buffer: &mut [u8]) -> Result<(usize, SocketAddr), Error> {
loop {
let mut sock = self.socket.try_lock().expect("sender should have ensured that this mutex is free");
match select(
self.release_socket_notification.wait(),
sock.receive_into(buffer),
).await {
Either::First(_) => {
// Release Mutex for send_to
drop(sock);
// ... and wait until available again
self.socket_released_notification.wait().await;
continue;
}
Either::Second(res) => {
let (bytes_recvd, remote_addr) = res.map(|(bytes_recvd, _, remote_addr)|
(bytes_recvd, remote_addr)
).map_err(|_| Error::new(ErrorCode::StdIoError))?;
return Ok((bytes_recvd, remote_addr));
}
}
}
}
}

/// Generate random bytes using the RIOT RNG
#[cfg(riot_module_random)]
pub fn sys_rand(buf: &mut [u8]) {
Random::new().fill_bytes(buf);
}

/// Returns Duration since UNIX Epoch (01.01.1970 00:00:00)
pub fn sys_epoch() -> Duration {
let epoch_sec = RIOT_EPOCH_SECS + get_riot_sec() as u64;
let mut epoch_ms: u32 = unsafe {
ztimer_now(ZTIMER_MSEC as *mut ztimer_clock) % 1000
};
Duration::from_millis((epoch_sec*1000) + epoch_ms as u64)
}

/// Returns elapsed seconds since `RIOT_EPOCH` using `periph_rtc`
#[cfg(riot_module_periph_rtc)]
fn get_riot_sec() -> u32 {
use riot_sys::{rtc_get_time, rtc_tm_normalize, rtc_mktime};
unsafe {
let mut now = riot_sys::tm::default();
rtc_get_time(&mut now);
rtc_tm_normalize(&mut now);
rtc_mktime(&mut now)
}
}

/// Returns elapsed seconds since `RIOT_EPOCH` using `ztimer_sec`
#[cfg(all(not(riot_module_periph_rtc), riot_module_ztimer_sec))]
fn get_riot_sec() -> u32 {
use riot_sys::ZTIMER_SEC;
unsafe {
ztimer_now(ZTIMER_SEC as *mut ztimer_clock)
}
}

pub trait CommissioningDataFetcher {
/// Reads Discriminator and Passcode) from device firmware
fn read_commissioning_data(&self) -> Result<(u16, u32), NumericError>;
}

/// Provides methods for reading Matter data from VFS (Virtual File System Layer)
/// such as commissioning and device attestation data.
/// For now, only reading from constfs (mountpoint `/const`) is supported, but could be extended to support various filesystems and proper encryption.
///
/// It is expected that the following files exist in the VFS layer of the target device, otherwise it will panic:
/// - `/const/cd`: Certificate Declaration
/// - `/const/pai`: Product Attestation Intermediary Certificate
/// - `/const/dac`: Device Attestation Certificate
/// - `/const/dac_pubkey`: DAC Public Key
/// - `/const/dac_privkey`: DAC Private Key
/// - `/const/passcode`: Passcode required for successful commissioning
/// - `/const/discriminator`: Required for Node Discovery via mDNS
pub struct FirmwareDataFetcher;

impl FirmwareDataFetcher {
pub fn new() -> Self{
Self {}
}
}

impl CommissioningDataFetcher for FirmwareDataFetcher {
fn read_commissioning_data(&self) -> Result<(u16, u32), NumericError> {
// TODO: Read Commissioning Data from VFS
todo!("not implemented - see https://github.com/RIOT-OS/rust-riot-wrappers/issues/93");

let mut passcode_data: [u8; 4] = [0; 4];
let mut passcode_file = vfs::File::open("/const/passcode")?;
passcode_file.read(&mut passcode_data)?;
let passcode = u32::from_le_bytes(passcode_data);

let mut discriminator_data: [u8; 2]= [0; 2];
let mut discriminator_file = vfs::File::open("/const/discriminator")?;
discriminator_file.read(&mut discriminator_data)?;
let discriminator = u16::from_le_bytes(discriminator_data);

Ok((discriminator, passcode))
}
}

impl DevAttDataFetcher for FirmwareDataFetcher {
fn get_devatt_data(&self, data_type: DataType, data: &mut [u8]) -> Result<usize, Error> {
// TODO: Read Device Attestation Data from VFS
todo!("not implemented - see https://github.com/RIOT-OS/rust-riot-wrappers/issues/93");
let src_path = match data_type {
DataType::CertDeclaration => "/const/cd",
DataType::PAI => "/const/pai",
DataType::DAC => "/const/dac",
DataType::DACPubKey => "/const/dac_pubkey",
DataType::DACPrivKey => "/const/dac_privkey"
};
let mut src = vfs::File::open(src_path).map_err(|_| Error::new(ErrorCode::StdIoError))?;
let len = src.read(data)
.map_err(|_| Error::new(ErrorCode::StdIoError))?;
if len <= data.len() {
let data = &mut data[0..len];
Ok(len)
} else {
Err(ErrorCode::NoSpace.into())
}
}
}

/// Used for storing and loading ACL and Fabric data in a key-value store
pub struct PersistenceManager<'a> {
matter: &'a Matter<'a>,
buf: [u8; 4096],
dir: &'a str,
}

impl<'a> PersistenceManager<'a> {
#[inline(always)]
pub fn new(matter: &'a Matter<'a>) -> Result<Self, Error> {
let mut buf = [0; 4096];
Ok(Self { matter, buf, dir: "data" })
}

/// Waits for data changes by the Matter service and saves ACL and/or fabric data
pub async fn run(&mut self) -> Result<(), Error> {
todo!("A key-value store must be implemented for PersistenceManager");
loop {
self.matter.wait_changed().await;
if self.matter.is_changed() {
if let Some(data) = self.matter.store_acls(&mut self.buf)? {
todo!("not implemented yet")
//Self::store("acls", data)?;
}

if let Some(data) = self.matter.store_fabrics(&mut self.buf)? {
todo!("not implemented yet")
//Self::store("fabrics", data)?;
}
}
}
}

/// Loads data from the key-value store and writes it to the buffer
fn load<'b>(key: &str, buf: &'b mut [u8]) -> Result<Option<&'b [u8]>, Error> {
todo!("not implemented yet")
}

/// Stores data to the key-value store
fn store(key: &str, data: &[u8]) -> Result<(), Error> {
todo!("not implemented yet")
}
}