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