Heap-allocated packets not necessary; no_std and no-alloc build supported end-to-end

This commit is contained in:
ivmarkov 2023-04-25 07:22:58 +00:00
parent 8b3bb9527c
commit 7ef7e93eb4
12 changed files with 14 additions and 333 deletions

View file

@ -1,4 +1,4 @@
[workspace]
members = ["matter", "matter_macro_derive", "boxslab", "tools/tlv_tool"]
members = ["matter", "matter_macro_derive", "tools/tlv_tool"]
exclude = ["examples/*"]

View file

@ -1,9 +0,0 @@
[package]
name = "boxslab"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
bitmaps={version="3.2.0", features=[]}

View file

@ -1,237 +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 std::{
mem::MaybeUninit,
ops::{Deref, DerefMut},
sync::Mutex,
};
// TODO: why is max bitmap size 64 a correct max size? Could we match
// boxslabs instead or store used/not used inside the box slabs themselves?
const MAX_BITMAP_SIZE: usize = 64;
pub struct Bitmap {
inner: bitmaps::Bitmap<MAX_BITMAP_SIZE>,
max_size: usize,
}
impl Bitmap {
pub fn new(max_size: usize) -> Self {
assert!(max_size <= MAX_BITMAP_SIZE);
Bitmap {
inner: bitmaps::Bitmap::new(),
max_size,
}
}
pub fn set(&mut self, index: usize) -> bool {
assert!(index < self.max_size);
self.inner.set(index, true)
}
pub fn reset(&mut self, index: usize) -> bool {
assert!(index < self.max_size);
self.inner.set(index, false)
}
pub fn first_false_index(&self) -> Option<usize> {
match self.inner.first_false_index() {
Some(idx) if idx < self.max_size => Some(idx),
_ => None,
}
}
pub fn is_empty(&self) -> bool {
self.inner.is_empty()
}
pub fn is_full(&self) -> bool {
self.first_false_index().is_none()
}
}
#[macro_export]
macro_rules! box_slab {
($name:ident,$t:ty,$v:expr) => {
use std::mem::MaybeUninit;
use std::sync::Once;
use $crate::{BoxSlab, Slab, SlabPool};
pub struct $name;
impl SlabPool for $name {
type SlabType = $t;
fn get_slab() -> &'static Slab<Self> {
const MAYBE_INIT: MaybeUninit<$t> = MaybeUninit::uninit();
static mut SLAB_POOL: [MaybeUninit<$t>; $v] = [MAYBE_INIT; $v];
static mut SLAB_SPACE: Option<Slab<$name>> = None;
static mut INIT: Once = Once::new();
unsafe {
INIT.call_once(|| {
SLAB_SPACE = Some(Slab::<$name>::init(&mut SLAB_POOL, $v));
});
SLAB_SPACE.as_ref().unwrap()
}
}
}
};
}
pub trait SlabPool {
type SlabType: 'static;
fn get_slab() -> &'static Slab<Self>
where
Self: Sized;
}
pub struct Inner<T: 'static + SlabPool> {
pool: &'static mut [MaybeUninit<T::SlabType>],
map: Bitmap,
}
// TODO: Instead of a mutex, we should replace this with a CAS loop
pub struct Slab<T: 'static + SlabPool>(Mutex<Inner<T>>);
impl<T: SlabPool> Slab<T> {
pub fn init(pool: &'static mut [MaybeUninit<T::SlabType>], size: usize) -> Self {
Self(Mutex::new(Inner {
pool,
map: Bitmap::new(size),
}))
}
pub fn try_new(new_object: T::SlabType) -> Option<BoxSlab<T>> {
let slab = T::get_slab();
let mut inner = slab.0.lock().unwrap();
if let Some(index) = inner.map.first_false_index() {
inner.map.set(index);
inner.pool[index].write(new_object);
let cell_ptr = unsafe { &mut *inner.pool[index].as_mut_ptr() };
Some(BoxSlab {
data: cell_ptr,
index,
})
} else {
None
}
}
pub fn free(&self, index: usize) {
let mut inner = self.0.lock().unwrap();
inner.map.reset(index);
let old_value = std::mem::replace(&mut inner.pool[index], MaybeUninit::uninit());
let _old_value = unsafe { old_value.assume_init() };
// This will drop the old_value
}
}
pub struct BoxSlab<T: 'static + SlabPool> {
// Because the data is a reference within the MaybeUninit, we don't have a mechanism
// to go out to the MaybeUninit from this reference. Hence this index
index: usize,
// TODO: We should figure out a way to get rid of the index too
data: &'static mut T::SlabType,
}
impl<T: 'static + SlabPool> Drop for BoxSlab<T> {
fn drop(&mut self) {
T::get_slab().free(self.index);
}
}
impl<T: SlabPool> Deref for BoxSlab<T> {
type Target = T::SlabType;
fn deref(&self) -> &Self::Target {
self.data
}
}
impl<T: SlabPool> DerefMut for BoxSlab<T> {
fn deref_mut(&mut self) -> &mut Self::Target {
self.data
}
}
#[cfg(test)]
mod tests {
use std::{ops::Deref, sync::Arc};
pub struct Test {
val: Arc<u32>,
}
box_slab!(TestSlab, Test, 3);
#[test]
fn simple_alloc_free() {
{
let a = Slab::<TestSlab>::try_new(Test { val: Arc::new(10) }).unwrap();
assert_eq!(*a.val.deref(), 10);
let inner = TestSlab::get_slab().0.lock().unwrap();
assert!(!inner.map.is_empty());
}
// Validates that the 'Drop' got executed
let inner = TestSlab::get_slab().0.lock().unwrap();
assert!(inner.map.is_empty());
println!("Box Size {}", std::mem::size_of::<Box<Test>>());
println!("BoxSlab Size {}", std::mem::size_of::<BoxSlab<TestSlab>>());
}
#[test]
fn alloc_full_block() {
{
let a = Slab::<TestSlab>::try_new(Test { val: Arc::new(10) }).unwrap();
let b = Slab::<TestSlab>::try_new(Test { val: Arc::new(11) }).unwrap();
let c = Slab::<TestSlab>::try_new(Test { val: Arc::new(12) }).unwrap();
// Test that at overflow, we return None
assert!(Slab::<TestSlab>::try_new(Test { val: Arc::new(13) }).is_none(),);
assert_eq!(*b.val.deref(), 11);
{
let inner = TestSlab::get_slab().0.lock().unwrap();
// Test that the bitmap is marked as full
assert!(inner.map.is_full());
}
// Purposefully drop, to test that new allocation is possible
std::mem::drop(b);
let d = Slab::<TestSlab>::try_new(Test { val: Arc::new(21) }).unwrap();
assert_eq!(*d.val.deref(), 21);
// Ensure older allocations are still valid
assert_eq!(*a.val.deref(), 10);
assert_eq!(*c.val.deref(), 12);
}
// Validates that the 'Drop' got executed - test that the bitmap is empty
let inner = TestSlab::get_slab().0.lock().unwrap();
assert!(inner.map.is_empty());
}
#[test]
fn test_drop_logic() {
let root = Arc::new(10);
{
let _a = Slab::<TestSlab>::try_new(Test { val: root.clone() }).unwrap();
let _b = Slab::<TestSlab>::try_new(Test { val: root.clone() }).unwrap();
let _c = Slab::<TestSlab>::try_new(Test { val: root.clone() }).unwrap();
assert_eq!(Arc::strong_count(&root), 4);
}
// Test that Drop was correctly called on all the members of the pool
assert_eq!(Arc::strong_count(&root), 1);
}
}

View file

@ -51,7 +51,7 @@ fn main() {
//let mut mdns = matter::mdns::astro::AstroMdns::new().unwrap();
let mut mdns = matter::mdns::libmdns::LibMdns::new().unwrap();
let matter = Matter::new_default(&dev_info, &mut mdns);
let matter = Matter::new_default(&dev_info, &mut mdns, matter::transport::udp::MATTER_PORT);
let dev_att = dev_att::HardCodedDevAtt::new();

View file

@ -25,7 +25,6 @@ crypto_esp_mbedtls = ["esp-idf-sys"]
crypto_rustcrypto = ["sha2", "hmac", "pbkdf2", "hkdf", "aes", "ccm", "p256", "elliptic-curve", "crypto-bigint", "x509-cert"]
[dependencies]
boxslab = { path = "../boxslab" }
matter_macro_derive = { path = "../matter_macro_derive" }
bitflags = "1.3"
byteorder = "1.4.3"
@ -35,6 +34,7 @@ num-derive = "0.3.3"
num-traits = "0.2.15"
strum = { version = "0.24", features = ["derive"], default-features = false, no-default-feature = true }
log = { version = "0.4.17", features = ["max_level_debug", "release_max_level_debug"] }
no-std-net = "0.6"
subtle = "2.4.1"
safemem = "0.3.3"
colored = "2.0.0" # TODO: Requires STD

View file

@ -25,7 +25,6 @@ use crate::{
mdns::{Mdns, MdnsMgr},
pairing::{print_pairing_code_and_qr, DiscoveryCapabilities},
secure_channel::{pake::PaseMgr, spake2p::VerifierData},
transport::udp::MATTER_PORT,
utils::{
epoch::{Epoch, UtcCalendar},
rand::Rand,
@ -55,11 +54,11 @@ pub struct Matter<'a> {
impl<'a> Matter<'a> {
#[cfg(feature = "std")]
pub fn new_default(dev_det: &'a BasicInfoConfig, mdns: &'a mut dyn Mdns) -> Self {
pub fn new_default(dev_det: &'a BasicInfoConfig, mdns: &'a mut dyn Mdns, port: u16) -> Self {
use crate::utils::epoch::{sys_epoch, sys_utc_calendar};
use crate::utils::rand::sys_rand;
Self::new(dev_det, mdns, sys_epoch, sys_rand, sys_utc_calendar)
Self::new(dev_det, mdns, sys_epoch, sys_rand, sys_utc_calendar, port)
}
/// Creates a new Matter object
@ -74,6 +73,7 @@ impl<'a> Matter<'a> {
epoch: Epoch,
rand: Rand,
utc_calendar: UtcCalendar,
port: u16,
) -> Self {
Self {
fabric_mgr: RefCell::new(FabricMgr::new()),
@ -84,7 +84,7 @@ impl<'a> Matter<'a> {
dev_det.vid,
dev_det.pid,
dev_det.device_name,
MATTER_PORT,
port,
mdns,
)),
epoch,

View file

@ -17,8 +17,6 @@
use core::{array::TryFromSliceError, fmt, str::Utf8Error};
use log::error;
#[derive(Debug, PartialEq, Clone, Copy)]
pub enum Error {
AttributeNotFound,
@ -122,7 +120,7 @@ impl<T> From<std::sync::PoisonError<T>> for Error {
#[cfg(feature = "crypto_openssl")]
impl From<openssl::error::ErrorStack> for Error {
fn from(e: openssl::error::ErrorStack) -> Self {
error!("Error in TLS: {}", e);
::log::error!("Error in TLS: {}", e);
Self::TLSStack
}
}
@ -130,7 +128,7 @@ impl From<openssl::error::ErrorStack> for Error {
#[cfg(feature = "crypto_mbedtls")]
impl From<mbedtls::Error> for Error {
fn from(e: mbedtls::Error) -> Self {
error!("Error in TLS: {}", e);
::log::error!("Error in TLS: {}", e);
Self::TLSStack
}
}

View file

@ -85,7 +85,6 @@ pub mod mdns;
pub mod pairing;
pub mod persist;
pub mod secure_channel;
pub mod sys;
pub mod tlv;
pub mod transport;
pub mod utils;

View file

@ -1,20 +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.
*/
// The Packet Pool that is allocated from. POSIX systems can use
// higher values unlike embedded systems
pub const MAX_PACKET_POOL_SIZE: usize = 25;

View file

@ -25,4 +25,5 @@ pub mod plain_hdr;
pub mod proto_ctx;
pub mod proto_hdr;
pub mod session;
#[cfg(feature = "std")]
pub mod udp;

View file

@ -16,6 +16,9 @@
*/
use core::fmt::{Debug, Display};
#[cfg(not(feature = "std"))]
use no_std_net::{IpAddr, Ipv4Addr, SocketAddr};
#[cfg(feature = "std")]
use std::net::{IpAddr, Ipv4Addr, SocketAddr};
#[derive(PartialEq, Copy, Clone)]

View file

@ -15,14 +15,10 @@
* limitations under the License.
*/
use log::{error, trace};
use std::sync::Mutex;
use boxslab::box_slab;
use log::error;
use crate::{
error::Error,
sys::MAX_PACKET_POOL_SIZE,
utils::{parsebuf::ParseBuf, writebuf::WriteBuf},
};
@ -34,54 +30,6 @@ use super::{
pub const MAX_RX_BUF_SIZE: usize = 1583;
pub const MAX_TX_BUF_SIZE: usize = 1280 - 40/*IPV6 header size*/ - 8/*UDP header size*/;
type Buffer = [u8; MAX_RX_BUF_SIZE];
// TODO: I am not very happy with this construction, need to find another way to do this
pub struct BufferPool {
buffers: [Option<Buffer>; MAX_PACKET_POOL_SIZE],
}
impl BufferPool {
const INIT: Option<Buffer> = None;
fn get() -> &'static Mutex<BufferPool> {
static mut BUFFER_HOLDER: Option<Mutex<BufferPool>> = None;
static ONCE: Once = Once::new();
unsafe {
ONCE.call_once(|| {
BUFFER_HOLDER = Some(Mutex::new(BufferPool {
buffers: [BufferPool::INIT; MAX_PACKET_POOL_SIZE],
}));
});
BUFFER_HOLDER.as_ref().unwrap()
}
}
pub fn alloc() -> Option<(usize, &'static mut Buffer)> {
trace!("Buffer Alloc called\n");
let mut pool = BufferPool::get().lock().unwrap();
for i in 0..MAX_PACKET_POOL_SIZE {
if pool.buffers[i].is_none() {
pool.buffers[i] = Some([0; MAX_RX_BUF_SIZE]);
// Sigh! to by-pass the borrow-checker telling us we are stealing a mutable reference
// from under the lock
// In this case the lock only protects against the setting of Some/None,
// the objects then are independently accessed in a unique way
let buffer = unsafe { &mut *(pool.buffers[i].as_mut().unwrap() as *mut Buffer) };
return Some((i, buffer));
}
}
None
}
pub fn free(index: usize) {
trace!("Buffer Free called\n");
let mut pool = BufferPool::get().lock().unwrap();
if pool.buffers[index].is_some() {
pool.buffers[index] = None;
}
}
}
#[derive(PartialEq)]
enum RxState {
@ -229,5 +177,3 @@ impl<'a> Packet<'a> {
}
}
}
box_slab!(PacketPool, Packet<'static>, MAX_PACKET_POOL_SIZE);