hashtree_core/
blob_route.rs1use std::sync::Arc;
4
5use async_trait::async_trait;
6use thiserror::Error;
7
8use crate::{Hash, Store, StoreError};
9
10pub const BLOB_MAX_BYTES: usize = 16 * 1024 * 1024;
11pub const BLOB_REQUEST_BYTES: usize = 36;
12pub const BLOB_REPLY_HEADER_BYTES: usize = 7;
13
14const MAGIC: u8 = 0x48;
15const VERSION: u8 = 1;
16const GET: u8 = 1;
17const DATA: u8 = 1;
18
19#[derive(Clone, Copy, Debug, PartialEq, Eq)]
20pub struct BlobRequest {
21 pub hash: Hash,
22 pub htl: u8,
23}
24
25#[derive(Clone, Debug, PartialEq, Eq)]
26pub enum BlobReply {
27 Data(Vec<u8>),
28 NoResult,
29}
30
31#[async_trait]
32pub trait BlobRoute: Send + Sync {
33 async fn route(&self, request: BlobRequest) -> Result<BlobReply, StoreError>;
34}
35
36pub struct StoreBlobRoute<S: Store + ?Sized> {
38 store: Arc<S>,
39}
40
41impl<S: Store + ?Sized> StoreBlobRoute<S> {
42 pub fn new(store: Arc<S>) -> Self {
43 Self { store }
44 }
45}
46
47#[async_trait]
48impl<S: Store + ?Sized + 'static> BlobRoute for StoreBlobRoute<S> {
49 async fn route(&self, request: BlobRequest) -> Result<BlobReply, StoreError> {
50 Ok(match self.store.get(&request.hash).await? {
51 Some(data) => BlobReply::Data(data),
52 None => BlobReply::NoResult,
53 })
54 }
55}
56
57#[derive(Clone, Copy, Debug, PartialEq, Eq)]
58pub enum BlobReplyHeader {
59 Data(usize),
60 NoResult,
61}
62
63#[derive(Debug, Error, PartialEq, Eq)]
64pub enum BlobCodecError {
65 #[error("invalid Hashtree blob wire message: {0}")]
66 Invalid(&'static str),
67 #[error("Hashtree blob size {0} exceeds the 16 MiB limit")]
68 BlobTooLarge(usize),
69}
70
71pub fn encode_blob_request(request: &BlobRequest) -> [u8; BLOB_REQUEST_BYTES] {
72 let mut bytes = [0; BLOB_REQUEST_BYTES];
73 bytes[..4].copy_from_slice(&[MAGIC, VERSION, GET, request.htl]);
74 bytes[4..].copy_from_slice(&request.hash);
75 bytes
76}
77
78pub fn decode_blob_request(bytes: &[u8]) -> Result<BlobRequest, BlobCodecError> {
79 if bytes.len() != BLOB_REQUEST_BYTES {
80 return Err(BlobCodecError::Invalid("request has the wrong length"));
81 }
82 if bytes[..3] != [MAGIC, VERSION, GET] {
83 return Err(BlobCodecError::Invalid(
84 "request has an unsupported prelude",
85 ));
86 }
87 let mut hash = [0; 32];
88 hash.copy_from_slice(&bytes[4..]);
89 Ok(BlobRequest {
90 hash,
91 htl: bytes[3],
92 })
93}
94
95pub fn encode_blob_reply_header(
96 reply: &BlobReply,
97) -> Result<[u8; BLOB_REPLY_HEADER_BYTES], BlobCodecError> {
98 let (status, size) = match reply {
99 BlobReply::NoResult => (0, 0),
100 BlobReply::Data(data) => (DATA, data.len()),
101 };
102 if size > BLOB_MAX_BYTES {
103 return Err(BlobCodecError::BlobTooLarge(size));
104 }
105 let mut header = [0; BLOB_REPLY_HEADER_BYTES];
106 header[..3].copy_from_slice(&[MAGIC, VERSION, status]);
107 header[3..].copy_from_slice(&(size as u32).to_be_bytes());
108 Ok(header)
109}
110
111pub fn decode_blob_reply_header(bytes: &[u8]) -> Result<BlobReplyHeader, BlobCodecError> {
112 if bytes.len() != BLOB_REPLY_HEADER_BYTES {
113 return Err(BlobCodecError::Invalid(
114 "response header has the wrong length",
115 ));
116 }
117 if bytes[0] != MAGIC || bytes[1] != VERSION {
118 return Err(BlobCodecError::Invalid(
119 "response has an unsupported prelude",
120 ));
121 }
122 let size = u32::from_be_bytes(bytes[3..].try_into().expect("four-byte length")) as usize;
123 if size > BLOB_MAX_BYTES {
124 return Err(BlobCodecError::BlobTooLarge(size));
125 }
126 match (bytes[2], size) {
127 (0, 0) => Ok(BlobReplyHeader::NoResult),
128 (0, _) => Err(BlobCodecError::Invalid(
129 "NoResult response has a non-zero payload length",
130 )),
131 (DATA, size) => Ok(BlobReplyHeader::Data(size)),
132 _ => Err(BlobCodecError::Invalid(
133 "response has an unsupported status",
134 )),
135 }
136}