use std::fmt;
use std::io::{self, Write};
use base64;
use serde::{
de::{Deserialize, Deserializer, Error},
ser::{Serialize, Serializer},
};
use super::*;
#[derive(Debug, PartialEq, Eq, Clone, PartialOrd, Ord, Hash)]
pub enum Multihash {
Message([u8; 32]),
Blob([u8; 32]),
}
enum Target {
Message,
Blob,
}
impl Multihash {
pub fn from_legacy(mut s: &[u8]) -> Result<(Multihash, &[u8]), DecodeLegacyError> {
let target;
if let Some(tail) = skip_prefix(s, b"%") {
s = tail;
target = Target::Message;
} else {
let tail = skip_prefix(s, b"&").ok_or_else(|| DecodeLegacyError::Sigil)?;
s = tail;
target = Target::Blob;
}
let (data, suffix) = split_at_byte(s, 0x2E).ok_or_else(|| DecodeLegacyError::NoDot)?;
let tail =
skip_prefix(suffix, SHA256_SUFFIX).ok_or_else(|| DecodeLegacyError::UnknownSuffix)?;
if data.len() != SHA256_BASE64_LEN {
return Err(DecodeLegacyError::Sha256WrongSize);
}
if data[SHA256_BASE64_LEN - 2] == b"="[0] {
return Err(DecodeLegacyError::Sha256WrongSize);
}
if data[SHA256_BASE64_LEN - 1] != b"="[0] {
return Err(DecodeLegacyError::Sha256WrongSize);
}
let mut dec_data = [0u8; 32];
base64::decode_config_slice(data, base64::STANDARD, &mut dec_data[..])
.map_err(|e| DecodeLegacyError::InvalidBase64(e))
.map(|_| {
let multihash = match target {
Target::Blob => Multihash::Blob(dec_data),
Target::Message => Multihash::Message(dec_data),
};
(multihash, tail)
})
}
pub fn to_legacy<W: Write>(&self, w: &mut W) -> Result<(), io::Error> {
match self {
Multihash::Message(ref bytes) => {
w.write_all(b"%")?;
Multihash::write_legacy_hash_and_suffix(bytes, w)
}
Multihash::Blob(ref bytes) => {
w.write_all(b"&")?;
Multihash::write_legacy_hash_and_suffix(bytes, w)
}
}
}
fn write_legacy_hash_and_suffix<W: Write>(bytes: &[u8], w: &mut W) -> Result<(), io::Error> {
let data = base64::encode_config(bytes, base64::STANDARD);
w.write_all(data.as_bytes())?;
w.write_all(b".")?;
w.write_all(SHA256_SUFFIX)
}
pub fn to_legacy_vec(&self) -> Vec<u8> {
let mut out = Vec::with_capacity(SSB_SHA256_ENCODED_LEN);
self.to_legacy(&mut out).unwrap();
out
}
pub fn to_legacy_string(&self) -> String {
unsafe { String::from_utf8_unchecked(self.to_legacy_vec()) }
}
}
impl Serialize for Multihash {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
serializer.serialize_str(&self.to_legacy_string())
}
}
impl<'de> Deserialize<'de> for Multihash {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
let s = String::deserialize(deserializer)?;
Multihash::from_legacy(&s.as_bytes())
.map(|(mh, _)| mh)
.map_err(|err| D::Error::custom(format!("Invalid multihash: {}", err)))
}
}
#[derive(Debug, PartialEq, Eq, Clone)]
pub enum DecodeLegacyError {
Sigil,
NoDot,
InvalidBase64(base64::DecodeError),
UnknownSuffix,
Sha256WrongSize,
}
impl fmt::Display for DecodeLegacyError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
&DecodeLegacyError::Sigil => write!(f, "Invalid sigil"),
&DecodeLegacyError::InvalidBase64(ref err) => write!(f, "{}", err),
&DecodeLegacyError::NoDot => write!(f, "No dot"),
&DecodeLegacyError::UnknownSuffix => write!(f, "Unknown suffix"),
&DecodeLegacyError::Sha256WrongSize => write!(f, "Data of wrong length"),
}
}
}
impl std::error::Error for DecodeLegacyError {}
const SHA256_SUFFIX: &'static [u8] = b"sha256";
const SHA256_BASE64_LEN: usize = 44;
const SSB_SHA256_ENCODED_LEN: usize = SHA256_BASE64_LEN + 9;
#[test]
fn test_from_legacy() {
assert!(
Multihash::from_legacy(b"%MwjdLV95P7VqHfrgS49nScXsyIwJfL229e5OSKc+0rc=.sha256").is_ok()
);
assert!(
Multihash::from_legacy(b"&MwjdLV95P7VqHfrgS49nScXsyIwJfL229e5OSKc+0rc=.sha256").is_ok()
);
assert!(
Multihash::from_legacy(b"%MwjdLV95P7VqHfrgS49nScXsyIwJfL229e5OSKc+0rd=.sha256").is_err()
);
assert!(
Multihash::from_legacy(b"@MwjdLV95P7VqHfrgS49nScXsyIwJfL229e5OSKc+0rc=.sha256").is_err()
);
assert!(
Multihash::from_legacy(b"%MwjdLV95P7VqHfrgS49nScXsyIwJfL229e5OSKc+0rc=.tha256").is_err()
);
assert!(
Multihash::from_legacy(b"%MwjdLV95P7VqHfrgS49nScXsyIwJfL229e5OSKc+0rc=sha256").is_err()
);
assert!(
Multihash::from_legacy(b"%MwjdLV95P7VqHfrgS49nScXsyIwJfL229e5OSKc+0rc.sha256").is_err()
);
assert!(
Multihash::from_legacy(b"%MwjdLV95P7VqHfrgS49nScXsyIwJfL229e5OSKc+0rc==.sha256").is_err()
);
}