apt_swarm/db/
header.rs

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        // determine the `sha256:` and binary boundary
25        let (hash_id, hash_data) = Self::split_marker(bytes)?;
26
27        // allocate memory for our decoded hash
28        let mut hash = hash_id.to_owned();
29        hash.resize(hash.len() + hash_data.len() * 2, 0u8);
30
31        // decode binary to hex
32        hex::encode_to_slice(hash_data, &mut hash[hash_id.len()..])
33            .context("Failed to encode header hash into buffer")?;
34
35        // ensure everything is utf8 and return
36        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        // determine the `sha256:` and hex boundary
42        let (hash_id, hash_data) = Self::split_marker(self.0.as_bytes())?;
43
44        // allocate memory for our encoded hash
45        let mut hash = hash_id.to_owned();
46        hash.resize(hash.len() + hash_data.len().div_ceil(2), 0u8);
47
48        // encode binary to hex
49        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        // read hash length field
81        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        // read hash bytes
89        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        // read data length field
97        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        // data is not part of the header
147        // bytes.extend(b"ohai");
148        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}