blot/seal.rs
1// Copyright 2018 Arnau Siches
2//
3// Licensed under the MIT license <LICENSE or http://opensource.org/licenses/MIT>.
4// This file may not be copied, modified, or distributed except according to
5// those terms.
6
7//! Sealed digest multihash.
8//!
9//! Type [`Seal`] represents a sealed digest multihash.
10
11use core::Blot;
12use hex::{FromHex, FromHexError};
13use multihash::{Harvest, Multihash};
14use uvar::{Uvar, UvarError};
15
16#[derive(Debug)]
17pub enum SealError {
18    InvalidStamp { actual: Uvar, expected: Uvar },
19    NotRedacted,
20    DigestTooShort,
21    UnexpectedLength { actual: u8, expected: u8 },
22    UvarParseError(UvarError),
23    HexError(FromHexError),
24}
25
26impl From<UvarError> for SealError {
27    fn from(err: UvarError) -> SealError {
28        SealError::UvarParseError(err)
29    }
30}
31
32impl From<FromHexError> for SealError {
33    fn from(err: FromHexError) -> SealError {
34        SealError::HexError(err)
35    }
36}
37
38/// 0x77 is equivalent to the original `**REDACTED**` mark.
39pub const SEAL_MARK: u8 = 0x77;
40
41/// The `Seal` type. See [the module level documentation](index.html) for more.
42#[derive(Clone, Debug, PartialEq)]
43pub struct Seal<T: Multihash> {
44    tag: T,
45    digest: Vec<u8>,
46}
47
48impl<T: Multihash> Seal<T> {
49    pub fn digest(&self) -> &[u8] {
50        &self.digest
51    }
52
53    pub fn tag(&self) -> &T {
54        &self.tag
55    }
56
57    pub fn digest_hex(&self) -> String {
58        let mut result = String::new();
59
60        for byte in &self.digest {
61            result.push_str(&format!("{:02x}", byte));
62        }
63
64        result
65    }
66
67    /// Creates a `Seal` from a string. The string must have either the Objecthash prefix
68    /// `**REDACTED**` or the blot [`SEAL_MARK`].
69    ///
70    /// You can use [`from_bytes`] if you have a list of bytes.
71    ///
72    /// # Examples
73    ///
74    /// ```
75    /// # extern crate blot;
76    /// use blot::seal::Seal;
77    /// use blot::multihash::{Multihash, Sha2256};
78    ///
79    /// let seal_classic: Result<Seal<Sha2256>, _> = Seal::from_str("**REDACTED**1220a6a6e5e783c363cd95693ec189c2682315d956869397738679b56305f2095038");
80    /// let seal: Result<Seal<Sha2256>, _> = Seal::from_str("771220a6a6e5e783c363cd95693ec189c2682315d956869397738679b56305f2095038");
81    ///
82    /// assert!(seal_classic.is_ok());
83    /// assert!(seal.is_ok());
84    /// assert_eq!(seal.unwrap(), seal_classic.unwrap());
85    /// ```
86    pub fn from_str(input: &str) -> Result<Seal<T>, SealError> {
87        let bare = if input.starts_with("**REDACTED**") {
88            input
89                .get(12..)
90                .expect("Expected a redacted hash starting with `**REDACTED**`")
91        } else if input.starts_with("77") {
92            input
93                .get(2..)
94                .expect("Expected a redacted hash starting with `0x77`")
95        } else {
96            return Err(SealError::NotRedacted);
97        };
98
99        let bytes = Vec::from_hex(bare)?;
100
101        Seal::from_bytes_without_mark(&bytes)
102    }
103
104    /// Creates a `Seal` from a list of bytes. The first byte must be the
105    /// [`SEAL_MARK`].
106    ///
107    /// You can use [`from_str`] if your redacted hash uses the original Objecthash `"**REDACTED**"` prefix.
108    ///
109    /// # Examples
110    ///
111    /// ```
112    /// # extern crate hex;
113    /// # extern crate blot;
114    /// use blot::seal::Seal;
115    /// use blot::multihash::{Multihash, Sha2256};
116    /// use hex::FromHex;
117    ///
118    /// let bytes = Vec::from_hex("771220a6a6e5e783c363cd95693ec189c2682315d956869397738679b56305f2095038").unwrap();
119    /// let seal: Result<Seal<Sha2256>, _> = Seal::from_bytes(&bytes);
120    ///
121    /// assert!(seal.is_ok());
122    /// ```
123    ///
124    /// # Errors
125    ///
126    /// This operation fails with [`SealError::NotRedacted`] if the first byte is not `0x77`, the
127    /// seal mark.
128    pub fn from_bytes(bytes: &[u8]) -> Result<Seal<T>, SealError> {
129        if bytes[0] != SEAL_MARK {
130            return Err(SealError::NotRedacted);
131        }
132
133        Seal::from_bytes_without_mark(&bytes[1..])
134    }
135
136    fn from_bytes_without_mark(bytes: &[u8]) -> Result<Seal<T>, SealError> {
137        let (code, rest) = Uvar::take(&bytes)?;
138        let tag = T::default();
139
140        if tag.code() != code {
141            return Err(SealError::InvalidStamp {
142                actual: code,
143                expected: tag.code(),
144            });
145        }
146
147        if rest.len() < 2 {
148            return Err(SealError::DigestTooShort);
149        }
150
151        let length = *&rest[0];
152        let digest = &rest[1..];
153
154        if length != tag.length() {
155            return Err(SealError::UnexpectedLength {
156                expected: tag.length(),
157                actual: length,
158            });
159        }
160
161        if digest.len() as u8 != length {
162            return Err(SealError::UnexpectedLength {
163                expected: tag.length(),
164                actual: digest.len() as u8,
165            });
166        }
167
168        Ok(Seal {
169            tag: tag,
170            digest: digest.into(),
171        })
172    }
173}
174
175impl<T: Multihash> Blot for Seal<T> {
176    fn blot<D: Multihash>(&self, _: &D) -> Harvest {
177        self.digest.clone().into_boxed_slice().into()
178    }
179}