packtool/lib.rs
1/*!
2`packtool` is a packing library. Useful to define how serializing
3and deserializing data from a type level definition.
4
5# Example
6
7## Unit types
8
9unit types can be packed. What this means is that the object
10is known to have the same constant value. That way it is possible
11to define values that are expected to be found and to be the same.
12
13All [`Packed`] unit structures must have a `#[packed(value = ...)]`
14attribute. The value can be set to any literal except: `bool`, `float`.
15
16```
17use packtool::{Packed, View};
18# use packtool::Error;
19
20/// a unit that is always the utf8 string `"my protocol"`
21/// and takes 11 bytes in the packed structure
22#[derive(Packed)]
23#[packed(value = "my protocol")]
24pub struct ProtocolPrefix;
25
26/// a unit that is always `4` and takes 1 byte long
27#[derive(Packed)]
28#[packed(value = 0b0000_0100u8)]
29pub struct OtherUnit();
30
31/// a unit that is always `0xcafe` and takes 4 bytes
32/// in the packed structure
33#[derive(Packed)]
34#[packed(value = 0xcafeu32)]
35pub struct LastButNotLeast {}
36
37# fn test() -> Result<(), Error> {
38const SLICE: &[u8] = b"my protocol";
39let view: View<'_, ProtocolPrefix> = View::try_from_slice(SLICE)?;
40
41# Ok(()) }
42# test().unwrap();
43
44# assert_eq!(ProtocolPrefix::SIZE, 11);
45# assert_eq!(OtherUnit::SIZE, 1);
46# assert_eq!(LastButNotLeast::SIZE, 4);
47```
48
49Here we are expecting the `ProtocolPrefix` to always have the
50same value in the packed representation. When serializing the
51`ProtocolPrefix`, the `value` will be set with these 11
52characters.
53
54## Enumeration
55
56Only enumerations without fields are allowed for now.
57
58```
59use packtool::{Packed, View};
60# use packtool::Error;
61
62#[derive(Packed)]
63#[repr(u8)]
64pub enum Version {
65 V1 = 1,
66 V2 = 2,
67}
68
69# fn test() -> Result<(), Error> {
70# const SLICE: &[u8] = &[1];
71let view: View<'_, Version> = View::try_from_slice(SLICE)?;
72
73assert!(matches!(view.unpack(), Version::V1));
74
75# Ok(()) }
76# test().unwrap();
77# assert_eq!(Version::SIZE, 1);
78```
79
80the `repr(...)` is necessary in order to set a size to the enum.
81
82```compile_fail
83use packtool::Packed;
84
85#[derive(Packed)]
86pub enum Color {
87 Red = 1,
88 Green = 2,
89 Blue = -1
90}
91```
92
93Enumerations with data-carrying variants are not supported yet and
94are rejected at compile time:
95
96```compile_fail
97use packtool::Packed;
98
99#[derive(Packed)]
100pub enum ThisOrThat {
101 This,
102 That(u32),
103}
104```
105
106Unions cannot be packed and are rejected at compile time:
107
108```compile_fail
109use packtool::Packed;
110
111#[derive(Packed)]
112pub union Choice {
113 a: u32,
114 b: f32,
115}
116```
117
118Type-parameter generics are supported on **named** structs. The packed layout
119and `SIZE` are computed through `<T as Packed>::SIZE`, and a `T: Packed` bound is
120injected for every type parameter (composing with any `where` clause you write):
121
122```
123use packtool::Packed;
124
125#[derive(Packed)]
126pub struct Log<T> {
127 parent_id: [u8; 64],
128 content: T,
129 signature: [u8; 64],
130}
131
132# assert_eq!(<Log<u32> as Packed>::SIZE, 64 + 4 + 64);
133```
134
135Generics on enums and on tuple structs are still rejected at compile time:
136
137```compile_fail
138use packtool::Packed;
139
140#[derive(Packed)]
141pub struct Wrapper<T>(T);
142```
143
144```compile_fail
145use packtool::Packed;
146
147#[derive(Packed)]
148pub enum Either<L, R> {
149 Left(L),
150 Right(R),
151}
152```
153
154A generic struct with no fields is rejected too: its type parameters could not
155appear in the (empty) packed layout.
156
157```compile_fail
158use packtool::Packed;
159
160#[derive(Packed)]
161pub struct Empty<T> {}
162```
163
164## combining packed objects
165
166It is possible to compose packed objects in named or tuple structures.
167
168```
169use packtool::Packed;
170
171#[derive(Packed)]
172#[packed(value = "packcoin")]
173pub struct Tag;
174
175/// 1 byte that will be used to store a version number
176#[derive(Packed)]
177#[repr(u8)]
178pub enum Version {
179 V1 = 1,
180 V2 = 2,
181}
182
183/// 8 bytes that will be used to store a block number
184#[derive(Packed)]
185pub struct BlockNumber(u32, u32);
186
187/// 9 bytes packed header
188#[derive(Packed)]
189pub struct Header {
190 tag: Tag,
191 version: Version,
192 block_number: BlockNumber
193}
194
195# assert_eq!(Version::SIZE, 1);
196# assert_eq!(BlockNumber::SIZE, 8);
197# assert_eq!(Header::SIZE, 17);
198```
199
200Each of the packed objects have a view accessor for each fields:
201
202* for named fields, the name of the accessor is the name of the field
203* for tuples, the name of the accessor is the index of the field preceded by an underscore (`_`): `_0`, `_1` etc.
204
205```
206# use packtool::{Packed, View, Packet};
207#
208# #[derive(Packed)]
209# #[packed(value = "packcoin")]
210# pub struct Tag;
211#
212# /// 1 byte that will be used to store a version number
213# #[derive(Packed)]
214# #[repr(u8)]
215# pub enum Version {
216# V1 = 1,
217# V2 = 2,
218# }
219#
220# /// 8 bytes that will be used to store a block number
221# #[derive(Packed)]
222# pub struct BlockNumber(u32, u32);
223#
224# /// 9 bytes packed header
225# #[derive(Packed)]
226# pub struct Header {
227# tag: Tag,
228# version: Version,
229# block_number: BlockNumber
230# }
231#
232# let header = Header { tag: Tag, version: Version::V1, block_number: BlockNumber(0, 1) };
233# let header = Packet::pack(&header);
234# let header = header.view();
235#
236let tag: View<'_, Tag> = Header::tag(header);
237let block_number: View<'_, BlockNumber> = Header::block_number(header);
238
239let epoch: View<'_, u32> = BlockNumber::_0(block_number);
240let slot: u32 = BlockNumber::_1(block_number).unpack();
241#
242# assert_eq!(slot, 1);
243```
244
245You can rename the accessor with the attribute `accessor`:
246
247```
248# use packtool::{Packed, View, Packet};
249#
250#[derive(Packed)]
251pub struct BlockNumber(
252 #[packed(accessor = "epoch")]
253 u32,
254 #[packed(accessor = "slot")]
255 u32
256);
257#
258# let block_number = Packet::pack(&BlockNumber(0, 1));
259# let block_number = block_number.view();
260let epoch = BlockNumber::epoch(block_number); // instead of _0
261let slot = BlockNumber::slot(block_number).unpack(); // instead of _1
262#
263# assert_eq!(slot, 1);
264```
265
266It is also possible to prevent the accessor to be created. You can set
267the accessor with a literal boolean to say if you want the accessor or
268not. `true` will simply means the default case (use the index of the field
269or use the name for the name of the accessor):
270
271```
272# use packtool::{Packed, View, Packet};
273#
274#[derive(Packed)]
275pub struct Hash(
276 #[packed(accessor = true)]
277 [u8; 32]
278);
279#
280# let hash = Packet::pack(&Hash([0; 32]));
281# let hash = hash.view();
282let bytes = Hash::_0(hash);
283# assert_eq!(bytes.unpack(), [0; 32]);
284```
285
286However if you set it to `false` there will be no accessor created for you:
287
288```compile_fail
289# use packtool::{Packed, View, Packet};
290#
291#[derive(Packed)]
292pub struct Hash(
293 #[packed(accessor = false)]
294 [u8; 32]
295);
296#
297# let hash = Packet::pack(&Hash([0; 32]));
298# let hash = hash.view();
299let bytes = Hash::_0(hash);
300```
301
302*/
303
304#[cfg(test)]
305extern crate quickcheck;
306#[cfg(test)]
307#[macro_use(quickcheck)]
308extern crate quickcheck_macros;
309
310mod array;
311mod error;
312mod packet;
313mod primitives;
314mod tuple;
315mod view;
316
317pub use self::{
318 error::{Context, Error},
319 packet::Packet,
320 view::View,
321};
322pub use packtool_macro::Packed;
323
324/// trait to define how a fixed size Packed object is serialized
325/// into a byte slice representation.
326///
327/// see crate documentation for more information.
328pub trait Packed: Sized {
329 /// the static size of a packed object in a byte array
330 ///
331 /// this is not necessarily the [`::std::mem::size_of::<Self>()`]
332 /// but the size it takes to have this object on a slice of memory.
333 const SIZE: usize;
334
335 /// assuming the given slice if valid, perform a conversion
336 /// from the slice to the object.
337 fn unchecked_read_from_slice(slice: &[u8]) -> Self;
338
339 /// assuming there is enough slice available in the
340 fn unchecked_write_to_slice(&self, _slice: &mut [u8]);
341
342 /// check the validity of the given slice to hold the appropriate value
343 ///
344 /// the length of the slice is already handled by the [`View::try_from_slice`]
345 /// method so no need to do that again in here.
346 fn check(slice: &[u8]) -> Result<(), Error>;
347
348 /// assuming the given slice if valid, perform a conversion
349 /// from the slice to the object.
350 ///
351 /// it should be assumed the `checks` have been performed
352 /// appropriately since we are passing in the [`View`]
353 /// and not the raw slice.
354 #[inline]
355 fn read(view: View<'_, Self>) -> Self {
356 Self::unchecked_read_from_slice(view.as_ref())
357 }
358}