ssb_multiformats/
multihash.rs1use std::fmt;
3use std::io::{self, Write};
4
5use serde::{
6 de::{Deserialize, Deserializer, Error},
7 ser::{Serialize, Serializer},
8};
9
10use super::{base64, serde, skip_prefix, split_at_byte};
11
12#[derive(Debug, PartialEq, Eq, Clone, PartialOrd, Ord, Hash)]
14pub enum Multihash {
15 Message([u8; 32]),
17 Blob([u8; 32]),
19}
20
21enum Target {
22 Message,
23 Blob,
24}
25
26impl Multihash {
27 pub fn from_legacy(mut s: &[u8]) -> Result<(Multihash, &[u8]), DecodeLegacyError> {
31 let target;
32
33 if let Some(tail) = skip_prefix(s, b"%") {
34 s = tail;
35 target = Target::Message;
36 } else {
37 let tail = skip_prefix(s, b"&").ok_or(DecodeLegacyError::Sigil)?;
38
39 s = tail;
40 target = Target::Blob;
41 }
42
43 let (data, suffix) = split_at_byte(s, 0x2E).ok_or(DecodeLegacyError::NoDot)?;
44
45 let tail = skip_prefix(suffix, SHA256_SUFFIX).ok_or(DecodeLegacyError::UnknownSuffix)?;
46
47 if data.len() != SHA256_BASE64_LEN {
48 return Err(DecodeLegacyError::Sha256WrongSize);
49 }
50
51 if data[SHA256_BASE64_LEN - 2] == b"="[0] {
52 return Err(DecodeLegacyError::Sha256WrongSize);
53 }
54
55 if data[SHA256_BASE64_LEN - 1] != b"="[0] {
56 return Err(DecodeLegacyError::Sha256WrongSize);
57 }
58
59 let mut dec_data = [0_u8; 32];
60 base64::decode_config_slice(data, base64::STANDARD, &mut dec_data[..])
61 .map_err(DecodeLegacyError::InvalidBase64)
62 .map(|_| {
63 let multihash = match target {
64 Target::Blob => Multihash::Blob(dec_data),
65 Target::Message => Multihash::Message(dec_data),
66 };
67 (multihash, tail)
68 })
69 }
70
71 pub fn to_legacy<W: Write>(&self, w: &mut W) -> Result<(), io::Error> {
74 match self {
75 Multihash::Message(ref bytes) => {
76 w.write_all(b"%")?;
77 Multihash::write_legacy_hash_and_suffix(bytes, w)
78 }
79 Multihash::Blob(ref bytes) => {
80 w.write_all(b"&")?;
81 Multihash::write_legacy_hash_and_suffix(bytes, w)
82 }
83 }
84 }
85
86 fn write_legacy_hash_and_suffix<W: Write>(bytes: &[u8], w: &mut W) -> Result<(), io::Error> {
87 let data = base64::encode_config(bytes, base64::STANDARD);
88 w.write_all(data.as_bytes())?;
89
90 w.write_all(b".")?;
91 w.write_all(SHA256_SUFFIX)
92 }
93
94 pub fn to_legacy_vec(&self) -> Vec<u8> {
97 let mut out = Vec::with_capacity(SSB_SHA256_ENCODED_LEN);
98 self.to_legacy(&mut out).unwrap();
99 out
100 }
101
102 pub fn to_legacy_string(&self) -> String {
105 unsafe { String::from_utf8_unchecked(self.to_legacy_vec()) }
106 }
107}
108
109impl Serialize for Multihash {
110 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
111 where
112 S: Serializer,
113 {
114 serializer.serialize_str(&self.to_legacy_string())
115 }
116}
117
118impl<'de> Deserialize<'de> for Multihash {
119 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
120 where
121 D: Deserializer<'de>,
122 {
123 let s = String::deserialize(deserializer)?;
124 Multihash::from_legacy(s.as_bytes())
125 .map(|(mh, _)| mh)
126 .map_err(|err| D::Error::custom(format!("Invalid multihash: {}", err)))
127 }
128}
129
130#[derive(Debug, PartialEq, Eq, Clone)]
132pub enum DecodeLegacyError {
133 Sigil,
135 NoDot,
137 InvalidBase64(base64::DecodeError),
139 UnknownSuffix,
141 Sha256WrongSize,
143}
144
145impl fmt::Display for DecodeLegacyError {
146 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
147 match self {
148 DecodeLegacyError::Sigil => write!(f, "Invalid sigil"),
149 DecodeLegacyError::InvalidBase64(ref err) => write!(f, "{}", err),
150 DecodeLegacyError::NoDot => write!(f, "No dot"),
151 DecodeLegacyError::UnknownSuffix => write!(f, "Unknown suffix"),
152 DecodeLegacyError::Sha256WrongSize => write!(f, "Data of wrong length"),
153 }
154 }
155}
156
157impl std::error::Error for DecodeLegacyError {}
158
159const SHA256_SUFFIX: &[u8] = b"sha256";
161const SHA256_BASE64_LEN: usize = 44;
163const SSB_SHA256_ENCODED_LEN: usize = SHA256_BASE64_LEN + 9;
165
166#[test]
167fn test_from_legacy() {
168 assert!(
169 Multihash::from_legacy(b"%MwjdLV95P7VqHfrgS49nScXsyIwJfL229e5OSKc+0rc=.sha256").is_ok()
170 );
171 assert!(
172 Multihash::from_legacy(b"&MwjdLV95P7VqHfrgS49nScXsyIwJfL229e5OSKc+0rc=.sha256").is_ok()
173 );
174 assert!(
175 Multihash::from_legacy(b"%MwjdLV95P7VqHfrgS49nScXsyIwJfL229e5OSKc+0rd=.sha256").is_err()
176 );
177 assert!(
178 Multihash::from_legacy(b"@MwjdLV95P7VqHfrgS49nScXsyIwJfL229e5OSKc+0rc=.sha256").is_err()
179 );
180 assert!(
181 Multihash::from_legacy(b"%MwjdLV95P7VqHfrgS49nScXsyIwJfL229e5OSKc+0rc=.tha256").is_err()
182 );
183 assert!(
184 Multihash::from_legacy(b"%MwjdLV95P7VqHfrgS49nScXsyIwJfL229e5OSKc+0rc=sha256").is_err()
185 );
186 assert!(
187 Multihash::from_legacy(b"%MwjdLV95P7VqHfrgS49nScXsyIwJfL229e5OSKc+0rc.sha256").is_err()
188 );
189 assert!(
190 Multihash::from_legacy(b"%MwjdLV95P7VqHfrgS49nScXsyIwJfL229e5OSKc+0rc==.sha256").is_err()
191 );
192}