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}