Skip to main content

lzf_rust/
framed.rs

1// SPDX-License-Identifier: ISC
2use alloc::vec;
3use alloc::vec::Vec;
4
5use crate::decompress;
6#[cfg(feature = "encoder")]
7use crate::{CompressionMode, compress_with_mode};
8use crate::{Error, Result};
9
10const MAGIC_0: u8 = b'Z';
11const MAGIC_1: u8 = b'V';
12const TYPE_UNCOMPRESSED: u8 = 0;
13const TYPE_COMPRESSED: u8 = 1;
14const TYPE0_HDR_SIZE: usize = 5;
15const TYPE1_HDR_SIZE: usize = 7;
16
17/// Encodes input into `lzf` block stream format (`ZV\0`/`ZV\1` blocks).
18///
19/// `block_size` must be in `1..=65535`.
20///
21/// For each block, compressed payload is used when it fits in the framed
22/// compressed form; otherwise an uncompressed block is emitted.
23#[cfg(feature = "encoder")]
24pub fn encode_blocks(input: &[u8], block_size: usize) -> Result<Vec<u8>> {
25    encode_blocks_with_mode(input, block_size, CompressionMode::Normal)
26}
27
28/// Encodes input into `lzf` block stream format (`ZV\0`/`ZV\1` blocks),
29/// selecting the raw compressor mode.
30///
31/// `block_size` must be in `1..=65535`.
32///
33/// This function is format-compatible with the historical `lzf` utility block
34/// stream representation.
35#[cfg(feature = "encoder")]
36pub fn encode_blocks_with_mode(
37    input: &[u8],
38    block_size: usize,
39    mode: CompressionMode,
40) -> Result<Vec<u8>> {
41    if block_size == 0 || block_size > usize::from(u16::MAX) {
42        return Err(Error::InvalidParameter);
43    }
44
45    let mut output = Vec::new();
46
47    for block in input.chunks(block_size) {
48        let max_try = block.len().saturating_sub(4);
49        let mut compressed = vec![0u8; max_try];
50
51        let encoded_len = if max_try == 0 {
52            Err(Error::OutputTooSmall)
53        } else {
54            compress_with_mode(block, &mut compressed, mode)
55        };
56
57        match encoded_len {
58            Ok(cs) => {
59                let cs_u16 = u16::try_from(cs).map_err(|_| Error::InvalidParameter)?;
60                let us_u16 = u16::try_from(block.len()).map_err(|_| Error::InvalidParameter)?;
61
62                output.push(MAGIC_0);
63                output.push(MAGIC_1);
64                output.push(TYPE_COMPRESSED);
65                output.extend_from_slice(&cs_u16.to_be_bytes());
66                output.extend_from_slice(&us_u16.to_be_bytes());
67                output.extend_from_slice(&compressed[..cs]);
68            }
69            Err(Error::OutputTooSmall) => {
70                let us_u16 = u16::try_from(block.len()).map_err(|_| Error::InvalidParameter)?;
71
72                output.push(MAGIC_0);
73                output.push(MAGIC_1);
74                output.push(TYPE_UNCOMPRESSED);
75                output.extend_from_slice(&us_u16.to_be_bytes());
76                output.extend_from_slice(block);
77            }
78            Err(err) => return Err(err),
79        }
80    }
81
82    Ok(output)
83}
84
85/// Decodes data encoded with `encode_blocks` or the `lzf` utility stream format.
86///
87/// Returns `Error::InvalidHeader` for malformed frame headers and
88/// `Error::UnknownBlockType` for unsupported block type tags.
89///
90/// # Example
91///
92/// ```
93/// use lzf_rust::{decode_blocks, encode_blocks};
94///
95/// let input = b"hello framed world";
96/// let framed = encode_blocks(input, 4096).unwrap();
97/// let decoded = decode_blocks(&framed).unwrap();
98/// assert_eq!(decoded, input);
99/// ```
100pub fn decode_blocks(input: &[u8]) -> Result<Vec<u8>> {
101    let mut ip = 0usize;
102    let mut output = Vec::new();
103
104    while ip < input.len() {
105        if input[ip] == 0 {
106            break;
107        }
108
109        if input.len() - ip < TYPE0_HDR_SIZE {
110            return Err(Error::InvalidHeader);
111        }
112        if input[ip] != MAGIC_0 || input[ip + 1] != MAGIC_1 {
113            return Err(Error::InvalidHeader);
114        }
115
116        let block_type = input[ip + 2];
117        match block_type {
118            TYPE_UNCOMPRESSED => {
119                let uncompressed_len =
120                    usize::from(u16::from_be_bytes([input[ip + 3], input[ip + 4]]));
121                ip += TYPE0_HDR_SIZE;
122                if input.len() - ip < uncompressed_len {
123                    return Err(Error::InvalidData);
124                }
125                output.extend_from_slice(&input[ip..ip + uncompressed_len]);
126                ip += uncompressed_len;
127            }
128            TYPE_COMPRESSED => {
129                if input.len() - ip < TYPE1_HDR_SIZE {
130                    return Err(Error::InvalidHeader);
131                }
132                let compressed_len =
133                    usize::from(u16::from_be_bytes([input[ip + 3], input[ip + 4]]));
134                let uncompressed_len =
135                    usize::from(u16::from_be_bytes([input[ip + 5], input[ip + 6]]));
136                ip += TYPE1_HDR_SIZE;
137
138                if input.len() - ip < compressed_len {
139                    return Err(Error::InvalidData);
140                }
141
142                let mut block = vec![0u8; uncompressed_len];
143                let written = decompress(&input[ip..ip + compressed_len], &mut block)?;
144                if written != uncompressed_len {
145                    return Err(Error::InvalidData);
146                }
147                output.extend_from_slice(&block);
148                ip += compressed_len;
149            }
150            other => return Err(Error::UnknownBlockType(other)),
151        }
152    }
153
154    Ok(output)
155}