kitsune_p2p_types/
codec.rs

1//! Encoding / Decoding utilities.
2
3/// Encode a serde::Serialize item as message-pack data to given writer.
4/// You may wish to first wrap your writer in a BufWriter.
5pub fn rmp_encode<W, S>(write: &mut W, item: S) -> Result<(), std::io::Error>
6where
7    W: std::io::Write,
8    S: serde::Serialize,
9{
10    let mut se = rmp_serde::encode::Serializer::new(write).with_struct_map();
11    item.serialize(&mut se)
12        .map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, e))?;
13    Ok(())
14}
15
16/// Decode message-pack data from given reader into an owned item.
17/// You may wish to first wrap your reader in a BufReader.
18pub fn rmp_decode<R, D>(r: &mut R) -> Result<D, std::io::Error>
19where
20    R: std::io::Read,
21    for<'de> D: Sized + serde::Deserialize<'de>,
22{
23    let mut de = rmp_serde::decode::Deserializer::new(r);
24    D::deserialize(&mut de).map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, e))
25}
26
27/// Apply to a data item to indicate it can be encoded / decoded.
28pub trait Codec: Clone + Sized {
29    /// Variant identifier (for debugging or as a cheap discriminant).
30    fn variant_type(&self) -> &'static str;
31
32    /// Encode this item to given writer.
33    /// You may wish to first wrap your writer in a BufWriter.
34    fn encode<W>(&self, w: &mut W) -> Result<(), std::io::Error>
35    where
36        W: std::io::Write;
37
38    /// Encode this item to an owned vector of bytes.
39    /// Uses `encode()` internally.
40    fn encode_vec(&self) -> Result<Vec<u8>, std::io::Error> {
41        let mut data = Vec::new();
42        self.encode(&mut data)?;
43        Ok(data)
44    }
45
46    /// Decode a reader into this item.
47    /// You may wish to first wrap your reader in a BufReader.
48    fn decode<R>(r: &mut R) -> Result<Self, std::io::Error>
49    where
50        R: std::io::Read;
51
52    /// Decode a range of bytes into this item.
53    /// Will also return the byte count read.
54    /// Uses `decode()` internally.
55    fn decode_ref(r: &[u8]) -> Result<(u64, Self), std::io::Error> {
56        let mut r = std::io::Cursor::new(r);
57        let item = Self::decode(&mut r)?;
58        Ok((r.position(), item))
59    }
60}
61
62/// DSL-style macro for generating a serialization protocol message enum.
63///
64/// DSL:
65///
66/// ```ignore
67/// /// [codec doc here]
68/// codec $codec_name {
69///     /// [var doc here]
70///     $var_name($var_id) {
71///         /// [type doc here]
72///         $type_name.$type_idx: $type_ty,
73///     },
74/// }
75/// ```
76///
77/// - $codec_name - camel-case codec enum name
78/// - $var_name   - camel-case variant/struct name
79/// - $var_id     - protocol variant identifier byte (u8) literal
80/// - $type_name  - snake-case type name
81/// - $type_idx   - zero-index type index in message array (usize)
82/// - $type_ty    - type rust type
83///
84/// E.G.:
85///
86/// ```ignore
87/// /// My codec is awesome.
88/// codec MyCodec {
89///     /// My codec has only one variant.
90///     MyVariant(0x00) {
91///         /// My variant has only one type
92///         my_type.0: String,
93///     },
94/// }
95/// ```
96#[macro_export]
97macro_rules! write_codec_enum {
98    ($(#[doc = $codec_doc:expr])* codec $codec_name:ident {$(
99        $(#[doc = $var_doc:expr])* $var_name:ident($var_id:literal) {$(
100            $(#[doc = $type_doc:expr])* $type_name:ident.$type_idx:literal: $type_ty:ty,
101        )*},
102    )*}) => {
103        $crate::dependencies::paste::item! {
104            $(
105                $(#[doc = $var_doc])*
106                #[derive(Clone, Debug, serde::Serialize, serde::Deserialize, PartialEq, Eq)]
107                // NOTE: calling crate must depend on kitsune_p2p_types with feature fuzzing
108                #[cfg_attr(feature = "fuzzing", derive($crate::dependencies::proptest_derive::Arbitrary))]
109                pub struct [< $var_name:camel >] {
110                    $(
111                        $(#[doc = $type_doc])* pub [< $type_name:snake >]: $type_ty,
112                    )*
113                }
114
115                impl $crate::codec::Codec for [< $var_name:camel >] {
116                    fn variant_type(&self) -> &'static str {
117                        concat!(
118                            stringify!([< $codec_name:camel >]),
119                            "::",
120                            stringify!([< $var_name:camel >]),
121                        )
122                    }
123
124                    fn encode<W>(&self, w: &mut W) -> ::std::io::Result<()>
125                    where
126                        W: ::std::io::Write
127                    {
128                        #[cfg(debug_assertions)]
129                        #[allow(dead_code)]
130                        {
131                            const MSG: &str = "type index must begin at 0 and increment by exactly 1 per type - switching type order will break parsing compatibility";
132                            let mut _idx = -1;
133                            $(
134                                _idx += 1;
135                                assert_eq!(_idx, $type_idx, "{}", MSG);
136                            )*
137                        }
138                        let t: (
139                            $(&$type_ty,)*
140                        ) = (
141                            $(&self.[< $type_name:snake >],)*
142                        );
143                        $crate::codec::rmp_encode(w, &t)
144                    }
145
146                    fn decode<R>(r: &mut R) -> ::std::io::Result<Self>
147                    where
148                        R: ::std::io::Read
149                    {
150                        let (
151                            $([< $type_name:snake >],)*
152                        ): (
153                            $($type_ty,)*
154                        ) = $crate::codec::rmp_decode(r)?;
155                        Ok([< $var_name:camel >] {
156                            $(
157                                [< $type_name:snake >],
158                            )*
159                        })
160                    }
161                }
162            )*
163
164            $(#[doc = $codec_doc])*
165            #[derive(Clone, Debug, PartialEq, Eq)]
166            // NOTE: calling crate must depend on kitsune_p2p_types with feature fuzzing
167            #[cfg_attr(feature = "fuzzing", derive($crate::dependencies::proptest_derive::Arbitrary))]
168            pub enum [< $codec_name:camel >] {
169                $(
170                    $(#[doc = $var_doc])*
171                    [< $var_name:camel >]([< $var_name:camel >]),
172                )*
173            }
174
175            impl [< $codec_name:camel >] {
176                $(
177                    /// Variant constructor helper function.
178                    pub fn [< $var_name:snake >]($(
179                        [< $type_name:snake >]: $type_ty,
180                    )*) -> Self {
181                        Self::[< $var_name:camel >]([< $var_name:camel >] {
182                            $(
183                                [< $type_name:snake >],
184                            )*
185                        })
186                    }
187                )*
188            }
189
190            impl $crate::codec::Codec for [< $codec_name:camel >] {
191                fn variant_type(&self) -> &'static str {
192                    match self {
193                        $(
194                            Self::[< $var_name:camel >](data) =>
195                                $crate::codec::Codec::variant_type(data),
196                        )*
197                    }
198                }
199
200                fn encode<W>(&self, w: &mut W) -> ::std::io::Result<()>
201                where
202                    W: ::std::io::Write
203                {
204                    match self {
205                        $(
206                            Self::[< $var_name:camel >](data) => {
207                                ::std::io::Write::write_all(w, &[$var_id])?;
208                                $crate::codec::Codec::encode(data, w)
209                            }
210                        )*
211                    }
212                }
213
214                fn decode<R>(r: &mut R) -> Result<Self, ::std::io::Error>
215                where
216                    R: ::std::io::Read
217                {
218                    let mut c = [0_u8; 1];
219                    ::std::io::Read::read_exact(r, &mut c)?;
220                    match c[0] {
221                        $(
222                            $var_id => {
223                                Ok(Self::[< $var_name:camel >]($crate::codec::Codec::decode(r)?))
224                            },
225                        )*
226                        _ => Err(::std::io::Error::new(::std::io::ErrorKind::Other, "invalid protocol byte")),
227                    }
228                }
229            }
230        }
231    };
232}
233
234#[cfg(test)]
235mod tests {
236    #![allow(dead_code)]
237
238    use super::*;
239    use std::sync::Arc;
240
241    #[derive(
242        Clone,
243        Debug,
244        PartialEq,
245        Eq,
246        serde::Serialize,
247        serde::Deserialize,
248        proptest_derive::Arbitrary,
249    )]
250    pub struct Sub(pub Vec<u8>);
251
252    write_codec_enum! {
253        /// Codec
254        codec Bob {
255            /// variant
256            BobOne(0x42) {
257                /// type 1
258                yay.0: bool,
259
260                /// type 2
261                age.1: u32,
262
263                /// type 3
264                sub.2: Arc<Sub>,
265            },
266            /// nother variant
267            BobTwo(0x43) {
268            },
269        }
270    }
271
272    #[test]
273    fn test_encode_decode() {
274        let bob = Bob::bob_one(true, 42, Arc::new(Sub(b"test".to_vec())));
275        let data = bob.encode_vec().unwrap();
276        let res = Bob::decode_ref(&data).unwrap().1;
277        assert_eq!(bob, res);
278    }
279}