1use raptorq::{Decoder, Encoder, EncodingPacket, ObjectTransmissionInformation, PayloadId};
31
32use super::RpcCodecError;
33use crate::aux_mux::AUX_MAX_PAYLOAD;
34use crate::node_status::MAX_DEVICE_ID;
35
36pub const RPC_RESPONSE_OVERHEAD_BASE: usize = 17;
39
40pub const RESPONSE_HEADERS_FLAG: u16 = 0x8000;
66
67pub const MAX_RESPONSE_HEADER_BLOCK: usize = 1024;
72
73pub const RPC_REPAIR_SYMBOLS: u32 = 4;
79
80pub const MAX_RESPONSE_FRAGMENT: usize =
87 AUX_MAX_PAYLOAD - RPC_RESPONSE_OVERHEAD_BASE - MAX_DEVICE_ID;
88
89pub const MAX_RESPONSE_FRAGMENTS: usize = 64;
94
95pub const MAX_RESPONSE_BODY: usize =
102 (MAX_RESPONSE_FRAGMENTS - RPC_REPAIR_SYMBOLS as usize) * MAX_RESPONSE_FRAGMENT;
103
104#[derive(Debug, Clone, PartialEq, Eq)]
106pub struct RpcResponse<'a> {
107 pub id: u32,
108 pub sender: &'a [u8],
113 pub status: u16,
114 pub index: u16,
117 pub total: u16,
120 pub oti: u32,
124 pub body: &'a [u8],
126}
127
128#[derive(Debug, Clone, PartialEq, Eq)]
130pub struct ResponseSymbols {
131 pub oti: u32,
140 pub symbols: Vec<Vec<u8>>,
143}
144
145fn response_shape(len: usize) -> (usize, usize) {
156 let k = len.div_ceil(MAX_RESPONSE_FRAGMENT);
157 (k, len.div_ceil(k))
158}
159
160fn response_config(len: u32, symbol_size: usize) -> ObjectTransmissionInformation {
166 ObjectTransmissionInformation::new(len as u64, symbol_size as u16, 1, 1, 1)
167}
168
169pub fn split_response(body: &[u8]) -> Option<ResponseSymbols> {
176 if body.is_empty() {
177 return Some(ResponseSymbols {
180 oti: 0,
181 symbols: vec![Vec::new()],
182 });
183 }
184 if body.len() > MAX_RESPONSE_BODY {
185 return None;
186 }
187 let (_, symbol_size) = response_shape(body.len());
188 let encoder = Encoder::new(body, response_config(body.len() as u32, symbol_size));
189 let oti = encoder.get_config().transfer_length() as u32;
192 Some(ResponseSymbols {
193 oti,
194 symbols: encoder
195 .get_encoded_packets(RPC_REPAIR_SYMBOLS)
196 .into_iter()
197 .map(|packet| packet.split().1)
198 .collect(),
199 })
200}
201
202pub type ResponseHeader = (String, String);
204
205pub fn pack_response(status: u16, body: &[u8], headers: &[ResponseHeader]) -> (u16, Vec<u8>) {
228 let mut block: Vec<u8> = Vec::new();
229 for (name, value) in headers {
230 if name.is_empty() || name.len() > u8::MAX as usize || value.len() > u16::MAX as usize {
231 continue;
232 }
233 let entry = 1 + name.len() + 2 + value.len();
234 if block.len() + entry > MAX_RESPONSE_HEADER_BLOCK {
235 break;
236 }
237 block.push(name.len() as u8);
238 block.extend_from_slice(name.as_bytes());
239 block.extend_from_slice(&(value.len() as u16).to_be_bytes());
240 block.extend_from_slice(value.as_bytes());
241 }
242 if block.is_empty() {
243 return (status, body.to_vec());
244 }
245 let mut object = Vec::with_capacity(body.len() + block.len() + 4);
246 object.extend_from_slice(body);
247 let block_len = block.len() as u32;
248 object.extend_from_slice(&block);
249 object.extend_from_slice(&block_len.to_be_bytes());
250 (status | RESPONSE_HEADERS_FLAG, object)
251}
252
253pub fn unpack_response(status: u16, object: Vec<u8>) -> (u16, Vec<u8>, Vec<ResponseHeader>) {
260 if status & RESPONSE_HEADERS_FLAG == 0 {
261 return (status, object, Vec::new());
262 }
263 let status = status & !RESPONSE_HEADERS_FLAG;
264 let Some(len_at) = object.len().checked_sub(4) else {
265 return (status, object, Vec::new());
266 };
267 let block_len = u32::from_be_bytes([
268 object[len_at],
269 object[len_at + 1],
270 object[len_at + 2],
271 object[len_at + 3],
272 ]) as usize;
273 let Some(block_at) = len_at.checked_sub(block_len) else {
274 return (status, object, Vec::new());
275 };
276 let Some(headers) = parse_header_block(&object[block_at..len_at]) else {
277 return (status, object, Vec::new());
278 };
279 let mut body = object;
280 body.truncate(block_at);
281 (status, body, headers)
282}
283
284fn parse_header_block(mut block: &[u8]) -> Option<Vec<ResponseHeader>> {
287 let mut out = Vec::new();
288 while !block.is_empty() {
289 let name_len = *block.first()? as usize;
290 if name_len == 0 {
291 return None;
292 }
293 let rest = block.get(1..)?;
294 let name = std::str::from_utf8(rest.get(..name_len)?).ok()?;
295 let rest = rest.get(name_len..)?;
296 let value_len = u16::from_be_bytes([*rest.first()?, *rest.get(1)?]) as usize;
297 let rest = rest.get(2..)?;
298 let value = std::str::from_utf8(rest.get(..value_len)?).ok()?;
299 out.push((name.to_string(), value.to_string()));
300 block = rest.get(value_len..)?;
301 }
302 Some(out)
303}
304
305pub fn encode_response_fragment(
311 sender: &[u8],
312 id: u32,
313 status: u16,
314 index: u16,
315 total: u16,
316 oti: u32,
317 symbol: &[u8],
318) -> Option<Vec<u8>> {
319 if sender.len() > MAX_DEVICE_ID {
320 return None;
321 }
322 if total == 0 || index >= total {
323 return None;
324 }
325 if symbol.len() > u16::MAX as usize {
326 return None;
327 }
328 let encoded_len = RPC_RESPONSE_OVERHEAD_BASE + sender.len() + symbol.len();
329 if encoded_len > AUX_MAX_PAYLOAD {
330 return None;
331 }
332 let mut out = Vec::with_capacity(encoded_len);
333 out.push(sender.len() as u8);
334 out.extend_from_slice(sender);
335 out.extend_from_slice(&id.to_be_bytes());
336 out.extend_from_slice(&status.to_be_bytes());
337 out.extend_from_slice(&index.to_be_bytes());
338 out.extend_from_slice(&total.to_be_bytes());
339 out.extend_from_slice(&oti.to_be_bytes());
340 out.extend_from_slice(&(symbol.len() as u16).to_be_bytes());
341 out.extend_from_slice(symbol);
342 Some(out)
343}
344
345pub fn decode_response(payload: &[u8]) -> Result<RpcResponse<'_>, RpcCodecError> {
347 let &sender_len = payload.first().ok_or(RpcCodecError::TooShort)?;
348 if sender_len as usize > MAX_DEVICE_ID {
349 return Err(RpcCodecError::BadSenderLen(sender_len));
350 }
351 let sender_len = sender_len as usize;
352 let header = RPC_RESPONSE_OVERHEAD_BASE + sender_len;
353 if payload.len() < header {
354 return Err(RpcCodecError::TooShort);
355 }
356 let sender = &payload[1..1 + sender_len];
357 let fixed = &payload[1 + sender_len..];
359 let id = u32::from_be_bytes([fixed[0], fixed[1], fixed[2], fixed[3]]);
360 let status = u16::from_be_bytes([fixed[4], fixed[5]]);
361 let index = u16::from_be_bytes([fixed[6], fixed[7]]);
362 let total = u16::from_be_bytes([fixed[8], fixed[9]]);
363 let oti = u32::from_be_bytes([fixed[10], fixed[11], fixed[12], fixed[13]]);
364 let frag_len = u16::from_be_bytes([fixed[14], fixed[15]]) as usize;
365 if header + frag_len != payload.len() {
366 return Err(RpcCodecError::LengthMismatch {
367 declared: header + frag_len,
368 actual: payload.len(),
369 });
370 }
371 if total == 0 || index >= total {
372 return Err(RpcCodecError::BadFragmentIndex { index, total });
373 }
374 Ok(RpcResponse {
375 id,
376 sender,
377 status,
378 index,
379 total,
380 oti,
381 body: &payload[header..],
382 })
383}
384
385#[derive(Debug, Clone, PartialEq, Eq)]
387pub enum FragmentOutcome {
388 Pending,
390 Complete(Vec<u8>),
392 BadSymbol,
396}
397
398#[derive(Debug)]
406pub struct ResponseDecoder {
407 inner: Option<Decoder>,
409 symbol_size: usize,
410 symbol_id_limit: u32,
412}
413
414impl ResponseDecoder {
415 pub fn new(oti: u32) -> Option<Self> {
422 let len = oti as usize;
423 if len > MAX_RESPONSE_BODY {
424 return None;
425 }
426 if len == 0 {
427 return Some(Self {
428 inner: None,
429 symbol_size: 0,
430 symbol_id_limit: RPC_REPAIR_SYMBOLS,
431 });
432 }
433 let (k, symbol_size) = response_shape(len);
434 Some(Self {
435 inner: Some(Decoder::new(response_config(oti, symbol_size))),
436 symbol_size,
437 symbol_id_limit: k as u32 + RPC_REPAIR_SYMBOLS,
438 })
439 }
440
441 pub fn symbol_size(&self) -> usize {
443 self.symbol_size
444 }
445
446 pub fn push(&mut self, index: u16, symbol: &[u8]) -> FragmentOutcome {
448 if symbol.len() != self.symbol_size || index as u32 >= self.symbol_id_limit {
449 return FragmentOutcome::BadSymbol;
450 }
451 let Some(decoder) = self.inner.as_mut() else {
452 return FragmentOutcome::Complete(Vec::new());
453 };
454 let packet = EncodingPacket::new(PayloadId::new(0, index as u32), symbol.to_vec());
455 match decoder.decode(packet) {
456 Some(body) => FragmentOutcome::Complete(body),
457 None => FragmentOutcome::Pending,
458 }
459 }
460}
461
462#[cfg(test)]
463mod tests {
464 use super::*;
465
466 const SENDER: &[u8] = b"ados-abc123";
467
468 fn hdr(name: &str, value: &str) -> ResponseHeader {
469 (name.to_string(), value.to_string())
470 }
471
472 #[test]
473 fn a_headerless_response_is_byte_identical_to_a_build_without_header_support() {
474 let body = br#"{"ok":true}"#;
478 let (status, object) = pack_response(200, body, &[]);
479 assert_eq!(status, 200, "no flag when there is nothing to carry");
480 assert_eq!(object, body);
481 }
482
483 #[test]
484 fn a_content_type_survives_the_round_trip() {
485 let body = b"id,lat,lon\n1,0,0\n";
486 let headers = vec![
487 hdr("content-type", "text/csv; charset=utf-8"),
488 hdr("content-disposition", "attachment; filename=\"log.csv\""),
489 ];
490 let (wire_status, object) = pack_response(200, body, &headers);
491 assert_eq!(wire_status & RESPONSE_HEADERS_FLAG, RESPONSE_HEADERS_FLAG);
492
493 let (status, got_body, got_headers) = unpack_response(wire_status, object);
494 assert_eq!(
495 status, 200,
496 "the flag is stripped, not leaked to the caller"
497 );
498 assert_eq!(got_body, body);
499 assert_eq!(got_headers, headers);
500 }
501
502 #[test]
503 fn headers_survive_the_whole_fragment_round_trip_with_a_lost_fragment() {
504 let body = vec![0xABu8; 4000];
507 let headers = vec![hdr("content-type", "application/octet-stream")];
508 let (wire_status, object) = pack_response(200, &body, &headers);
509 let split = split_response(&object).unwrap();
510 let total = split.symbols.len() as u16;
511 let mut decoder = ResponseDecoder::new(split.oti).unwrap();
512 let mut assembled = None;
513 for (i, symbol) in split.symbols.iter().enumerate() {
514 if i == 1 {
515 continue; }
517 let payload = encode_response_fragment(
518 SENDER,
519 9,
520 wire_status,
521 i as u16,
522 total,
523 split.oti,
524 symbol,
525 )
526 .unwrap();
527 let frag = decode_response(&payload).unwrap();
528 assert_eq!(frag.status, wire_status);
529 if let FragmentOutcome::Complete(out) = decoder.push(frag.index, frag.body) {
530 assembled = Some(out);
531 break;
532 }
533 }
534 let (status, got_body, got_headers) = unpack_response(
535 wire_status,
536 assembled.expect("repair symbols must close the block"),
537 );
538 assert_eq!(status, 200);
539 assert_eq!(got_body, body);
540 assert_eq!(got_headers, headers);
541 }
542
543 #[test]
544 fn a_damaged_trailer_yields_the_object_verbatim_rather_than_a_truncated_body() {
545 let (wire_status, mut object) = pack_response(200, b"payload", &[hdr("a", "b")]);
548 let last = object.len() - 1;
549 object[last] = 0xFF; let (status, body, headers) = unpack_response(wire_status, object.clone());
551 assert_eq!(status, 200);
552 assert_eq!(body, object);
553 assert!(headers.is_empty());
554 }
555
556 #[test]
557 fn a_header_block_past_the_ceiling_is_truncated_not_allowed_to_eat_the_body() {
558 let huge = "x".repeat(MAX_RESPONSE_HEADER_BLOCK);
559 let headers = vec![hdr("content-type", "text/plain"), hdr("bloat", &huge)];
560 let (wire_status, object) = pack_response(200, b"body", &headers);
561 let (_, body, got) = unpack_response(wire_status, object);
562 assert_eq!(body, b"body");
563 assert_eq!(got, vec![hdr("content-type", "text/plain")]);
564 }
565
566 fn round_trip(body: &[u8], order: &[usize], drop: &[usize]) -> Option<Vec<u8>> {
569 let split = split_response(body).unwrap();
570 let total = split.symbols.len() as u16;
571 let mut decoder: Option<ResponseDecoder> = None;
572 for &i in order {
573 if drop.contains(&i) {
574 continue;
575 }
576 let payload = encode_response_fragment(
577 SENDER,
578 7,
579 200,
580 i as u16,
581 total,
582 split.oti,
583 &split.symbols[i],
584 )
585 .unwrap();
586 let frag = decode_response(&payload).unwrap();
587 assert_eq!(frag.sender, SENDER);
588 assert_eq!(frag.oti, split.oti);
589 assert_eq!(frag.total, total);
590 let d = decoder.get_or_insert_with(|| ResponseDecoder::new(frag.oti).unwrap());
591 if let FragmentOutcome::Complete(out) = d.push(frag.index, frag.body) {
592 return Some(out);
593 }
594 }
595 None
596 }
597
598 #[test]
599 fn round_trips_a_single_fragment_response() {
600 let body = br#"{"device_id":"abc","name":"Skynode A7S"}"#;
601 let split = split_response(body).unwrap();
602 assert_eq!(split.oti, body.len() as u32);
603 let enc = encode_response_fragment(
604 SENDER,
605 42,
606 200,
607 0,
608 split.symbols.len() as u16,
609 split.oti,
610 &split.symbols[0],
611 )
612 .unwrap();
613 let dec = decode_response(&enc).unwrap();
614 assert_eq!(dec.id, 42);
615 assert_eq!(dec.sender, SENDER);
616 assert_eq!(dec.status, 200);
617 assert_eq!(dec.index, 0);
618 assert_eq!(round_trip(body, &[0], &[]).as_deref(), Some(&body[..]));
619 }
620
621 #[test]
622 fn an_empty_body_still_produces_one_fragment() {
623 let split = split_response(&[]).unwrap();
626 assert_eq!(split.oti, 0);
627 assert_eq!(split.symbols.len(), 1);
628 assert!(split.symbols[0].is_empty());
629 let enc = encode_response_fragment(SENDER, 7, 204, 0, 1, 0, &split.symbols[0]).unwrap();
630 let dec = decode_response(&enc).unwrap();
631 assert_eq!(dec.status, 204);
632 assert_eq!(dec.total, 1);
633 assert!(dec.body.is_empty());
634 let mut decoder = ResponseDecoder::new(dec.oti).unwrap();
635 assert_eq!(
636 decoder.push(dec.index, dec.body),
637 FragmentOutcome::Complete(Vec::new())
638 );
639 }
640
641 #[test]
642 fn a_response_carries_k_systematic_plus_the_repair_symbols() {
643 let body: Vec<u8> = (0..3000u32).map(|i| (i % 251) as u8).collect();
644 let split = split_response(&body).unwrap();
645 let k = body.len().div_ceil(MAX_RESPONSE_FRAGMENT);
646 assert_eq!(k, 3);
647 assert_eq!(split.symbols.len(), k + RPC_REPAIR_SYMBOLS as usize);
648 let size = split.symbols[0].len();
651 assert!(split.symbols.iter().all(|s| s.len() == size));
652 assert!(size <= MAX_RESPONSE_FRAGMENT);
653 }
654
655 #[test]
656 fn a_thirty_kilobyte_body_survives_losing_any_four_fragments() {
657 let body: Vec<u8> = (0..29_339u32).map(|i| (i % 251) as u8).collect();
661 let split = split_response(&body).unwrap();
662 assert_eq!(split.symbols.len(), 30, "26 systematic + 4 repair");
663 let order: Vec<usize> = (0..30).collect();
664 for drop in [
665 vec![0usize, 1, 2, 3],
666 vec![26usize, 27, 28, 29],
667 vec![0usize, 9, 17, 29],
668 vec![5usize, 6, 7, 25],
669 ] {
670 assert_eq!(
671 round_trip(&body, &order, &drop).as_deref(),
672 Some(&body[..]),
673 "dropping {drop:?}"
674 );
675 }
676 }
677
678 #[test]
679 fn fragments_rejoin_out_of_order() {
680 let body: Vec<u8> = (0..8000u32).map(|i| (i % 251) as u8).collect();
681 let split = split_response(&body).unwrap();
682 let mut order: Vec<usize> = (0..split.symbols.len()).collect();
683 order.reverse();
684 assert_eq!(round_trip(&body, &order, &[]).as_deref(), Some(&body[..]));
685 }
686
687 #[test]
688 fn a_body_past_the_reassembly_ceiling_does_not_split() {
689 assert!(split_response(&vec![0u8; MAX_RESPONSE_BODY + 1]).is_none());
690 let at_ceiling = vec![0u8; MAX_RESPONSE_BODY];
691 assert_eq!(
692 split_response(&at_ceiling).unwrap().symbols.len(),
693 MAX_RESPONSE_FRAGMENTS
694 );
695 }
696
697 #[test]
698 fn rejects_a_fragment_larger_than_one_aux_frame() {
699 let big = vec![0u8; MAX_RESPONSE_FRAGMENT + 1];
700 let sender = vec![b'a'; MAX_DEVICE_ID];
701 assert!(encode_response_fragment(&sender, 1, 200, 0, 1, 1, &big).is_none());
702 let fits = vec![0u8; MAX_RESPONSE_FRAGMENT];
703 let enc = encode_response_fragment(&sender, 1, 200, 0, 1, 1, &fits).unwrap();
704 assert_eq!(enc.len(), AUX_MAX_PAYLOAD, "the budget is exact");
705 assert_eq!(
706 decode_response(&enc).unwrap().body.len(),
707 MAX_RESPONSE_FRAGMENT
708 );
709 }
710
711 #[test]
712 fn rejects_a_sender_longer_than_a_device_id() {
713 let too_long = vec![b'a'; MAX_DEVICE_ID + 1];
714 assert!(encode_response_fragment(&too_long, 1, 200, 0, 1, 1, b"x").is_none());
715 let mut bad = vec![(MAX_DEVICE_ID + 1) as u8];
719 bad.extend_from_slice(&too_long);
720 bad.extend_from_slice(&[0u8; 16]);
721 assert_eq!(
722 decode_response(&bad).unwrap_err(),
723 RpcCodecError::BadSenderLen((MAX_DEVICE_ID + 1) as u8)
724 );
725 }
726
727 #[test]
728 fn rejects_a_fragment_whose_index_is_outside_its_total() {
729 assert!(encode_response_fragment(SENDER, 1, 200, 3, 3, 1, b"x").is_none());
730 assert!(encode_response_fragment(SENDER, 1, 200, 0, 0, 1, b"x").is_none());
731 let mut bad = vec![0u8];
733 bad.extend_from_slice(&1u32.to_be_bytes());
734 bad.extend_from_slice(&200u16.to_be_bytes());
735 bad.extend_from_slice(&4u16.to_be_bytes());
736 bad.extend_from_slice(&2u16.to_be_bytes());
737 bad.extend_from_slice(&1u32.to_be_bytes());
738 bad.extend_from_slice(&1u16.to_be_bytes());
739 bad.push(b'x');
740 assert_eq!(
741 decode_response(&bad).unwrap_err(),
742 RpcCodecError::BadFragmentIndex { index: 4, total: 2 }
743 );
744 }
745
746 #[test]
747 fn rejects_a_truncated_response() {
748 let full = encode_response_fragment(SENDER, 9, 200, 0, 1, 4, b"body").unwrap();
749 for cut in [0usize, 1, 4, RPC_RESPONSE_OVERHEAD_BASE + SENDER.len() - 1] {
750 let mut truncated = full.clone();
751 truncated.truncate(cut);
752 assert_eq!(
753 decode_response(&truncated).unwrap_err(),
754 RpcCodecError::TooShort,
755 "cut to {cut}"
756 );
757 }
758 }
759
760 #[test]
761 fn catches_a_length_mismatch_in_a_response() {
762 let mut bad = vec![0u8];
764 bad.extend_from_slice(&1u32.to_be_bytes());
765 bad.extend_from_slice(&200u16.to_be_bytes());
766 bad.extend_from_slice(&0u16.to_be_bytes());
767 bad.extend_from_slice(&1u16.to_be_bytes());
768 bad.extend_from_slice(&50u32.to_be_bytes());
769 bad.extend_from_slice(&50u16.to_be_bytes());
770 assert_eq!(
771 decode_response(&bad).unwrap_err(),
772 RpcCodecError::LengthMismatch {
773 declared: RPC_RESPONSE_OVERHEAD_BASE + 50,
774 actual: RPC_RESPONSE_OVERHEAD_BASE
775 }
776 );
777 }
778
779 #[test]
780 fn an_impossible_index_total_pair_is_its_own_fault_not_a_length_fault() {
781 let mut bad = vec![0u8];
784 bad.extend_from_slice(&1u32.to_be_bytes());
785 bad.extend_from_slice(&200u16.to_be_bytes());
786 bad.extend_from_slice(&3u16.to_be_bytes());
787 bad.extend_from_slice(&2u16.to_be_bytes());
788 bad.extend_from_slice(&0u32.to_be_bytes());
789 bad.extend_from_slice(&0u16.to_be_bytes());
790 assert_eq!(
791 decode_response(&bad).unwrap_err(),
792 RpcCodecError::BadFragmentIndex { index: 3, total: 2 }
793 );
794 }
795
796 #[test]
797 fn a_decoder_refuses_an_unbounded_transfer_length() {
798 assert!(ResponseDecoder::new(u32::MAX).is_none());
801 assert!(ResponseDecoder::new(MAX_RESPONSE_BODY as u32 + 1).is_none());
802 assert!(ResponseDecoder::new(MAX_RESPONSE_BODY as u32).is_some());
803 }
804
805 #[test]
806 fn a_decoder_drops_a_symbol_of_the_wrong_size_or_id() {
807 let body: Vec<u8> = (0..5000u32).map(|i| (i % 251) as u8).collect();
808 let split = split_response(&body).unwrap();
809 let mut decoder = ResponseDecoder::new(split.oti).unwrap();
810 let size = decoder.symbol_size();
811 assert_eq!(size, split.symbols[0].len());
812 assert_eq!(
814 decoder.push(0, &vec![0u8; size - 1]),
815 FragmentOutcome::BadSymbol
816 );
817 let k = body.len().div_ceil(MAX_RESPONSE_FRAGMENT);
820 assert_eq!(
821 decoder.push((k + RPC_REPAIR_SYMBOLS as usize) as u16, &vec![0u8; size]),
822 FragmentOutcome::BadSymbol
823 );
824 for (i, symbol) in split.symbols.iter().enumerate() {
826 if let FragmentOutcome::Complete(out) = decoder.push(i as u16, symbol) {
827 assert_eq!(out, body);
828 return;
829 }
830 }
831 panic!("the full symbol set must decode");
832 }
833
834 #[test]
835 fn every_body_size_across_the_fragment_range_round_trips() {
836 for k in [1usize, 2, 26, 60] {
839 for len in [
840 (k - 1) * MAX_RESPONSE_FRAGMENT + 1,
841 k * MAX_RESPONSE_FRAGMENT,
842 ] {
843 let body: Vec<u8> = (0..len).map(|i| ((i * 7 + 13) % 251) as u8).collect();
844 let split = split_response(&body).unwrap();
845 assert_eq!(
846 split.symbols.len(),
847 k + RPC_REPAIR_SYMBOLS as usize,
848 "len {len}"
849 );
850 let order: Vec<usize> = (0..split.symbols.len()).collect();
851 assert_eq!(
852 round_trip(&body, &order, &[0, 1]).as_deref(),
853 Some(&body[..]),
854 "len {len}"
855 );
856 }
857 }
858 }
859
860 #[test]
861 fn frame_budgets_account_for_fixed_overhead() {
862 assert_eq!(RPC_RESPONSE_OVERHEAD_BASE, 17);
868 assert_eq!(MAX_RESPONSE_FRAGMENT, 1200 - 17 - 32);
869 assert_eq!(MAX_RESPONSE_FRAGMENT, 1151);
870 assert_eq!(MAX_RESPONSE_BODY, 60 * 1151);
872 const { assert!(MAX_RESPONSE_BODY > 29_339) };
875 assert_eq!(29_339usize.div_ceil(MAX_RESPONSE_FRAGMENT), 26);
876 }
877}