1use hashtree_core::Hash;
12use serde::{Deserialize, Serialize};
13
14fn default_htl() -> u8 {
15 crate::types::MAX_HTL
16}
17
18fn is_max_htl(htl: &u8) -> bool {
19 *htl == crate::types::MAX_HTL
20}
21
22pub const MSG_TYPE_REQUEST: u8 = 0x00;
24pub const MSG_TYPE_RESPONSE: u8 = 0x01;
25pub const MSG_TYPE_QUOTE_REQUEST: u8 = 0x02;
26pub const MSG_TYPE_QUOTE_RESPONSE: u8 = 0x03;
27
28pub const FRAGMENT_SIZE: usize = 32 * 1024;
30
31#[derive(Debug, Clone, Serialize, Deserialize)]
33pub struct DataRequest {
34 #[serde(with = "serde_bytes")]
36 pub h: Vec<u8>,
37 #[serde(default = "default_htl", skip_serializing_if = "is_max_htl")]
39 pub htl: u8,
40 #[serde(skip_serializing_if = "Option::is_none")]
42 pub q: Option<u64>,
43}
44
45#[derive(Debug, Clone, Serialize, Deserialize)]
47pub struct DataResponse {
48 #[serde(with = "serde_bytes")]
50 pub h: Vec<u8>,
51 #[serde(with = "serde_bytes")]
53 pub d: Vec<u8>,
54 #[serde(skip_serializing_if = "Option::is_none")]
56 pub i: Option<u32>,
57 #[serde(skip_serializing_if = "Option::is_none")]
59 pub n: Option<u32>,
60}
61
62#[derive(Debug, Clone, Serialize, Deserialize)]
64pub struct DataQuoteRequest {
65 #[serde(with = "serde_bytes")]
67 pub h: Vec<u8>,
68 pub p: u64,
70 pub t: u32,
72 #[serde(skip_serializing_if = "Option::is_none")]
74 pub m: Option<String>,
75}
76
77#[derive(Debug, Clone, Serialize, Deserialize)]
79pub struct DataQuoteResponse {
80 #[serde(with = "serde_bytes")]
82 pub h: Vec<u8>,
83 pub a: bool,
85 #[serde(skip_serializing_if = "Option::is_none")]
87 pub q: Option<u64>,
88 #[serde(skip_serializing_if = "Option::is_none")]
90 pub p: Option<u64>,
91 #[serde(skip_serializing_if = "Option::is_none")]
93 pub t: Option<u32>,
94 #[serde(skip_serializing_if = "Option::is_none")]
96 pub m: Option<String>,
97}
98
99#[derive(Debug, Clone)]
101pub enum DataMessage {
102 Request(DataRequest),
103 Response(DataResponse),
104 QuoteRequest(DataQuoteRequest),
105 QuoteResponse(DataQuoteResponse),
106}
107
108pub fn encode_request(req: &DataRequest) -> Vec<u8> {
111 let body = rmp_serde::to_vec_named(req).expect("Failed to encode request");
112 let mut result = Vec::with_capacity(1 + body.len());
113 result.push(MSG_TYPE_REQUEST);
114 result.extend(body);
115 result
116}
117
118pub fn encode_response(res: &DataResponse) -> Vec<u8> {
121 let body = rmp_serde::to_vec_named(res).expect("Failed to encode response");
122 let mut result = Vec::with_capacity(1 + body.len());
123 result.push(MSG_TYPE_RESPONSE);
124 result.extend(body);
125 result
126}
127
128pub fn encode_quote_request(req: &DataQuoteRequest) -> Vec<u8> {
130 let body = rmp_serde::to_vec_named(req).expect("Failed to encode quote request");
131 let mut result = Vec::with_capacity(1 + body.len());
132 result.push(MSG_TYPE_QUOTE_REQUEST);
133 result.extend(body);
134 result
135}
136
137pub fn encode_quote_response(res: &DataQuoteResponse) -> Vec<u8> {
139 let body = rmp_serde::to_vec_named(res).expect("Failed to encode quote response");
140 let mut result = Vec::with_capacity(1 + body.len());
141 result.push(MSG_TYPE_QUOTE_RESPONSE);
142 result.extend(body);
143 result
144}
145
146pub fn parse_message(data: &[u8]) -> Option<DataMessage> {
148 if data.len() < 2 {
149 return None;
150 }
151
152 let msg_type = data[0];
153 let body = &data[1..];
154
155 match msg_type {
156 MSG_TYPE_REQUEST => rmp_serde::from_slice::<DataRequest>(body)
157 .ok()
158 .map(DataMessage::Request),
159 MSG_TYPE_RESPONSE => rmp_serde::from_slice::<DataResponse>(body)
160 .ok()
161 .map(DataMessage::Response),
162 MSG_TYPE_QUOTE_REQUEST => rmp_serde::from_slice::<DataQuoteRequest>(body)
163 .ok()
164 .map(DataMessage::QuoteRequest),
165 MSG_TYPE_QUOTE_RESPONSE => rmp_serde::from_slice::<DataQuoteResponse>(body)
166 .ok()
167 .map(DataMessage::QuoteResponse),
168 _ => None,
169 }
170}
171
172pub fn create_request(hash: &Hash, htl: u8) -> DataRequest {
174 DataRequest {
175 h: hash.to_vec(),
176 htl,
177 q: None,
178 }
179}
180
181pub fn create_request_with_quote(hash: &Hash, htl: u8, quote_id: u64) -> DataRequest {
183 DataRequest {
184 h: hash.to_vec(),
185 htl,
186 q: Some(quote_id),
187 }
188}
189
190pub fn create_response(hash: &Hash, data: Vec<u8>) -> DataResponse {
192 DataResponse {
193 h: hash.to_vec(),
194 d: data,
195 i: None,
196 n: None,
197 }
198}
199
200pub fn create_quote_request(
202 hash: &Hash,
203 ttl_ms: u32,
204 payment_sat: u64,
205 mint_url: Option<&str>,
206) -> DataQuoteRequest {
207 DataQuoteRequest {
208 h: hash.to_vec(),
209 p: payment_sat,
210 t: ttl_ms,
211 m: mint_url.map(str::to_string),
212 }
213}
214
215pub fn create_quote_response_available(
217 hash: &Hash,
218 quote_id: u64,
219 payment_sat: u64,
220 ttl_ms: u32,
221 mint_url: Option<&str>,
222) -> DataQuoteResponse {
223 DataQuoteResponse {
224 h: hash.to_vec(),
225 a: true,
226 q: Some(quote_id),
227 p: Some(payment_sat),
228 t: Some(ttl_ms),
229 m: mint_url.map(str::to_string),
230 }
231}
232
233pub fn create_quote_response_unavailable(hash: &Hash) -> DataQuoteResponse {
235 DataQuoteResponse {
236 h: hash.to_vec(),
237 a: false,
238 q: None,
239 p: None,
240 t: None,
241 m: None,
242 }
243}
244
245pub fn create_fragment_response(
247 hash: &Hash,
248 data: Vec<u8>,
249 index: u32,
250 total: u32,
251) -> DataResponse {
252 DataResponse {
253 h: hash.to_vec(),
254 d: data,
255 i: Some(index),
256 n: Some(total),
257 }
258}
259
260pub fn is_fragmented(res: &DataResponse) -> bool {
262 res.i.is_some() && res.n.is_some()
263}
264
265pub fn hash_to_key(hash: &[u8]) -> String {
267 hex::encode(hash)
268}
269
270pub fn hash_to_bytes(hash: &Hash) -> Vec<u8> {
272 hash.to_vec()
273}
274
275pub fn bytes_to_hash(bytes: &[u8]) -> Option<Hash> {
277 if bytes.len() == 32 {
278 let mut hash = [0u8; 32];
279 hash.copy_from_slice(bytes);
280 Some(hash)
281 } else {
282 None
283 }
284}
285
286#[cfg(test)]
287mod tests {
288 use super::*;
289
290 #[test]
291 fn test_encode_decode_request() {
292 let hash = [0xab; 32];
293 let req = create_request(&hash, 10);
294 let encoded = encode_request(&req);
295
296 assert_eq!(encoded[0], MSG_TYPE_REQUEST);
297
298 let parsed = parse_message(&encoded).unwrap();
299 match parsed {
300 DataMessage::Request(r) => {
301 assert_eq!(r.h, hash.to_vec());
302 assert_eq!(r.htl, 10);
303 }
304 _ => panic!("Expected request"),
305 }
306 }
307
308 #[test]
309 fn test_decode_request_without_explicit_htl_defaults_to_max() {
310 #[derive(Serialize)]
311 struct LegacyRequest {
312 #[serde(with = "serde_bytes")]
313 h: Vec<u8>,
314 }
315
316 let hash = [0x21; 32];
317 let body = rmp_serde::to_vec_named(&LegacyRequest { h: hash.to_vec() }).unwrap();
318 let mut encoded = vec![MSG_TYPE_REQUEST];
319 encoded.extend(body);
320
321 let parsed = parse_message(&encoded).unwrap();
322 match parsed {
323 DataMessage::Request(r) => {
324 assert_eq!(r.h, hash.to_vec());
325 assert_eq!(r.htl, crate::types::MAX_HTL);
326 }
327 _ => panic!("Expected request"),
328 }
329 }
330
331 #[test]
332 fn test_encode_decode_response() {
333 let hash = [0xcd; 32];
334 let data = vec![1, 2, 3, 4, 5];
335 let res = create_response(&hash, data.clone());
336 let encoded = encode_response(&res);
337
338 assert_eq!(encoded[0], MSG_TYPE_RESPONSE);
339
340 let parsed = parse_message(&encoded).unwrap();
341 match parsed {
342 DataMessage::Response(r) => {
343 assert_eq!(r.h, hash.to_vec());
344 assert_eq!(r.d, data);
345 assert!(!is_fragmented(&r));
346 }
347 _ => panic!("Expected response"),
348 }
349 }
350
351 #[test]
352 fn test_encode_decode_fragment_response() {
353 let hash = [0xef; 32];
354 let data = vec![10, 20, 30];
355 let res = create_fragment_response(&hash, data.clone(), 2, 5);
356 let encoded = encode_response(&res);
357
358 let parsed = parse_message(&encoded).unwrap();
359 match parsed {
360 DataMessage::Response(r) => {
361 assert_eq!(r.h, hash.to_vec());
362 assert_eq!(r.d, data);
363 assert!(is_fragmented(&r));
364 assert_eq!(r.i, Some(2));
365 assert_eq!(r.n, Some(5));
366 }
367 _ => panic!("Expected response"),
368 }
369 }
370
371 #[test]
372 fn test_encode_decode_quote_request() {
373 let hash = [0x44; 32];
374 let req = create_quote_request(&hash, 7, 2_500, Some("https://mint.example"));
375 let encoded = encode_quote_request(&req);
376
377 assert_eq!(encoded[0], MSG_TYPE_QUOTE_REQUEST);
378
379 let parsed = parse_message(&encoded).unwrap();
380 match parsed {
381 DataMessage::QuoteRequest(r) => {
382 assert_eq!(r.h, hash.to_vec());
383 assert_eq!(r.t, 7);
384 assert_eq!(r.p, 2_500);
385 assert_eq!(r.m.as_deref(), Some("https://mint.example"));
386 }
387 _ => panic!("Expected quote request"),
388 }
389 }
390
391 #[test]
392 fn test_encode_decode_quote_response_and_quoted_request() {
393 let hash = [0x55; 32];
394 let quote =
395 create_quote_response_available(&hash, 19, 2_500, 7, Some("https://mint.example"));
396 let encoded_quote = encode_quote_response("e);
397
398 assert_eq!(encoded_quote[0], MSG_TYPE_QUOTE_RESPONSE);
399
400 let parsed_quote = parse_message(&encoded_quote).unwrap();
401 match parsed_quote {
402 DataMessage::QuoteResponse(r) => {
403 assert_eq!(r.h, hash.to_vec());
404 assert!(r.a);
405 assert_eq!(r.q, Some(19));
406 assert_eq!(r.p, Some(2_500));
407 assert_eq!(r.t, Some(7));
408 assert_eq!(r.m.as_deref(), Some("https://mint.example"));
409 }
410 _ => panic!("Expected quote response"),
411 }
412
413 let req = create_request_with_quote(&hash, 9, 19);
414 let encoded_req = encode_request(&req);
415 let parsed_req = parse_message(&encoded_req).unwrap();
416 match parsed_req {
417 DataMessage::Request(r) => {
418 assert_eq!(r.h, hash.to_vec());
419 assert_eq!(r.htl, 9);
420 assert_eq!(r.q, Some(19));
421 }
422 _ => panic!("Expected quoted request"),
423 }
424 }
425
426 #[test]
427 fn test_hash_conversions() {
428 let hash = [0x12; 32];
429 let bytes = hash_to_bytes(&hash);
430 let back = bytes_to_hash(&bytes).unwrap();
431 assert_eq!(hash, back);
432 }
433}