Skip to main content

brotli/concat/
mod.rs

1use core::cmp::min;
2
3#[repr(C)]
4#[derive(Debug, Clone, Copy, PartialEq)]
5pub enum BroCatliResult {
6    Success = 0,
7    NeedsMoreInput = 1,
8    NeedsMoreOutput = 2,
9    BrotliFileNotCraftedForAppend = 124,
10    InvalidWindowSize = 125,
11    WindowSizeLargerThanPreviousFile = 126,
12    BrotliFileNotCraftedForConcatenation = 127,
13}
14
15const NUM_STREAM_HEADER_BYTES: usize = 5;
16
17#[derive(Clone, Copy)]
18struct NewStreamData {
19    bytes_so_far: [u8; NUM_STREAM_HEADER_BYTES],
20    num_bytes_read: u8,
21    num_bytes_written: Option<u8>,
22}
23impl NewStreamData {
24    pub fn new() -> NewStreamData {
25        NewStreamData {
26            bytes_so_far: [0, 0, 0, 0, 0],
27            num_bytes_read: 0,
28            num_bytes_written: None,
29        }
30    }
31    fn sufficient(&self) -> bool {
32        if self.num_bytes_read == 4 && (127 & self.bytes_so_far[0]) != 17 {
33            return true;
34        }
35        self.num_bytes_read == 5
36    }
37}
38
39fn parse_window_size(bytes_so_far: &[u8]) -> Result<(u8, usize), ()> {
40    // returns window_size and offset in stream in bits
41    if bytes_so_far[0] & 1 == 0 {
42        return Ok((16, 1));
43    }
44    match bytes_so_far[0] & 15 {
45        0x3 => return Ok((18, 4)),
46        0x5 => return Ok((19, 4)),
47        0x7 => return Ok((20, 4)),
48        0x9 => return Ok((21, 4)),
49        0xb => return Ok((22, 4)),
50        0xd => return Ok((23, 4)),
51        0xf => return Ok((24, 4)),
52        _ => match bytes_so_far[0] & 127 {
53            0x71 => return Ok((15, 7)),
54            0x61 => return Ok((14, 7)),
55            0x51 => return Ok((13, 7)),
56            0x41 => return Ok((12, 7)),
57            0x31 => return Ok((11, 7)),
58            0x21 => return Ok((10, 7)),
59            0x1 => return Ok((17, 7)),
60            _ => {}
61        },
62    }
63    if (bytes_so_far[0] & 0x80) != 0 {
64        return Err(());
65    }
66    let ret = bytes_so_far[1] & 0x3f;
67    if !(10..=30).contains(&ret) {
68        return Err(());
69    }
70    Ok((ret, 14))
71}
72
73fn detect_varlen_offset(bytes_so_far: &[u8]) -> Result<(usize), ()> {
74    // returns offfset in bits
75    let (_, mut offset) = match parse_window_size(bytes_so_far) {
76        Ok(x) => x,
77        Err(_) => return Err(()),
78    };
79    let mut bytes = 0u64;
80    for (index, item) in bytes_so_far.iter().enumerate() {
81        bytes |= u64::from(*item) << (index * 8);
82    }
83    bytes >>= offset;
84    offset += 1;
85    if (bytes & 1) != 0 {
86        // ISLAST
87        bytes >>= 1;
88        offset += 1;
89        if (bytes & 1) != 0 {
90            // ISLASTEMPTY
91            return Ok(offset);
92        }
93    }
94    bytes >>= 1;
95    let mut mnibbles = bytes & 3;
96    bytes >>= 2;
97    offset += 2;
98    if mnibbles == 3 {
99        // metadata block
100        if (bytes & 1) != 0 {
101            return Err(()); // reserved, must be zero
102        }
103        bytes >>= 1;
104        offset += 1;
105        let mskipbytes = bytes & ((1 << 2) - 1);
106        offset += 2;
107        offset += (mskipbytes as usize) * 8; // next item is byte aligned
108        return Ok(offset);
109    }
110    mnibbles += 4;
111    offset += (mnibbles as usize) * 4;
112    bytes >>= mnibbles * 4;
113    offset += 1;
114    if (bytes & 1) == 0 {
115        // not UNCOMPRESSED
116        Err(()) // not valid bitstream for concatenation
117    } else {
118        // UNCOMPRESSED: now things are aligend
119        Ok(offset)
120    }
121}
122
123// eat your vegetables
124#[derive(Default)]
125pub struct BroCatli {
126    last_bytes: [u8; 2],
127    last_bytes_len: u8,
128    last_byte_sanitized: bool,
129    any_bytes_emitted: bool,
130    last_byte_bit_offset: u8,
131    // need to make sure that window sizes stay similar or get smaller
132    window_size: u8,
133    new_stream_pending: Option<NewStreamData>,
134}
135
136impl BroCatli {
137    pub fn new() -> Self {
138        Self::default()
139    }
140
141    pub fn deserialize_from_buffer(buffer: &[u8]) -> Result<BroCatli, ()> {
142        if 16 + NUM_STREAM_HEADER_BYTES > buffer.len() {
143            return Err(());
144        }
145        let mut possible_new_stream_pending = NewStreamData {
146            num_bytes_read: buffer[12],
147            num_bytes_written: if (buffer[9] & (1 << 7)) != 0 {
148                Some(buffer[13])
149            } else {
150                None
151            },
152            bytes_so_far: [0; NUM_STREAM_HEADER_BYTES],
153        };
154        let xlen = possible_new_stream_pending.bytes_so_far.len();
155        possible_new_stream_pending
156            .bytes_so_far
157            .clone_from_slice(&buffer[16..16 + xlen]);
158        let new_stream_pending: Option<NewStreamData> = if (buffer[9] & (1 << 6)) != 0 {
159            Some(possible_new_stream_pending)
160        } else {
161            None
162        };
163        let mut ret = BroCatli {
164            last_bytes: [0, 0],
165            last_bytes_len: buffer[8],
166            last_byte_sanitized: (buffer[9] & 0x1) != 0,
167            last_byte_bit_offset: buffer[10],
168            any_bytes_emitted: (buffer[9] & (1 << 5)) != 0,
169            window_size: buffer[11],
170            new_stream_pending,
171        };
172        if ret.last_bytes.len() > 8 {
173            return Err(());
174        }
175        let xlen = ret.last_bytes.len();
176        ret.last_bytes.clone_from_slice(&buffer[..xlen]);
177        Ok(ret)
178    }
179    #[inline(always)]
180    pub fn serialize_to_buffer(&self, buffer: &mut [u8]) -> Result<(), ()> {
181        if 16 + NUM_STREAM_HEADER_BYTES > buffer.len() {
182            return Err(());
183        }
184        buffer[..self.last_bytes.len()].clone_from_slice(&self.last_bytes[..]);
185        buffer[8] = self.last_bytes_len;
186        buffer[9] = (self.last_byte_sanitized as u8)
187            | ((self.new_stream_pending.is_some() as u8) << 6)
188            | ((self.any_bytes_emitted as u8) << 5);
189        buffer[10] = self.last_byte_bit_offset;
190        buffer[11] = self.window_size;
191        if let Some(new_stream_pending) = self.new_stream_pending {
192            if new_stream_pending.num_bytes_written.is_some() {
193                buffer[9] |= (1 << 7);
194            }
195            buffer[12] = new_stream_pending.num_bytes_read;
196            buffer[13] = new_stream_pending.num_bytes_written.unwrap_or(0);
197            // 14, 15 reserved
198            buffer[16..16 + new_stream_pending.bytes_so_far.len()]
199                .clone_from_slice(&new_stream_pending.bytes_so_far[..]);
200        }
201        Ok(())
202    }
203    /// Creates a `BroCatli` with an initial window size.
204    ///
205    /// Panics for invalid window sizes. Use `try_new_with_window_size` to detect
206    /// invalid input without panicking.
207    pub fn new_with_window_size(log_window_size: u8) -> BroCatli {
208        Self::try_new_with_window_size(log_window_size).expect("invalid brotli window size")
209    }
210
211    /// Creates a `BroCatli` with an initial window size, or returns an error for invalid sizes.
212    pub fn try_new_with_window_size(log_window_size: u8) -> Result<BroCatli, BroCatliResult> {
213        // in this case setup the last_bytes of the stream to perfectly mimic what would
214        // appear in an empty stream with the selected window size...
215        // this means the window size followed by 2 sequential 1 bits (LAST_METABLOCK, EMPTY)
216        // the new_stream code should naturally find the sequential 1 bits and mask them
217        // out and then prepend the window size... then the following window sizes should
218        // be checked to be shorter
219        let last_bytes_len;
220        let last_bytes;
221
222        if log_window_size > 24 {
223            last_bytes = [17u8, log_window_size | 64 | 128];
224            last_bytes_len = 2;
225        } else if log_window_size == 16 {
226            last_bytes = [1 | 2 | 4, 0];
227            last_bytes_len = 1;
228        } else if log_window_size > 17 {
229            last_bytes = [(3 + (log_window_size - 18) * 2) | (16 | 32), 0];
230            last_bytes_len = 1;
231        } else {
232            match log_window_size {
233                15 => last_bytes = [0x71 | 0x80, 1],
234                14 => last_bytes = [0x61 | 0x80, 1],
235                13 => last_bytes = [0x51 | 0x80, 1],
236                12 => last_bytes = [0x41 | 0x80, 1],
237                11 => last_bytes = [0x31 | 0x80, 1],
238                10 => last_bytes = [0x21 | 0x80, 1],
239                17 => last_bytes = [0x1 | 0x80, 1],
240                _ => return Err(BroCatliResult::InvalidWindowSize),
241            }
242            last_bytes_len = 2;
243        }
244        Ok(BroCatli {
245            last_bytes,
246            last_bytes_len,
247            last_byte_bit_offset: 0,
248            last_byte_sanitized: false,
249            any_bytes_emitted: false,
250            new_stream_pending: None,
251            window_size: log_window_size,
252        })
253    }
254
255    pub fn new_brotli_file(&mut self) {
256        self.new_stream_pending = Some(NewStreamData::new());
257    }
258    fn flush_previous_stream(
259        &mut self,
260        out_bytes: &mut [u8],
261        out_offset: &mut usize,
262    ) -> BroCatliResult {
263        if !self.last_byte_sanitized {
264            // if the previous stream hasn't had the last metablock (bit 1,1) sanitized
265            if self.last_bytes_len == 0 {
266                // first stream or otherwise sanitized
267                self.last_byte_sanitized = true;
268                return BroCatliResult::Success;
269            }
270            // create a 16 bit integer with the last 2 bytes of data
271            let mut last_bytes = self.last_bytes[0] as u16 + ((self.last_bytes[1] as u16) << 8);
272            let max = self.last_bytes_len * 8;
273            let mut index = max - 1;
274            for i in 0..max {
275                index = max - 1 - i;
276                if ((1 << index) & last_bytes) != 0 {
277                    break; // find the highest set bit
278                }
279            }
280            if index == 0 {
281                // if the bit is too low, return failure, since both bits could not possibly have been set
282                return BroCatliResult::BrotliFileNotCraftedForAppend;
283            }
284            if (last_bytes >> (index - 1)) != 3 {
285                // last two bits need to be set for the final metablock
286                return BroCatliResult::BrotliFileNotCraftedForAppend;
287            }
288            index -= 1; // discard the final two bits
289            last_bytes &= (1 << index) - 1; // mask them out
290            self.last_bytes[0] = last_bytes as u8; // reset the last_bytes pair
291            self.last_bytes[1] = (last_bytes >> 8) as u8;
292            if index >= 8 {
293                // if both bits and one useful bit were in the second block, then write that
294                if out_bytes.len() > *out_offset {
295                    out_bytes[*out_offset] = self.last_bytes[0];
296                    self.last_bytes[0] = self.last_bytes[1];
297                    *out_offset += 1;
298                    self.any_bytes_emitted = true;
299                    index -= 8;
300                    self.last_bytes_len -= 1;
301                } else {
302                    return BroCatliResult::NeedsMoreOutput;
303                }
304            }
305            self.last_byte_bit_offset = index;
306            assert!(index < 8);
307            self.last_byte_sanitized = true;
308        }
309        BroCatliResult::Success
310    }
311
312    fn shift_and_check_new_stream_header(
313        &mut self,
314        mut new_stream_pending: NewStreamData,
315        out_bytes: &mut [u8],
316        out_offset: &mut usize,
317    ) -> BroCatliResult {
318        if new_stream_pending.num_bytes_written.is_none() {
319            let (window_size, window_offset) = if let Ok(results) = parse_window_size(
320                &new_stream_pending.bytes_so_far[..usize::from(new_stream_pending.num_bytes_read)],
321            ) {
322                results
323            } else {
324                return BroCatliResult::InvalidWindowSize;
325            };
326            if self.window_size == 0 {
327                // parse window size and just copy everything
328                self.window_size = window_size;
329                assert_eq!(self.last_byte_bit_offset, 0); // we are first stream
330                out_bytes[*out_offset] = new_stream_pending.bytes_so_far[0];
331                new_stream_pending.num_bytes_written = Some(1);
332                self.any_bytes_emitted = true;
333                *out_offset += 1;
334            } else {
335                if window_size > self.window_size {
336                    return BroCatliResult::WindowSizeLargerThanPreviousFile;
337                }
338                let mut realigned_header: [u8; NUM_STREAM_HEADER_BYTES + 1] =
339                    [self.last_bytes[0], 0, 0, 0, 0, 0];
340                let varlen_offset = if let Ok(voffset) = detect_varlen_offset(
341                    &new_stream_pending.bytes_so_far
342                        [..usize::from(new_stream_pending.num_bytes_read)],
343                ) {
344                    voffset
345                } else {
346                    return BroCatliResult::BrotliFileNotCraftedForConcatenation;
347                };
348                let mut bytes_so_far = 0u64;
349                for index in 0..usize::from(new_stream_pending.num_bytes_read) {
350                    bytes_so_far |=
351                        u64::from(new_stream_pending.bytes_so_far[index]) << (index * 8);
352                }
353                bytes_so_far >>= window_offset; // mask out the window size
354                bytes_so_far &= (1u64 << (varlen_offset - window_offset)) - 1;
355                let var_len_bytes = (((varlen_offset - window_offset) + 7) / 8);
356                for byte_index in 0..var_len_bytes {
357                    let cur_byte = (bytes_so_far >> (byte_index * 8));
358                    realigned_header[byte_index] |=
359                        ((cur_byte & ((1 << (8 - self.last_byte_bit_offset)) - 1))
360                            << self.last_byte_bit_offset) as u8;
361                    realigned_header[byte_index + 1] =
362                        (cur_byte >> (8 - self.last_byte_bit_offset)) as u8;
363                }
364                let whole_byte_destination =
365                    ((usize::from(self.last_byte_bit_offset) + varlen_offset - window_offset) + 7)
366                        / 8;
367                let whole_byte_source = (varlen_offset + 7) / 8;
368                let num_whole_bytes_to_copy =
369                    usize::from(new_stream_pending.num_bytes_read) - whole_byte_source;
370                for aligned_index in 0..num_whole_bytes_to_copy {
371                    realigned_header[whole_byte_destination + aligned_index] =
372                        new_stream_pending.bytes_so_far[whole_byte_source + aligned_index];
373                }
374                out_bytes[*out_offset] = realigned_header[0];
375                self.any_bytes_emitted = true;
376                *out_offset += 1;
377                // subtract one since that has just been written out and we're only copying realigned_header[1..]
378                new_stream_pending.num_bytes_read =
379                    (whole_byte_destination + num_whole_bytes_to_copy) as u8 - 1;
380                new_stream_pending.num_bytes_written = Some(0);
381                new_stream_pending
382                    .bytes_so_far
383                    .clone_from_slice(&realigned_header[1..]);
384            }
385        } else {
386            assert_ne!(self.window_size, 0);
387        }
388        let to_copy = min(
389            out_bytes.len() - *out_offset,
390            usize::from(
391                new_stream_pending.num_bytes_read - new_stream_pending.num_bytes_written.unwrap(),
392            ),
393        );
394        out_bytes
395            .split_at_mut(*out_offset)
396            .1
397            .split_at_mut(to_copy)
398            .0
399            .clone_from_slice(
400                new_stream_pending
401                    .bytes_so_far
402                    .split_at(usize::from(new_stream_pending.num_bytes_written.unwrap()))
403                    .1
404                    .split_at(to_copy)
405                    .0,
406            );
407        *out_offset += to_copy;
408        if to_copy != 0 {
409            self.any_bytes_emitted = true;
410        }
411        new_stream_pending.num_bytes_written =
412            Some((new_stream_pending.num_bytes_written.unwrap() + to_copy as u8));
413        if new_stream_pending.num_bytes_written.unwrap() != new_stream_pending.num_bytes_read {
414            self.new_stream_pending = Some(new_stream_pending);
415            return BroCatliResult::NeedsMoreOutput;
416        }
417        self.new_stream_pending = None;
418        self.last_byte_sanitized = false;
419        self.last_byte_bit_offset = 0;
420        self.last_bytes_len = 0;
421        self.last_bytes = [0, 0];
422        //now unwrite from the stream, since the last byte may need to be adjusted to be EOF
423        *out_offset -= 1;
424        self.last_bytes[0] = out_bytes[*out_offset];
425        self.last_bytes_len = 1;
426        BroCatliResult::Success
427    }
428    pub fn stream(
429        &mut self,
430        in_bytes: &[u8],
431        in_offset: &mut usize,
432        out_bytes: &mut [u8],
433        out_offset: &mut usize,
434    ) -> BroCatliResult {
435        if let Some(mut new_stream_pending) = self.new_stream_pending {
436            let flush_result = self.flush_previous_stream(out_bytes, out_offset);
437            if let BroCatliResult::Success = flush_result {
438                if usize::from(new_stream_pending.num_bytes_read)
439                    < new_stream_pending.bytes_so_far.len()
440                {
441                    {
442                        let dst = &mut new_stream_pending.bytes_so_far
443                            [usize::from(new_stream_pending.num_bytes_read)..];
444                        let to_copy = min(dst.len(), in_bytes.len() - *in_offset);
445                        dst[..to_copy]
446                            .clone_from_slice(in_bytes.split_at(*in_offset).1.split_at(to_copy).0);
447                        *in_offset += to_copy;
448                        new_stream_pending.num_bytes_read += to_copy as u8;
449                    }
450                    self.new_stream_pending = Some(new_stream_pending); // write back changes
451                }
452                if !new_stream_pending.sufficient() {
453                    return BroCatliResult::NeedsMoreInput;
454                }
455                if out_bytes.len() == *out_offset {
456                    return BroCatliResult::NeedsMoreOutput;
457                }
458                let shift_result = self.shift_and_check_new_stream_header(
459                    new_stream_pending,
460                    out_bytes,
461                    out_offset,
462                );
463                if let BroCatliResult::Success = shift_result {
464                } else {
465                    return shift_result;
466                }
467            } else {
468                return flush_result;
469            }
470            if *out_offset == out_bytes.len() {
471                return BroCatliResult::NeedsMoreOutput; // need to be able to write at least one byte of data to make progress
472            }
473        }
474        assert!(self.new_stream_pending.is_none()); // this should have been handled above
475        if self.last_bytes_len != 2 {
476            if out_bytes.len() == *out_offset {
477                return BroCatliResult::NeedsMoreOutput;
478            }
479            if in_bytes.len() == *in_offset {
480                return BroCatliResult::NeedsMoreInput;
481            }
482            self.last_bytes[usize::from(self.last_bytes_len)] = in_bytes[*in_offset];
483            *in_offset += 1;
484            self.last_bytes_len += 1;
485            if self.last_bytes_len != 2 {
486                if out_bytes.len() == *out_offset {
487                    return BroCatliResult::NeedsMoreOutput;
488                }
489                if in_bytes.len() == *in_offset {
490                    return BroCatliResult::NeedsMoreInput;
491                }
492                self.last_bytes[usize::from(self.last_bytes_len)] = in_bytes[*in_offset];
493                self.last_bytes_len += 1;
494                *in_offset += 1;
495            }
496        }
497        if out_bytes.len() == *out_offset {
498            return BroCatliResult::NeedsMoreOutput;
499        }
500        if in_bytes.len() == *in_offset {
501            return BroCatliResult::NeedsMoreInput;
502        }
503        let mut to_copy = min(out_bytes.len() - *out_offset, in_bytes.len() - *in_offset);
504        assert_ne!(to_copy, 0);
505        if to_copy == 1 {
506            out_bytes[*out_offset] = self.last_bytes[0];
507            self.last_bytes[0] = self.last_bytes[1];
508            self.last_bytes[1] = in_bytes[*in_offset];
509            *in_offset += 1;
510            *out_offset += 1;
511            if *out_offset == out_bytes.len() {
512                return BroCatliResult::NeedsMoreOutput;
513            }
514            return BroCatliResult::NeedsMoreInput;
515        }
516        out_bytes
517            .split_at_mut(*out_offset)
518            .1
519            .split_at_mut(2)
520            .0
521            .clone_from_slice(&self.last_bytes[..]);
522        *out_offset += 2;
523        let (new_in_offset, last_two) = in_bytes
524            .split_at(*in_offset)
525            .1
526            .split_at(to_copy)
527            .0
528            .split_at(to_copy - 2);
529        self.last_bytes.clone_from_slice(last_two);
530        *in_offset += 2; // add this after the clone since we grab the last 2 bytes, not the first
531        to_copy -= 2;
532        out_bytes
533            .split_at_mut(*out_offset)
534            .1
535            .split_at_mut(to_copy)
536            .0
537            .clone_from_slice(new_in_offset);
538        *out_offset += to_copy;
539        *in_offset += to_copy;
540        if *out_offset == out_bytes.len() {
541            return BroCatliResult::NeedsMoreOutput;
542        }
543        BroCatliResult::NeedsMoreInput
544    }
545    fn append_eof_metablock_to_last_bytes(&mut self) {
546        assert!(self.last_byte_sanitized);
547        let mut last_bytes = self.last_bytes[0] as u16 | ((self.last_bytes[1] as u16) << 8);
548        let bit_end = (self.last_bytes_len - 1) * 8 + self.last_byte_bit_offset;
549        last_bytes |= 3 << bit_end;
550        self.last_bytes[0] = last_bytes as u8;
551        self.last_bytes[1] = (last_bytes >> 8) as u8;
552        self.last_byte_sanitized = false;
553        self.last_byte_bit_offset += 2;
554        if self.last_byte_bit_offset >= 8 {
555            self.last_byte_bit_offset -= 8;
556            self.last_bytes_len += 1;
557        }
558    }
559    pub fn finish(&mut self, out_bytes: &mut [u8], out_offset: &mut usize) -> BroCatliResult {
560        if self.last_byte_sanitized && self.last_bytes_len != 0 {
561            self.append_eof_metablock_to_last_bytes();
562        }
563        while self.last_bytes_len != 0 {
564            if *out_offset == out_bytes.len() {
565                return BroCatliResult::NeedsMoreOutput;
566            }
567            out_bytes[*out_offset] = self.last_bytes[0];
568            *out_offset += 1;
569            self.last_bytes_len -= 1;
570            self.last_bytes[0] = self.last_bytes[1];
571            self.any_bytes_emitted = true;
572        }
573        if !self.any_bytes_emitted {
574            if out_bytes.len() == *out_offset {
575                return BroCatliResult::NeedsMoreOutput;
576            }
577            self.any_bytes_emitted = true;
578            out_bytes[*out_offset] = b';';
579            *out_offset += 1;
580        }
581        BroCatliResult::Success
582    }
583}
584
585#[cfg(test)]
586mod test {
587    use super::BroCatli;
588
589    #[test]
590    fn test_deserialization() {
591        let broccoli = BroCatli {
592            new_stream_pending: Some(super::NewStreamData {
593                bytes_so_far: [0x33; super::NUM_STREAM_HEADER_BYTES],
594                num_bytes_read: 16,
595                num_bytes_written: Some(3),
596            }),
597            last_bytes: [0x45, 0x46],
598            last_bytes_len: 1,
599            last_byte_sanitized: true,
600            any_bytes_emitted: false,
601            last_byte_bit_offset: 7,
602            window_size: 22,
603        };
604        let mut buffer = [0u8; 248];
605        broccoli.serialize_to_buffer(&mut buffer[..]).unwrap();
606        let bc = BroCatli::deserialize_from_buffer(&buffer[..]).unwrap();
607        assert_eq!(broccoli.last_bytes, bc.last_bytes);
608        assert_eq!(broccoli.last_bytes_len, bc.last_bytes_len);
609        assert_eq!(broccoli.last_byte_sanitized, bc.last_byte_sanitized);
610        assert_eq!(broccoli.last_byte_bit_offset, bc.last_byte_bit_offset);
611        assert_eq!(broccoli.window_size, bc.window_size);
612        assert_eq!(
613            broccoli.new_stream_pending.unwrap().bytes_so_far,
614            bc.new_stream_pending.unwrap().bytes_so_far
615        );
616        assert_eq!(
617            broccoli.new_stream_pending.unwrap().num_bytes_read,
618            bc.new_stream_pending.unwrap().num_bytes_read
619        );
620        assert_eq!(
621            broccoli.new_stream_pending.unwrap().num_bytes_written,
622            bc.new_stream_pending.unwrap().num_bytes_written
623        );
624    }
625    #[test]
626    fn test_deserialization_any_written() {
627        let broccoli = BroCatli {
628            new_stream_pending: Some(super::NewStreamData {
629                bytes_so_far: [0x33; super::NUM_STREAM_HEADER_BYTES],
630                num_bytes_read: 16,
631                num_bytes_written: Some(3),
632            }),
633            last_bytes: [0x45, 0x46],
634            last_bytes_len: 1,
635            last_byte_sanitized: true,
636            any_bytes_emitted: true,
637            last_byte_bit_offset: 7,
638            window_size: 22,
639        };
640        let mut buffer = [0u8; 248];
641        broccoli.serialize_to_buffer(&mut buffer[..]).unwrap();
642        let bc = BroCatli::deserialize_from_buffer(&buffer[..]).unwrap();
643        assert_eq!(broccoli.last_bytes, bc.last_bytes);
644        assert_eq!(broccoli.last_bytes_len, bc.last_bytes_len);
645        assert_eq!(broccoli.last_byte_sanitized, bc.last_byte_sanitized);
646        assert_eq!(broccoli.last_byte_bit_offset, bc.last_byte_bit_offset);
647        assert_eq!(broccoli.window_size, bc.window_size);
648        assert_eq!(
649            broccoli.new_stream_pending.unwrap().bytes_so_far,
650            bc.new_stream_pending.unwrap().bytes_so_far
651        );
652        assert_eq!(
653            broccoli.new_stream_pending.unwrap().num_bytes_read,
654            bc.new_stream_pending.unwrap().num_bytes_read
655        );
656        assert_eq!(
657            broccoli.new_stream_pending.unwrap().num_bytes_written,
658            bc.new_stream_pending.unwrap().num_bytes_written
659        );
660    }
661    #[test]
662    fn test_serialization() {
663        let mut buffer = [0u8; 248];
664        let mut broccoli = BroCatli::deserialize_from_buffer(&buffer).unwrap();
665        let mut buffer2 = [0u8; 248];
666        broccoli.serialize_to_buffer(&mut buffer2[..]).unwrap();
667        assert_eq!(&buffer[..], &buffer2[..]);
668        for (index, item) in buffer.iter_mut().enumerate() {
669            *item = index as u8;
670        }
671        broccoli = BroCatli::deserialize_from_buffer(&buffer).unwrap();
672        broccoli.serialize_to_buffer(&mut buffer2[..]).unwrap();
673        broccoli = BroCatli::deserialize_from_buffer(&buffer2).unwrap();
674        for (_index, item) in buffer.iter_mut().enumerate() {
675            *item = 0;
676        }
677        broccoli.serialize_to_buffer(&mut buffer[..]).unwrap();
678        assert_eq!(&buffer[..], &buffer2[..]);
679        for (index, item) in buffer.iter_mut().enumerate() {
680            *item = 0xff ^ index as u8;
681        }
682        broccoli = BroCatli::deserialize_from_buffer(&buffer).unwrap();
683        broccoli.serialize_to_buffer(&mut buffer2[..]).unwrap();
684        broccoli = BroCatli::deserialize_from_buffer(&buffer2).unwrap();
685        for (_index, item) in buffer.iter_mut().enumerate() {
686            *item = 0;
687        }
688        broccoli.serialize_to_buffer(&mut buffer[..]).unwrap();
689        assert_eq!(&buffer[..], &buffer2[..]);
690    }
691    #[test]
692    fn test_cat_empty_stream() {
693        let empty_catable = [b';'];
694        let mut bcat = super::BroCatli::default();
695        let mut in_offset = 0usize;
696        let mut out_bytes = [0u8; 32];
697        let mut out_offset = 0usize;
698        bcat.new_brotli_file();
699        let mut res = bcat.stream(
700            &empty_catable[..],
701            &mut in_offset,
702            &mut out_bytes[..],
703            &mut out_offset,
704        );
705        assert_eq!(res, super::BroCatliResult::NeedsMoreInput);
706        bcat.new_brotli_file();
707        in_offset = 0;
708        res = bcat.stream(
709            &empty_catable[..],
710            &mut in_offset,
711            &mut out_bytes[..],
712            &mut out_offset,
713        );
714        assert_eq!(res, super::BroCatliResult::NeedsMoreInput);
715        res = bcat.finish(&mut out_bytes[..], &mut out_offset);
716        assert_eq!(res, super::BroCatliResult::Success);
717        assert_ne!(out_offset, 0);
718        assert_eq!(&out_bytes[..out_offset], &[b';']);
719    }
720    #[test]
721    fn test_try_new_with_window_size_invalid_returns_error() {
722        use super::BroCatliResult;
723        // Values 0..=9 are invalid window sizes and must return an error, not panic.
724        for ws in 0u8..=9 {
725            match BroCatli::try_new_with_window_size(ws) {
726                Err(BroCatliResult::InvalidWindowSize) => {}
727                Err(_) => panic!("window_size {} returned wrong error variant", ws),
728                Ok(_) => panic!("window_size {} should be rejected", ws),
729            }
730            #[cfg(feature = "std")]
731            assert!(
732                std::panic::catch_unwind(|| BroCatli::new_with_window_size(ws)).is_err(),
733                "window_size {} should panic through the legacy constructor",
734                ws
735            );
736        }
737    }
738    #[test]
739    fn test_new_with_window_size_valid() {
740        // Values 10..=24 and >24 are valid window sizes.
741        for ws in 10u8..=30 {
742            let _ = BroCatli::new_with_window_size(ws);
743            assert!(
744                BroCatli::try_new_with_window_size(ws).is_ok(),
745                "window_size {} should be accepted",
746                ws
747            );
748        }
749    }
750}