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