saa_common/
binary.rs

1use core::fmt;
2use core::ops::Deref;
3use base64::engine::{Engine, GeneralPurpose};
4use serde::{de::{self, DeserializeOwned}, ser, Deserialize, Deserializer, Serialize};
5
6use crate::AuthError;
7
8#[derive(Clone, Default, PartialEq, Eq, Hash, PartialOrd, Ord)]
9#[cfg_attr(feature = "wasm", derive(
10    saa_schema::schemars::JsonSchema
11))]
12#[cfg_attr(feature = "substrate", derive(
13    saa_schema::scale::Encode, 
14    saa_schema::scale::Decode
15))]
16#[cfg_attr(feature = "solana", derive(
17    saa_schema::borsh::BorshSerialize, 
18    saa_schema::borsh::BorshDeserialize
19))]
20#[cfg_attr(all(feature = "std", feature="substrate"), derive(
21    saa_schema::scale_info::TypeInfo)
22)]
23pub struct Binary(
24    #[cfg_attr(feature = "wasm", schemars(with = "String"))]
25    Vec<u8>
26);
27
28impl Binary {
29    /// Creates a new `Binary` containing the given data.
30    pub const fn new(data: Vec<u8>) -> Self {
31        Self(data)
32    }
33
34    /// Base64 encoding engine used in conversion to/from base64.
35    ///
36    /// The engine adds padding when encoding and accepts strings with or
37    /// without padding when decoding.
38    const B64_ENGINE: GeneralPurpose = GeneralPurpose::new(
39        &base64::alphabet::STANDARD,
40        base64::engine::GeneralPurposeConfig::new()
41            .with_decode_padding_mode(base64::engine::DecodePaddingMode::Indifferent),
42    );
43
44    /// take an (untrusted) string and decode it into bytes.
45    /// fails if it is not valid base64
46    pub fn from_base64(encoded: &str) -> Result<Self, AuthError> {
47        Self::B64_ENGINE
48            .decode(encoded.as_bytes())
49            .map(Binary::from)
50            .map_err(|_| AuthError::generic("invalid base64"))
51    }
52
53    /// encode to base64 string (guaranteed to be success as we control the data inside).
54    /// this returns normalized form (with trailing = if needed)
55    pub fn to_base64(&self) -> String {
56        Self::B64_ENGINE.encode(self.0.as_slice())
57    }
58
59    pub fn as_slice(&self) -> &[u8] {
60        self.0.as_slice()
61    }
62
63    /// Copies content into fixed-sized array.
64    ///
65    /// # Examples
66    ///
67    /// Copy to array of explicit length
68    ///
69    /// ```
70    /// # use cosmwasm_std::Binary;
71    /// let binary = Binary::from(&[0xfb, 0x1f, 0x37]);
72    /// let array: [u8; 3] = binary.to_array().unwrap();
73    /// assert_eq!(array, [0xfb, 0x1f, 0x37]);
74    /// ```
75    ///
76    /// Copy to integer
77    ///
78    /// ```
79    /// # use cosmwasm_std::Binary;
80    /// let binary = Binary::from(&[0x8b, 0x67, 0x64, 0x84, 0xb5, 0xfb, 0x1f, 0x37]);
81    /// let num = u64::from_be_bytes(binary.to_array().unwrap());
82    /// assert_eq!(num, 10045108015024774967);
83    /// ```
84    pub fn to_array<const LENGTH: usize>(&self) -> Result<[u8; LENGTH], AuthError> {
85        if self.len() != LENGTH {
86            return Err(AuthError::InvalidLength(LENGTH as u16, self.len() as u16));
87        }
88
89        let mut out: [u8; LENGTH] = [0; LENGTH];
90        out.copy_from_slice(&self.0);
91        Ok(out)
92    }
93}
94
95impl fmt::Display for Binary {
96    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
97        write!(f, "{}", self.to_base64())
98    }
99}
100
101impl fmt::Debug for Binary {
102    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
103        // Use an output inspired by tuples (https://doc.rust-lang.org/std/fmt/struct.Formatter.html#method.debug_tuple)
104        // but with a custom implementation to avoid the need for an intemediate hex string.
105        write!(f, "Binary(")?;
106        for byte in self.0.iter() {
107            write!(f, "{byte:02x}")?;
108        }
109        write!(f, ")")?;
110        Ok(())
111    }
112}
113
114/// Just like Vec<u8>, Binary is a smart pointer to [u8].
115/// This implements `*binary` for us and allows us to
116/// do `&*binary`, returning a `&[u8]` from a `&Binary`.
117/// With [deref coercions](https://doc.rust-lang.org/1.22.1/book/first-edition/deref-coercions.html#deref-coercions),
118/// this allows us to use `&binary` whenever a `&[u8]` is required.
119impl Deref for Binary {
120    type Target = [u8];
121
122    fn deref(&self) -> &Self::Target {
123        self.as_slice()
124    }
125}
126
127impl AsRef<[u8]> for Binary {
128    fn as_ref(&self) -> &[u8] {
129        self.as_slice()
130    }
131}
132
133// Slice
134impl From<&[u8]> for Binary {
135    fn from(binary: &[u8]) -> Self {
136        Self(binary.to_vec())
137    }
138}
139
140// Array reference
141impl<const LENGTH: usize> From<&[u8; LENGTH]> for Binary {
142    fn from(source: &[u8; LENGTH]) -> Self {
143        Self(source.to_vec())
144    }
145}
146
147// Owned array
148impl<const LENGTH: usize> From<[u8; LENGTH]> for Binary {
149    fn from(source: [u8; LENGTH]) -> Self {
150        Self(source.into())
151    }
152}
153
154impl From<Vec<u8>> for Binary {
155    fn from(vec: Vec<u8>) -> Self {
156        Self(vec)
157    }
158}
159
160impl From<Binary> for Vec<u8> {
161    fn from(original: Binary) -> Vec<u8> {
162        original.0
163    }
164}
165
166/// Implement `encoding::Binary == alloc::vec::Vec<u8>`
167impl PartialEq<Vec<u8>> for Binary {
168    fn eq(&self, rhs: &Vec<u8>) -> bool {
169        // Use Vec<u8> == Vec<u8>
170        self.0 == *rhs
171    }
172}
173
174/// Implement `alloc::vec::Vec<u8> == encoding::Binary`
175impl PartialEq<Binary> for Vec<u8> {
176    fn eq(&self, rhs: &Binary) -> bool {
177        // Use Vec<u8> == Vec<u8>
178        *self == rhs.0
179    }
180}
181
182/// Implement `Binary == &[u8]`
183impl PartialEq<&[u8]> for Binary {
184    fn eq(&self, rhs: &&[u8]) -> bool {
185        // Use &[u8] == &[u8]
186        self.as_slice() == *rhs
187    }
188}
189
190/// Implement `&[u8] == Binary`
191impl PartialEq<Binary> for &[u8] {
192    fn eq(&self, rhs: &Binary) -> bool {
193        // Use &[u8] == &[u8]
194        *self == rhs.as_slice()
195    }
196}
197
198/// Implement `Binary == &[u8; LENGTH]`
199impl<const LENGTH: usize> PartialEq<&[u8; LENGTH]> for Binary {
200    fn eq(&self, rhs: &&[u8; LENGTH]) -> bool {
201        self.as_slice() == rhs.as_slice()
202    }
203}
204
205/// Implement `&[u8; LENGTH] == Binary`
206impl<const LENGTH: usize> PartialEq<Binary> for &[u8; LENGTH] {
207    fn eq(&self, rhs: &Binary) -> bool {
208        self.as_slice() == rhs.as_slice()
209    }
210}
211
212/// Implement `Binary == [u8; LENGTH]`
213impl<const LENGTH: usize> PartialEq<[u8; LENGTH]> for Binary {
214    fn eq(&self, rhs: &[u8; LENGTH]) -> bool {
215        self.as_slice() == rhs.as_slice()
216    }
217}
218
219/// Implement `[u8; LENGTH] == Binary`
220impl<const LENGTH: usize> PartialEq<Binary> for [u8; LENGTH] {
221    fn eq(&self, rhs: &Binary) -> bool {
222        self.as_slice() == rhs.as_slice()
223    }
224}
225
226/// Serializes as a base64 string
227impl Serialize for Binary {
228    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
229    where
230        S: ser::Serializer,
231    {
232        if serializer.is_human_readable() {
233            serializer.serialize_str(&self.to_base64())
234        } else {
235            panic!("Binary is only intended to be used with JSON serialization for now. If you are hitting this panic please open an issue at https://github.com/CosmWasm/cosmwasm describing your use case.")
236        }
237    }
238}
239
240/// Deserializes as a base64 string
241impl<'de> Deserialize<'de> for Binary {
242    fn deserialize<D>(deserializer: D) -> Result<Binary, D::Error>
243    where
244        D: Deserializer<'de>,
245    {
246        if deserializer.is_human_readable() {
247            deserializer.deserialize_str(Base64Visitor)
248        } else {
249            panic!("Binary is only intended to be used with JSON serialization for now. If you are hitting this panic please open an issue at https://github.com/CosmWasm/cosmwasm describing your use case.")
250        }
251    }
252}
253
254
255
256struct Base64Visitor;
257
258impl<'de> de::Visitor<'de> for Base64Visitor {
259    type Value = Binary;
260
261    fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
262        formatter.write_str("valid base64 encoded string")
263    }
264
265    fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
266    where
267        E: de::Error,
268    {
269        match Binary::from_base64(v) {
270            Ok(binary) => Ok(binary),
271            Err(_) => Err(E::custom(format!("invalid base64: {v}"))),
272        }
273    }
274}
275
276#[cfg(feature = "wasm")]
277impl From<Binary> for crate::cosmwasm::Binary {
278    fn from(binary: Binary) -> Self {
279        binary.to_vec().into()
280    }
281}
282
283#[cfg(feature = "wasm")]
284impl Into<Binary> for crate::cosmwasm::Binary {
285    fn into(self) -> Binary {
286        self.to_vec().into()
287    }
288}
289
290
291
292pub fn to_json_binary<T>(data: &T) -> Result<Binary, AuthError>
293where
294    T: Serialize + ?Sized,
295{   
296    serde_json_wasm::to_vec(data).map_err(|e| AuthError::generic(e.to_string())).map(Binary)
297}
298
299
300pub fn from_json<T: DeserializeOwned>(value: impl AsRef<[u8]>) -> Result<T, AuthError> {
301    serde_json_wasm::from_slice(value.as_ref())
302        .map_err(|e| AuthError::generic(e.to_string()))
303}