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

Provisioning sketch #453

Draft
wants to merge 19 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
e211d0e
feat: Add arch::identity
chrysn Sep 26, 2024
da1b92b
feat(examples): Show device ID next to board in hello-world
chrysn Sep 26, 2024
318d41b
fix(device-id/nrf): Support all targets
chrysn Sep 27, 2024
4e2284f
feat(device-id/nrf): Use traits to enforce consistency, offer general…
chrysn Sep 27, 2024
57e25b3
feat(device-id): Provide error-only implementation
chrysn Sep 27, 2024
d8c126c
feat(device-id): Advertise not-implemented type where not implemented
chrysn Sep 27, 2024
484586b
fix(device-id): Fix building for nRF53 devices
chrysn Sep 27, 2024
edd8205
feat(device-id/stm32): Implement device ID
chrysn Sep 27, 2024
ebf3171
fix(doc): Fix rustdoc complaints
chrysn Sep 27, 2024
df6bdf2
fixup! feat(device-id/nrf): Use traits to enforce consistency, offer …
chrysn Sep 30, 2024
d37d581
feat(device-id): Re-export DeviceId in riot_rs
chrysn Sep 30, 2024
39d5fe0
fixup! feat(device-id): Advertise not-implemented type where not impl…
chrysn Sep 30, 2024
f519f09
fixup! feat(device-id/nrf): Use traits to enforce consistency, offer …
chrysn Sep 30, 2024
c1d03e9
doc(device-id): Define identifier characteristics
chrysn Sep 30, 2024
44933ff
feat(device-id): Move ID data into type
chrysn Oct 1, 2024
8133c28
squash! doc(device-id): Define identifier characteristics
chrysn Oct 2, 2024
2a1472a
doc(device-id): Polish documentation
chrysn Oct 2, 2024
a39f967
WIP: Provisioning data exfiltration
chrysn Oct 4, 2024
e3e6590
WIP: Use https://github.com/probe-rs/probe-rs/pull/2846
chrysn Oct 16, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

13 changes: 13 additions & 0 deletions example-embed.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
[default]
gdb.enabled = true

[default.rtt]
enabled = true

up_channels = [
{ channel = 1, mode = "BlockIfFull", format = "Defmt", socket = "[::1]:1338" },
]

tabs = [
{ up_channel = 1, name = "defmt" },
]
8 changes: 8 additions & 0 deletions examples/coap/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,14 @@ static A: Bump<[u8; 1 << 16]> = Bump::uninit();

#[riot_rs::task(autostart)]
async fn coap_run() {
use riot_rs::identity::DeviceId;
info!(
"Device provisioned: board={}\tdevice_id={}\tedhoc_kccs={}",
riot_rs::buildinfo::BOARD,
riot_rs::identity::device_identity().unwrap().bytes(),
&[1, 2, 3]
);

let stack = network::network_stack().await.unwrap();

// FIXME trim to CoAP requirements
Expand Down
3 changes: 2 additions & 1 deletion examples/hello-world/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,9 @@ use riot_rs::debug::{exit, log::*};
#[riot_rs::thread(autostart)]
fn main() {
info!(
"Hello from main()! Running on a {} board.",
"Hello from main()! Running on {} board identified as {:x}.",
riot_rs::buildinfo::BOARD,
riot_rs::identity::device_identity(),
);

exit(Ok(()));
Expand Down
13 changes: 13 additions & 0 deletions laze-project.yml
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,19 @@ contexts:
cmd:
- cd ${appdir} && ${CARGO_ENV} cargo ${CARGO_ARGS} run --${PROFILE} ${FEATURES}

embed:
build: false
cmd:
# why not PROBE_RS_PROTOCOL?
- cd ${appdir} && ${CARGO_ENV} cargo ${CARGO_ARGS} embed --${PROFILE} ${FEATURES} --chip ${PROBE_RS_CHIP}

provision:
build: false
cmd:
- ./scripts/provisioning-processor
# just like embed
- cd ${appdir} && ${CARGO_ENV} cargo ${CARGO_ARGS} embed --${PROFILE} ${FEATURES} --chip ${PROBE_RS_CHIP} --config-file ../../example-embed.toml

cargo-test:
cmd:
- cd ${relpath} && ${CARGO_ENV} cargo test --${PROFILE} --features=riot-rs-boards/${builder},riot-rs-rt/debug-console --manifest-path ${app}/Cargo.toml
Expand Down
57 changes: 57 additions & 0 deletions scripts/provisioning-processor
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
#!/usr/bin/env python3

"""Helper to take provisioning data from another process.

This binds to [::1]:1338, where it parses text from there for provisioning data
(semantically a statement that "This is a device with board=... and
DeviceId=..., and the public key it has generated is ...").

So far, that information is merely logged; on the long run, this should store
keys in a local Authorization Server, revoking earlier identities of that
device.

To play race-free with other scripts, it terminates its main process when
everything is ready to connect, and listens for up to 60 seconds for a
connection on :1338. Other input methods than TCP sockets might be added if
they are needed for where there is no `cargo embed` that pipes around stdout.
At that point, the input method will be guided by arguments.
"""

import json
import logging
import os
import socket
import sys

logging.basicConfig(level=logging.INFO)

s = socket.create_server(("::1", 1338), family=socket.AF_INET6, backlog=1)
s.settimeout(60)
logging.info("Provisioning processor ready, listening on %s", s)
if os.fork() != 0:
sys.exit(0)

(conn, addr) = s.accept()
s.close()
logging.info("Connection received from %s", addr)
needle = "Device provisioned: "
for line in socket.SocketIO(conn, "r"):
line = line.decode('utf-8')
(_, _, found) = line.partition(needle)
if not found:
logging.debug("Ignoring line without provisioning information: %r", line)
continue
remaining = found.strip()
logging.info("Processing provisioning line: %r", remaining)

details = {}
while remaining:
current, _, remaining = remaining.partition('\t')
key, _, value = current.partition('=')
if value.startswith('['):
value = json.loads(value)
details[key] = value

logging.error("No method for provisioning established yet, discarding data %r", details)
break
conn.close()
6 changes: 2 additions & 4 deletions src/riot-rs-embassy-common/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@ repository.workspace = true
workspace = true

[dependencies]
defmt = { workspace = true, optional = true }
fugit = { workspace = true, optional = true }
defmt = { workspace = true }
fugit = { workspace = true, optional = true, features = ["defmt"] }
embassy-futures = { workspace = true }
embassy-time = { workspace = true }
embedded-hal = { workspace = true }
Expand All @@ -23,5 +23,3 @@ external-interrupts = []

## Enables I2C support.
i2c = ["dep:fugit"]

defmt = ["dep:defmt", "fugit?/defmt"]
112 changes: 112 additions & 0 deletions src/riot-rs-embassy-common/src/identity.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
//! Tools and traits for describing device identities.
//!
//! See `riot_rs::identity` for general documentation.
#![deny(missing_docs)]

/// Trait desribing the unique identifier available on a board.
///
/// See the module level documentation on the characteristics of the identifier.
///
/// # Evolution
///
/// In its current state, this type is mainly a wrapper around a binary identifier with a
/// length constant at build time.
///
/// As it is used more, additional methods can be provided for concrete types of identifiers, such
/// as MAC addresses. By default, those would be generated in some way from what is available in
/// the identifier -- but boards where the identifier already *is* a MAC address (or possibly a
/// range thereof) can provide their official addresses.
pub trait DeviceId: Sized + core::fmt::Debug + defmt::Format {
/// Error type indicating that no identifier is available.
///
/// This is part of the return type of the [`::get()`][Self::get] constructor.
///
/// It is encouraged to be [`core::convert::Infallible`] where possible.
///
/// # Open questions
///
/// Some architectures will have to read this (eg. at QSPI initialization time); is there guidance
/// on how to report "Not yet available"?
type Error: core::error::Error + defmt::Format;

/// Some `[u8; N]` type, returned by [`.bytes()`][Self::bytes].
///
/// This may not represent all the identifying information available on the board, but can
/// represent a unique portion thereof.
///
/// (For example, if a device has two consecutive MAC addresses assigned, the type as a whole
/// may represent both, but the conventional serialized identity of the board may just be one
/// of them).
///
/// # Evolution
///
/// On the long run, it will be preferable to add a `const BYTES_LEN: usize;` and enforce the
/// type `[u8; Self::BYTES_LEN]` as the return value of [`.bytes(_)]`][Self::bytes]. This can
/// not be done yet as it depends on the `generic_const_exprs` featureVg
type Bytes: AsRef<[u8]>;

/// Obtains a unique identifier of the device.
///
/// For callers, there is the convenience function `riot_rs::identity::device_identity()`
/// available, which just calls this trait method on `riot_rs::arch::identity::DeviceId`.
///
/// # Errors
///
/// This produces an error if no device ID is available on this board, or is not implemented.
fn get() -> Result<Self, Self::Error>;

/// The device identifier in serialized bytes format.
fn bytes(&self) -> Self::Bytes;
}

/// An uninhabited type implementing [`DeviceId`] that always errs.
///
/// This can be used both on architectures that do not have a unique identifier on their boards,
/// and when it has not yet been implemented.
///
/// Typical types for `E` are [`NotImplemented`] or [`NotAvailable`].
#[derive(Debug, defmt::Format)]
pub struct NoDeviceId<E: core::error::Error + defmt::Format + Default>(
core::convert::Infallible,
core::marker::PhantomData<E>,
);

impl<E: core::error::Error + defmt::Format + Default> DeviceId for NoDeviceId<E> {
type Error = E;

// We could also come up with a custom never type that AsRef's into [u8], but that won't fly
// once there is a BYTES_LEN.
type Bytes = [u8; 0];

fn get() -> Result<Self, Self::Error> {
Err(Default::default())
}

fn bytes(&self) -> [u8; 0] {
match self.0 {}
}
}

/// Error indicating that a [`DeviceId`] may be available on this platform, but is not implemented.
#[derive(Debug, Default, defmt::Format)]
pub struct NotImplemented;

impl core::fmt::Display for NotImplemented {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.write_str("Device ID not implemented on this platform")
}
}

impl core::error::Error for NotImplemented {}

/// Error indicating that a [`DeviceId`] is not available on this platform.
#[derive(Debug, Default, defmt::Format)]
pub struct NotAvailable;

impl core::fmt::Display for NotAvailable {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.write_str("Device ID not available on this platform")
}
}

impl core::error::Error for NotAvailable {}
2 changes: 2 additions & 0 deletions src/riot-rs-embassy-common/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ pub mod executor_swi;
#[cfg(feature = "i2c")]
pub mod i2c;

pub mod identity;

pub mod reexports {
//! Crate re-exports.

Expand Down
1 change: 0 additions & 1 deletion src/riot-rs-embassy/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,6 @@ defmt = [
"embassy-net?/defmt",
"embassy-time?/defmt",
"embassy-usb?/defmt",
"riot-rs-embassy-common/defmt",
"riot-rs-esp/defmt",
"riot-rs-nrf/defmt",
"riot-rs-rp/defmt",
Expand Down
6 changes: 6 additions & 0 deletions src/riot-rs-embassy/src/arch/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,12 @@ pub mod hwrng;
#[cfg(feature = "i2c")]
pub mod i2c;

pub mod identity {
use riot_rs_embassy_common::identity;

pub type DeviceId = identity::NoDeviceId<identity::NotImplemented>;
}

#[cfg(feature = "usb")]
pub mod usb;

Expand Down
6 changes: 6 additions & 0 deletions src/riot-rs-esp/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,12 @@ pub mod gpio;
#[cfg(feature = "i2c")]
pub mod i2c;

pub mod identity {
use riot_rs_embassy_common::identity;

pub type DeviceId = identity::NoDeviceId<identity::NotImplemented>;
}

#[cfg(feature = "wifi")]
pub mod wifi;

Expand Down
3 changes: 3 additions & 0 deletions src/riot-rs-nrf/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -30,12 +30,15 @@ riot-rs-random = { workspace = true, optional = true }

[target.'cfg(context = "nrf52832")'.dependencies]
embassy-nrf = { workspace = true, features = ["nrf52832"] }
nrf52832-pac = "0.12.2"

[target.'cfg(context = "nrf52840")'.dependencies]
embassy-nrf = { workspace = true, features = ["nrf52840"] }
nrf52840-pac = "0.12.2"

[target.'cfg(context = "nrf5340")'.dependencies]
embassy-nrf = { workspace = true, features = ["nrf5340-app-s"] }
nrf5340-app-pac = "0.12.2"

[features]
## Enables GPIO interrupt support.
Expand Down
30 changes: 30 additions & 0 deletions src/riot-rs-nrf/src/identity.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
#[derive(Debug, defmt::Format)]

Check failure on line 1 in src/riot-rs-nrf/src/identity.rs

View workflow job for this annotation

GitHub Actions / lint

failed to resolve: use of undeclared crate or module `defmt`

error[E0433]: failed to resolve: use of undeclared crate or module `defmt` --> src/riot-rs-nrf/src/identity.rs:1:17 | 1 | #[derive(Debug, defmt::Format)] | ^^^^^ use of undeclared crate or module `defmt`
pub struct DeviceId(u64);

impl riot_rs_embassy_common::identity::DeviceId for DeviceId {

Check failure on line 4 in src/riot-rs-nrf/src/identity.rs

View workflow job for this annotation

GitHub Actions / lint

the trait bound `identity::DeviceId: defmt::traits::Format` is not satisfied

error[E0277]: the trait bound `identity::DeviceId: defmt::traits::Format` is not satisfied --> src/riot-rs-nrf/src/identity.rs:4:53 | 4 | impl riot_rs_embassy_common::identity::DeviceId for DeviceId { | ^^^^^^^^ the trait `defmt::traits::Format` is not implemented for `identity::DeviceId` | = help: the following other types implement trait `defmt::traits::Format`: &T &mut T () (T0, T1) (T0, T1, T2) (T0, T1, T2, T3) (T0, T1, T2, T3, T4) (T0, T1, T2, T3, T4, T5) and 128 others note: required by a bound in `riot_rs_embassy_common::identity::DeviceId` --> /home/runner/work/RIOT-rs/RIOT-rs/src/riot-rs-embassy-common/src/identity.rs:19:48 | 19 | pub trait DeviceId: Sized + core::fmt::Debug + defmt::Format { | ^^^^^^^^^^^^^ required by this bound in `DeviceId` = note: `DeviceId` is a "sealed trait", because to implement it you also need to implement `defmt::traits::Format`, which is not accessible; this is usually done to force you to use one of the provided types that already implement it = help: the following types implement the trait: riot_rs_embassy_common::identity::NoDeviceId<E> riot_rs_embassy_common::identity::NotImplemented riot_rs_embassy_common::identity::NotAvailable fugit::duration::Duration<u32, NOM, DENOM> fugit::duration::Duration<u64, NOM, DENOM> fugit::instant::Instant<u32, NOM, DENOM> fugit::instant::Instant<u64, NOM, DENOM> fugit::rate::Rate<u32, NOM, DENOM> and 128 others
type Error = core::convert::Infallible;

fn get() -> Result<Self, Self::Error> {
// Embassy does not wrap the FICR register, and given that all we need from there is a register
// read that is perfectly fine to do through a stolen register, let's do that rather than
// thread the access through several layers.

// SAFETY: The register is used for read-only operations on constant values.
#[cfg(context = "nrf52840")]
let ficr = unsafe { nrf52840_pac::Peripherals::steal().FICR };
#[cfg(context = "nrf52832")]
let ficr = unsafe { nrf52832_pac::Peripherals::steal().FICR };
#[cfg(context = "nrf5340")]
let ficr = &unsafe { nrf5340_app_pac::Peripherals::steal().FICR_S }.info;

let low = ficr.deviceid[0].read().bits();
let high = ficr.deviceid[1].read().bits();
Ok(Self((u64::from(high) << u32::BITS) | u64::from(low)))
}

type Bytes = [u8; 8];

fn bytes(&self) -> Self::Bytes {
self.0.to_le_bytes()
}
}
2 changes: 2 additions & 0 deletions src/riot-rs-nrf/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ pub mod hwrng;
#[cfg(feature = "i2c")]
pub mod i2c;

pub mod identity;

#[cfg(feature = "usb")]
pub mod usb;

Expand Down
6 changes: 6 additions & 0 deletions src/riot-rs-rp/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,12 @@ pub mod hwrng;
#[cfg(feature = "i2c")]
pub mod i2c;

pub mod identity {
use riot_rs_embassy_common::identity;

pub type DeviceId = identity::NoDeviceId<identity::NotImplemented>;
}

#[cfg(feature = "usb")]
pub mod usb;

Expand Down
16 changes: 16 additions & 0 deletions src/riot-rs-stm32/src/identity.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
#[derive(Debug, defmt::Format)]
pub struct DeviceId(&'static [u8; 12]);

impl riot_rs_embassy_common::identity::DeviceId for DeviceId {
type Bytes = &'static [u8; 12];

type Error = core::convert::Infallible;

fn get() -> Result<Self, Self::Error> {
Ok(Self(embassy_stm32::uid::uid()))
}

fn bytes(&self) -> Self::Bytes {
self.0
}
}
Loading
Loading