1use crate::BlockSize;
13use crate::Jobs;
14use crate::MAGIC_NUMBER;
15use crate::block::BlockDecoder;
16use crate::block::BlockEncoder;
17use crate::compress_bound;
18use crate::error::Error;
19use crate::error::Result;
20use crate::stream::decode_blocks;
21use crate::stream::encode_blocks;
22use crate::stream::run_in_pool;
23
24pub const ONE_SHOT_HEADER_LEN: usize = 13;
26
27#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
43pub struct BlockHeader {
44 pub compressed_size: u32,
46 pub original_size: u32,
48}
49
50impl BlockHeader {
51 pub const LEN: usize = 8;
53
54 #[must_use]
66 #[inline]
67 pub const fn new(compressed_size: u32, original_size: u32) -> Self {
68 Self {
69 compressed_size,
70 original_size,
71 }
72 }
73
74 #[inline]
92 pub fn for_block(payload_len: usize, original_len: usize) -> Result<Self> {
93 let comp = u32::try_from(payload_len).map_err(|_| Error::BlockTooLarge)?;
94 let orig = u32::try_from(original_len).map_err(|_| Error::BlockTooLarge)?;
95 Ok(Self::new(comp, orig))
96 }
97
98 #[must_use]
109 #[inline]
110 pub const fn to_bytes(self) -> [u8; 8] {
111 let comp = self.compressed_size.to_le_bytes();
112 let orig = self.original_size.to_le_bytes();
113 [
114 comp[0], comp[1], comp[2], comp[3], orig[0], orig[1], orig[2], orig[3],
115 ]
116 }
117
118 #[must_use]
129 #[inline]
130 pub const fn from_bytes(bytes: &[u8; 8]) -> Self {
131 Self {
132 compressed_size: u32::from_le_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]),
133 original_size: u32::from_le_bytes([bytes[4], bytes[5], bytes[6], bytes[7]]),
134 }
135 }
136}
137
138pub struct FramedBlock<'a> {
140 pub header: BlockHeader,
142 pub payload: &'a [u8],
144}
145
146#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
167pub struct StreamHeader {
168 pub block_size: BlockSize,
170}
171
172impl StreamHeader {
173 pub const LEN: usize = 9;
175
176 #[must_use]
187 #[inline]
188 pub const fn new(block_size: BlockSize) -> Self {
189 Self { block_size }
190 }
191
192 #[must_use]
207 pub const fn to_bytes(self) -> [u8; Self::LEN] {
208 let size = self.block_size.get().to_le_bytes();
209 [
210 MAGIC_NUMBER[0],
211 MAGIC_NUMBER[1],
212 MAGIC_NUMBER[2],
213 MAGIC_NUMBER[3],
214 MAGIC_NUMBER[4],
215 size[0],
216 size[1],
217 size[2],
218 size[3],
219 ]
220 }
221
222 #[inline]
245 pub fn from_bytes(bytes: &[u8; Self::LEN]) -> Result<Self> {
246 if bytes[..5] != MAGIC_NUMBER {
247 return Err(Error::InvalidMagic);
248 }
249 Ok(Self::new(block_size_from_field(&bytes[5..9])?))
250 }
251
252 #[inline]
260 pub(crate) fn parse(data: &[u8]) -> Result<Self> {
261 let Some(bytes) = data.first_chunk::<{ Self::LEN }>() else {
262 return Err(Error::TruncatedHeader);
263 };
264 Self::from_bytes(bytes)
265 }
266}
267
268#[inline]
270fn block_size_from_field(bytes: &[u8]) -> Result<BlockSize> {
271 let value = u32::try_from(read_i32(bytes)).map_err(|_| Error::InvalidBlockSize)?;
272 BlockSize::new(value).map_err(|_| Error::InvalidBlockSize)
273}
274
275pub fn parse_block_header(bytes: &[u8], block_size: BlockSize) -> Result<BlockHeader> {
285 let header = BlockHeader::new(read_u32(bytes), read_u32(&bytes[4..]));
286 if header.compressed_size as usize > compress_bound(block_size.as_usize())
287 || header.original_size > block_size.get()
288 {
289 return Err(Error::InvalidHeader);
290 }
291 Ok(header)
292}
293
294pub struct StreamBlocks<'a> {
299 rest: &'a [u8],
301 block_size: BlockSize,
303}
304
305impl<'a> StreamBlocks<'a> {
306 pub fn new(data: &'a [u8]) -> Result<Self> {
314 let block_size = StreamHeader::parse(data)?.block_size;
315 Ok(Self {
316 rest: &data[StreamHeader::LEN..],
317 block_size,
318 })
319 }
320
321 #[must_use]
323 #[inline]
324 pub const fn block_size(&self) -> BlockSize {
325 self.block_size
326 }
327
328 fn split(&self) -> Result<(FramedBlock<'a>, &'a [u8])> {
330 let Some(block_header) = self.rest.get(..BlockHeader::LEN) else {
331 return Err(Error::TruncatedHeader);
332 };
333 let header = parse_block_header(block_header, self.block_size)?;
334 let end = BlockHeader::LEN + header.compressed_size as usize;
335 let Some(payload) = self.rest.get(BlockHeader::LEN..end) else {
336 return Err(Error::TruncatedData);
337 };
338 Ok((FramedBlock { header, payload }, &self.rest[end..]))
339 }
340}
341
342impl<'a> Iterator for StreamBlocks<'a> {
343 type Item = Result<FramedBlock<'a>>;
344
345 fn next(&mut self) -> Option<Self::Item> {
354 if self.rest.is_empty() {
355 return None;
356 }
357 match self.split() {
358 Ok((block, rest)) => {
359 self.rest = rest;
360 Some(Ok(block))
361 }
362 Err(fault) => {
363 self.rest = &[];
364 Some(Err(fault))
365 }
366 }
367 }
368}
369
370#[inline]
380pub fn split_stream(data: &[u8]) -> Result<(BlockSize, Vec<FramedBlock<'_>>)> {
381 let walk = StreamBlocks::new(data)?;
382 let block_size = walk.block_size();
383 Ok((block_size, walk.collect::<Result<_>>()?))
384}
385
386pub fn append_framed_block(
397 state: &mut BlockEncoder,
398 chunk: &[u8],
399 out: &mut Vec<u8>,
400) -> Result<()> {
401 let at = out.len();
402 out.extend_from_slice(&[0_u8; BlockHeader::LEN]);
403 let outcome = state
404 .encode_block_into(chunk, out)
405 .and_then(|()| BlockHeader::for_block(out.len() - at - BlockHeader::LEN, chunk.len()));
406 match outcome {
407 Ok(header) => {
408 out[at..at + BlockHeader::LEN].copy_from_slice(&header.to_bytes());
409 Ok(())
410 }
411 Err(e) => {
412 out.truncate(at);
413 Err(e)
414 }
415 }
416}
417
418#[inline]
420const fn first_four(bytes: &[u8]) -> [u8; 4] {
421 [bytes[0], bytes[1], bytes[2], bytes[3]]
422}
423
424#[inline]
430pub const fn read_i32(bytes: &[u8]) -> i32 {
431 i32::from_le_bytes(first_four(bytes))
432}
433
434#[inline]
440pub const fn read_u32(bytes: &[u8]) -> u32 {
441 u32::from_le_bytes(first_four(bytes))
442}
443
444fn clamp_block_size(requested: BlockSize, input_len: usize) -> Result<BlockSize> {
449 let size = if requested.as_usize() > input_len {
450 compress_bound(input_len).max(BlockSize::MIN.as_usize())
451 } else {
452 requested.as_usize()
453 };
454 BlockSize::new(u32::try_from(size).map_err(|_| Error::InvalidBlockSize)?)
455}
456
457pub fn encode_frame(data: &[u8], block_size: BlockSize, jobs: Jobs) -> Result<Vec<u8>> {
473 let effective = clamp_block_size(block_size, data.len())?;
474 let width = effective.as_usize();
475 let block_count = data.len().div_ceil(width);
476 let count_field = u32::try_from(block_count).map_err(|_| Error::BlockTooLarge)?;
477
478 let mut out =
479 Vec::with_capacity(ONE_SHOT_HEADER_LEN + block_count * BlockHeader::LEN + data.len() + 8);
480 out.extend_from_slice(&StreamHeader::new(effective).to_bytes());
481 out.extend_from_slice(&count_field.to_le_bytes());
482
483 if jobs.is_serial() || block_count < 2 {
484 let mut state = BlockEncoder::new(effective)?;
485 for chunk in data.chunks(width) {
486 append_framed_block(&mut state, chunk, &mut out)?;
487 }
488 } else {
489 let parts = run_in_pool(jobs, block_count, || encode_blocks(data, effective))?;
490 for part in &parts {
491 out.extend_from_slice(part);
492 }
493 }
494 Ok(out)
495}
496
497pub fn decode_frame(data: &[u8], jobs: Jobs) -> Result<Vec<u8>> {
514 let Some(header) = data.get(..ONE_SHOT_HEADER_LEN) else {
515 return Err(Error::TruncatedHeader);
516 };
517 let block_size = StreamHeader::parse(header)?.block_size;
518 let block_count = read_u32(&header[StreamHeader::LEN..]);
519 let limit = compress_bound(block_size.as_usize());
520
521 if jobs.is_serial() {
524 let mut decoder = BlockDecoder::new(block_size)?;
525 let mut out = Vec::new();
526 let mut rest = &data[ONE_SHOT_HEADER_LEN..];
527 for _ in 0..block_count {
528 let (block, next) = split_block(rest, block_size, limit)?;
529 decoder.decode_block_into(block.payload, block.header.original_size, &mut out)?;
530 rest = next;
531 }
532 return Ok(out);
533 }
534
535 let mut rest = &data[ONE_SHOT_HEADER_LEN..];
538 let mut blocks = Vec::new();
539 let mut walk_fault = None;
540 for _ in 0..block_count {
541 match split_block(rest, block_size, limit) {
542 Ok((block, next)) => {
543 blocks.push(block);
544 rest = next;
545 }
546 Err(fault) => {
547 walk_fault = Some(fault);
548 break;
549 }
550 }
551 }
552
553 let mut out = Vec::new();
554 if blocks.len() < 2 {
555 let mut decoder = BlockDecoder::new(block_size)?;
556 for block in &blocks {
557 decoder.decode_block_into(block.payload, block.header.original_size, &mut out)?;
558 }
559 } else {
560 out = run_in_pool(jobs, blocks.len(), || decode_blocks(&blocks, block_size))?;
561 }
562 walk_fault.map_or(Ok(out), Err)
563}
564
565fn split_block(
576 rest: &[u8],
577 block_size: BlockSize,
578 limit: usize,
579) -> Result<(FramedBlock<'_>, &[u8])> {
580 let Some(block_header) = rest.get(..BlockHeader::LEN) else {
581 return Err(Error::TruncatedHeader);
582 };
583 let header = BlockHeader::new(read_u32(block_header), read_u32(&block_header[4..]));
585 let comp_len = header.compressed_size as usize;
586 if comp_len > limit {
587 return Err(Error::InvalidHeader);
588 }
589 let Some(payload) = rest.get(BlockHeader::LEN..BlockHeader::LEN + comp_len) else {
590 return Err(Error::TruncatedData);
591 };
592 if header.original_size > block_size.get() {
593 return Err(Error::InvalidHeader);
594 }
595 Ok((
596 FramedBlock { header, payload },
597 &rest[BlockHeader::LEN + comp_len..],
598 ))
599}
600
601#[cfg(test)]
602mod tests {
603 use super::BlockHeader;
604 use super::StreamBlocks;
605 use super::StreamHeader;
606 use super::append_framed_block;
607 use super::decode_frame;
608 use super::encode_frame;
609 use super::parse_block_header;
610 use super::split_stream;
611 use crate::BlockSize;
612 use crate::Jobs;
613 use crate::block::BlockEncoder;
614 use crate::compress_bound;
615 use crate::error::Error;
616 use crate::stream;
617
618 #[test]
619 fn empty_yields_zero_block_header() {
620 let out = encode_frame(b"", BlockSize::DEFAULT, Jobs::SERIAL).unwrap();
621 assert_eq!(out.len(), 13);
622 assert_eq!(&out[..5], b"BZ3v1");
623 assert_eq!(u32::from_le_bytes(out[5..9].try_into().unwrap()), 65 * 1024);
624 assert_eq!(u32::from_le_bytes(out[9..13].try_into().unwrap()), 0);
625 assert_eq!(decode_frame(&out, Jobs::SERIAL).unwrap(), b"");
626 }
627
628 #[test]
629 fn short_inputs_use_one_stored_block() {
630 let out = encode_frame(b"A", BlockSize::DEFAULT, Jobs::SERIAL).unwrap();
631 assert_eq!(u32::from_le_bytes(out[9..13].try_into().unwrap()), 1);
632 assert_eq!(decode_frame(&out, Jobs::SERIAL).unwrap(), b"A");
633 }
634
635 #[test]
636 fn truncated_payload_reports_truncation() {
637 let mut out = encode_frame(b"A", BlockSize::DEFAULT, Jobs::SERIAL).unwrap();
638 out.truncate(out.len() - 2);
639 assert!(matches!(
640 decode_frame(&out, Jobs::SERIAL),
641 Err(Error::TruncatedData)
642 ));
643 }
644
645 #[test]
646 fn negative_block_size_fails_invalid_block_size() {
647 let mut out = encode_frame(b"A", BlockSize::DEFAULT, Jobs::SERIAL).unwrap();
648 out[5..9].copy_from_slice(&(-1_i32).to_le_bytes());
649 assert!(matches!(
650 decode_frame(&out, Jobs::SERIAL),
651 Err(Error::InvalidBlockSize)
652 ));
653 }
654
655 #[test]
656 fn for_block_rejects_sizes_above_u32() {
657 let huge = u32::MAX as usize + 1;
658 assert!(matches!(
659 BlockHeader::for_block(huge, 0),
660 Err(Error::BlockTooLarge)
661 ));
662 assert!(matches!(
663 BlockHeader::for_block(0, huge),
664 Err(Error::BlockTooLarge)
665 ));
666 assert!(BlockHeader::for_block(10, 100).is_ok());
667 }
668
669 #[test]
670 fn parse_block_header_rejects_oversize() {
671 let size = BlockSize::MIN;
672 let limit = compress_bound(size.as_usize());
673 let mut bad_comp = Vec::new();
674 bad_comp.extend_from_slice(&u32::try_from(limit + 1).unwrap().to_le_bytes());
675 bad_comp.extend_from_slice(&0_u32.to_le_bytes());
676 assert!(matches!(
677 parse_block_header(&bad_comp, size),
678 Err(Error::InvalidHeader)
679 ));
680 let mut bad_orig = Vec::new();
681 bad_orig.extend_from_slice(&0_u32.to_le_bytes());
682 bad_orig.extend_from_slice(&(size.get() + 1).to_le_bytes());
683 assert!(matches!(
684 parse_block_header(&bad_orig, size),
685 Err(Error::InvalidHeader)
686 ));
687 let mut ok = Vec::new();
688 ok.extend_from_slice(&0_u32.to_le_bytes());
689 ok.extend_from_slice(&size.get().to_le_bytes());
690 assert!(parse_block_header(&ok, size).is_ok());
691 }
692
693 #[test]
694 fn split_stream_rejects_short_inputs() {
695 assert!(matches!(split_stream(&[]), Err(Error::TruncatedHeader)));
696 assert!(matches!(
697 split_stream(b"BZ3v1"),
698 Err(Error::TruncatedHeader)
699 ));
700 let mut coded = Vec::new();
701 stream::compress_slice(b"hi", &mut coded, BlockSize::MIN, Jobs::SERIAL).unwrap();
702 let (size, blocks) = split_stream(&coded).unwrap();
703 assert_eq!(size, BlockSize::MIN);
704 assert_eq!(blocks.len(), 1);
705 let mut cut = coded.clone();
706 cut.truncate(StreamHeader::LEN + BlockHeader::LEN - 1);
707 assert!(matches!(split_stream(&cut), Err(Error::TruncatedHeader)));
708 let header = BlockHeader::new(10, 2).to_bytes();
709 let mut short = StreamHeader::new(BlockSize::MIN).to_bytes().to_vec();
710 short.extend_from_slice(&header);
711 short.extend_from_slice(&[0_u8; 2]);
712 assert!(matches!(split_stream(&short), Err(Error::TruncatedData)));
713 let mut bad = StreamHeader::new(BlockSize::MIN).to_bytes().to_vec();
714 let limit = compress_bound(BlockSize::MIN.as_usize()) + 1;
715 bad.extend_from_slice(&u32::try_from(limit).unwrap().to_le_bytes());
716 bad.extend_from_slice(&0_u32.to_le_bytes());
717 assert!(matches!(split_stream(&bad), Err(Error::InvalidHeader)));
718 }
719
720 #[test]
721 fn stream_blocks_empty_rest_yields_none() {
722 let mut coded = Vec::new();
723 stream::compress_slice(b"", &mut coded, BlockSize::MIN, Jobs::SERIAL).unwrap();
724 let mut walk = StreamBlocks::new(&coded).unwrap();
725 assert!(walk.next().is_none());
726 assert!(walk.next().is_none());
727 }
728
729 #[test]
730 fn append_framed_block_rolls_back_on_error() {
731 let mut state = BlockEncoder::new(BlockSize::MIN).unwrap();
732 let big = vec![0_u8; BlockSize::MIN.as_usize() + 1];
733 let mut out = vec![9_u8, 9, 9];
734 assert!(matches!(
735 append_framed_block(&mut state, &big, &mut out),
736 Err(Error::BlockTooLarge)
737 ));
738 assert_eq!(out, vec![9_u8, 9, 9]);
739 let mut ok_out = Vec::new();
740 append_framed_block(&mut state, b"hi", &mut ok_out).unwrap();
741 assert!(ok_out.len() > BlockHeader::LEN);
742 }
743}