### Encoding and Decoding Setup Source: https://docs.rs/femtopb/latest/src/femtopb/lib.rs.html An example showing the setup for encoding and decoding a message using the femtopb::Message trait. ```rust use femtopb::Message as _; // Adding some more derived traits to aid with the example below: #[derive(Clone, Debug, PartialEq, femtopb::Message)] pub struct Person<'a> { #[femtopb(uint32, tag = 1)] pub age: u32, #[femtopb(string, tag = 2)] pub name: &'a str, #[femtopb(unknown_fields)] pub unknown_fields: femtopb::UnknownFields<'a>, } fn main() { let person = Person { age: 32, name: "David", ..Default::default() }; // Create a new buffer filled with zeroes. This of course doesn't need to be dynamically ``` -------------------------------- ### Window<'a> sub-window type and tests Source: https://docs.rs/femtopb/latest/src/femtopb/window.rs.html Defines the `Window<'a>` type and its methods (`empty`, `anchored`, `covering`, `extend_to`, `region`) plus unit tests. The type stores a borrowed buffer and `start..end` offsets as two `usize` fields to remain `Copy`; `extend_to` uses a saturating subtraction to keep the decode hot path panic-free. Tests cover empty, anchored, covering, and repeated `extend_to` behavior. ```rust //! A sub-window into a borrowed message buffer. /// A sub-window of a message buffer: the whole buffer `data`, plus the `start..end` byte offsets of /// the span of interest within it (e.g. a repeated field's occurrences, or the unknown fields). The /// bytes outside `start..end` belong to other fields and are ignored (see [`Self::region`]). /// /// The offsets are kept as two `usize` fields rather than a `Range` because `Range` is not /// `Copy`, and this type is embedded in the `Copy` field wrappers. /// /// Growing the window (see [`Self::extend_to`]) is a flat, unconditional store of `end`, which keeps /// the decode hot path — where it happens once per occurrence — provably panic-free. Writing through /// an `enum` variant, or re-deriving a sub-slice there, perturbs the optimizer enough to defeat the /// crate's no-panic guarantee. #[derive(Clone, Copy, Debug, Default, PartialEq)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] pub(crate) struct Window<'a> { data: &'a [u8], start: usize, end: usize, } impl<'a> Window<'a> { /// An empty window that covers nothing. pub(crate) const fn empty() -> Self { Self { data: &[], start: 0, end: 0, } } /// A window into `data` starting where `from` begins (a suffix of `data`) and covering *nothing* /// initially; grow it with [`Self::extend_to`] as decoding walks the bytes of interest. Use this /// when the safe fallback for an un-extended window is "empty" (e.g. unknown fields, where an /// un-narrowed window would wrongly re-encode trailing known fields). pub(crate) fn anchored(data: &'a [u8], from: &[u8]) -> Self { let start = data.len().saturating_sub(from.len()); Self { data, start, end: start, } } /// A window into `data` starting where `from` begins (a suffix of `data`) and covering all the /// way to the end of `data`. [`Self::extend_to`] then narrows the tail to end at the last /// occurrence. Use this when the safe fallback for an un-extended window is "the whole rest of /// the buffer" (e.g. a lazily-parsed repeated/packed field, which simply re-scans to the end — /// correct, just not narrowed — if `extend_to` never runs). pub(crate) fn covering(data: &'a [u8], from: &[u8]) -> Self { let start = data.len().saturating_sub(from.len()); Self { data, start, end: data.len(), } } /// Sets the window to end where `remaining` begins — i.e. at the end of the occurrence that was /// just skipped. `remaining` must be a suffix of `data`; since decoding advances through the /// buffer in order, `data.len() - remaining.len()` is the absolute offset of that point. Only /// ever called from the decoding runtime, once per occurrence. #[inline] pub(crate) fn extend_to(&mut self, remaining: &[u8]) { self.end = self.data.len().saturating_sub(remaining.len()); } /// The bytes the window covers: `data[start..end]`. #[inline] #[cfg_attr(feature = "assert-no-panic", no_panic::no_panic)] pub(crate) fn region(&self) -> &'a [u8] { self.data.get(self.start..self.end).unwrap_or(&[]) } } #[cfg(test)] mod tests { use super::*; #[test] fn empty_covers_nothing() { let w = Window::empty(); assert_eq!((w.start, w.end), (0, 0)); assert_eq!(w.region(), b""); } #[test] fn anchored_starts_at_the_field_and_covers_nothing_until_extended() { let data = b"abcdef"; let w = Window::anchored(data, &data[2..]); // `from` begins at offset 2 assert_eq!((w.start, w.end), (2, 2)); assert_eq!(w.region(), b""); } #[test] fn covering_spans_from_the_field_to_the_end() { let data = b"abcdef"; let w = Window::covering(data, &data[2..]); assert_eq!((w.start, w.end), (2, 6)); assert_eq!(w.region(), b"cdef"); } #[test] fn extend_to_narrows_the_tail() { let data = b"abcdef"; let mut w = Window::covering(data, &data[2..]); // start 2, end 6 w.extend_to(&data[4..]); // `remaining` begins at offset 4 assert_eq!((w.start, w.end), (2, 4)); assert_eq!(w.region(), b"cd"); } #[test] fn extend_to_can_be_called_repeatedly() { let data = b"abcdef"; let mut w = Window::covering(data, &data[1..]); // start 1 w.extend_to(&data[3..]); // end -> 3 assert_eq!(w.region(), b"bc"); } } ``` -------------------------------- ### Test region with inverted offsets Source: https://docs.rs/femtopb/latest/src/femtopb/window.rs.html Confirms that a Window with a start index greater than the end index returns an empty slice rather than panicking. ```rust #[test] fn region_with_start_greater_than_end_is_empty_not_a_panic() { let w = Window { data: b"abcdef", start: 4, end: 2, }; assert_eq!(w.region(), b""); } ``` -------------------------------- ### Initialize Packed from message buffer Source: https://docs.rs/femtopb/latest/src/femtopb/packed.rs.html Demonstrates creating a Packed instance from a message buffer and verifying the decoded values. ```rust let packed: Packed = Packed::from_msg_buf(tag as u32, &msgbuf, &msgbuf); assert_eq!( packed.iter().collect::>().as_slice(), &[Ok(1.5), Ok(-2.0)] ); ``` -------------------------------- ### Property test for region panic safety Source: https://docs.rs/femtopb/latest/src/femtopb/window.rs.html Uses proptest to verify that the region() method never panics for arbitrary combinations of data, start, and end offsets. ```rust proptest::proptest! { /// `region()` is on the panic-free decode path; it must never panic for *any* combination /// of `data`, `start`, and `end` (including out-of-range and inverted offsets), always /// returning a sub-slice of `data` or empty. #[test] fn region_never_panics_for_arbitrary_offsets( data: Vec, start in 0usize..64, end in 0usize..64, ) { let w = Window { data: &data, start, end }; let region = w.region(); proptest::prop_assert!(region.len() <= data.len()); } } ``` -------------------------------- ### Define Repeated Fields with WeatherStationEvent Source: https://docs.rs/femtopb Shows how to define structures with repeated fields using femtopb, which borrows memory from the original buffer to avoid dynamic allocation. ```rust use femtopb::Message as _; use femtopb::item_encoding; use femtopb::repeated; #[derive(Clone, Debug, PartialEq, femtopb::Message)] pub struct WeatherStationEvent<'a> { #[femtopb(bytes, tag = 1)] serial_id: &'a [u8], #[femtopb(message, repeated, tag = 2)] new_readings: repeated::Repeated<'a, TempReading<'a>, item_encoding::Message<'a, TempReading<'a>>>, #[femtopb(unknown_fields)] pub unknown_fields: femtopb::UnknownFields<'a>, } #[derive(Clone, Debug, PartialEq, femtopb::Message)] pub struct TempReading<'a> { #[femtopb(uint64, tag = 1)] id: u64, #[femtopb(float, tag = 2)] degrees_c: f32, #[femtopb(float, tag = 3)] pressure_hpa: f32, #[femtopb(unknown_fields)] pub unknown_fields: femtopb::UnknownFields<'a>, } fn main() { let new_readings = &[ TempReading { id: 1234, degrees_c: 23.0, ..Default::default() }, TempReading { id: 1235, degrees_c: 23.2, ..Default::default() }, TempReading { id: 1236, degrees_c: 23.4, ..Default::default() }, ]; let event = WeatherStationEvent { serial_id: b"mystation-abc123", new_readings: repeated::Repeated::from_slice(new_readings), ..Default::default() }; } ``` -------------------------------- ### Message without unknown_fields does not compile Source: https://docs.rs/femtopb/latest/femtopb/index.html Shows a Person struct that omits the required unknown_fields field. This fails to compile with a non-exhaustive match error because the derived decode implementation needs the catch-all arm. The source marks this example with a warning icon (ⓘ). ```rust #[derive(Clone, femtopb::Message)] pub struct Person<'a> { #[femtopb(uint32, tag = 1)] pub age: u32, #[femtopb(string, tag = 2)] pub name: &'a str, // note: no `#[femtopb(unknown_fields)]` field } ``` -------------------------------- ### Test anchored and covering window saturation Source: https://docs.rs/femtopb/latest/src/femtopb/window.rs.html Verifies that Window::anchored and Window::covering correctly handle cases where the provided 'from' slice is longer than the data, clamping indices to prevent underflow. ```rust #[test] fn anchored_and_covering_saturate_when_from_is_longer_than_data() { // A degenerate/hostile `from` longer than `data` must not underflow `start`; it clamps to 0. let data = b"ab"; let longer = b"xxxxx"; let anchored = Window::anchored(data, longer); assert_eq!((anchored.start, anchored.end), (0, 0)); assert_eq!(anchored.region(), b""); let covering = Window::covering(data, longer); assert_eq!((covering.start, covering.end), (0, 2)); assert_eq!(covering.region(), b"ab"); } ``` -------------------------------- ### Repeated and packed fields with femtopb::repeated::Repeated Source: https://docs.rs/femtopb/latest/src/femtopb/lib.rs.html Defines a `WeatherStationEvent` message with a repeated field of `TempReading` messages using `repeated::Repeated` and `item_encoding::Message`. The repeated field decodes lazily on iteration, and the example shows encoding and decoding with a pre-sized buffer. ```rust use femtopb::Message as _; use femtopb::item_encoding; use femtopb::repeated; #[derive(Clone, Debug, PartialEq, femtopb::Message)] pub struct WeatherStationEvent<'a> { #[femtopb(bytes, tag = 1)] serial_id: &'a [u8], #[femtopb(message, repeated, tag = 2)] new_readings: repeated::Repeated<'a, TempReading<'a>, item_encoding::Message<'a, TempReading<'a>>>, #[femtopb(unknown_fields)] pub unknown_fields: femtopb::UnknownFields<'a>, } #[derive(Clone, Debug, PartialEq, femtopb::Message)] pub struct TempReading<'a> { #[femtopb(uint64, tag = 1)] id: u64, #[femtopb(float, tag = 2)] degrees_c: f32, #[femtopb(float, tag = 3)] pressure_hpa: f32, #[femtopb(unknown_fields)] pub unknown_fields: femtopb::UnknownFields<'a>, } fn main() { let new_readings = &[ TempReading { id: 1234, degrees_c: 23.0, ..Default::default() }, TempReading { id: 1235, degrees_c: 23.2, ..Default::default() }, TempReading { id: 1236, degrees_c: 23.4, ..Default::default() }, ]; let event = WeatherStationEvent { serial_id: b"mystation-abc123", new_readings: repeated::Repeated::from_slice(new_readings), ..Default::default() }; let mut buf = vec![0; event.encoded_len()]; event.encode(&mut buf.as_mut_slice()).unwrap(); // ... send buf over a LoRa network or something ... let new_event = WeatherStationEvent::decode(buf.as_slice()).unwrap(); assert_eq!(new_event.serial_id, b"mystation-abc123"); for reading in &new_event.new_readings { let TempReading { id, degrees_c, .. } = reading.unwrap(); println!("{id}: {degrees_c}"); // Prints: // 1234: 23.0 // 1235: 23.2 // 1236: 23.4 } } ``` -------------------------------- ### bits.rs: public little-endian put/get functions for f32, f64, u32, u64, i32, i64 Source: https://docs.rs/femtopb/latest/src/femtopb/bits.rs.html Public API functions that write or read little-endian values of the given type using the internal chunk helpers. Each put function advances the write cursor; each get function advances the read cursor. ```rust pub fn put_f32_le(cursor: &mut &mut [u8], value: f32) { put_chunk(cursor, value.to_le_bytes()); } pub fn get_f32_le(cursor: &mut &[u8]) -> f32 { f32::from_le_bytes(take_chunk(cursor)) } pub fn put_f64_le(cursor: &mut &mut [u8], value: f64) { put_chunk(cursor, value.to_le_bytes()); } pub fn get_f64_le(cursor: &mut &[u8]) -> f64 { f64::from_le_bytes(take_chunk(cursor)) } pub fn put_u32_le(cursor: &mut &mut [u8], value: u32) { put_chunk(cursor, value.to_le_bytes()); } pub fn get_u32_le(cursor: &mut &[u8]) -> u32 { u32::from_le_bytes(take_chunk(cursor)) } pub fn put_u64_le(cursor: &mut &mut [u8], value: u64) { put_chunk(cursor, value.to_le_bytes()); } pub fn get_u64_le(cursor: &mut &[u8]) -> u64 { u64::from_le_bytes(take_chunk(cursor)) } pub fn put_i32_le(cursor: &mut &mut [u8], value: i32) { put_chunk(cursor, value.to_le_bytes()); } pub fn get_i32_le(cursor: &mut &[u8]) -> i32 { i32::from_le_bytes(take_chunk(cursor)) } pub fn put_i64_le(cursor: &mut &mut [u8], value: i64) { put_chunk(cursor, value.to_le_bytes()); } pub fn get_i64_le(cursor: &mut &[u8]) -> i64 { i64::from_le_bytes(take_chunk(cursor)) } ``` -------------------------------- ### Compare Packed instances Source: https://docs.rs/femtopb/latest/src/femtopb/packed.rs.html Shows that Packed instances created from different sources (buffer vs slice) are equal if they contain the same data. ```rust #[test] fn packed_partial_eq_holds_across_representations() { let tag = 1; let msgbuf = &[key(tag, encoding::WireType::LengthDelimited), 3, 1, 2, 3]; let from_buf: Packed = Packed::from_msg_buf(tag as u32, msgbuf, msgbuf); let from_slice: Packed = Packed::from_slice(&[1, 2, 3]); assert_eq!(from_buf, from_slice); let different: Packed = Packed::from_slice(&[1, 2, 4]); assert_ne!(from_buf, different); } ``` -------------------------------- ### Encode and decode a message Source: https://docs.rs/femtopb/latest/femtopb/index.html Demonstrates how to encode a message into a pre-allocated buffer and decode it back, ensuring data integrity. ```rust fn main() { let person = Person { age: 32, name: "David", ..Default::default() }; // Create a new buffer filled with zeroes. This of course doesn't need to be dynamically // allocated; at this point, you could use a stack-allocated buffer, or `'static` memory // region, for example. Here, we use a `Vec` for simplicity. // The buffer MUST already have the right length, since we can't grow the buffer without // dynamic memory allocation. let mut buf = vec![0; person.encoded_len()]; // Encode the Person to the buffer person.encode(&mut buf.as_mut_slice()).unwrap(); // Decode a new Person from the same buffer let new_person = Person::decode(buf.as_slice()).unwrap(); // The same information should be preserved! assert_eq!(person, new_person); } ``` -------------------------------- ### Encode and decode a message with femtopb Source: https://docs.rs/femtopb/latest/femtopb/index.html Shows encoding a message into a buffer, sending it (e.g., over LoRa), decoding it back, and iterating over repeated fields. The output demonstrates the printed values for three readings. ```rust let mut buf = vec![0; event.encoded_len()]; event.encode(&mut buf.as_mut_slice()).unwrap(); // ... send buf over a LoRa network or something ... let new_event = WeatherStationEvent::decode(buf.as_slice()).unwrap(); assert_eq!(new_event.serial_id, b"mystation-abc123"); for reading in &new_event.new_readings { let TempReading { id, degrees_c, .. } = reading.unwrap(); println!("{id}: {degrees_c}"); // Prints: // 1234: 23.0 // 1235: 23.2 // 1236: 23.4 } ``` -------------------------------- ### bytes.rs module implementation Source: https://docs.rs/femtopb/latest/src/femtopb/runtime/scalar/bytes.rs.html Full source of the bytes.rs module, including encoding/decoding functions and macros for length-delimited byte fields. ```rust use crate::{encoding, error}; use core::mem; // Also used by `crate::item_encoding` pub(crate) const WIRE_TYPE: encoding::WireType = encoding::WireType::LengthDelimited; #[inline] pub fn encode(tag: u32, value: &[u8], default: &[u8], cursor: &mut &mut [u8]) { if value != default { encode_key_value(tag, value, cursor); } } #[inline] pub fn encode_optional(tag: u32, value: Option<&[u8]>, default: &[u8], cursor: &mut &mut [u8]) { if let Some(value) = value { if value != default { encode_key_value(tag, value, cursor); } } } crate::runtime::macros::length_delimited!('a, &'a [u8], crate::item_encoding::Bytes); #[inline] fn encode_key_value(tag: u32, value: &[u8], cursor: &mut &mut [u8]) { encoding::encode_key(tag, WIRE_TYPE, cursor); encode_single_value(value, cursor); } #[inline] fn encode_single_value(value: &[u8], cursor: &mut &mut [u8]) { let len = value.len(); encoding::encode_varint(len as u64, cursor); let buf = mem::take(cursor); let (bytes, rest) = buf.split_at_mut(len); *cursor = rest; bytes.copy_from_slice(value); } #[inline] pub fn decode<'a>( _tag: u32, wire_type: encoding::WireType, _msg_buf: &'a [u8], _field_start: &'a [u8], cursor: &mut &'a [u8], field: &mut &'a [u8], ) -> Result<(), error::DecodeError> { encoding::check_wire_type(WIRE_TYPE, wire_type)?; *field = decode_single_value(cursor)?; Ok(()) } #[inline] pub fn decode_optional<'a>( _tag: u32, wire_type: encoding::WireType, _msg_buf: &'a [u8], _field_start: &'a [u8], cursor: &mut &'a [u8], field: &mut Option<&'a [u8]>, ) -> Result<(), error::DecodeError> { encoding::check_wire_type(WIRE_TYPE, wire_type)?; *field = Some(decode_single_value(cursor)?); Ok(()) } // Also used by `crate::item_encoding` pub(crate) fn decode_single_value<'a>( cursor: &mut &'a [u8], ) -> Result<&'a [u8], error::DecodeError> { let len = encoding::decode_varint(cursor)?; let len = usize::try_from(len).map_err(|_| error::DecodeError::LengthTooLargeForPlatform(len))?; if cursor.len() >= len { let (bytes, rest) = cursor.split_at(len); *cursor = rest; Ok(bytes) } else { Err(error::DecodeError::BufferUnderflow) } } crate::runtime::macros::decode_packed_repeated!('a, &'a [u8], crate::item_encoding::Bytes); crate::runtime::macros::trivial_clear!('a, &'a [u8], &'static [u8], crate::item_encoding::Bytes); #[cfg(test)] mod tests { use super::*; #[test] fn decode_single_value_rejects_length_beyond_buffer() { // declares length 5 but only one content byte follows let buf = [0x05u8, b'a']; let mut cursor: &[u8] = &buf; assert_eq!( decode_single_value(&mut cursor).unwrap_err(), error::DecodeError::BufferUnderflow ); } // Only reachable where `usize` is narrower than `u64`; exercised by the 32-bit CI target. #[cfg(target_pointer_width = "32")] #[test] fn decode_single_value_rejects_length_exceeding_usize() { // varint encoding of 2^32, which does not fit in a 32-bit `usize` let buf = [0x80u8, 0x80, 0x80, 0x80, 0x10]; let mut cursor: &[u8] = &buf; assert_eq!( decode_single_value(&mut cursor).unwrap_err(), error::DecodeError::LengthTooLargeForPlatform(0x1_0000_0000) ); } } ``` -------------------------------- ### bits.rs: test module with write helper and little_endian_byte_layout test Source: https://docs.rs/femtopb/latest/src/femtopb/bits.rs.html Test helper `write` that writes a value into a fresh N-byte buffer and asserts the cursor is advanced to the end. The `little_endian_byte_layout` test verifies the byte order for u32, u64, i32, and f32 values. ```rust #[cfg(test)] mod tests { use super::*; /// Writes `value` into a fresh `N`-byte buffer via `put`, returns the bytes, and asserts the /// write cursor was advanced to the end. fn write(put: impl FnOnce(&mut &mut [u8]), _n: usize) -> [u8; N] { let mut buf = [0u8; N]; let mut cursor: &mut [u8] = &mut buf; put(&mut cursor); assert!(cursor.is_empty(), "put must advance the cursor to the end"); buf } #[test] fn little_endian_byte_layout() { assert_eq!(write::<4>(|c| put_u32_le(c, 0x0403_0201), 4), [1, 2, 3, 4]); assert_eq!( write::<8>(|c| put_u64_le(c, 0x0807_0605_0403_0201), 8), [1, 2, 3, 4, 5, 6, 7, 8] ); assert_eq!( write::<4>(|c| put_i32_le(c, -1), 4), [0xFF, 0xFF, 0xFF, 0xFF] ); // f32 1.0 == 0x3F800000 little-endian. assert_eq!( write::<4>(|c| put_f32_le(c, 1.0), 4), [0x00, 0x00, 0x80, 0x3F] ); } ``` -------------------------------- ### Encode and decode a Person message with femtopb::Message Source: https://docs.rs/femtopb/latest/src/femtopb/lib.rs.html Shows how to define a `Person` message with `#[derive(femtopb::Message)]`, encode it into a pre-sized buffer, and decode it back. The buffer must already have the right length because the library cannot grow it without dynamic memory allocation. ```rust use femtopb::Message as _; #[derive(Clone, Debug, PartialEq, femtopb::Message)] pub struct Person<'a> { #[femtopb(string, tag = 1)] pub name: &'a str, #[femtopb(uint64, tag = 2)] pub id: u64, #[femtopb(string, tag = 3)] pub email: &'a str, #[femtopb(unknown_fields)] pub unknown_fields: femtopb::UnknownFields<'a>, } fn main() { let person = Person { name: "Alice", id: 1234, email: "alice@example.com", ..Default::default() }; // The buffer is allocated here, but it could be stack-allocated or `'static` memory // The buffer MUST already have the right length, since we can't grow the buffer without // dynamic memory allocation. let mut buf = vec![0; person.encoded_len()]; // Encode the Person to the buffer person.encode(&mut buf.as_mut_slice()).unwrap(); // Decode a new Person from the same buffer let new_person = Person::decode(buf.as_slice()).unwrap(); // The same information should be preserved! assert_eq!(person, new_person); } ``` -------------------------------- ### WireType Source: https://docs.rs/femtopb/latest/src/femtopb/encoding.rs.html Represents all possible protobuf wire types for encoding fields, with conversion from u64. ```APIDOC ## [ENUM] WireType ### Description Represents all possible protobuf wire types for encoding fields. ### Method Enum ### Endpoint `WireType` ### Variants - **Varint** (u8 = 0) - Variable-length integer. - **SixtyFourBit** (u8 = 1) - 64-bit fixed-width value. - **LengthDelimited** (u8 = 2) - Length-delimited value. - **StartGroup** (u8 = 3) - Start of a group. - **EndGroup** (u8 = 4) - End of a group. - **ThirtyTwoBit** (u8 = 5) - 32-bit fixed-width value. ### Conversion `TryFrom` is implemented, returning `Result`. Invalid values produce `error::DecodeError::InvalidWireTypeValue(value)`. ### Request Example ```rust let wire_type = WireType::try_from(2u64).unwrap(); ``` ### Response #### Success Response Returns `Ok(WireType)` for valid values. #### Response Example ```rust Ok(WireType::LengthDelimited) ``` #### Error Response Returns `Err(error::DecodeError)` for invalid values. #### Error Example ```rust Err(error::DecodeError::InvalidWireTypeValue(6)) ``` ``` -------------------------------- ### bits.rs: get_advances_cursor_and_leaves_the_rest test Source: https://docs.rs/femtopb/latest/src/femtopb/bits.rs.html Verifies that get_u32_le reads the first 4 bytes and leaves trailing bytes untouched while advancing the cursor by 4. ```rust #[test] fn get_advances_cursor_and_leaves_the_rest() { let buf = [1u8, 0, 0, 0, 0xAA, 0xBB]; let mut cursor: &[u8] = &buf; assert_eq!(get_u32_le(&mut cursor), 1); assert_eq!(cursor, &[0xAA, 0xBB]); // trailing bytes untouched, cursor advanced by 4 } ``` -------------------------------- ### Iterate unknown fields with `for_each_unknown_field` Source: https://docs.rs/femtopb/latest/src/femtopb/runtime/unknown_fields.rs.html Walks the retained message buffer and invokes `visit` with the raw bytes of each unknown field. Stops at the first malformed field to remain total and panic-free. ```rust /// Walks the retained message buffer and invokes `visit` with the raw bytes of each field whose tag /// is *not* known to the surrounding message — i.e. each unknown field that must be preserved. /// /// Iteration stops at the first malformed field. The buffer was already parsed successfully during /// decoding, so for data produced by this crate that never happens; the early-out just keeps the /// function total and panic-free. #[inline] fn for_each_unknown_field(field: &unknown_fields::UnknownFields, mut visit: F) where F: FnMut(&[u8]), { let known_tags = field.known_tags(); let mut buf = field.region(); while !buf.is_empty() { let before = buf; let Ok((tag, wire_type)) = encoding::decode_key(&mut buf) else { break; }; if encoding::skip_field(wire_type, tag, &mut buf).is_err() { break; } let field_len = before.len() - buf.len(); if !known_tags.contains(&tag) { if let Some((field_bytes, _)) = before.split_at_checked(field_len) { visit(field_bytes); } } } } ``` -------------------------------- ### Module declarations in runtime.rs Source: https://docs.rs/femtopb/latest/src/femtopb/runtime.rs.html Declares the public submodules of the femtopb runtime: enumeration, message, oneof, scalar, and unknown_fields, plus a private macros module. ```rust pub mod enumeration; mod macros; pub mod message; pub mod oneof; pub mod scalar; pub mod unknown_fields; ``` -------------------------------- ### Enumeration derive macro attribute syntax Source: https://docs.rs/femtopb/latest/femtopb/derive.Enumeration.html Shows the attribute syntax for the Enumeration derive macro. The #[femtopb] attribute is available within the derive. ```rust #[derive(Enumeration)] { // Attributes available to this derive: #[femtopb] } ``` -------------------------------- ### Encoding and decoding a message with repeated fields Source: https://docs.rs/femtopb Shows encoding a message into a buffer, sending it (commented), then decoding and iterating over repeated fields. The output comments show expected printed values. ```rust let mut buf = vec![0; event.encoded_len()]; event.encode(&mut buf.as_mut_slice()).unwrap(); // ... send buf over a LoRa network or something ... let new_event = WeatherStationEvent::decode(buf.as_slice()).unwrap(); assert_eq!(new_event.serial_id, b"mystation-abc123"); for reading in &new_event.new_readings { let TempReading { id, degrees_c, .. } = reading.unwrap(); println!("{id}: {degrees_c}"); // Prints: // 1234: 23.0 // 1235: 23.2 // 1236: 23.4 } ``` -------------------------------- ### Calculate key length Source: https://docs.rs/femtopb/latest/src/femtopb/encoding.rs.html Verifies that key_len returns the correct number of bytes for different tag values. ```rust #[test] fn key_len_matches_encoded_width() { assert_eq!(key_len(1), 1); assert_eq!(key_len(15), 1); // 15 << 3 = 120, still one byte assert_eq!(key_len(16), 2); // 16 << 3 = 128, needs two bytes assert_eq!(key_len(MAX_TAG), 5); } ``` -------------------------------- ### Message runtime implementation Source: https://docs.rs/femtopb/latest/src/femtopb/runtime/message.rs.html The message runtime module provides core functions for encoding, decoding, and length calculation of Protobuf messages. These functions are designed to be used within the generated code for Protobuf messages. ```rust 1use crate::repeated; 2use crate::{encoding, error}; 3use crate::{item_encoding, message}; 4 5#[inline] 6#[cfg_attr(feature = "assert-no-panic", no_panic::no_panic)] 7pub fn encode<'a, M>(tag: u32, value: &M, cursor: &mut &mut [u8]) 8where 9 M: message::Message<'a>, 10{ 11 encoding::encode_key(tag, encoding::WireType::LengthDelimited, cursor); 12 encoding::encode_varint(u64::try_from(value.encoded_len()).unwrap(), cursor); 13 value.encode_raw(cursor); 14} 15 16#[inline] 17#[cfg_attr(feature = "assert-no-panic", no_panic::no_panic)] 18pub fn encode_optional<'a, M>(tag: u32, value: &Option, cursor: &mut &mut [u8]) 19where 20 M: message::Message<'a>, 21{ 22 if let Some(m) = value.as_ref() { 23 encode(tag, m, cursor) 24 } 25} 26 27#[inline] 28#[cfg_attr(feature = "assert-no-panic", no_panic::no_panic)] 29pub fn encode_repeated<'a, M>( 30 tag: u32, 31 values: &repeated::Repeated<'a, M, item_encoding::Message<'a, M>>, 32 cursor: &mut &mut [u8], 33) where 34 M: message::Message<'a>, 35{ 36 for value in values.into_iter().flatten() { 37 encode(tag, &value, cursor); 38 } 39} 40 41#[inline] 42#[cfg_attr(feature = "assert-no-panic", no_panic::no_panic)] 43pub fn encoded_len<'a, M>(tag: u32, value: &M) -> usize 44where 45 M: message::Message<'a>, 46{ 47 let len = value.encoded_len(); 48 encoding::key_len(tag) + encoding::encoded_len_varint(u64::try_from(len).unwrap()) + len 49} 50 51#[inline] 52#[cfg_attr(feature = "assert-no-panic", no_panic::no_panic)] 53pub fn encoded_len_optional<'a, M>(tag: u32, value: &Option) -> usize 54where 55 M: message::Message<'a>, 56{ 57 if let Some(m) = value.as_ref() { 58 encoded_len(tag, m) 59 } else { 60 0 61 } 62} 63 64#[inline] 65#[cfg_attr(feature = "assert-no-panic", no_panic::no_panic)] 66pub fn encoded_len_repeated<'a, M>( 67 tag: u32, 68 values: &repeated::Repeated<'a, M, item_encoding::Message<'a, M>>, 69) -> usize 70where 71 M: message::Message<'a>, 72{ 73 values 74 .iter() 75 .map(|r| { 76 r.map(|v| { 77 let len = v.encoded_len(); 78 encoding::key_len(tag) + encoding::encoded_len_varint(len as u64) + len 79 }) 80 .unwrap_or(0) 81 }) 82 .sum::() 83} 84 85#[inline] 86#[cfg_attr(feature = "assert-no-panic", no_panic::no_panic)] 87pub fn decode<'a, M>( 88 _tag: u32, 89 wire_type: encoding::WireType, 90 _msg_buf: &'a [u8], 91 _field_start: &'a [u8], 92 remaining: &mut &'a [u8], 93 field: &mut M, 94) -> Result<(), error::DecodeError> 95where 96 M: message::Message<'a>, 97{ 98 encoding::check_wire_type(encoding::WireType::LengthDelimited, wire_type)?; 99 *field = decode_single_value(remaining)?; 100 Ok(()) 101} 102 103#[inline] 104#[cfg_attr(feature = "assert-no-panic", no_panic::no_panic)] 105pub fn decode_optional<'a, M>( 106 _tag: u32, 107 wire_type: encoding::WireType, 108 _msg_buf: &'a [u8], 109 _field_start: &'a [u8], 110 remaining: &mut &'a [u8], 111 field: &mut Option, 112) -> Result<(), error::DecodeError> 113where 114 M: message::Message<'a>, 115{ 116 encoding::check_wire_type(encoding::WireType::LengthDelimited, wire_type)?; 117 *field = Some(decode_single_value(remaining)?); 118 Ok(()) 119} 120 121#[inline] 122#[cfg_attr(feature = "assert-no-panic", no_panic::no_panic)] 123pub fn decode_single_value<'a, M>(cursor: &mut &'a [u8]) -> Result 124where 125 M: message::Message<'a>, 126{ 127 let len = encoding::decode_varint(cursor)?; 128 let len = 129 usize::try_from(len).map_err(|_| error::DecodeError::LengthTooLargeForPlatform(len))?; 130 if cursor.len() >= len { 131 let (bytes, rest) = cursor.split_at(len); 132 let msg = M::decode(bytes)?; 133 *cursor = rest; 134 Ok(msg) 135 } else { 136 Err(error::DecodeError::BufferUnderflow) 137 } 138} 139 140#[inline(never)] 141#[cfg_attr(feature = "assert-no-panic", no_panic::no_panic)] 142pub fn decode_repeated<'a, M>( 143 tag: u32, 144 wire_type: encoding::WireType, 145 msg_buf: &'a [u8], 146 field_start: &'a [u8], 147 cursor: &mut &'a [u8], 148 field: &mut repeated::Repeated<'a, M, item_encoding::Message<'a, M>>, 149) -> Result<(), error::DecodeError> 150where 151 M: message::Message<'a>, 152{ 153 if field.is_unpopulated() { 154 *field = repeated::Repeated::from_msg_buf(tag, msg_buf, field_start); 155 } 156 encoding::skip_field(wire_type, tag, cursor)?; 157 field.extend_region(cursor); 158 Ok(()) 159} 160 161#[inline] 162#[cfg_attr(feature = "assert-no-panic", no_panic::no_panic)] 163pub fn clear<'a, M>(_tag: u32, field: &mut M) 164where 165 M: message::Message<'a>, 166{ ``` -------------------------------- ### Define List enum, MessageBuffer struct, constructors, and manual Clone and Copy implementations Source: https://docs.rs/femtopb/latest/src/femtopb/list.rs.html Defines the `List` enum with `Empty`, `MessageBuffer(u32)`, and `Slice(&'a [A])` variants. Also defines the `MessageBuffer` cursor struct with `tag` and `data`. The `Clone` impl is manual to avoid a `A: Clone` bound, and `Copy` is implemented explicitly. ```rust 1/// The backing storage of a lazily-parsed `Repeated`/`Packed` field. 2/// 3/// The message-buffer window itself lives *alongside* this enum on the wrapper (as a 4/// [`window::Window`](crate::window::Window)), not inside the `MessageBuffer` variant. Keeping the 5/// per-occurrence region update a flat, unconditional write on the wrapper — rather than a write 6/// through this enum's variant — is what keeps the decode hot path provably panic-free. 7#[derive(Default)] 8pub enum List<'a, A> { 9 #[default] 10 Empty, 11 /// A field decoded lazily from a message buffer; holds the field's tag. The buffer window it 12 /// scans is stored separately on the wrapping `List`/`Packed`. 13 MessageBuffer(u32), 14 Slice(&'a [A]), 15} 16 17/// The cursor an iterator walks: a field tag plus the remaining bytes of the field's window. 18#[derive(Clone, Copy, Debug)] 19pub struct MessageBuffer<'a> { 20 /// The field tag to look for inside `data`. 21 /// 22 /// The field might occur several times, with other fields before, after, or in between. 23 pub tag: u32, 24 /// The remaining bytes of the field's window, consumed as the iterator advances. 25 pub data: &'a [u8], 26} 27 28impl<'a, A> List<'a, A> { 29 pub const fn empty() -> Self { 30 Self::Empty 31 } 32 33 pub const fn from_slice(slice: &'a [A]) -> Self { 34 List::Slice(slice) 35 } 36 37 pub const fn from_msg_buf(tag: u32) -> Self { 38 List::MessageBuffer(tag) 39 } 40} 41 42// Implemented manually since deriving `Clone` would wrongly require `A: Clone`; `List` is `Copy` 43// regardless of `A`, so a plain copy suffices. 44impl<'a, A> Clone for List<'a, A> { 45 fn clone(&self) -> Self { 46 *self 47 } } 48 49impl<'a, A> Copy for List<'a, A> {} ``` -------------------------------- ### Define fixed-width encoding for f64 using fixed_width! macro Source: https://docs.rs/femtopb/latest/src/femtopb/runtime/scalar/double.rs.html Invokes the fixed_width! macro to define the encoding for f64 values. It specifies the wire type as SixtyFourBit and uses put_f64_le and get_f64_le for little-endian serialization. This is the core definition for the double scalar type in the femtopb runtime. ```rust crate::runtime::macros::fixed_width!( 'a, f64, crate::item_encoding::Double, 8, encoding::WireType::SixtyFourBit, put_f64_le, get_f64_le ); ``` -------------------------------- ### Encode and Decode Message Types Source: https://docs.rs/femtopb/latest/index.html Demonstrates encoding a message into a buffer and decoding it back into a struct, followed by iterating over nested readings. ```rust let mut buf = vec![0; event.encoded_len()]; event.encode(&mut buf.as_mut_slice()).unwrap(); // ... send buf over a LoRa network or something ... let new_event = WeatherStationEvent::decode(buf.as_slice()).unwrap(); assert_eq!(new_event.serial_id, b"mystation-abc123"); for reading in &new_event.new_readings { let TempReading { id, degrees_c, .. } = reading.unwrap(); println!("{id}: {degrees_c}"); // Prints: // 1234: 23.0 // 1235: 23.2 // 1236: 23.4 } } ``` -------------------------------- ### Define f32 fixed-width encoding with fixed_width! macro Source: https://docs.rs/femtopb/latest/src/femtopb/runtime/scalar/float.rs.html Invokes the fixed_width! macro to implement encoding for f32, using the Float item encoding, 4-byte size, ThirtyTwoBit wire type, and put_f32_le/get_f32_le functions. ```rust crate::runtime::macros::fixed_width!( 'a, f32, crate::item_encoding::Float, 4, encoding::WireType::ThirtyTwoBit, put_f32_le, get_f32_le ); ``` -------------------------------- ### Test EncodeError field initialization Source: https://docs.rs/femtopb/latest/src/femtopb/error.rs.html Verifies that EncodeError::new correctly sets the required and remaining fields. ```rust #[test] fn encode_error_new_sets_fields() { let e = EncodeError::new(10, 4); assert_eq!(e.required, 10); assert_eq!(e.remaining, 4); } ```