1use crate::errors::*;
2use sha2::{Digest, Sha256};
3use tokio::io::{AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt};
4
5pub type HashLength = u16;
6pub type DataLength = u64;
7
8#[derive(Debug, PartialEq)]
9pub struct CryptoHash(pub String);
10
11impl CryptoHash {
12 pub fn as_str(&self) -> &str {
13 &self.0
14 }
15
16 #[inline]
17 fn split_marker(bytes: &[u8]) -> Result<(&[u8], &[u8])> {
18 let offset = memchr::memchr(b':', bytes).context("Failed to find hash id marker `:`")?;
19 let (hash_id, hash_data) = bytes.split_at(offset + 1);
20 Ok((hash_id, hash_data))
21 }
22
23 pub fn decode(bytes: &[u8]) -> Result<Self> {
24 let (hash_id, hash_data) = Self::split_marker(bytes)?;
26
27 let mut hash = hash_id.to_owned();
29 hash.resize(hash.len() + hash_data.len() * 2, 0u8);
30
31 hex::encode_to_slice(hash_data, &mut hash[hash_id.len()..])
33 .context("Failed to encode header hash into buffer")?;
34
35 let hash = String::from_utf8(hash).context("Decoded crypto hash is invalid utf8")?;
37 Ok(CryptoHash(hash))
38 }
39
40 pub fn encode(&self) -> Result<Vec<u8>> {
41 let (hash_id, hash_data) = Self::split_marker(self.0.as_bytes())?;
43
44 let mut hash = hash_id.to_owned();
46 hash.resize(hash.len() + hash_data.len().div_ceil(2), 0u8);
47
48 hex::decode_to_slice(hash_data, &mut hash[hash_id.len()..])
50 .context("Failed to decode header hash into buffer")?;
51
52 Ok(hash)
53 }
54
55 pub fn calculate(bytes: &[u8]) -> Self {
56 let mut hasher = Sha256::new();
57 hasher.update(bytes);
58 let result = hasher.finalize();
59 CryptoHash(format!("sha256:{result:x}"))
60 }
61}
62
63#[derive(Debug, PartialEq)]
64pub struct BlockHeader {
65 pub hash: CryptoHash,
66 pub length: u64,
67}
68
69impl BlockHeader {
70 pub fn new(hash: CryptoHash, length: usize) -> Self {
71 Self {
72 hash,
73 length: length as u64,
74 }
75 }
76
77 pub async fn parse<R: AsyncRead + Unpin>(mut reader: R) -> Result<(Self, usize)> {
78 let mut n = 0;
79
80 let mut hash_length_bytes = [0u8; HashLength::BITS as usize / 8];
82 n += reader
83 .read_exact(&mut hash_length_bytes)
84 .await
85 .context("Failed to read hash length")?;
86 let hash_length = HashLength::from_be_bytes(hash_length_bytes);
87
88 let mut hash_bytes = vec![0u8; hash_length as usize];
90 n += reader
91 .read_exact(&mut hash_bytes)
92 .await
93 .context("Failed to read hash bytes")?;
94 let hash = CryptoHash::decode(&hash_bytes)?;
95
96 let mut data_length_bytes = [0u8; DataLength::BITS as usize / 8];
98 n += reader
99 .read_exact(&mut data_length_bytes)
100 .await
101 .context("Failed to read data length")?;
102 let data_length = DataLength::from_be_bytes(data_length_bytes);
103
104 let header = BlockHeader {
105 hash,
106 length: data_length,
107 };
108 trace!("Parsed block header: {header:?}");
109 Ok((header, n))
110 }
111
112 pub async fn write<W: AsyncWrite + Unpin>(&self, mut writer: W) -> Result<usize> {
113 let mut n = 0;
114
115 let encoded = self.hash.encode()?;
116 let hash_length_bytes = HashLength::to_be_bytes(encoded.len() as u16);
117 writer.write_all(&hash_length_bytes).await?;
118 n += hash_length_bytes.len();
119
120 writer.write_all(&encoded).await?;
121 n += encoded.len();
122
123 let data_length_bytes = DataLength::to_be_bytes(self.length);
124 writer.write_all(&data_length_bytes).await?;
125 n += data_length_bytes.len();
126
127 Ok(n)
128 }
129}
130
131#[cfg(test)]
132mod tests {
133 use super::*;
134
135 #[tokio::test]
136 async fn test_parse_header() {
137 let mut bytes = Vec::<u8>::new();
138 bytes.extend(39u16.to_be_bytes());
139 bytes.extend(b"sha256:");
140 bytes.extend(&[
141 0xe8, 0x47, 0x12, 0x23, 0x87, 0x09, 0x39, 0x8f, 0x6d, 0x34, 0x9d, 0xc2, 0x25, 0x0b,
142 0x0e, 0xfc, 0xa4, 0xb7, 0x2d, 0x8c, 0x2b, 0xfb, 0x7b, 0x74, 0x33, 0x9d, 0x30, 0xba,
143 0x94, 0x05, 0x6b, 0x14,
144 ]);
145 bytes.extend(4u64.to_be_bytes());
146 let (header, bytes_read) = BlockHeader::parse(&bytes[..]).await.unwrap();
149 assert_eq!(
150 header,
151 BlockHeader {
152 hash: CryptoHash(
153 "sha256:e84712238709398f6d349dc2250b0efca4b72d8c2bfb7b74339d30ba94056b14"
154 .to_string()
155 ),
156 length: 4,
157 }
158 );
159 assert_eq!(bytes_read, 49);
160 }
161
162 #[tokio::test]
163 async fn test_write_header() {
164 let header = BlockHeader {
165 hash: CryptoHash(
166 "sha256:e84712238709398f6d349dc2250b0efca4b72d8c2bfb7b74339d30ba94056b14"
167 .to_string(),
168 ),
169 length: 4,
170 };
171 let mut buf = Vec::new();
172 header.write(&mut buf).await.unwrap();
173
174 let mut expected = Vec::<u8>::new();
175 expected.extend(39u16.to_be_bytes());
176 expected.extend(b"sha256:");
177 expected.extend(&[
178 0xe8, 0x47, 0x12, 0x23, 0x87, 0x09, 0x39, 0x8f, 0x6d, 0x34, 0x9d, 0xc2, 0x25, 0x0b,
179 0x0e, 0xfc, 0xa4, 0xb7, 0x2d, 0x8c, 0x2b, 0xfb, 0x7b, 0x74, 0x33, 0x9d, 0x30, 0xba,
180 0x94, 0x05, 0x6b, 0x14,
181 ]);
182 expected.extend(4u64.to_be_bytes());
183
184 assert_eq!(buf, expected);
185 }
186
187 #[test]
188 fn test_hash_decode_encode() {
189 let mut bytes = Vec::<u8>::new();
190 bytes.extend(b"sha256:");
191 bytes.extend(&[
192 0xe8, 0x47, 0x12, 0x23, 0x87, 0x09, 0x39, 0x8f, 0x6d, 0x34, 0x9d, 0xc2, 0x25, 0x0b,
193 0x0e, 0xfc, 0xa4, 0xb7, 0x2d, 0x8c, 0x2b, 0xfb, 0x7b, 0x74, 0x33, 0x9d, 0x30, 0xba,
194 0x94, 0x05, 0x6b, 0x14,
195 ]);
196
197 let hash = CryptoHash::decode(&bytes).unwrap();
198 assert_eq!(
199 hash,
200 CryptoHash(
201 "sha256:e84712238709398f6d349dc2250b0efca4b72d8c2bfb7b74339d30ba94056b14"
202 .to_string()
203 )
204 );
205
206 let encoded = hash.encode().unwrap();
207 assert_eq!(encoded, bytes);
208 }
209}