Skip to main content

dpp/serialization/
serde_bytes.rs

1//! Generic serde helper for fixed-size byte arrays `[u8; N]`.
2//!
3//! Default serde serializes `[u8; N]` for N ≤ 32 as a tuple of u8 elements
4//! (a sequence of numbers in JSON, opaque in non-self-describing formats).
5//! For N > 32 there is no default impl at all.
6//!
7//! This module gives a single, length-agnostic shape:
8//!
9//! - **Human-readable** formats (JSON): base64-encoded string (matches
10//!   `Bytes20` / `Bytes32` / `Bytes36` / `BinaryData` in `rs-platform-value`)
11//! - **Binary** formats (bincode, CBOR, `platform_value`): raw byte sequence
12//!   (which becomes `Uint8Array` through `serde_wasm_bindgen` with
13//!   `serialize_bytes_as_arrays(false)`)
14//!
15//! Used via `#[serde(with = "crate::serialization::serde_bytes")]` on any
16//! `[u8; N]` field. The `#[json_safe_fields]` proc-macro injects this for
17//! every fixed-size byte field.
18
19use base64::prelude::BASE64_STANDARD;
20use base64::Engine;
21use serde::de::{self, SeqAccess, Visitor};
22use serde::{Deserialize, Deserializer, Serializer};
23use std::fmt;
24
25pub fn serialize<S: Serializer, const N: usize>(
26    bytes: &[u8; N],
27    serializer: S,
28) -> Result<S::Ok, S::Error> {
29    if serializer.is_human_readable() {
30        serializer.serialize_str(&BASE64_STANDARD.encode(bytes))
31    } else {
32        serializer.serialize_bytes(bytes)
33    }
34}
35
36pub fn deserialize<'de, D: Deserializer<'de>, const N: usize>(
37    deserializer: D,
38) -> Result<[u8; N], D::Error> {
39    if deserializer.is_human_readable() {
40        let s = <String>::deserialize(deserializer)?;
41        let vec = BASE64_STANDARD
42            .decode(&s)
43            .map_err(serde::de::Error::custom)?;
44        vec.try_into().map_err(|v: Vec<u8>| {
45            serde::de::Error::custom(format!("expected {} bytes, got {}", N, v.len()))
46        })
47    } else {
48        // Accept both byte-buffer formats (`serde_wasm_bindgen` Uint8Array,
49        // `platform_value::Value::Bytes` → `visit_bytes` / `visit_byte_buf`)
50        // and length-prefixed sequences (bincode → `visit_seq`). Going through
51        // `<Vec<u8>>::deserialize` would only cover the seq path.
52        struct BytesOrSeqVisitor<const N: usize>;
53
54        impl<'de, const N: usize> Visitor<'de> for BytesOrSeqVisitor<N> {
55            type Value = [u8; N];
56
57            fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result {
58                write!(f, "{} bytes (as a byte buffer or sequence of u8)", N)
59            }
60
61            fn visit_bytes<E: de::Error>(self, v: &[u8]) -> Result<Self::Value, E> {
62                v.try_into()
63                    .map_err(|_| E::custom(format!("expected {} bytes, got {}", N, v.len())))
64            }
65
66            fn visit_byte_buf<E: de::Error>(self, v: Vec<u8>) -> Result<Self::Value, E> {
67                let len = v.len();
68                v.try_into()
69                    .map_err(|_| E::custom(format!("expected {} bytes, got {}", N, len)))
70            }
71
72            fn visit_seq<A: SeqAccess<'de>>(self, mut seq: A) -> Result<Self::Value, A::Error> {
73                let mut buf = Vec::with_capacity(N);
74                while let Some(b) = seq.next_element::<u8>()? {
75                    buf.push(b);
76                }
77                let len = buf.len();
78                buf.try_into()
79                    .map_err(|_| de::Error::custom(format!("expected {} bytes, got {}", N, len)))
80            }
81        }
82
83        deserializer.deserialize_byte_buf(BytesOrSeqVisitor::<N>)
84    }
85}
86
87#[cfg(test)]
88mod tests {
89    use base64::prelude::BASE64_STANDARD;
90    use base64::Engine;
91    use serde::{Deserialize, Serialize};
92
93    #[derive(Serialize, Deserialize, PartialEq, Debug)]
94    struct Wrap32(#[serde(with = "super")] [u8; 32]);
95
96    #[derive(Serialize, Deserialize, PartialEq, Debug)]
97    struct Wrap64(#[serde(with = "super")] [u8; 64]);
98
99    #[derive(Serialize, Deserialize, PartialEq, Debug)]
100    struct Wrap20(#[serde(with = "super")] [u8; 20]);
101
102    #[test]
103    fn json_round_trip_32_bytes_uses_base64_string() {
104        let original = Wrap32([0xab; 32]);
105        let value = serde_json::to_value(&original).expect("serialize");
106        assert_eq!(value, serde_json::json!(BASE64_STANDARD.encode([0xab; 32])));
107        let restored: Wrap32 = serde_json::from_value(value).expect("deserialize");
108        assert_eq!(original, restored);
109    }
110
111    #[test]
112    fn json_round_trip_64_bytes_uses_base64_string() {
113        let original = Wrap64([0xcd; 64]);
114        let value = serde_json::to_value(&original).expect("serialize");
115        assert_eq!(value, serde_json::json!(BASE64_STANDARD.encode([0xcd; 64])));
116        let restored: Wrap64 = serde_json::from_value(value).expect("deserialize");
117        assert_eq!(original, restored);
118    }
119
120    #[test]
121    fn json_round_trip_20_bytes_works_with_const_generic() {
122        let original = Wrap20([0x12; 20]);
123        let value = serde_json::to_value(&original).expect("serialize");
124        assert_eq!(value, serde_json::json!(BASE64_STANDARD.encode([0x12; 20])));
125        let restored: Wrap20 = serde_json::from_value(value).expect("deserialize");
126        assert_eq!(original, restored);
127    }
128
129    #[test]
130    fn rejects_wrong_length_base64() {
131        let result: Result<Wrap32, _> =
132            serde_json::from_value(serde_json::json!(BASE64_STANDARD.encode([0u8; 8])));
133        assert!(result.is_err());
134    }
135
136    #[test]
137    fn binary_round_trip_uses_raw_bytes() {
138        let original = Wrap32([0x55; 32]);
139        let bytes = bincode::serde::encode_to_vec(&original, bincode::config::standard())
140            .expect("bincode encode");
141        let (restored, _): (Wrap32, usize) =
142            bincode::serde::decode_from_slice(&bytes, bincode::config::standard())
143                .expect("bincode decode");
144        assert_eq!(original, restored);
145    }
146}