femtopb/lib.rs
1#![cfg_attr(not(test), no_std)]
2//! # `femtopb`
3//!
4//! A tiny footprint, `#[no_std]`, no-`alloc`, no-panic Protobuf serialization library. This allows
5//! you to communicate using Protobuf on constrained platforms, like bare-metal MCUs with very
6//! limited RAM.
7//!
8//! Yes, you heard it right: this library lets you serialize and deserialize Protobuf messages
9//! without any dynamic memory/heap allocation.
10//!
11//! The library takes care of using simple types with limited use of generics when possible, to
12//! avoid monomorphization code size explosion. The runtime also consists of many tiny functions
13//! so that the ones that aren't used can get optimized away.
14//!
15//! During testing of this crate, checks are made to ensure that `femtopb` code cannot panic. If
16//! you want to leverage the no-panic checks yourself to debug your own project, enable the
17//! `assert-no-panic` crate feature. It is not necessarily a good idea to enable this feature for
18//! your release code, as enabling this feature might change the generated code slightly.
19//!
20//! ## Defining message types
21//!
22//! `femtopb` enables encoding and decoding of messages by deriving the `femtopb::Message` trait.
23//! Writing the code for these message types is usually done using the `femtopb-build` crate, but
24//! we will write some code manually here for illustration purposes.
25//!
26//! A simple message type might look like this:
27//!
28//! ```
29//! #[derive(Clone, femtopb::Message)]
30//! pub struct Person<'a> {
31//! #[femtopb(uint32, tag = 1)]
32//! pub age: u32,
33//! #[femtopb(string, tag = 2)]
34//! pub name: &'a str,
35//! #[femtopb(unknown_fields)]
36//! pub unknown_fields: femtopb::UnknownFields<'a>,
37//! }
38//! ```
39//!
40//! The struct can be used just like any other Rust struct, using the simple built-in types of the
41//! language for the most part.
42//!
43//! The lifetime parameter on the struct is mandatory, and is used to refer to the lifetime of any
44//! dynamically sized data inside the message. When a message is decoded from raw bytes, types like
45//! `&'a str` will borrow its contents from the original raw byte buffer.
46//!
47//! The `#[femtopb(...)]` attribute provides additional metadata for how the type gets serialized.
48//! All the semantics are described in the [official protobuf docs](https://protobuf.dev/programming-guides/encoding/),
49//! but as a brief overview, the `tag` number corresponds to the binary ID of the field as it is
50//! serialized on the wire (and should hence be unique, and not change, for your given message type).
51//! Other bits in the attribute influence how the data gets serialized more specifically; again,
52//! please consult the official protobuf documentation for details.
53//!
54//! The `#[femtopb(unknown_fields)]` field is used to preserve fields that are not yet known to our
55//! current code. For example, if the writer of a message has added a new field to the message
56//! (which is a backwards-compatible change), the data of this field will be preserved inside the
57//! `unknown_fields` field. The API for this is still a work in progress.
58//!
59//! ## Encoding and decoding
60//!
61//! Given a message definition like the one above, you can easily encode and decode data to/from
62//! bytes, using the associated trait methods:
63//!
64//! ```
65//! use femtopb::Message as _;
66//!
67//! // Adding some more derived traits to aid with the example below:
68//! #[derive(Clone, Debug, PartialEq, femtopb::Message)]
69//! pub struct Person<'a> {
70//! #[femtopb(uint32, tag = 1)]
71//! pub age: u32,
72//! #[femtopb(string, tag = 2)]
73//! pub name: &'a str,
74//! #[femtopb(unknown_fields)]
75//! pub unknown_fields: femtopb::UnknownFields<'a>,
76//! }
77//!
78//! fn main() {
79//! let person = Person { age: 32, name: "David", ..Default::default() };
80//! // Create a new buffer filled with zeroes. This of course doesn't need to be dynamically
81//! // allocated; at this point, you could use a stack-allocated buffer, or `'static` memory
82//! // region, for example. Here, we use a `Vec` for simplicity.
83//! // The buffer MUST already have the right length, since we can't grow the buffer without
84//! // dynamic memory allocation.
85//! let mut buf = vec![0; person.encoded_len()];
86//!
87//! // Encode the Person to the buffer
88//! person.encode(&mut buf.as_mut_slice()).unwrap();
89//! // Decode a new Person from the same buffer
90//! let new_person = Person::decode(buf.as_slice()).unwrap();
91//!
92//! // The same information should be preserved!
93//! assert_eq!(person, new_person);
94//! }
95//! ```
96//!
97//! ## Repeated and packed fields
98//!
99//! Quite commonly, you'll need to encode a collection of things. In `femtopb`, since we can't
100//! do any dynamic memory allocation, we need to use special collection types that borrow all of
101//! their memory from the original buffer, instead of types like `Vec` that use dynamic allocation.
102//!
103//! These types decode their values on-the-fly, when you iterate through them, instead of eagerly
104//! at `decode()` time. Hence, they also need to know about an `ItemEncoding`, which is used to
105//! decode values lazily after the main `Message::decode()` call has returned. This means that the
106//! type is quite long, but you usually won't see it in code generated by `femtopb-build`.
107//!
108//! There are repeated fields, which are usually used for composite types like messages, and packed
109//! fields, which use a more efficient encoding for scalar types. You usually don't have to make
110//! an active choice as to which type to use; `femtopb-build` will generate the right type for you.
111//!
112//! ```
113//! use femtopb::Message as _;
114//! use femtopb::item_encoding;
115//! use femtopb::repeated;
116//!
117//! #[derive(Clone, Debug, PartialEq, femtopb::Message)]
118//! pub struct WeatherStationEvent<'a> {
119//! #[femtopb(bytes, tag = 1)]
120//! serial_id: &'a [u8],
121//! #[femtopb(message, repeated, tag = 2)]
122//! new_readings: repeated::Repeated<'a, TempReading<'a>, item_encoding::Message<'a, TempReading<'a>>>,
123//! #[femtopb(unknown_fields)]
124//! pub unknown_fields: femtopb::UnknownFields<'a>,
125//! }
126//!
127//! #[derive(Clone, Debug, PartialEq, femtopb::Message)]
128//! pub struct TempReading<'a> {
129//! #[femtopb(uint64, tag = 1)]
130//! id: u64,
131//! #[femtopb(float, tag = 2)]
132//! degrees_c: f32,
133//! #[femtopb(float, tag = 3)]
134//! pressure_hpa: f32,
135//! #[femtopb(unknown_fields)]
136//! pub unknown_fields: femtopb::UnknownFields<'a>,
137//! }
138//!
139//! fn main() {
140//! let new_readings = &[
141//! TempReading { id: 1234, degrees_c: 23.0, ..Default::default() },
142//! TempReading { id: 1235, degrees_c: 23.2, ..Default::default() },
143//! TempReading { id: 1236, degrees_c: 23.4, ..Default::default() },
144//! ];
145//! let event = WeatherStationEvent {
146//! serial_id: b"mystation-abc123",
147//! new_readings: repeated::Repeated::from_slice(new_readings),
148//! ..Default::default()
149//! };
150//!
151//! let mut buf = vec![0; event.encoded_len()];
152//! event.encode(&mut buf.as_mut_slice()).unwrap();
153//! // ... send buf over a LoRa network or something ...
154//! let new_event = WeatherStationEvent::decode(buf.as_slice()).unwrap();
155//! assert_eq!(new_event.serial_id, b"mystation-abc123");
156//! for reading in &new_event.new_readings {
157//! let TempReading { id, degrees_c, .. } = reading.unwrap();
158//! println!("{id}: {degrees_c}");
159//! // Prints:
160//! // 1234: 23.0
161//! // 1235: 23.2
162//! // 1236: 23.4
163//! }
164//! }
165//! ```
166//!
167//! ## Enums
168//!
169//! Enums are represented via the `femtopb::Enumeration` trait. The only special requirements on
170//! enums are that the enum must derive `Clone` and `Copy`, and all variants must have a
171//! discriminant (number value) assigned to them. Also, generated enums will always have the first
172//! variant be its default value.
173//!
174//! Enums cannot themselves be encoded; they must exist as a field on a `Message`, and then the
175//! wrapping message can be encoded.
176//!
177//! ```
178//! #[derive(Clone, Copy, Default, femtopb::Enumeration)]
179//! pub enum BasicEnumeration {
180//! #[default]
181//! ZERO = 0,
182//! ONE = 1,
183//! TWO = 2,
184//! THREE = 3,
185//! }
186//! ```
187//!
188//! ## Oneofs
189//!
190//! Oneofs are enums where each variant wraps exactly one other value. The `oneof` terminology
191//! comes from the related [protobuf concept](https://protobuf.dev/programming-guides/proto3/#oneof).
192//!
193//! Oneofs can be used to encode mutually exclusive values. On the wire, oneofs are simply encoded
194//! as the field out of a group of mutually exclusive fields that was actually populated.
195//!
196//! Like with enums, oneofs can't be encoded on their own, and must exist as a field on a message.
197//!
198//! ```
199//! #[derive(femtopb::Oneof)]
200//! pub enum StorePageReview<'a> {
201//! #[femtopb(int32, tag = 8)]
202//! Rating(i32),
203//! #[femtopb(string, tag = 9)]
204//! WrittenReview(&'a str),
205//! }
206//! ```
207//!
208//! ## Other features
209//!
210//! There are simpler concepts like optional fields, recursive messages, etc. that are not yet
211//! covered in this documentation. Feel free to request documentation for anything that you feel
212//! is missing!
213//!
214//! ## Unsupported protobuf features
215//!
216//! This library does not (yet) support groups (which were deprecated since Protobuf 1) and maps.
217//!
218//! ## Acknowledgements
219//!
220//! This library is heavily inspired by the amazing `prost` library, and some tests and core
221//! algorithms were copied from that library. However, the architecture of `femtopb` compared to
222//! `prost` ended up being significantly different, so mostly only the build infrastructure of
223//! `prost-build` was re-used in the creation of `femtopb-build`.
224
225mod bits;
226mod list;
227
228pub mod deferred;
229pub mod encoding;
230pub mod enumeration;
231pub mod error;
232pub mod item_encoding;
233pub mod message;
234pub mod oneof;
235pub mod packed;
236pub mod repeated;
237pub mod unknown_fields;
238
239#[doc(hidden)]
240pub mod runtime;
241
242pub use enumeration::EnumValue;
243pub use enumeration::Enumeration;
244pub use femtopb_derive::Enumeration;
245pub use femtopb_derive::Message;
246pub use femtopb_derive::Oneof;
247pub use message::Message;
248pub use oneof::Oneof;
249pub use packed::Packed;
250pub use repeated::Repeated;
251pub use unknown_fields::UnknownFields;