Skip to main content

aven_core/sync/
base64_bytes.rs

1//! JSON transport spelling for opaque byte payloads: one canonical padded
2//! standard base64 string. The bytes themselves are what callers hash, sign
3//! and store.
4use base64::{Engine, engine::general_purpose::STANDARD};
5use serde::{Deserializer, Serializer, de};
6
7/// Serialized length of `n` bytes, excluding the JSON quotes.
8pub const fn encoded_len(n: usize) -> usize {
9    match base64::encoded_len(n, true) {
10        Some(len) => len,
11        None => panic!("base64 length overflow"),
12    }
13}
14
15pub fn serialize<S: Serializer>(bytes: &[u8], serializer: S) -> Result<S::Ok, S::Error> {
16    serializer.serialize_str(&STANDARD.encode(bytes))
17}
18
19pub fn deserialize<'de, D: Deserializer<'de>>(deserializer: D) -> Result<Vec<u8>, D::Error> {
20    deserializer.deserialize_str(Visitor(usize::MAX))
21}
22
23/// Refuses a string that would decode to more than `N` bytes before decoding it.
24pub fn bounded<'de, D: Deserializer<'de>, const N: usize>(
25    deserializer: D,
26) -> Result<Vec<u8>, D::Error> {
27    deserializer.deserialize_str(Visitor(encoded_len(N)))
28}
29
30/// The same spelling for an optional payload, with `null` for `None`.
31pub mod option {
32    use base64::Engine;
33    use serde::{Deserialize, Deserializer, Serializer};
34
35    #[derive(Deserialize)]
36    struct Bytes(#[serde(with = "super")] Vec<u8>);
37
38    pub fn serialize<S: Serializer>(
39        bytes: &Option<Vec<u8>>,
40        serializer: S,
41    ) -> Result<S::Ok, S::Error> {
42        match bytes {
43            Some(bytes) => serializer.serialize_some(&super::STANDARD.encode(bytes)),
44            None => serializer.serialize_none(),
45        }
46    }
47
48    pub fn deserialize<'de, D: Deserializer<'de>>(
49        deserializer: D,
50    ) -> Result<Option<Vec<u8>>, D::Error> {
51        Ok(Option::<Bytes>::deserialize(deserializer)?.map(|bytes| bytes.0))
52    }
53}
54
55struct Visitor(usize);
56impl de::Visitor<'_> for Visitor {
57    type Value = Vec<u8>;
58    fn expecting(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
59        write!(f, "a base64 string of at most {} characters", self.0)
60    }
61    fn visit_str<E: de::Error>(self, value: &str) -> Result<Vec<u8>, E> {
62        if value.len() > self.0 {
63            return Err(E::custom("byte limit"));
64        }
65        STANDARD.decode(value).map_err(E::custom)
66    }
67}
68
69#[cfg(test)]
70mod tests {
71    use serde::{Deserialize, Serialize};
72
73    #[derive(Serialize, Deserialize, PartialEq, Debug)]
74    struct Field(#[serde(with = "super")] Vec<u8>);
75
76    #[test]
77    fn round_trips_with_exact_length() {
78        for n in [0, 1, 2, 3, 4, 255, 1024] {
79            let bytes: Vec<u8> = (0..n).map(|i| (i * 7) as u8).collect();
80            let json = serde_json::to_string(&Field(bytes.clone())).unwrap();
81            assert_eq!(json.len(), super::encoded_len(n) + 2);
82            assert_eq!(serde_json::from_str::<Field>(&json).unwrap().0, bytes);
83        }
84    }
85
86    #[test]
87    fn rejects_other_spellings() {
88        for json in [
89            "[1,2,3]",
90            "\"AQID\\n\"",
91            "\"AQI\"",
92            "\"AQJ=\"",
93            "\"AQ-_\"",
94            "\"AQ==AQ==\"",
95        ] {
96            assert!(serde_json::from_str::<Field>(json).is_err(), "{json}");
97        }
98        #[derive(Deserialize)]
99        struct Bounded(#[serde(deserialize_with = "super::bounded::<_, 3>")] Vec<u8>);
100        assert_eq!(
101            serde_json::from_str::<Bounded>("\"AQID\"").unwrap().0,
102            [1, 2, 3]
103        );
104        assert!(serde_json::from_str::<Bounded>("\"AQIDBA==\"").is_err());
105        // An escaped JSON string still decodes to the same bytes.
106        assert_eq!(
107            serde_json::from_str::<Field>("\"\\u0041QID\"").unwrap().0,
108            [1, 2, 3]
109        );
110    }
111}