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