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
93## combining packed objects
94
95It is possible to compose packed objects in named or tuple structures.
96
97```
98use packtool::Packed;
99
100#[derive(Packed)]
101#[packed(value = "packcoin")]
102pub struct Tag;
103
104/// 1 byte that will be used to store a version number
105#[derive(Packed)]
106#[repr(u8)]
107pub enum Version {
108    V1 = 1,
109    V2 = 2,
110}
111
112/// 8 bytes that will be used to store a block number
113#[derive(Packed)]
114pub struct BlockNumber(u32, u32);
115
116/// 9 bytes packed header
117#[derive(Packed)]
118pub struct Header {
119    tag: Tag,
120    version: Version,
121    block_number: BlockNumber
122}
123
124# assert_eq!(Version::SIZE, 1);
125# assert_eq!(BlockNumber::SIZE, 8);
126# assert_eq!(Header::SIZE, 17);
127```
128
129Each of the packed objects have a view accessor for each fields:
130
131* for named fields, the name of the accessor is the name of the field
132* for tuples, the name of the accessor is the index of the field preceded by an underscore (`_`): `_0`, `_1` etc.
133
134```
135# use packtool::{Packed, View, Packet};
136#
137# #[derive(Packed)]
138# #[packed(value = "packcoin")]
139# pub struct Tag;
140#
141# /// 1 byte that will be used to store a version number
142# #[derive(Packed)]
143# #[repr(u8)]
144# pub enum Version {
145#     V1 = 1,
146#     V2 = 2,
147# }
148#
149# /// 8 bytes that will be used to store a block number
150# #[derive(Packed)]
151# pub struct BlockNumber(u32, u32);
152#
153# /// 9 bytes packed header
154# #[derive(Packed)]
155# pub struct Header {
156#     tag: Tag,
157#     version: Version,
158#     block_number: BlockNumber
159# }
160#
161# let header = Header { tag: Tag, version: Version::V1, block_number: BlockNumber(0, 1) };
162# let header = Packet::pack(&header);
163# let header = header.view();
164#
165let tag: View<'_, Tag> = Header::tag(header);
166let block_number: View<'_, BlockNumber> = Header::block_number(header);
167
168let epoch: View<'_, u32> = BlockNumber::_0(block_number);
169let slot: u32  = BlockNumber::_1(block_number).unpack();
170#
171# assert_eq!(slot, 1);
172```
173
174You can rename the accessor with the attribute `accessor`:
175
176```
177# use packtool::{Packed, View, Packet};
178#
179#[derive(Packed)]
180pub struct BlockNumber(
181    #[packed(accessor = "epoch")]
182    u32,
183    #[packed(accessor = "slot")]
184    u32
185);
186#
187# let block_number = Packet::pack(&BlockNumber(0, 1));
188# let block_number = block_number.view();
189let epoch = BlockNumber::epoch(block_number); // instead of _0
190let slot = BlockNumber::slot(block_number).unpack(); // instead of _1
191#
192# assert_eq!(slot, 1);
193```
194
195It is also possible to prevent the accessor to be created. You can set
196the accessor with a literal boolean to say if you want the accessor or
197not. `true` will simply means the default case (use the index of the field
198or use the name for the name of the accessor):
199
200```
201# use packtool::{Packed, View, Packet};
202#
203#[derive(Packed)]
204pub struct Hash(
205    #[packed(accessor = true)]
206    [u8; 32]
207);
208#
209# let hash = Packet::pack(&Hash([0; 32]));
210# let hash = hash.view();
211let bytes = Hash::_0(hash);
212# assert_eq!(bytes.unpack(), [0; 32]);
213```
214
215However if you set it to `false` there will be no accessor created for you:
216
217```compile_fail
218# use packtool::{Packed, View, Packet};
219#
220#[derive(Packed)]
221pub struct Hash(
222    #[packed(accessor = false)]
223    [u8; 32]
224);
225#
226# let hash = Packet::pack(&Hash([0; 32]));
227# let hash = hash.view();
228let bytes = Hash::_0(hash);
229```
230
231*/
232
233#[cfg(test)]
234extern crate quickcheck;
235#[cfg(test)]
236#[macro_use(quickcheck)]
237extern crate quickcheck_macros;
238
239mod array;
240mod error;
241mod packet;
242mod primitives;
243mod tuple;
244mod view;
245
246pub use self::{
247    error::{Context, Error},
248    packet::Packet,
249    view::View,
250};
251pub use packtool_macro::Packed;
252
253/// trait to define how a fixed size Packed object is serialized
254/// into a byte slice representation.
255///
256/// see crate documentation for more information.
257pub trait Packed: Sized {
258    /// the static size of a packed object in a byte array
259    ///
260    /// this is not necessarily the [`::std::mem::size_of::<Self>()`]
261    /// but the size it takes to have this object on a slice of memory.
262    const SIZE: usize;
263
264    /// assuming the given slice if valid, perform a conversion
265    /// from the slice to the object.
266    fn unchecked_read_from_slice(slice: &[u8]) -> Self;
267
268    /// assuming there is enough slice available in the
269    fn unchecked_write_to_slice(&self, _slice: &mut [u8]);
270
271    /// check the validity of the given slice to hold the appropriate value
272    ///
273    /// the length of the slice is already handled by the [`View::try_from_slice`]
274    /// method so no need to do that again in here.
275    fn check(slice: &[u8]) -> Result<(), Error>;
276
277    /// assuming the given slice if valid, perform a conversion
278    /// from the slice to the object.
279    ///
280    /// it should be assumed the `checks` have been performed
281    /// appropriately since we are passing in the [`View`]
282    /// and not the raw slice.
283    #[inline]
284    fn read(view: View<'_, Self>) -> Self {
285        Self::unchecked_read_from_slice(view.as_ref())
286    }
287}