exonum_proto/
lib.rs

1// Copyright 2020 The Exonum Team
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//   http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15//! Protobuf generated structs and traits for conversion.
16//!
17//! The central part of this module is [`ProtobufConvert`](./trait.ProtobufConvert.html).
18//! The main purpose of this trait is to allow
19//! users to create a map between their types and the types generated from .proto descriptions, while
20//! providing a mechanism for additional validation of protobuf data.
21//!
22//! Most of the time you do not have to implement this trait because most of the use cases are covered
23//! by `#[derive(ProtobufConvert)]` from `exonum_derive` crate.
24//!
25//! A typical example of such mapping with validation is manual implementation of this trait for `crypto::Hash`.
26//! `crypto::Hash` is a fixed sized array of bytes but protobuf does not allow us to express this constraint since
27//! only dynamically sized arrays are supported.
28//! If you would like to use `Hash` as a part of your
29//! protobuf struct, you would have to write a conversion function from protobuf `proto::Hash`(which
30//! is dynamically sized array of bytes) to`crypto::Hash` and call it every time when you want to
31//! use `crypto::Hash` in your application.
32//!
33//! The provided `ProtobufConvert` implementation for Hash allows you to embed this field into your
34//! struct and generate `ProtobufConvert` for it using `#[derive(ProtobufConvert)]`, which will validate
35//! your struct based on the validation function for `Hash`.
36
37#![warn(
38    missing_debug_implementations,
39    missing_docs,
40    unsafe_code,
41    bare_trait_objects
42)]
43#![warn(clippy::pedantic)]
44#![allow(
45    // Next `cast_*` lints don't give alternatives.
46    clippy::cast_possible_wrap, clippy::cast_possible_truncation, clippy::cast_sign_loss,
47    // Next lints produce too much noise/false positives.
48    clippy::module_name_repetitions, clippy::similar_names, clippy::must_use_candidate,
49    clippy::pub_enum_variant_names,
50    // '... may panic' lints.
51    clippy::indexing_slicing,
52    // Too much work to fix.
53    clippy::missing_errors_doc
54)]
55
56#[macro_use]
57extern crate serde_derive; // Required for Protobuf.
58
59pub use protobuf_convert::*;
60
61pub mod proto;
62
63use anyhow::{ensure, format_err, Error};
64use chrono::{DateTime, TimeZone, Utc};
65use protobuf::well_known_types;
66use serde::{de::Visitor, Deserializer, Serializer};
67
68use std::{collections::HashMap, convert::TryFrom, fmt};
69
70use crate::proto::bit_vec::BitVec;
71
72#[cfg(test)]
73mod tests;
74
75/// Used for establishing correspondence between a Rust struct and a type generated from Protobuf.
76pub trait ProtobufConvert: Sized {
77    /// Type generated from the Protobuf definition.
78    type ProtoStruct;
79
80    /// Performs conversion to the type generated from Protobuf.
81    fn to_pb(&self) -> Self::ProtoStruct;
82    /// Performs conversion from the type generated from Protobuf.
83    fn from_pb(pb: Self::ProtoStruct) -> Result<Self, Error>;
84}
85
86impl ProtobufConvert for DateTime<Utc> {
87    type ProtoStruct = well_known_types::Timestamp;
88
89    fn to_pb(&self) -> Self::ProtoStruct {
90        let mut ts = Self::ProtoStruct::new();
91        ts.set_seconds(self.timestamp());
92        ts.set_nanos(self.timestamp_subsec_nanos() as i32);
93        ts
94    }
95
96    fn from_pb(pb: Self::ProtoStruct) -> Result<Self, Error> {
97        Utc.timestamp_opt(pb.get_seconds(), pb.get_nanos() as u32)
98            .single()
99            .ok_or_else(|| format_err!("Failed to convert timestamp from bytes"))
100    }
101}
102
103impl ProtobufConvert for String {
104    type ProtoStruct = Self;
105
106    fn to_pb(&self) -> Self::ProtoStruct {
107        self.clone()
108    }
109
110    fn from_pb(pb: Self::ProtoStruct) -> Result<Self, Error> {
111        Ok(pb)
112    }
113}
114
115impl ProtobufConvert for u16 {
116    type ProtoStruct = u32;
117
118    fn to_pb(&self) -> Self::ProtoStruct {
119        u32::from(*self)
120    }
121
122    fn from_pb(pb: Self::ProtoStruct) -> Result<Self, Error> {
123        u16::try_from(pb).map_err(|_| format_err!("Value is out of range"))
124    }
125}
126
127impl ProtobufConvert for i16 {
128    type ProtoStruct = i32;
129
130    fn to_pb(&self) -> Self::ProtoStruct {
131        i32::from(*self)
132    }
133
134    fn from_pb(pb: Self::ProtoStruct) -> Result<Self, Error> {
135        i16::try_from(pb).map_err(|_| format_err!("Value is out of range"))
136    }
137}
138
139impl<T> ProtobufConvert for Vec<T>
140where
141    T: ProtobufConvert,
142{
143    type ProtoStruct = Vec<T::ProtoStruct>;
144
145    fn to_pb(&self) -> Self::ProtoStruct {
146        self.iter().map(ProtobufConvert::to_pb).collect()
147    }
148    fn from_pb(pb: Self::ProtoStruct) -> Result<Self, Error> {
149        pb.into_iter()
150            .map(ProtobufConvert::from_pb)
151            .collect::<Result<Vec<_>, _>>()
152    }
153}
154
155impl ProtobufConvert for () {
156    type ProtoStruct = protobuf::well_known_types::Empty;
157
158    fn to_pb(&self) -> Self::ProtoStruct {
159        Self::ProtoStruct::default()
160    }
161
162    fn from_pb(_pb: Self::ProtoStruct) -> Result<Self, Error> {
163        Ok(())
164    }
165}
166
167/// Special case for protobuf bytes.
168impl ProtobufConvert for Vec<u8> {
169    type ProtoStruct = Vec<u8>;
170
171    fn to_pb(&self) -> Self::ProtoStruct {
172        self.clone()
173    }
174    fn from_pb(pb: Self::ProtoStruct) -> Result<Self, Error> {
175        Ok(pb)
176    }
177}
178
179// According to protobuf specification only simple scalar types (not floats) and strings can be used
180// as a map keys.
181impl<K, T, S> ProtobufConvert for HashMap<K, T, S>
182where
183    K: Eq + std::hash::Hash + std::fmt::Debug + Clone,
184    T: ProtobufConvert,
185    S: Default + std::hash::BuildHasher,
186{
187    type ProtoStruct = HashMap<K, T::ProtoStruct, S>;
188    fn to_pb(&self) -> Self::ProtoStruct {
189        self.iter().map(|(k, v)| (k.clone(), v.to_pb())).collect()
190    }
191    fn from_pb(mut pb: Self::ProtoStruct) -> Result<Self, Error> {
192        pb.drain()
193            .map(|(k, v)| ProtobufConvert::from_pb(v).map(|v| (k, v)))
194            .collect::<Result<HashMap<_, _, _>, _>>()
195    }
196}
197
198impl ProtobufConvert for bit_vec::BitVec {
199    type ProtoStruct = BitVec;
200
201    fn to_pb(&self) -> Self::ProtoStruct {
202        let mut bit_vec = BitVec::new();
203        bit_vec.set_data(self.to_bytes());
204        bit_vec.set_len(self.len() as u64);
205        bit_vec
206    }
207
208    fn from_pb(pb: Self::ProtoStruct) -> Result<Self, Error> {
209        let data = pb.get_data();
210        let mut bit_vec = bit_vec::BitVec::from_bytes(data);
211        bit_vec.truncate(pb.get_len() as usize);
212        Ok(bit_vec)
213    }
214}
215
216macro_rules! impl_protobuf_convert_scalar {
217    ( $( $name:tt ),* )=> {
218        $(
219            impl ProtobufConvert for $name {
220                type ProtoStruct = $name;
221                fn to_pb(&self) -> Self::ProtoStruct {
222                    *self
223                }
224                fn from_pb(pb: Self::ProtoStruct) -> Result<Self, Error> {
225                    Ok(pb)
226                }
227            }
228        )*
229    };
230}
231
232impl_protobuf_convert_scalar! { bool, u32, u64, i32, i64, f32, f64 }
233
234macro_rules! impl_protobuf_convert_fixed_byte_array {
235    ( $( $arr_len:expr ),* ) => {
236        $(
237            /// Special case for fixed sized arrays.
238            impl ProtobufConvert for [u8; $arr_len] {
239                type ProtoStruct = Vec<u8>;
240
241                fn to_pb(&self) -> Self::ProtoStruct {
242                    self.to_vec()
243                }
244
245                fn from_pb(pb: Self::ProtoStruct) -> Result<Self, Error> {
246                    ensure!(
247                        pb.len() == $arr_len,
248                        "wrong array size: actual {}, expected {}",
249                        pb.len(),
250                        $arr_len
251                    );
252
253                    Ok({
254                        let mut array = [0; $arr_len];
255                        array.copy_from_slice(&pb);
256                        array
257                    })
258                }
259            }
260        )*
261    };
262}
263
264// We implement array conversion only for most common array sizes that uses
265// for example in cryptography.
266impl_protobuf_convert_fixed_byte_array! {
267    8, 16, 24, 32, 40, 48, 56, 64,
268    72, 80, 88, 96, 104, 112, 120, 128,
269    160, 256, 512, 1024, 2048
270}
271
272/// Marker type for use with `#[serde(with)]`, which provides Protobuf-compatible base64 encoding
273/// and decoding. For now, works only on `Vec<u8>` fields.
274///
275/// The encoder uses the standard base64 alphabet (i.e., `0..9A..Za..z+/`) with no padding.
276/// The decoder accepts any of the 4 possible combinations: the standard or the URL-safe alphabet
277/// with or without padding.
278///
279/// If the (de)serializer is not human-readable (e.g., CBOR or `bincode`), the bytes will be
280/// (de)serialized without base64 transform, directly as a byte slice.
281///
282/// # Examples
283///
284/// ```
285/// use exonum_proto::ProtobufBase64;
286/// # use serde_derive::*;
287/// # use serde_json::json;
288///
289/// #[derive(Serialize, Deserialize)]
290/// struct Test {
291///     /// Corresponds to a `bytes buffer = ...` field in Protobuf.
292///     #[serde(with = "ProtobufBase64")]
293///     buffer: Vec<u8>,
294///     // other fields...
295/// }
296///
297/// # fn main() -> anyhow::Result<()> {
298/// let test = Test {
299///     buffer: b"Hello!".to_vec(),
300///     // ...
301/// };
302/// let obj = serde_json::to_value(&test)?;
303/// assert_eq!(obj, json!({ "buffer": "SGVsbG8h", /* ... */ }));
304/// # Ok(())
305/// # }
306/// ```
307#[derive(Debug)]
308pub struct ProtobufBase64(());
309
310impl ProtobufBase64 {
311    /// Serializes the provided `bytes` with the `serializer`.
312    pub fn serialize<S, T>(bytes: &T, serializer: S) -> Result<S::Ok, S::Error>
313    where
314        S: Serializer,
315        T: AsRef<[u8]> + ?Sized,
316    {
317        if serializer.is_human_readable() {
318            serializer.serialize_str(&base64::encode_config(bytes, base64::STANDARD_NO_PAD))
319        } else {
320            serializer.serialize_bytes(bytes.as_ref())
321        }
322    }
323
324    /// Decodes bytes from any of four base64 variations supported as per Protobuf spec
325    /// (standard or URL-safe alphabet, with or without padding).
326    pub fn decode(value: &str) -> Result<Vec<u8>, base64::DecodeError> {
327        // Remove padding if any.
328        let value_without_padding = if value.ends_with("==") {
329            &value[..value.len() - 2]
330        } else if value.ends_with('=') {
331            &value[..value.len() - 1]
332        } else {
333            value
334        };
335
336        let is_url_safe = value_without_padding.contains(|ch: char| ch == '-' || ch == '_');
337        let config = if is_url_safe {
338            base64::URL_SAFE_NO_PAD
339        } else {
340            base64::STANDARD_NO_PAD
341        };
342        base64::decode_config(value_without_padding, config)
343    }
344
345    /// Deserializes `Vec<u8>` using the provided serializer.
346    pub fn deserialize<'de, D>(deserializer: D) -> Result<Vec<u8>, D::Error>
347    where
348        D: Deserializer<'de>,
349    {
350        use serde::de::Error as DeError;
351
352        struct Base64Visitor;
353
354        impl<'de> Visitor<'de> for Base64Visitor {
355            type Value = Vec<u8>;
356
357            fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result {
358                f.write_str("base64-encoded byte array")
359            }
360
361            fn visit_str<E: DeError>(self, value: &str) -> Result<Self::Value, E> {
362                ProtobufBase64::decode(value).map_err(E::custom)
363            }
364
365            // Needed to guard against non-obvious serialization of flattened fields in `serde`.
366            fn visit_bytes<E: DeError>(self, value: &[u8]) -> Result<Self::Value, E> {
367                Ok(value.to_vec())
368            }
369        }
370
371        struct BytesVisitor;
372
373        impl<'de> Visitor<'de> for BytesVisitor {
374            type Value = Vec<u8>;
375
376            fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result {
377                f.write_str("byte array")
378            }
379
380            fn visit_bytes<E: DeError>(self, value: &[u8]) -> Result<Self::Value, E> {
381                Ok(value.to_vec())
382            }
383        }
384
385        if deserializer.is_human_readable() {
386            deserializer.deserialize_str(Base64Visitor)
387        } else {
388            deserializer.deserialize_bytes(BytesVisitor)
389        }
390    }
391}