1use hashtree_core::Hash;
16use serde::{Deserialize, Serialize};
17
18fn default_htl() -> u8 {
19 crate::types::MAX_HTL
20}
21
22fn is_max_htl(htl: &u8) -> bool {
23 *htl == crate::types::MAX_HTL
24}
25
26pub const MSG_TYPE_REQUEST: u8 = 0x00;
28pub const MSG_TYPE_RESPONSE: u8 = 0x01;
29pub const MSG_TYPE_QUOTE_REQUEST: u8 = 0x02;
30pub const MSG_TYPE_QUOTE_RESPONSE: u8 = 0x03;
31pub const MSG_TYPE_PAYMENT: u8 = 0x04;
32pub const MSG_TYPE_PAYMENT_ACK: u8 = 0x05;
33pub const MSG_TYPE_CHUNK: u8 = 0x06;
34pub const MSG_TYPE_PEER_HINTS: u8 = 0x07;
35
36pub const FRAGMENT_SIZE: usize = 32 * 1024;
38
39#[derive(Debug, Clone, Serialize, Deserialize)]
41pub struct DataRequest {
42 #[serde(with = "serde_bytes")]
44 pub h: Vec<u8>,
45 #[serde(default = "default_htl", skip_serializing_if = "is_max_htl")]
47 pub htl: u8,
48 #[serde(skip_serializing_if = "Option::is_none")]
50 pub q: Option<u64>,
51}
52
53#[derive(Debug, Clone, Serialize, Deserialize)]
55pub struct DataResponse {
56 #[serde(with = "serde_bytes")]
58 pub h: Vec<u8>,
59 #[serde(with = "serde_bytes")]
61 pub d: Vec<u8>,
62 #[serde(skip_serializing_if = "Option::is_none")]
64 pub i: Option<u32>,
65 #[serde(skip_serializing_if = "Option::is_none")]
67 pub n: Option<u32>,
68}
69
70#[derive(Debug, Clone, Serialize, Deserialize)]
72pub struct DataQuoteRequest {
73 #[serde(with = "serde_bytes")]
75 pub h: Vec<u8>,
76 pub p: u64,
78 pub t: u32,
80 #[serde(skip_serializing_if = "Option::is_none")]
82 pub m: Option<String>,
83}
84
85#[derive(Debug, Clone, Serialize, Deserialize)]
87pub struct DataQuoteResponse {
88 #[serde(with = "serde_bytes")]
90 pub h: Vec<u8>,
91 pub a: bool,
93 #[serde(skip_serializing_if = "Option::is_none")]
95 pub q: Option<u64>,
96 #[serde(skip_serializing_if = "Option::is_none")]
98 pub p: Option<u64>,
99 #[serde(skip_serializing_if = "Option::is_none")]
101 pub t: Option<u32>,
102 #[serde(skip_serializing_if = "Option::is_none")]
104 pub m: Option<String>,
105}
106
107#[derive(Debug, Clone, Serialize, Deserialize)]
109pub struct DataPayment {
110 #[serde(with = "serde_bytes")]
111 pub h: Vec<u8>,
112 pub q: u64,
113 pub c: u32,
114 pub p: u64,
115 #[serde(skip_serializing_if = "Option::is_none")]
116 pub m: Option<String>,
117 pub tok: String,
118}
119
120#[derive(Debug, Clone, Serialize, Deserialize)]
122pub struct DataPaymentAck {
123 #[serde(with = "serde_bytes")]
124 pub h: Vec<u8>,
125 pub q: u64,
126 pub c: u32,
127 pub a: bool,
128 #[serde(skip_serializing_if = "Option::is_none")]
129 pub e: Option<String>,
130}
131
132#[derive(Debug, Clone, Serialize, Deserialize)]
134pub struct DataChunk {
135 #[serde(with = "serde_bytes")]
136 pub h: Vec<u8>,
137 pub q: u64,
138 pub c: u32,
139 pub n: u32,
140 pub p: u64,
141 #[serde(with = "serde_bytes")]
142 pub d: Vec<u8>,
143}
144
145#[derive(Debug, Clone, Serialize, Deserialize)]
147pub struct PeerHints {
148 #[serde(default, rename = "u")]
150 pub signal_urls: Vec<String>,
151}
152
153#[derive(Debug, Clone)]
155pub enum DataMessage {
156 Request(DataRequest),
157 Response(DataResponse),
158 QuoteRequest(DataQuoteRequest),
159 QuoteResponse(DataQuoteResponse),
160 Payment(DataPayment),
161 PaymentAck(DataPaymentAck),
162 Chunk(DataChunk),
163 PeerHints(PeerHints),
164}
165
166pub fn encode_request(req: &DataRequest) -> Vec<u8> {
169 let body = rmp_serde::to_vec_named(req).expect("Failed to encode request");
170 let mut result = Vec::with_capacity(1 + body.len());
171 result.push(MSG_TYPE_REQUEST);
172 result.extend(body);
173 result
174}
175
176pub fn encode_response(res: &DataResponse) -> Vec<u8> {
179 let body = rmp_serde::to_vec_named(res).expect("Failed to encode response");
180 let mut result = Vec::with_capacity(1 + body.len());
181 result.push(MSG_TYPE_RESPONSE);
182 result.extend(body);
183 result
184}
185
186pub fn encode_quote_request(req: &DataQuoteRequest) -> Vec<u8> {
188 let body = rmp_serde::to_vec_named(req).expect("Failed to encode quote request");
189 let mut result = Vec::with_capacity(1 + body.len());
190 result.push(MSG_TYPE_QUOTE_REQUEST);
191 result.extend(body);
192 result
193}
194
195pub fn encode_quote_response(res: &DataQuoteResponse) -> Vec<u8> {
197 let body = rmp_serde::to_vec_named(res).expect("Failed to encode quote response");
198 let mut result = Vec::with_capacity(1 + body.len());
199 result.push(MSG_TYPE_QUOTE_RESPONSE);
200 result.extend(body);
201 result
202}
203
204pub fn encode_payment(req: &DataPayment) -> Vec<u8> {
206 let body = rmp_serde::to_vec_named(req).expect("Failed to encode payment");
207 let mut result = Vec::with_capacity(1 + body.len());
208 result.push(MSG_TYPE_PAYMENT);
209 result.extend(body);
210 result
211}
212
213pub fn encode_payment_ack(res: &DataPaymentAck) -> Vec<u8> {
215 let body = rmp_serde::to_vec_named(res).expect("Failed to encode payment ack");
216 let mut result = Vec::with_capacity(1 + body.len());
217 result.push(MSG_TYPE_PAYMENT_ACK);
218 result.extend(body);
219 result
220}
221
222pub fn encode_chunk(chunk: &DataChunk) -> Vec<u8> {
224 let body = rmp_serde::to_vec_named(chunk).expect("Failed to encode chunk");
225 let mut result = Vec::with_capacity(1 + body.len());
226 result.push(MSG_TYPE_CHUNK);
227 result.extend(body);
228 result
229}
230
231pub fn encode_peer_hints(hints: &PeerHints) -> Vec<u8> {
233 let body = rmp_serde::to_vec_named(hints).expect("Failed to encode peer hints");
234 let mut result = Vec::with_capacity(1 + body.len());
235 result.push(MSG_TYPE_PEER_HINTS);
236 result.extend(body);
237 result
238}
239
240pub fn parse_message(data: &[u8]) -> Option<DataMessage> {
242 if data.len() < 2 {
243 return None;
244 }
245
246 let msg_type = data[0];
247 let body = &data[1..];
248
249 match msg_type {
250 MSG_TYPE_REQUEST => rmp_serde::from_slice::<DataRequest>(body)
251 .ok()
252 .map(DataMessage::Request),
253 MSG_TYPE_RESPONSE => rmp_serde::from_slice::<DataResponse>(body)
254 .ok()
255 .map(DataMessage::Response),
256 MSG_TYPE_QUOTE_REQUEST => rmp_serde::from_slice::<DataQuoteRequest>(body)
257 .ok()
258 .map(DataMessage::QuoteRequest),
259 MSG_TYPE_QUOTE_RESPONSE => rmp_serde::from_slice::<DataQuoteResponse>(body)
260 .ok()
261 .map(DataMessage::QuoteResponse),
262 MSG_TYPE_PAYMENT => rmp_serde::from_slice::<DataPayment>(body)
263 .ok()
264 .map(DataMessage::Payment),
265 MSG_TYPE_PAYMENT_ACK => rmp_serde::from_slice::<DataPaymentAck>(body)
266 .ok()
267 .map(DataMessage::PaymentAck),
268 MSG_TYPE_CHUNK => rmp_serde::from_slice::<DataChunk>(body)
269 .ok()
270 .map(DataMessage::Chunk),
271 MSG_TYPE_PEER_HINTS => rmp_serde::from_slice::<PeerHints>(body)
272 .ok()
273 .map(DataMessage::PeerHints),
274 _ => None,
275 }
276}
277
278pub fn create_request(hash: &Hash, htl: u8) -> DataRequest {
280 DataRequest {
281 h: hash.to_vec(),
282 htl,
283 q: None,
284 }
285}
286
287pub fn create_request_with_quote(hash: &Hash, htl: u8, quote_id: u64) -> DataRequest {
289 DataRequest {
290 h: hash.to_vec(),
291 htl,
292 q: Some(quote_id),
293 }
294}
295
296pub fn create_response(hash: &Hash, data: Vec<u8>) -> DataResponse {
298 DataResponse {
299 h: hash.to_vec(),
300 d: data,
301 i: None,
302 n: None,
303 }
304}
305
306pub fn create_quote_request(
308 hash: &Hash,
309 ttl_ms: u32,
310 payment_sat: u64,
311 mint_url: Option<&str>,
312) -> DataQuoteRequest {
313 DataQuoteRequest {
314 h: hash.to_vec(),
315 p: payment_sat,
316 t: ttl_ms,
317 m: mint_url.map(str::to_string),
318 }
319}
320
321pub fn create_quote_response_available(
323 hash: &Hash,
324 quote_id: u64,
325 payment_sat: u64,
326 ttl_ms: u32,
327 mint_url: Option<&str>,
328) -> DataQuoteResponse {
329 DataQuoteResponse {
330 h: hash.to_vec(),
331 a: true,
332 q: Some(quote_id),
333 p: Some(payment_sat),
334 t: Some(ttl_ms),
335 m: mint_url.map(str::to_string),
336 }
337}
338
339pub fn create_quote_response_unavailable(hash: &Hash) -> DataQuoteResponse {
341 DataQuoteResponse {
342 h: hash.to_vec(),
343 a: false,
344 q: None,
345 p: None,
346 t: None,
347 m: None,
348 }
349}
350
351pub fn create_fragment_response(
353 hash: &Hash,
354 data: Vec<u8>,
355 index: u32,
356 total: u32,
357) -> DataResponse {
358 DataResponse {
359 h: hash.to_vec(),
360 d: data,
361 i: Some(index),
362 n: Some(total),
363 }
364}
365
366pub fn is_fragmented(res: &DataResponse) -> bool {
368 res.i.is_some() && res.n.is_some()
369}
370
371pub fn hash_to_key(hash: &[u8]) -> String {
373 hex::encode(hash)
374}
375
376pub fn hash_to_bytes(hash: &Hash) -> Vec<u8> {
378 hash.to_vec()
379}
380
381pub fn bytes_to_hash(bytes: &[u8]) -> Option<Hash> {
383 if bytes.len() == 32 {
384 let mut hash = [0u8; 32];
385 hash.copy_from_slice(bytes);
386 Some(hash)
387 } else {
388 None
389 }
390}
391
392#[cfg(test)]
393mod tests {
394 use super::*;
395
396 #[test]
397 fn test_encode_decode_request() {
398 let hash = [0xab; 32];
399 let req = create_request(&hash, 10);
400 let encoded = encode_request(&req);
401
402 assert_eq!(encoded[0], MSG_TYPE_REQUEST);
403
404 let parsed = parse_message(&encoded).unwrap();
405 match parsed {
406 DataMessage::Request(r) => {
407 assert_eq!(r.h, hash.to_vec());
408 assert_eq!(r.htl, 10);
409 }
410 _ => panic!("Expected request"),
411 }
412 }
413
414 #[test]
415 fn test_decode_request_without_explicit_htl_defaults_to_max() {
416 #[derive(Serialize)]
417 struct LegacyRequest {
418 #[serde(with = "serde_bytes")]
419 h: Vec<u8>,
420 }
421
422 let hash = [0x21; 32];
423 let body = rmp_serde::to_vec_named(&LegacyRequest { h: hash.to_vec() }).unwrap();
424 let mut encoded = vec![MSG_TYPE_REQUEST];
425 encoded.extend(body);
426
427 let parsed = parse_message(&encoded).unwrap();
428 match parsed {
429 DataMessage::Request(r) => {
430 assert_eq!(r.h, hash.to_vec());
431 assert_eq!(r.htl, crate::types::MAX_HTL);
432 }
433 _ => panic!("Expected request"),
434 }
435 }
436
437 #[test]
438 fn test_encode_decode_response() {
439 let hash = [0xcd; 32];
440 let data = vec![1, 2, 3, 4, 5];
441 let res = create_response(&hash, data.clone());
442 let encoded = encode_response(&res);
443
444 assert_eq!(encoded[0], MSG_TYPE_RESPONSE);
445
446 let parsed = parse_message(&encoded).unwrap();
447 match parsed {
448 DataMessage::Response(r) => {
449 assert_eq!(r.h, hash.to_vec());
450 assert_eq!(r.d, data);
451 assert!(!is_fragmented(&r));
452 }
453 _ => panic!("Expected response"),
454 }
455 }
456
457 #[test]
458 fn test_encode_decode_fragment_response() {
459 let hash = [0xef; 32];
460 let data = vec![10, 20, 30];
461 let res = create_fragment_response(&hash, data.clone(), 2, 5);
462 let encoded = encode_response(&res);
463
464 let parsed = parse_message(&encoded).unwrap();
465 match parsed {
466 DataMessage::Response(r) => {
467 assert_eq!(r.h, hash.to_vec());
468 assert_eq!(r.d, data);
469 assert!(is_fragmented(&r));
470 assert_eq!(r.i, Some(2));
471 assert_eq!(r.n, Some(5));
472 }
473 _ => panic!("Expected response"),
474 }
475 }
476
477 #[test]
478 fn test_encode_decode_quote_request() {
479 let hash = [0x44; 32];
480 let req = create_quote_request(&hash, 7, 2_500, Some("https://mint.example"));
481 let encoded = encode_quote_request(&req);
482
483 assert_eq!(encoded[0], MSG_TYPE_QUOTE_REQUEST);
484
485 let parsed = parse_message(&encoded).unwrap();
486 match parsed {
487 DataMessage::QuoteRequest(r) => {
488 assert_eq!(r.h, hash.to_vec());
489 assert_eq!(r.t, 7);
490 assert_eq!(r.p, 2_500);
491 assert_eq!(r.m.as_deref(), Some("https://mint.example"));
492 }
493 _ => panic!("Expected quote request"),
494 }
495 }
496
497 #[test]
498 fn test_encode_decode_quote_response_and_quoted_request() {
499 let hash = [0x55; 32];
500 let quote =
501 create_quote_response_available(&hash, 19, 2_500, 7, Some("https://mint.example"));
502 let encoded_quote = encode_quote_response("e);
503
504 assert_eq!(encoded_quote[0], MSG_TYPE_QUOTE_RESPONSE);
505
506 let parsed_quote = parse_message(&encoded_quote).unwrap();
507 match parsed_quote {
508 DataMessage::QuoteResponse(r) => {
509 assert_eq!(r.h, hash.to_vec());
510 assert!(r.a);
511 assert_eq!(r.q, Some(19));
512 assert_eq!(r.p, Some(2_500));
513 assert_eq!(r.t, Some(7));
514 assert_eq!(r.m.as_deref(), Some("https://mint.example"));
515 }
516 _ => panic!("Expected quote response"),
517 }
518
519 let req = create_request_with_quote(&hash, 9, 19);
520 let encoded_req = encode_request(&req);
521 let parsed_req = parse_message(&encoded_req).unwrap();
522 match parsed_req {
523 DataMessage::Request(r) => {
524 assert_eq!(r.h, hash.to_vec());
525 assert_eq!(r.htl, 9);
526 assert_eq!(r.q, Some(19));
527 }
528 _ => panic!("Expected quoted request"),
529 }
530 }
531
532 #[test]
533 fn test_hash_conversions() {
534 let hash = [0x12; 32];
535 let bytes = hash_to_bytes(&hash);
536 let back = bytes_to_hash(&bytes).unwrap();
537 assert_eq!(hash, back);
538 }
539
540 #[test]
541 fn test_encode_decode_payment_ack_and_chunk() {
542 let payment = DataPayment {
543 h: vec![0x61; 32],
544 q: 9,
545 c: 1,
546 p: 3,
547 m: Some("https://mint.example".to_string()),
548 tok: "cashuBtoken".to_string(),
549 };
550 let payment_ack = DataPaymentAck {
551 h: vec![0x62; 32],
552 q: 9,
553 c: 1,
554 a: true,
555 e: None,
556 };
557 let chunk = DataChunk {
558 h: vec![0x63; 32],
559 q: 9,
560 c: 1,
561 n: 2,
562 p: 3,
563 d: vec![1, 2, 3],
564 };
565
566 match parse_message(&encode_payment(&payment)).unwrap() {
567 DataMessage::Payment(parsed) => {
568 assert_eq!(parsed.q, payment.q);
569 assert_eq!(parsed.p, payment.p);
570 assert_eq!(parsed.m, payment.m);
571 }
572 _ => panic!("Expected payment"),
573 }
574
575 match parse_message(&encode_payment_ack(&payment_ack)).unwrap() {
576 DataMessage::PaymentAck(parsed) => {
577 assert_eq!(parsed.q, payment_ack.q);
578 assert_eq!(parsed.c, payment_ack.c);
579 assert!(parsed.a);
580 }
581 _ => panic!("Expected payment ack"),
582 }
583
584 match parse_message(&encode_chunk(&chunk)).unwrap() {
585 DataMessage::Chunk(parsed) => {
586 assert_eq!(parsed.q, chunk.q);
587 assert_eq!(parsed.n, chunk.n);
588 assert_eq!(parsed.d, chunk.d);
589 }
590 _ => panic!("Expected chunk"),
591 }
592 }
593
594 #[test]
595 fn test_encode_decode_peer_hints() {
596 let hints = PeerHints {
597 signal_urls: vec!["http://127.0.0.1:18080".to_string()],
598 };
599
600 match parse_message(&encode_peer_hints(&hints)).unwrap() {
601 DataMessage::PeerHints(parsed) => {
602 assert_eq!(parsed.signal_urls, hints.signal_urls);
603 }
604 _ => panic!("Expected peer hints"),
605 }
606 }
607}