Skip to main content

deser_msgpack/
ext.rs

1use alloc::borrow::Cow;
2use alloc::vec::Vec;
3
4use deser_core::State;
5use deser_core::de::{Deserialize, Sink, SinkHandle};
6use deser_core::ext::{ExtValue, Extension, Timestamp};
7use deser_core::ser::{Chunk, Serialize};
8use deser_core::{Atom, Bytes, Error};
9
10/// The extension type of timestamps.
11pub(crate) const TIMESTAMP: i8 = -1;
12
13/// A MessagePack extension value: a type and binary data.
14///
15/// Extensions whose type the format does not understand are passed through
16/// deser as extension atoms of this type.  Their fallback is the binary
17/// data, so an extension deserializes into `Vec<u8>` if the type is not of
18/// interest.  Values of this type are written as extensions.
19///
20/// ```
21/// use deser_msgpack::Ext;
22///
23/// let bytes = deser_msgpack::to_vec(&Ext::new(7, vec![1, 2, 3])).unwrap();
24/// assert_eq!(bytes, [0xc7, 0x03, 0x07, 0x01, 0x02, 0x03]);
25/// let value: Ext = deser_msgpack::from_slice(&bytes).unwrap();
26/// assert_eq!(value, Ext::new(7, vec![1, 2, 3]));
27/// ```
28///
29/// Timestamps (type `-1`) are converted to and from
30/// [`Timestamp`](deser_core::ext::Timestamp) instead.  They are still
31/// accepted by this type which then holds their encoding.
32#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
33pub struct Ext {
34    /// The type of the extension (`-128` to `-1` are reserved by the
35    /// specification).
36    pub kind: i8,
37    /// The data of the extension.
38    pub data: Vec<u8>,
39}
40
41impl Ext {
42    /// Creates an extension value.
43    pub fn new(kind: i8, data: impl Into<Vec<u8>>) -> Ext {
44        Ext {
45            kind,
46            data: data.into(),
47        }
48    }
49}
50
51impl Extension for Ext {
52    fn name(&self) -> &str {
53        "msgpack extension"
54    }
55
56    fn fallback(&self) -> Atom<'_> {
57        Atom::Bytes(Bytes::borrowed(&self.data))
58    }
59}
60
61impl Serialize for Ext {
62    fn serialize(&self, _state: &mut State) -> Result<Chunk<'_>, Error> {
63        Ok(Chunk::Atom(Atom::Ext(ExtValue::borrowed(self))))
64    }
65}
66
67impl<'de> Deserialize<'de> for Ext {
68    fn deserialize_into<'out>(
69        out: &'out mut Option<Self>,
70        state: &mut State,
71    ) -> SinkHandle<'out, 'de> {
72        SinkHandle::arena(ExtSink(out), state)
73    }
74}
75
76struct ExtSink<'a>(&'a mut Option<Ext>);
77
78impl<'a, 'de> Sink<'de> for ExtSink<'a> {
79    fn expecting(&self) -> Cow<'_, str> {
80        Cow::Borrowed("msgpack extension")
81    }
82
83    fn atom(&mut self, atom: Atom, state: &mut State) -> Result<(), Error> {
84        let value = match atom {
85            Atom::Ext(ref ext) => {
86                if let Some(value) = ext.downcast_ref::<Ext>() {
87                    value.clone()
88                } else if let Some(value) = ext.downcast_ref::<Timestamp>() {
89                    let mut buf = [0; 12];
90                    Ext::new(TIMESTAMP, encode_timestamp(value, &mut buf))
91                } else {
92                    return self.unexpected_atom(atom, state);
93                }
94            }
95            other => return self.unexpected_atom(other, state),
96        };
97        *self.0 = Some(value);
98        Ok(())
99    }
100}
101
102/// Decodes the data of a timestamp extension.
103///
104/// Returns `None` if the data is not a valid timestamp.
105pub(crate) fn decode_timestamp(data: &[u8]) -> Option<Timestamp> {
106    let (seconds, nanosecond) = match data.len() {
107        // timestamp 32: seconds
108        4 => (i64::from(u32::from_be_bytes(data.try_into().unwrap())), 0),
109        // timestamp 64: 30 bits nanoseconds, 34 bits seconds
110        8 => {
111            let value = u64::from_be_bytes(data.try_into().unwrap());
112            ((value & 0x3_ffff_ffff) as i64, (value >> 34) as u32)
113        }
114        // timestamp 96: 32 bits nanoseconds, 64 bits signed seconds
115        12 => (
116            i64::from_be_bytes(data[4..].try_into().unwrap()),
117            u32::from_be_bytes(data[..4].try_into().unwrap()),
118        ),
119        _ => return None,
120    };
121    (nanosecond < 1_000_000_000).then_some(Timestamp {
122        seconds,
123        nanosecond,
124    })
125}
126
127/// Encodes a timestamp in the smallest format.
128pub(crate) fn encode_timestamp<'b>(value: &Timestamp, buf: &'b mut [u8; 12]) -> &'b [u8] {
129    if value.seconds >> 34 == 0 {
130        if value.nanosecond == 0 && value.seconds <= i64::from(u32::MAX) {
131            buf[..4].copy_from_slice(&(value.seconds as u32).to_be_bytes());
132            &buf[..4]
133        } else {
134            let packed = (u64::from(value.nanosecond) << 34) | value.seconds as u64;
135            buf[..8].copy_from_slice(&packed.to_be_bytes());
136            &buf[..8]
137        }
138    } else {
139        buf[..4].copy_from_slice(&value.nanosecond.to_be_bytes());
140        buf[4..].copy_from_slice(&value.seconds.to_be_bytes());
141        &buf[..]
142    }
143}
144
145#[test]
146fn test_timestamp_codec() {
147    for (seconds, nanosecond, len) in [
148        (0, 0, 4),
149        (u32::MAX as i64, 0, 4),
150        (u32::MAX as i64 + 1, 0, 8),
151        (0, 1, 8),
152        ((1 << 34) - 1, 999_999_999, 8),
153        (1 << 34, 0, 12),
154        (-1, 0, 12),
155        (i64::MIN, 999_999_999, 12),
156        (i64::MAX, 0, 12),
157    ] {
158        let value = Timestamp {
159            seconds,
160            nanosecond,
161        };
162        let mut buf = [0; 12];
163        let data = encode_timestamp(&value, &mut buf);
164        assert_eq!(data.len(), len, "{:?}", value);
165        assert_eq!(decode_timestamp(data), Some(value));
166    }
167    // nanoseconds out of range
168    assert_eq!(decode_timestamp(&[0xee, 0x6b, 0x28, 0, 0, 0, 0, 0]), None);
169    assert_eq!(
170        decode_timestamp(&[0x3b, 0x9a, 0xca, 0, 0, 0, 0, 0, 0, 0, 0, 0]),
171        None
172    );
173    assert_eq!(decode_timestamp(&[0, 0, 0]), None);
174}