fidl_fuchsia_pkg_ext/
types.rs

1// Copyright 2018 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5use crate::errors::{
6    BlobIdFromSliceError, BlobIdParseError, CupMissingField, ResolutionContextError,
7};
8use fidl_fuchsia_pkg as fidl;
9use proptest_derive::Arbitrary;
10use serde::{Deserialize, Serialize};
11use std::{fmt, str};
12use typed_builder::TypedBuilder;
13
14pub(crate) const BLOB_ID_SIZE: usize = 32;
15
16/// Convenience wrapper type for the autogenerated FIDL `BlobId`.
17#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Serialize, Deserialize, Arbitrary)]
18pub struct BlobId(#[serde(with = "hex_serde")] [u8; BLOB_ID_SIZE]);
19
20impl BlobId {
21    /// Parse a `BlobId` from a string containing 32 lower-case hex encoded bytes.
22    ///
23    /// # Examples
24    /// ```
25    /// let s = "00112233445566778899aabbccddeeffffeeddccbbaa99887766554433221100";
26    /// assert_eq!(
27    ///     BlobId::parse(s),
28    ///     s.parse()
29    /// );
30    /// ```
31    pub fn parse(s: &str) -> Result<Self, BlobIdParseError> {
32        s.parse()
33    }
34    /// Obtain a slice of bytes representing the `BlobId`.
35    pub fn as_bytes(&self) -> &[u8] {
36        &self.0[..]
37    }
38}
39
40impl str::FromStr for BlobId {
41    type Err = BlobIdParseError;
42
43    fn from_str(s: &str) -> Result<Self, Self::Err> {
44        let bytes = hex::decode(s)?;
45        if bytes.len() != BLOB_ID_SIZE {
46            return Err(BlobIdParseError::InvalidLength(bytes.len()));
47        }
48        if s.chars().any(|c| c.is_uppercase()) {
49            return Err(BlobIdParseError::CannotContainUppercase);
50        }
51        let mut res: [u8; BLOB_ID_SIZE] = [0; BLOB_ID_SIZE];
52        res.copy_from_slice(&bytes[..]);
53        Ok(Self(res))
54    }
55}
56
57impl From<[u8; BLOB_ID_SIZE]> for BlobId {
58    fn from(bytes: [u8; BLOB_ID_SIZE]) -> Self {
59        Self(bytes)
60    }
61}
62
63impl From<fidl::BlobId> for BlobId {
64    fn from(id: fidl::BlobId) -> Self {
65        Self(id.merkle_root)
66    }
67}
68
69impl From<BlobId> for fidl::BlobId {
70    fn from(id: BlobId) -> Self {
71        Self { merkle_root: id.0 }
72    }
73}
74
75impl From<fuchsia_hash::Hash> for BlobId {
76    fn from(hash: fuchsia_hash::Hash) -> Self {
77        Self(hash.into())
78    }
79}
80
81impl TryFrom<&[u8]> for BlobId {
82    type Error = BlobIdFromSliceError;
83
84    fn try_from(bytes: &[u8]) -> Result<Self, Self::Error> {
85        Ok(Self(bytes.try_into().map_err(|_| Self::Error::InvalidLength(bytes.len()))?))
86    }
87}
88
89impl From<BlobId> for fuchsia_hash::Hash {
90    fn from(id: BlobId) -> Self {
91        id.0.into()
92    }
93}
94
95impl fmt::Display for BlobId {
96    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
97        f.write_str(&hex::encode(self.0))
98    }
99}
100
101impl fmt::Debug for BlobId {
102    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
103        f.write_str(&self.to_string())
104    }
105}
106
107/// Convenience wrapper type for the autogenerated FIDL `BlobInfo`.
108#[derive(Copy, Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash, Arbitrary)]
109pub struct BlobInfo {
110    pub blob_id: BlobId,
111    pub length: u64,
112}
113
114impl BlobInfo {
115    pub fn new(blob_id: BlobId, length: u64) -> Self {
116        BlobInfo { blob_id: blob_id, length: length }
117    }
118}
119
120impl From<fidl::BlobInfo> for BlobInfo {
121    fn from(info: fidl::BlobInfo) -> Self {
122        BlobInfo { blob_id: info.blob_id.into(), length: info.length }
123    }
124}
125
126impl From<BlobInfo> for fidl::BlobInfo {
127    fn from(info: BlobInfo) -> Self {
128        Self { blob_id: info.blob_id.into(), length: info.length }
129    }
130}
131
132/// Convenience wrapper type for the autogenerated FIDL `ResolutionContext`.
133#[derive(Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
134pub struct ResolutionContext {
135    blob_id: Option<BlobId>,
136}
137
138impl ResolutionContext {
139    /// Creates an empty resolution context.
140    pub fn new() -> Self {
141        Self { blob_id: None }
142    }
143
144    /// The ResolutionContext's optional blob id.
145    pub fn blob_id(&self) -> Option<&BlobId> {
146        self.blob_id.as_ref()
147    }
148}
149
150impl From<BlobId> for ResolutionContext {
151    fn from(blob_id: BlobId) -> Self {
152        Self { blob_id: Some(blob_id) }
153    }
154}
155
156impl From<fuchsia_hash::Hash> for ResolutionContext {
157    fn from(hash: fuchsia_hash::Hash) -> Self {
158        Self { blob_id: Some(hash.into()) }
159    }
160}
161
162impl TryFrom<&[u8]> for ResolutionContext {
163    type Error = ResolutionContextError;
164
165    fn try_from(bytes: &[u8]) -> Result<Self, Self::Error> {
166        if bytes.is_empty() {
167            Ok(Self { blob_id: None })
168        } else {
169            Ok(Self { blob_id: Some(bytes.try_into().map_err(Self::Error::InvalidBlobId)?) })
170        }
171    }
172}
173
174impl TryFrom<&fidl::ResolutionContext> for ResolutionContext {
175    type Error = ResolutionContextError;
176
177    fn try_from(context: &fidl::ResolutionContext) -> Result<Self, Self::Error> {
178        Self::try_from(context.bytes.as_slice())
179    }
180}
181
182impl From<ResolutionContext> for Vec<u8> {
183    fn from(context: ResolutionContext) -> Self {
184        match context.blob_id {
185            Some(blob_id) => blob_id.as_bytes().to_vec(),
186            None => vec![],
187        }
188    }
189}
190
191impl From<ResolutionContext> for fidl::ResolutionContext {
192    fn from(context: ResolutionContext) -> Self {
193        Self { bytes: context.into() }
194    }
195}
196
197mod hex_serde {
198    use super::BLOB_ID_SIZE;
199    use hex::FromHex;
200    use serde::Deserialize;
201
202    pub fn serialize<S>(bytes: &[u8; BLOB_ID_SIZE], serializer: S) -> Result<S::Ok, S::Error>
203    where
204        S: serde::Serializer,
205    {
206        let s = hex::encode(bytes);
207        serializer.serialize_str(&s)
208    }
209
210    pub fn deserialize<'de, D>(deserializer: D) -> Result<[u8; BLOB_ID_SIZE], D::Error>
211    where
212        D: serde::Deserializer<'de>,
213    {
214        let value = String::deserialize(deserializer)?;
215        <[u8; BLOB_ID_SIZE]>::from_hex(value.as_bytes())
216            .map_err(|e| serde::de::Error::custom(format!("bad hex value: {:?}: {}", value, e)))
217    }
218}
219
220#[derive(Clone, Debug, Eq, PartialEq, TypedBuilder)]
221pub struct CupData {
222    #[builder(default, setter(into))]
223    pub request: Vec<u8>,
224    #[builder(default, setter(into))]
225    pub key_id: u64,
226    #[builder(default, setter(into))]
227    pub nonce: [u8; 32],
228    #[builder(default, setter(into))]
229    pub response: Vec<u8>,
230    #[builder(default, setter(into))]
231    pub signature: Vec<u8>,
232}
233
234impl From<CupData> for fidl::CupData {
235    fn from(c: CupData) -> Self {
236        fidl::CupData {
237            request: Some(c.request),
238            key_id: Some(c.key_id),
239            nonce: Some(c.nonce),
240            response: Some(c.response),
241            signature: Some(c.signature),
242            ..Default::default()
243        }
244    }
245}
246
247impl TryFrom<fidl::CupData> for CupData {
248    type Error = CupMissingField;
249    fn try_from(c: fidl::CupData) -> Result<Self, Self::Error> {
250        Ok(CupData::builder()
251            .request(c.request.ok_or(CupMissingField::Request)?)
252            .response(c.response.ok_or(CupMissingField::Response)?)
253            .key_id(c.key_id.ok_or(CupMissingField::KeyId)?)
254            .nonce(c.nonce.ok_or(CupMissingField::Nonce)?)
255            .signature(c.signature.ok_or(CupMissingField::Signature)?)
256            .build())
257    }
258}
259
260#[cfg(test)]
261mod test_blob_id {
262    use super::*;
263    use assert_matches::assert_matches;
264    use proptest::prelude::*;
265
266    prop_compose! {
267        fn invalid_hex_char()(c in "[[:ascii:]&&[^0-9a-fA-F]]") -> char {
268            assert_eq!(c.len(), 1);
269            c.chars().next().unwrap()
270        }
271    }
272
273    proptest! {
274        #![proptest_config(ProptestConfig{
275            // Disable persistence to avoid the warning for not running in the
276            // source code directory (since we're running on a Fuchsia target)
277            failure_persistence: None,
278            .. ProptestConfig::default()
279        })]
280
281        #[test]
282        fn parse_is_inverse_of_display(ref id in "[0-9a-f]{64}") {
283            prop_assert_eq!(
284                id,
285                &format!("{}", id.parse::<BlobId>().unwrap())
286            );
287        }
288
289        #[test]
290        fn parse_is_inverse_of_debug(ref id in "[0-9a-f]{64}") {
291            prop_assert_eq!(
292                id,
293                &format!("{:?}", id.parse::<BlobId>().unwrap())
294            );
295        }
296
297        #[test]
298        fn parse_rejects_uppercase(ref id in "[0-9A-F]{64}") {
299            prop_assert_eq!(
300                id.parse::<BlobId>(),
301                Err(BlobIdParseError::CannotContainUppercase)
302            );
303        }
304
305        #[test]
306        fn parse_rejects_unexpected_characters(mut id in "[0-9a-f]{64}", c in invalid_hex_char(), index in 0usize..63) {
307            id.remove(index);
308            id.insert(index, c);
309            prop_assert_eq!(
310                id.parse::<BlobId>(),
311                Err(BlobIdParseError::FromHexError(
312                    hex::FromHexError::InvalidHexCharacter { c: c, index: index }
313                ))
314            );
315        }
316
317        #[test]
318        fn parse_expects_even_sized_strings(ref id in "[0-9a-f]([0-9a-f]{2})*") {
319            prop_assert_eq!(
320                id.parse::<BlobId>(),
321                Err(BlobIdParseError::FromHexError(hex::FromHexError::OddLength))
322            );
323        }
324
325        #[test]
326        fn parse_expects_precise_count_of_bytes(ref id in "([0-9a-f]{2})*") {
327            prop_assume!(id.len() != BLOB_ID_SIZE * 2);
328            prop_assert_eq!(
329                id.parse::<BlobId>(),
330                Err(BlobIdParseError::InvalidLength(id.len() / 2))
331            );
332        }
333
334        #[test]
335        fn fidl_conversions_are_inverses(id: BlobId) {
336            let temp : fidl::BlobId = id.into();
337            prop_assert_eq!(
338                id,
339                BlobId::from(temp)
340            );
341        }
342    }
343
344    #[test]
345    fn try_from_slice_rejects_invalid_length() {
346        assert_matches!(
347            BlobId::try_from([0u8; BLOB_ID_SIZE - 1].as_slice()),
348            Err(BlobIdFromSliceError::InvalidLength(31))
349        );
350        assert_matches!(
351            BlobId::try_from([0u8; BLOB_ID_SIZE + 1].as_slice()),
352            Err(BlobIdFromSliceError::InvalidLength(33))
353        );
354    }
355
356    #[test]
357    fn try_from_slice_succeeds() {
358        let bytes = [1u8; 32];
359        assert_eq!(BlobId::try_from(bytes.as_slice()).unwrap().as_bytes(), bytes.as_slice());
360    }
361}
362
363#[cfg(test)]
364mod test_resolution_context {
365    use super::*;
366    use assert_matches::assert_matches;
367
368    #[test]
369    fn try_from_slice_succeeds() {
370        assert_eq!(
371            ResolutionContext::try_from([].as_slice()).unwrap(),
372            ResolutionContext { blob_id: None }
373        );
374
375        assert_eq!(
376            ResolutionContext::try_from([1u8; 32].as_slice()).unwrap(),
377            ResolutionContext { blob_id: Some([1u8; 32].into()) }
378        );
379    }
380
381    #[test]
382    fn try_from_slice_fails() {
383        assert_matches!(
384            ResolutionContext::try_from([1u8].as_slice()),
385            Err(ResolutionContextError::InvalidBlobId(_))
386        );
387    }
388
389    #[test]
390    fn into_vec() {
391        assert_eq!(Vec::from(ResolutionContext::new()), Vec::<u8>::new());
392
393        assert_eq!(Vec::from(ResolutionContext::from(BlobId::from([1u8; 32]))), vec![1u8; 32]);
394    }
395
396    #[test]
397    fn try_from_fidl_succeeds() {
398        assert_eq!(
399            ResolutionContext::try_from(&fidl::ResolutionContext { bytes: vec![] }).unwrap(),
400            ResolutionContext { blob_id: None }
401        );
402
403        assert_eq!(
404            ResolutionContext::try_from(&fidl::ResolutionContext { bytes: vec![1u8; 32] }).unwrap(),
405            ResolutionContext { blob_id: Some([1u8; 32].into()) }
406        );
407    }
408
409    #[test]
410    fn try_from_fidl_fails() {
411        assert_matches!(
412            ResolutionContext::try_from(&fidl::ResolutionContext { bytes: vec![1u8; 1] }),
413            Err(ResolutionContextError::InvalidBlobId(_))
414        );
415    }
416
417    #[test]
418    fn into_fidl() {
419        assert_eq!(
420            fidl::ResolutionContext::from(ResolutionContext::new()),
421            fidl::ResolutionContext { bytes: vec![] }
422        );
423
424        assert_eq!(
425            fidl::ResolutionContext::from(ResolutionContext::from(BlobId::from([1u8; 32]))),
426            fidl::ResolutionContext { bytes: vec![1u8; 32] }
427        );
428    }
429}