1use std::{fs::File, io::Read, path::Path};
2
3pub use multihash_codetable::{Code, Sha3_256, Sha3_512};
4pub use multihash_derive::{Hasher, Multihash, MultihashDigest};
5
6use crate::framing;
7
8const MULTIHASH_CODE_SIZE: usize = 2;
9
10pub trait MultihashDigestExt<const S: usize>: Hasher + Default {
12 fn size() -> usize;
13
14 fn to_multihash(&mut self) -> Multihash<S>;
15
16 fn input_signed_frame<I: framing::FrameReader>(
17 &mut self,
18 frame: &framing::MultihashFrame<S, Self, I>,
19 ) {
20 self.update(frame.multihash_bytes());
21 }
22
23 fn multihash_size() -> usize {
24 MULTIHASH_CODE_SIZE + Self::size()
25 }
26
27 fn update_from_reader<R: Read>(&mut self, mut read: R) -> Result<Multihash<S>, std::io::Error> {
28 let mut bytes = Vec::new();
29 read.read_to_end(&mut bytes)?;
30
31 self.update(&bytes);
32 Ok(self.to_multihash())
33 }
34}
35
36impl MultihashDigestExt<32> for Sha3_256 {
37 fn to_multihash(&mut self) -> Multihash<32> {
38 let digest = self.finalize();
39 Multihash::wrap(0x16, digest).unwrap() }
41
42 fn size() -> usize {
43 32
44 }
45}
46
47impl MultihashDigestExt<64> for Sha3_512 {
48 fn to_multihash(&mut self) -> Multihash<64> {
49 let digest = self.finalize();
50 Multihash::wrap(0x14, digest).unwrap() }
52
53 fn size() -> usize {
54 64
55 }
56}
57
58pub trait MultihashExt {
59 fn encode_bs58(&self) -> String;
60}
61
62impl<const S: usize> MultihashExt for Multihash<S> {
63 fn encode_bs58(&self) -> String {
64 bs58::encode(self.to_bytes()).into_string()
65 }
66}
67
68pub fn multihash_decode_bs58<const S: usize>(str: &str) -> Result<Multihash<S>, HashError> {
69 let bytes = bs58::decode(str).into_vec()?;
70 let mh = Multihash::from_bytes(&bytes)?;
71 Ok(mh)
72}
73
74pub fn multihash_sha3_256_file<P: AsRef<Path>>(path: P) -> Result<Multihash<32>, HashError> {
75 let file = File::open(path.as_ref())?;
76 multihash_sha3_256(file)
77}
78
79pub fn multihash_sha3_256<R: Read>(reader: R) -> Result<Multihash<32>, HashError> {
80 let mut digest = Sha3_256::default();
81 let mh = digest.update_from_reader(reader)?;
82 Ok(mh)
83}
84
85#[derive(thiserror::Error, Debug)]
86pub enum HashError {
87 #[error("Base58 decoding error: {0}")]
88 Bs58(#[from] bs58::decode::Error),
89 #[error("Multihash error: {0}")]
90 Multihash(#[from] multihash::Error),
91 #[error("IO error: {0}")]
92 Io(#[from] std::io::Error),
93}
94
95#[cfg(test)]
96mod tests {
97 use std::io::{Seek, Write};
98
99 use tempfile::tempdir;
100
101 use super::*;
102
103 #[test]
104 fn test_to_multihash() {
105 let stateless = Code::Sha3_256.digest(b"Hello world");
106
107 let mut hasher = Sha3_256::default();
108 hasher.update(b"Hello world");
109 let stateful = hasher.to_multihash();
110
111 assert_eq!(stateful, stateless);
112 }
113
114 #[test]
115 fn multihash_from_reader() {
116 let stateless = Code::Sha3_256.digest(b"Hello world");
117
118 let mut file = tempfile::tempfile().unwrap();
119 file.write_all(b"Hello world").unwrap();
120 file.rewind().unwrap();
121
122 let mut hasher = Sha3_256::default();
123 hasher.update_from_reader(file).unwrap();
124 let file_hash = hasher.to_multihash();
125
126 assert_eq!(stateless, file_hash);
127 }
128
129 #[test]
130 fn bs58_encode_decode() {
131 let mh_init = Code::Sha3_256.digest(b"Hello world");
132
133 let bs58 = mh_init.encode_bs58();
134 let mh_decoded = multihash_decode_bs58::<32>(&bs58).unwrap();
135 assert_eq!(mh_init, mh_decoded);
136 }
137
138 #[test]
139 fn multihash_file() {
140 let dir = tempdir().unwrap();
141 let file_path = dir.path().join("file.txt");
142
143 {
144 let mut file = File::create(&file_path).unwrap();
145 file.write_all(b"Hello world").unwrap();
146 }
147
148 let file_hash = multihash_sha3_256_file(file_path).unwrap();
149 let hw_hash = Code::Sha3_256.digest(b"Hello world");
150
151 assert_eq!(hw_hash, file_hash);
152 }
153}