pcast/
lib.rs

1//! Same-size tagged data structure conversions.
2//!
3//! This crate provides a few boilerplate macros to enable conversions between
4//! types that are unions with a built-in discriminatory field. An example is a
5//! network protocol that consists of multiple packet-types with their
6//! respective packet-type indicated by a field on the struct:
7//!
8//! ```
9//! #[repr(C)]
10//! pub struct Packet {
11//!     packet_type: u8,
12//!     // an unknown (depends on packet type) payload
13//!     data: [u8; 7],
14//! }
15//!
16//! #[repr(C)]
17//! pub struct StatusPacket {
18//!     /// must be 0x02 for a status packet
19//!     packet_type: u8,
20//!     status_0: u8,
21//!     status_1: u8,
22//!     status_2: u8,
23//!     ts: [u8; 4],
24//! }
25//!
26//! #[macro_use]
27//! extern crate pcast;
28//!
29//! pub enum ConversionError {
30//!     WrongPacketType
31//! }
32//!
33//! subtype_of!(Packet => StatusPacket | ConversionError {
34//!     Ok(())
35//! });
36//!
37//! fn main() {}
38//! ```
39//!
40//! The `StatusPacket` has three fields for various flags and a four byte
41//! timestamp here; its presence is indicated by a value of 0x02 in
42//! `packet_type`.
43//!
44//! The `subtype_of` macro can now be used to declare express this. As a
45//! result, a `Packet` can be `try_into`'d into a `StatusPacket` and references
46//! can be passed because `&StatusPacket` will `Deref` to `&Packet`.
47//!
48//! A conversion from `&mut StatusPacket` to `&mut Packet` is not included,
49//! as altering the `Packet`-structure might violate invariants required
50//! by `StatusPacket`.
51//!
52//! ## TryFrom and TryInto
53//!
54//! The `TryFrom` and `TryInto` traits are not stabilized yet. `pcast`
55//! reexports these for the time being from the `try_from` crate; as soon as
56//! they are stabilized in `std::convert`, the export will be updated.
57
58extern crate try_from;
59
60pub use try_from::TryInto;
61pub use try_from::TryFrom;
62
63/// Conversion trait used internally by the `subtype_of` macro.
64pub trait SubtypeCheck<F, T, E> {
65    fn check_is_valid_subtype(&self) -> Result<(), E>;
66}
67
68/// Generation conversion traits for subtype of base.
69///
70/// Syntax:
71///
72/// ```rust,no_run
73/// subtype_of!(Base => Sub | Error { ... })
74/// ```
75///
76/// The macro generates four conversion traits for `Sub`:
77///
78/// * `Deref<Target=Base>` for `Sub`
79/// * `TryFrom<Base, Err=Error>` for `Sub`
80/// * `TryFrom<&Base, Err=Error>` for `&Sub`
81/// * `TryFrom<&mut Base, Err=Error>` for `&mut Sub`
82///
83/// All traits use the `{ ... }` block which must expand to a `Result<(),
84/// Error>` to check whether or not a conversion is possible.
85#[macro_export]
86macro_rules! subtype_of {
87    ($base:ty => $sub:ty | $cerr:ty $check_fn:block) => (
88        impl $crate::SubtypeCheck<$base, $sub, $cerr> for $base {
89            fn check_is_valid_subtype(&self) -> Result<(), $cerr> $check_fn
90        }
91
92        impl ::std::ops::Deref for $sub {
93            type Target = $base;
94
95            #[inline(always)]
96            fn deref(&self) -> &$base {
97                unsafe { ::std::mem::transmute::<&$sub, &$base>(self) }
98            }
99        }
100
101        impl $crate::TryFrom<$base> for $sub {
102            type Err = $cerr;
103
104            #[inline(always)]
105            fn try_from(base: $base) -> Result<Self, Self::Err> {
106                try!($crate::SubtypeCheck::<$base, $sub, $cerr>::check_is_valid_subtype(&base));
107                Ok(unsafe { ::std::mem::transmute::<$base, $sub>(base) })
108            }
109        }
110
111        impl<'a> $crate::TryFrom<&'a $base> for &'a $sub {
112            type Err = $cerr;
113
114            #[inline(always)]
115            fn try_from(base_ref: &$base) -> Result<Self, Self::Err> {
116                try!($crate::SubtypeCheck::<$base, $sub, $cerr>::check_is_valid_subtype(base_ref));
117                Ok(unsafe { ::std::mem::transmute::<&$base, &$sub>(base_ref) })
118            }
119        }
120
121        impl<'a> $crate::TryFrom<&'a mut $base> for &'a mut $sub {
122            type Err = $cerr;
123
124            #[inline(always)]
125            fn try_from(base_ref: &mut $base) -> Result<Self, Self::Err> {
126                try!($crate::SubtypeCheck::<$base, $sub, $cerr>::check_is_valid_subtype(base_ref));
127                Ok(unsafe { ::std::mem::transmute::<&mut $base, &mut $sub>(base_ref) })
128            }
129        }
130
131    )
132}
133
134#[cfg(test)]
135mod test {
136    use super::TryInto;
137
138    #[repr(C)]
139    pub struct Packet {
140        // packet type: 0x02 is "status"
141        packet_type: u8,
142
143        // 7 byte payload.
144        // status: 4 byte u32 in big endian byteorder for node id, 3x1 byte status
145        data: [u8; 7],
146    }
147
148    #[repr(C)]
149    pub struct StatusPacket {
150        packet_type: u8,
151        ts: [u8; 4],
152        status_0: u8,
153        status_1: u8,
154        status_2: u8,
155    }
156
157    #[repr(C, packed)]
158    pub struct PingPacket {
159        packet_type: u8,
160        dummy: u32,
161        unused: [u8; 3],
162    }
163
164    #[repr(C, packed)]
165    pub struct PongPacket {
166        packet_type: u8,
167        dummy: u32,
168        unused: [u8; 3],
169    }
170
171    pub struct PongConvError {
172
173    }
174
175    subtype_of!(Packet => PingPacket | ConversionError {
176        Ok(())
177    });
178    subtype_of!(Packet => PongPacket | PongConvError {
179        Err(PongConvError {})
180    });
181    subtype_of!(Packet => StatusPacket | () {
182        Ok(())
183    });
184
185    #[derive(Debug)]
186    pub enum ConversionError {}
187
188    impl Packet {
189        pub fn get_raw_payload(&self) -> &[u8] {
190            &self.data
191        }
192
193        pub fn set_raw_payload(&mut self, data: [u8; 7]) {
194            self.data = data
195        }
196
197        pub fn new(packet_type: u8, data: [u8; 7]) -> Packet {
198            Packet {
199                packet_type: packet_type,
200                data: data,
201            }
202        }
203    }
204
205    impl StatusPacket {
206        pub fn get_status_2(&self) -> u8 {
207            self.status_2
208        }
209
210        pub fn set_status_2(&mut self, v: u8) {
211            self.status_2 = v
212        }
213    }
214
215    /// send takes a raw packet to send
216    fn send(packet: &Packet) {
217        let _ = packet.get_raw_payload();
218        // ...
219    }
220
221    fn swallow_status_packet(_: StatusPacket) {
222        // goodbye, s!
223    }
224
225    #[test]
226    fn test_send() {
227        let mut owned = Packet::new(2, b"0123456".to_owned());
228        send(&owned);
229
230        {
231            let status_view: &StatusPacket = (&owned).try_into().unwrap();
232            send(status_view);
233        }
234
235        let mut status_mut_ref: &mut StatusPacket = (&mut owned).try_into().unwrap();
236        status_mut_ref.set_status_2(0x12);
237        assert_eq!(status_mut_ref.get_status_2(), 0x12);
238        send(&status_mut_ref);
239    }
240
241    #[test]
242    fn send_from_ref() {
243        let mut owned = Packet::new(2, b"0123456".to_owned());
244
245        let pref: &mut Packet = &mut owned;
246
247        send(pref);
248
249        {
250            // FIXME: DerefMut not a good idea beacause we don't want
251            //        to allow manipulations on a reference -- it might
252            //        invalidate StatusPacket
253            //        &(*pref) seems kind of silly though to drop the mut
254            let status_view: &StatusPacket = (&(*pref)).try_into().unwrap();
255            send(&status_view);
256        }
257
258        let mut status_mut_view: &mut StatusPacket = pref.try_into().unwrap();
259        status_mut_view.set_status_2(0x12);
260        assert_eq!(status_mut_view.get_status_2(), 0x12);
261        send(&status_mut_view);
262    }
263
264    #[test]
265    fn call_base_and_sub_methods() {
266        let mut owned = Packet::new(2, b"0123456".to_owned());
267        owned.set_raw_payload(b"xxxxxxx".to_owned());
268
269        {
270            let status_view: &StatusPacket = (&owned).try_into().unwrap();
271            status_view.get_status_2();
272            status_view.get_raw_payload();
273        }
274
275        let mut status_mut_view: &mut StatusPacket = (&mut owned).try_into().unwrap();
276        status_mut_view.get_status_2();
277        status_mut_view.get_raw_payload();
278        status_mut_view.set_status_2(0x34);
279
280        // does not work (and shouldn't):
281        // status_mut_view.set_raw_payload(b"xxxxxxx".to_owned());
282    }
283
284    #[test]
285    fn create_from_immutable_ref() {
286        let v = vec![Packet::new(2, b"0123456".to_owned())];
287
288        for p in v.iter() {
289            let status_view: &StatusPacket = (&(*p)).try_into().unwrap();
290            status_view.get_status_2();
291        }
292    }
293
294    #[test]
295    fn use_owned_status() {
296        let p = Packet::new(2, b"0123456".to_owned());
297
298        let s: StatusPacket = p.try_into().unwrap();
299
300        swallow_status_packet(s);
301    }
302}