From d19b27897df583a4a226d7b54c43219148cbcb4d Mon Sep 17 00:00:00 2001 From: Jean-Pierre De Jesus DIAZ Date: Fri, 8 Nov 2024 20:11:46 +0100 Subject: [PATCH 1/4] SFT-4401: Add nom-embedded-storage crate. --- Cargo.toml | 3 + nom-embedded-storage/Cargo.toml | 18 + nom-embedded-storage/src/lib.rs | 673 ++++++++++++++++++++++++++++++++ nom-embedded-storage/src/rc.rs | 110 ++++++ 4 files changed, 804 insertions(+) create mode 100644 nom-embedded-storage/Cargo.toml create mode 100644 nom-embedded-storage/src/lib.rs create mode 100644 nom-embedded-storage/src/rc.rs diff --git a/Cargo.toml b/Cargo.toml index 771a6e8..ef9287b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,6 +6,7 @@ resolver = "2" members = [ "arena", "codecs", + "nom-embedded-storage", "ffi", "firmware", "stratum-v1", @@ -33,11 +34,13 @@ defmt = "0.3" derive_more = { version = "1.0", default-features = false } embedded-io = "0.6" embedded-io-async = "0.6" +embedded-storage = "0.3" faster-hex = { version = "0.9", default-features = false } heapless = { version = "0.8", default-features = false } itertools = { version = "0.10", default-features = false } libfuzzer-sys = "0.4" log = { version = "0.4" } +memchr = { version = "2", default-features = false } minicbor = { version = "0.24", features = ["derive"] } nom = { version = "7", default-features = false } phf = { version = "0.11", features = ["macros"], default-features = false } diff --git a/nom-embedded-storage/Cargo.toml b/nom-embedded-storage/Cargo.toml new file mode 100644 index 0000000..e07e575 --- /dev/null +++ b/nom-embedded-storage/Cargo.toml @@ -0,0 +1,18 @@ +# SPDX-FileCopyrightText: © 2024 Foundation Devices, Inc. +# SPDX-License-Identifier: GPL-3.0-or-later + +[package] +name = "embedded-storage-nom" +version = "0.1.0" +edition = "2021" +license = "GPL-3.0-or-later" + +[features] +default = ["std"] +std = [] + +[dependencies] +embedded-storage = { workspace = true } +heapless = { workspace = true } +memchr = { workspace = true } +nom = { workspace = true } diff --git a/nom-embedded-storage/src/lib.rs b/nom-embedded-storage/src/lib.rs new file mode 100644 index 0000000..1652b9e --- /dev/null +++ b/nom-embedded-storage/src/lib.rs @@ -0,0 +1,673 @@ +// SPDX-FileCopyrightText: © 2024 Foundation Devices, Inc. +// SPDX-License-Identifier: GPL-3.0-or-later + +//! # embedded-storage-nom +//! +//! Implementation of [`nom`] traits for [`embedded_storage::nor_flash`] to +//! allow parsing directly from a storage device. +//! +//! Ideally this should be implemented for `embedded-io` traits but for the +//! sake of simplicity for Passport we just use [`embedded_storage`]. + +#![cfg_attr(not(feature = "std"), no_std)] + +use core::{ + cell::RefCell, + iter::Enumerate, + ops::{Range, RangeFrom, RangeFull, RangeTo}, +}; +use embedded_storage::nor_flash::ReadNorFlash; +use heapless::Vec; +use nom::{ + Compare, CompareResult, FindSubstring, FindToken, InputIter, InputLength, InputTake, Needed, + Slice, +}; + +pub mod rc; + +use crate::rc::Rc; + +/// A byte slice in the NOR flash storage. +#[derive(Debug)] +pub struct Bytes { + offset: usize, + len: usize, + storage: Rc>, + buffer: RefCell>, +} + +impl Bytes +where + S: ReadNorFlash, +{ + /// Create a byte slice from `storage` of `len` bytes at `offset`. + /// + /// # Return value + /// + /// If `offset` combined with `len` is past the capacity of `storage` + /// an error is returned. + pub fn new(offset: usize, len: usize, storage: Rc>) -> Result { + // We expect to read at least one byte from the flash. + if S::READ_SIZE > 1 { + return Err(Error::UnsupportedReadSize); + } + + let capacity = if let Ok(s) = storage.try_borrow() { + s.capacity() + } else { + return Err(Error::AlreadyBorrowed); + }; + + if offset + len > capacity { + return Err(Error::OutOfBounds { + offset, + len, + capacity, + }); + } + + Ok(Self { + offset, + len, + storage, + buffer: RefCell::new(Vec::new()), + }) + } + + /// Find `needle` in haystack (self), returning the position of the found + /// byte or None if not found. + pub fn memchr(&self, needle: u8) -> Option { + let mut pos = 0; + + while pos < self.len() { + let mut buffer = self.buffer.borrow_mut(); + buffer.clear(); + buffer + .resize(self.len().min(N), 0) + .expect("size should be less than or equal to N"); + + let offset = match u32::try_from(self.offset + pos) { + Ok(v) => v, + Err(_) => return None, + }; + + if let Err(_) = self.storage.borrow_mut().read(offset, &mut buffer) { + return None; + } + + // We found the needle in this chunk, so return the found + // position inside of the chunk plus the current offset. + if let Some(byte_position) = memchr::memchr(needle, &buffer[..]) { + return Some(pos + byte_position); + } + + pos += self.len().min(N); + } + + None + } +} + +impl Bytes { + pub fn len(&self) -> usize { + self.len + } + + pub fn is_empty(&self) -> bool { + self.len() == 0 + } + + /// Return an iterator over [`Bytes`]. + pub fn iter(&self) -> BytesIter { + BytesIter { + inner: Bytes { + offset: self.offset, + len: self.len, + storage: Rc::clone(&self.storage), + buffer: RefCell::new(Vec::new()), + }, + pos: 0, + } + } +} + +impl Clone for Bytes { + fn clone(&self) -> Self { + Self { + offset: self.offset, + len: self.len, + storage: Rc::clone(&self.storage), + buffer: RefCell::new(Vec::new()), + } + } +} + +/// An iterator over [`Bytes`]. +#[derive(Debug)] +pub struct BytesIter { + inner: Bytes, + pos: usize, +} + +impl Iterator for BytesIter +where + S: ReadNorFlash, +{ + type Item = u8; + + // TODO: Optimize this by pre-fetching N bytes when needed. + fn next(&mut self) -> Option { + if self.pos >= self.inner.len() { + return None; + } + + let mut buf = [0; 1]; + let mut storage = self.inner.storage.borrow_mut(); + let offset = match u32::try_from(self.inner.offset + self.pos) { + Ok(v) => v, + Err(_) => return None, + }; + + match storage.read(offset, &mut buf) { + Ok(_) => { + self.pos += 1; + Some(buf[0]) + } + Err(_) => None, + } + } +} + +/// Errors that can happen when using [`Bytes`]. +#[derive(Debug)] +pub enum Error { + AlreadyBorrowed, + OutOfBounds { + offset: usize, + len: usize, + capacity: usize, + }, + UnsupportedReadSize, +} + +impl InputLength for Bytes { + fn input_len(&self) -> usize { + self.len() + } +} + +impl InputTake for Bytes { + fn take(&self, count: usize) -> Self { + if count > self.len() { + panic!("tried to take {count}, but the length is {}", self.len()); + } + + Self { + offset: self.offset, + len: count, + storage: Rc::clone(&self.storage), + buffer: RefCell::new(Vec::new()), + } + } + + fn take_split(&self, count: usize) -> (Self, Self) { + if count > self.len() { + panic!("tried to take {count}, but the length is {}", self.len()); + } + + let prefix = Self { + offset: self.offset, + len: count, + storage: Rc::clone(&self.storage), + buffer: RefCell::new(Vec::new()), + }; + + let suffix = Self { + offset: self.offset + count, + len: self.len - count, + storage: Rc::clone(&self.storage), + buffer: RefCell::new(Vec::new()), + }; + + (prefix, suffix) + } +} + +impl InputIter for Bytes +where + S: ReadNorFlash, +{ + type Item = u8; + type Iter = Enumerate>; + type IterElem = BytesIter; + + fn iter_indices(&self) -> Self::Iter { + self.iter().enumerate() + } + + fn iter_elements(&self) -> Self::IterElem { + self.iter() + } + + fn position

(&self, predicate: P) -> Option + where + P: Fn(Self::Item) -> bool, + { + self.iter().position(predicate) + } + + fn slice_index(&self, count: usize) -> Result { + if self.len() >= count { + Ok(count) + } else { + Err(Needed::new(count - self.len())) + } + } +} + +impl Slice> for Bytes { + fn slice(&self, range: Range) -> Self { + if range.is_empty() { + return Self { + offset: self.offset, + len: 0, + storage: Rc::clone(&self.storage), + buffer: RefCell::new(Vec::new()), + }; + } + + let new_len = range.end - range.start; + if new_len > self.len() { + panic!( + "tried to slice past the length, start {}, end {}, length {}", + range.start, + range.end, + self.len(), + ); + } + + Self { + offset: self.offset + range.start, + len: new_len, + storage: Rc::clone(&self.storage), + buffer: RefCell::new(Vec::new()), + } + } +} + +impl Slice> for Bytes { + fn slice(&self, range: RangeTo) -> Self { + if range.end > self.len() { + panic!( + "tried to take {}, but the length is {}", + range.end, + self.len() + ); + } + + Self { + offset: self.offset, + len: range.end, + storage: Rc::clone(&self.storage), + buffer: RefCell::new(Vec::new()), + } + } +} + +impl Slice> for Bytes +where + S: ReadNorFlash, +{ + fn slice(&self, range: RangeFrom) -> Self { + let new_offset = self.offset + range.start; + if new_offset >= self.storage.borrow().capacity() { + panic!( + "tried to slice past the capacity, starting point is {}, capacity is {}", + new_offset, + self.storage.borrow().capacity(), + ); + } + + let new_len = self.len - range.start; + if new_len > self.len { + panic!("tried to take {new_len}, but the length is {}", self.len()); + } + + Self { + offset: new_offset, + len: new_len, + storage: Rc::clone(&self.storage), + buffer: RefCell::new(Vec::new()), + } + } +} + +impl Slice for Bytes { + fn slice(&self, _: RangeFull) -> Self { + Self { + offset: self.offset, + len: self.len, + storage: Rc::clone(&self.storage), + buffer: RefCell::new(Vec::new()), + } + } +} + +impl<'a, S, const N: usize> Compare<&'a [u8]> for Bytes +where + S: ReadNorFlash, +{ + fn compare(&self, t: &'a [u8]) -> CompareResult { + if t.len() > self.len() { + return CompareResult::Incomplete; + } + + if t.is_empty() != self.is_empty() { + return CompareResult::Error; + } + + let mut pos = 0; + for chunk in t.chunks(N) { + let mut buffer = self.buffer.borrow_mut(); + buffer.clear(); + buffer + .resize(chunk.len(), 0) + .expect("chunk size should be less than or equal to N"); + + let offset = match u32::try_from(self.offset + pos) { + Ok(v) => v, + Err(_) => return CompareResult::Error, + }; + + if let Err(_) = self.storage.borrow_mut().read(offset, &mut buffer) { + return CompareResult::Error; + } + pos += chunk.len(); + + if &buffer[..] != chunk { + return CompareResult::Error; + } + } + + CompareResult::Ok + } + + fn compare_no_case(&self, t: &'a [u8]) -> CompareResult { + if t.len() > self.len() { + return CompareResult::Incomplete; + } + + if t.is_empty() != self.is_empty() { + return CompareResult::Error; + } + + let mut pos = 0; + for chunk in t.chunks(N) { + let mut buffer = self.buffer.borrow_mut(); + buffer.clear(); + buffer + .resize(chunk.len(), 0) + .expect("chunk size should be less than or equal to N"); + + let offset = match u32::try_from(self.offset + pos) { + Ok(v) => v, + Err(_) => return CompareResult::Error, + }; + + if let Err(_) = self.storage.borrow_mut().read(offset, &mut buffer) { + return CompareResult::Error; + } + pos += chunk.len(); + + if buffer + .iter() + .zip(chunk) + .any(|(a, b)| lowercase_byte(*a) != lowercase_byte(*b)) + { + return CompareResult::Error; + } + } + + CompareResult::Ok + } +} + +// Taken from: +// +// - . +// +// To match the `nom::Compare` implementations. +fn lowercase_byte(c: u8) -> u8 { + match c { + b'A'..=b'Z' => c - b'A' + b'a', + _ => c, + } +} + +// Based on: +// +// - +// +// Adapted for [`embedded-storage`]. +impl<'a, S, const N: usize> FindSubstring<&'a [u8]> for Bytes +where + S: ReadNorFlash, +{ + fn find_substring(&self, substr: &'a [u8]) -> Option { + if substr.len() > self.len() { + return None; + } + + let (&substr_first, substr_rest) = match substr.split_first() { + Some(split) => split, + // An empty substring is found at position 0 + // This matches the behavior of str.find(""). + None => return Some(0), + }; + + if substr_rest.is_empty() { + return self.memchr(substr_first); + } + + let mut offset = 0; + let haystack = self.slice(..self.len() - substr_rest.len()); + + while let Some(position) = haystack.slice(offset..).memchr(substr_first) { + offset += position; + let next_offset = offset + 1; + let maybe_substr_rest = self.slice(next_offset..).slice(..substr_rest.len()); + + if maybe_substr_rest.compare(substr_rest) == CompareResult::Ok { + return Some(offset); + } + + offset += next_offset; + } + + None + } +} + +impl FindToken for Bytes +where + S: ReadNorFlash, +{ + fn find_token(&self, token: u8) -> bool { + self.memchr(token).is_some() + } +} + +#[cfg(all(test, feature = "std"))] +mod tests { + use std::{cell::RefCell, ptr::NonNull}; + + use super::*; + use crate::rc::{Rc, RcInner}; + + #[derive(Debug)] + struct Storage<'a>(&'a [u8]); + + #[derive(Debug)] + struct Error; + + impl embedded_storage::nor_flash::NorFlashError for Error { + fn kind(&self) -> embedded_storage::nor_flash::NorFlashErrorKind { + embedded_storage::nor_flash::NorFlashErrorKind::Other + } + } + + impl<'a> embedded_storage::nor_flash::ErrorType for Storage<'a> { + type Error = Error; + } + + impl<'a> embedded_storage::nor_flash::ReadNorFlash for Storage<'a> { + const READ_SIZE: usize = 1; + + fn read(&mut self, offset: u32, bytes: &mut [u8]) -> Result<(), Self::Error> { + let offset = usize::try_from(offset).unwrap(); + if offset + bytes.len() > self.0.len() { + return Err(Error); + } + + bytes.copy_from_slice(&self.0[offset..offset + bytes.len()]); + + Ok(()) + } + + fn capacity(&self) -> usize { + self.0.len() + } + } + + macro_rules! assert_eq_iterators { + ($x:expr, $y:expr) => { + for (x, y) in $x.zip($y) { + assert_eq!(x, y, "iterators elements should be equal"); + } + }; + } + + #[test] + fn test_iter_elements() { + let original = b"abcd123"; + let storage = NonNull::from(Box::leak(Box::new(RcInner::new(RefCell::new(Storage( + original, + )))))); + let storage = unsafe { Rc::from_inner(storage) }; + let s = Bytes::<_, 16>::new(0, original.len(), storage).unwrap(); + + assert_eq_iterators!(s.iter_elements(), original.iter().copied()); + } + + #[test] + fn test_iter_indices() { + let original = b"abcd123"; + let storage = NonNull::from(Box::leak(Box::new(RcInner::new(RefCell::new(Storage( + original, + )))))); + let storage = unsafe { Rc::from_inner(storage) }; + let s = Bytes::<_, 16>::new(0, original.len(), storage).unwrap(); + + assert_eq_iterators!(s.iter_indices(), original.iter().copied().enumerate()); + } + + #[test] + fn test_slice() { + let original = b"abcd123"; + let storage = NonNull::from(Box::leak(Box::new(RcInner::new(RefCell::new(Storage( + original, + )))))); + let storage = unsafe { Rc::from_inner(storage) }; + let s = Bytes::<_, 16>::new(0, original.len(), storage).unwrap(); + + println!("test core::ops::Range"); + let a = s.slice(0..4); + let b = &original[0..4]; + assert_eq!(a.len(), b.len(), "length should be equal"); + assert_eq_iterators!(s.iter_elements(), original.iter().copied()); + + println!("test core::ops::RangeTo"); + let a = s.slice(..5); + let b = &original[..5]; + assert_eq!(a.len(), b.len(), "length should be equal"); + assert_eq_iterators!(s.iter_elements(), original.iter().copied()); + + println!("test core::ops::RangeFrom"); + let a = s.slice(3..); + let b = &original[3..]; + + assert_eq!(a.len(), b.len(), "length should be equal"); + assert_eq_iterators!(s.iter_elements(), original.iter().copied()); + + println!("test core::ops::RangeFull"); + let a = s.slice(..); + let b = &original[..]; + + assert_eq!(a.len(), b.len(), "length should be equal"); + assert_eq_iterators!(s.iter_elements(), original.iter().copied()); + } + + #[test] + fn test_find_substring() { + let original = b"abcd123"; + let storage = NonNull::from(Box::leak(Box::new(RcInner::new(RefCell::new(Storage( + original, + )))))); + let storage = unsafe { Rc::from_inner(storage) }; + let s = Bytes::<_, 16>::new(0, original.len(), storage).unwrap(); + + for (i, &c) in original.iter().enumerate() { + assert_eq!(s.find_substring(&[c]), Some(i)); + } + + assert_eq!(s.find_substring(b"123"), Some(4)); + assert_eq!(s.find_substring(b"abcd"), Some(0)); + assert_eq!(s.find_substring(b"cd"), Some(2)); + assert_eq!(s.find_substring(&[]), Some(0)); + } + + #[test] + fn test_find_token() { + let original = b"abcd123"; + let storage = NonNull::from(Box::leak(Box::new(RcInner::new(RefCell::new(Storage( + original, + )))))); + let storage = unsafe { Rc::from_inner(storage) }; + let s = Bytes::<_, 16>::new(0, original.len(), storage).unwrap(); + + for &c in original.iter() { + assert!( + s.find_token(c), + "failed to find token {}", + char::from_u32(u32::from(c)).unwrap() + ); + } + } + + #[test] + fn test_compare() { + let original = b"abcd123"; + let storage = NonNull::from(Box::leak(Box::new(RcInner::new(RefCell::new(Storage( + original, + )))))); + let storage = unsafe { Rc::from_inner(storage) }; + let s = Bytes::<_, 16>::new(0, original.len(), storage).unwrap(); + + assert_eq!( + s.compare(original), + CompareResult::Ok, + "bytes should be equal" + ); + assert_eq!( + s.compare(b"abcd1234"), + CompareResult::Incomplete, + "there should not enough bytes to compare" + ); + assert_eq!(s.compare(&[]), CompareResult::Error, "should not be equal"); + assert_eq!( + s.compare_no_case(b"ABCD123"), + CompareResult::Ok, + "case-insensitive comparison should succeed" + ); + } +} diff --git a/nom-embedded-storage/src/rc.rs b/nom-embedded-storage/src/rc.rs new file mode 100644 index 0000000..c91d3d5 --- /dev/null +++ b/nom-embedded-storage/src/rc.rs @@ -0,0 +1,110 @@ +// SPDX-FileCopyrightText: © 2024 Foundation Devices, Inc. +// SPDX-License-Identifier: GPL-3.0-or-later + +//! # Heapless [`Rc`] type. +//! +//! This is a reference countable pointer for Rust, imitating the official +//! `alloc::rc::Rc` pointer but without using the heap. +//! +//! This requires the user to create a [`NonNull`] [`RcInner`] type allocated +//! by their own mean that won't be de-allocated, essentially leaking this +//! memory, or in other words, it should have a `'static` lifetime. +//! +//! For example by using [`Box::leak`] or by using a global mutable static +//! variable. +//! +//! Notes: +//! +//! Consider splitting this code into a separate crate. + +use core::{cell::Cell, fmt, marker::PhantomData, ops::Deref, ptr, ptr::NonNull}; + +pub struct Rc { + ptr: NonNull>, + phantom: PhantomData>, +} + +pub struct RcInner { + strong: Cell, + value: T, +} + +impl RcInner { + pub const fn new(value: T) -> Self { + Self { + strong: Cell::new(1), + value, + } + } + + fn inc_strong(&self) { + let strong = self.strong.get().wrapping_add(1); + self.strong.set(strong); + + if strong == 0 { + panic!("the reference count overflowed"); + } + } + + fn dec_strong(&self) { + let strong = self.strong.get() - 1; + self.strong.set(strong); + } +} + +impl Rc { + /// Construct a [`Rc`] from the inner value. + pub unsafe fn from_inner(inner: NonNull>) -> Self { + Self { + ptr: inner, + phantom: PhantomData, + } + } + + #[inline(always)] + fn inner(&self) -> &RcInner { + unsafe { self.ptr.as_ref() } + } +} + +impl Deref for Rc { + type Target = T; + + fn deref(&self) -> &Self::Target { + &self.inner().value + } +} + +impl AsRef for Rc { + fn as_ref(&self) -> &T { + &**self + } +} + +impl Clone for Rc { + fn clone(&self) -> Self { + unsafe { + self.inner().inc_strong(); + Self::from_inner(self.ptr) + } + } +} + +impl Drop for Rc { + fn drop(&mut self) { + self.inner().dec_strong(); + + // Just drop the value after reaching 0, no de-allocation happens. + if self.inner().strong.get() == 0 { + unsafe { + ptr::drop_in_place(&mut (*self.ptr.as_ptr()).value); + } + } + } +} + +impl fmt::Debug for Rc { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt::Debug::fmt(&**self, f) + } +} From 1fae9c699a97efad1451aa5d25d9696d1ceed12a Mon Sep 17 00:00:00 2001 From: Jean-Pierre De Jesus DIAZ Date: Wed, 13 Nov 2024 19:40:10 +0100 Subject: [PATCH 2/4] SFT-4401: Fix bip39 compilation. --- test-vectors/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test-vectors/Cargo.toml b/test-vectors/Cargo.toml index bde41d7..7f935fb 100644 --- a/test-vectors/Cargo.toml +++ b/test-vectors/Cargo.toml @@ -21,7 +21,7 @@ seedqr = ["bip39/serde", "faster-hex/serde"] blockchain-commons = ["bitcoin/serde", "faster-hex/serde"] [dependencies] -bip39 = { workspace = true, optional = true } +bip39 = { workspace = true, optional = true, features = ["std"] } bitcoin = { workspace = true, optional = true, features = ["std"] } bs58 = { workspace = true, optional = true } faster-hex = { workspace = true, optional = true } From 1e74b4e06afea38615034acdd22a1bd0f924e5d3 Mon Sep 17 00:00:00 2001 From: Jean-Pierre De Jesus DIAZ Date: Wed, 13 Nov 2024 19:41:48 +0100 Subject: [PATCH 3/4] SFT-4401: Bump bip39 version to ^2.1. --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index ef9287b..c3ea178 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -23,7 +23,7 @@ homepage = "https://github.com/Foundation-Devices/foundation-rs" anyhow = { version = "1.0.83", default-features = false } arbitrary = { version = "1", features = ["derive"] } bech32 = { version = "0.9", default-features = false } -bip39 = { version = "2", default-features = false } +bip39 = { version = "2.1", default-features = false } bitcoin = { version = "0.31", default-features = false } bitcoin_hashes = { version = "0.14", default-features = false } bs58 = "0.5" From 0bb259908226fb16a13a25422932b83b80667c24 Mon Sep 17 00:00:00 2001 From: Jean-Pierre De Jesus DIAZ Date: Wed, 13 Nov 2024 19:52:05 +0100 Subject: [PATCH 4/4] SFT-4401: Fix typo. --- nom-embedded-storage/src/rc.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nom-embedded-storage/src/rc.rs b/nom-embedded-storage/src/rc.rs index c91d3d5..0def585 100644 --- a/nom-embedded-storage/src/rc.rs +++ b/nom-embedded-storage/src/rc.rs @@ -7,7 +7,7 @@ //! `alloc::rc::Rc` pointer but without using the heap. //! //! This requires the user to create a [`NonNull`] [`RcInner`] type allocated -//! by their own mean that won't be de-allocated, essentially leaking this +//! by their own means that won't be de-allocated, essentially leaking this //! memory, or in other words, it should have a `'static` lifetime. //! //! For example by using [`Box::leak`] or by using a global mutable static