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 last_bytes_len = buffer[8];
146        let last_byte_bit_offset = buffer[10];
147        let window_size = buffer[11];
148        let has_new_stream_pending = (buffer[9] & (1 << 6)) != 0;
149        let has_num_bytes_written = (buffer[9] & (1 << 7)) != 0;
150        if last_bytes_len > 2 || last_byte_bit_offset >= 8 {
151            return Err(());
152        }
153        if window_size != 0 && BroCatli::try_new_with_window_size(window_size).is_err() {
154            return Err(());
155        }
156        if has_new_stream_pending {
157            if usize::from(buffer[12]) > NUM_STREAM_HEADER_BYTES {
158                return Err(());
159            }
160            if has_num_bytes_written && buffer[13] > buffer[12] {
161                return Err(());
162            }
163        }
164        let mut possible_new_stream_pending = NewStreamData {
165            num_bytes_read: buffer[12],
166            num_bytes_written: if has_num_bytes_written {
167                Some(buffer[13])
168            } else {
169                None
170            },
171            bytes_so_far: [0; NUM_STREAM_HEADER_BYTES],
172        };
173        let xlen = possible_new_stream_pending.bytes_so_far.len();
174        possible_new_stream_pending
175            .bytes_so_far
176            .clone_from_slice(&buffer[16..16 + xlen]);
177        let new_stream_pending: Option<NewStreamData> = if has_new_stream_pending {
178            Some(possible_new_stream_pending)
179        } else {
180            None
181        };
182        let mut ret = BroCatli {
183            last_bytes: [0, 0],
184            last_bytes_len,
185            last_byte_sanitized: (buffer[9] & 0x1) != 0,
186            last_byte_bit_offset,
187            any_bytes_emitted: (buffer[9] & (1 << 5)) != 0,
188            window_size,
189            new_stream_pending,
190        };
191        if ret.last_bytes.len() > 8 {
192            return Err(());
193        }
194        let xlen = ret.last_bytes.len();
195        ret.last_bytes.clone_from_slice(&buffer[..xlen]);
196        Ok(ret)
197    }
198    #[inline(always)]
199    pub fn serialize_to_buffer(&self, buffer: &mut [u8]) -> Result<(), ()> {
200        if 16 + NUM_STREAM_HEADER_BYTES > buffer.len() {
201            return Err(());
202        }
203        buffer[..self.last_bytes.len()].clone_from_slice(&self.last_bytes[..]);
204        buffer[8] = self.last_bytes_len;
205        buffer[9] = (self.last_byte_sanitized as u8)
206            | ((self.new_stream_pending.is_some() as u8) << 6)
207            | ((self.any_bytes_emitted as u8) << 5);
208        buffer[10] = self.last_byte_bit_offset;
209        buffer[11] = self.window_size;
210        if let Some(new_stream_pending) = self.new_stream_pending {
211            if new_stream_pending.num_bytes_written.is_some() {
212                buffer[9] |= (1 << 7);
213            }
214            buffer[12] = new_stream_pending.num_bytes_read;
215            buffer[13] = new_stream_pending.num_bytes_written.unwrap_or(0);
216            // 14, 15 reserved
217            buffer[16..16 + new_stream_pending.bytes_so_far.len()]
218                .clone_from_slice(&new_stream_pending.bytes_so_far[..]);
219        }
220        Ok(())
221    }
222    /// Creates a `BroCatli` with an initial window size.
223    ///
224    /// Panics for invalid window sizes. Use `try_new_with_window_size` to detect
225    /// invalid input without panicking.
226    pub fn new_with_window_size(log_window_size: u8) -> BroCatli {
227        Self::try_new_with_window_size(log_window_size).expect("invalid brotli window size")
228    }
229
230    /// Creates a `BroCatli` with an initial window size, or returns an error for invalid sizes.
231    pub fn try_new_with_window_size(log_window_size: u8) -> Result<BroCatli, BroCatliResult> {
232        // in this case setup the last_bytes of the stream to perfectly mimic what would
233        // appear in an empty stream with the selected window size...
234        // this means the window size followed by 2 sequential 1 bits (LAST_METABLOCK, EMPTY)
235        // the new_stream code should naturally find the sequential 1 bits and mask them
236        // out and then prepend the window size... then the following window sizes should
237        // be checked to be shorter
238        let last_bytes_len;
239        let last_bytes;
240
241        if log_window_size > 24 {
242            last_bytes = [17u8, log_window_size | 64 | 128];
243            last_bytes_len = 2;
244        } else if log_window_size == 16 {
245            last_bytes = [1 | 2 | 4, 0];
246            last_bytes_len = 1;
247        } else if log_window_size > 17 {
248            last_bytes = [(3 + (log_window_size - 18) * 2) | (16 | 32), 0];
249            last_bytes_len = 1;
250        } else {
251            match log_window_size {
252                15 => last_bytes = [0x71 | 0x80, 1],
253                14 => last_bytes = [0x61 | 0x80, 1],
254                13 => last_bytes = [0x51 | 0x80, 1],
255                12 => last_bytes = [0x41 | 0x80, 1],
256                11 => last_bytes = [0x31 | 0x80, 1],
257                10 => last_bytes = [0x21 | 0x80, 1],
258                17 => last_bytes = [0x1 | 0x80, 1],
259                _ => return Err(BroCatliResult::InvalidWindowSize),
260            }
261            last_bytes_len = 2;
262        }
263        Ok(BroCatli {
264            last_bytes,
265            last_bytes_len,
266            last_byte_bit_offset: 0,
267            last_byte_sanitized: false,
268            any_bytes_emitted: false,
269            new_stream_pending: None,
270            window_size: log_window_size,
271        })
272    }
273
274    pub fn new_brotli_file(&mut self) {
275        self.new_stream_pending = Some(NewStreamData::new());
276    }
277    fn flush_previous_stream(
278        &mut self,
279        out_bytes: &mut [u8],
280        out_offset: &mut usize,
281    ) -> BroCatliResult {
282        if !self.last_byte_sanitized {
283            // if the previous stream hasn't had the last metablock (bit 1,1) sanitized
284            if self.last_bytes_len == 0 {
285                // first stream or otherwise sanitized
286                self.last_byte_sanitized = true;
287                return BroCatliResult::Success;
288            }
289            // create a 16 bit integer with the last 2 bytes of data
290            let mut last_bytes = self.last_bytes[0] as u16 + ((self.last_bytes[1] as u16) << 8);
291            let max = self.last_bytes_len * 8;
292            let mut index = max - 1;
293            for i in 0..max {
294                index = max - 1 - i;
295                if ((1 << index) & last_bytes) != 0 {
296                    break; // find the highest set bit
297                }
298            }
299            if index == 0 {
300                // if the bit is too low, return failure, since both bits could not possibly have been set
301                return BroCatliResult::BrotliFileNotCraftedForAppend;
302            }
303            if (last_bytes >> (index - 1)) != 3 {
304                // last two bits need to be set for the final metablock
305                return BroCatliResult::BrotliFileNotCraftedForAppend;
306            }
307            index -= 1; // discard the final two bits
308            last_bytes &= (1 << index) - 1; // mask them out
309            self.last_bytes[0] = last_bytes as u8; // reset the last_bytes pair
310            self.last_bytes[1] = (last_bytes >> 8) as u8;
311            if index >= 8 {
312                // if both bits and one useful bit were in the second block, then write that
313                if out_bytes.len() > *out_offset {
314                    out_bytes[*out_offset] = self.last_bytes[0];
315                    self.last_bytes[0] = self.last_bytes[1];
316                    *out_offset += 1;
317                    self.any_bytes_emitted = true;
318                    index -= 8;
319                    self.last_bytes_len -= 1;
320                } else {
321                    return BroCatliResult::NeedsMoreOutput;
322                }
323            }
324            self.last_byte_bit_offset = index;
325            assert!(index < 8);
326            self.last_byte_sanitized = true;
327        }
328        BroCatliResult::Success
329    }
330
331    fn shift_and_check_new_stream_header(
332        &mut self,
333        mut new_stream_pending: NewStreamData,
334        out_bytes: &mut [u8],
335        out_offset: &mut usize,
336    ) -> BroCatliResult {
337        if new_stream_pending.num_bytes_written.is_none() {
338            let (window_size, window_offset) = if let Ok(results) = parse_window_size(
339                &new_stream_pending.bytes_so_far[..usize::from(new_stream_pending.num_bytes_read)],
340            ) {
341                results
342            } else {
343                return BroCatliResult::InvalidWindowSize;
344            };
345            if self.window_size == 0 {
346                // parse window size and just copy everything
347                self.window_size = window_size;
348                assert_eq!(self.last_byte_bit_offset, 0); // we are first stream
349                out_bytes[*out_offset] = new_stream_pending.bytes_so_far[0];
350                new_stream_pending.num_bytes_written = Some(1);
351                self.any_bytes_emitted = true;
352                *out_offset += 1;
353            } else {
354                if window_size > self.window_size {
355                    return BroCatliResult::WindowSizeLargerThanPreviousFile;
356                }
357                let mut realigned_header: [u8; NUM_STREAM_HEADER_BYTES + 1] =
358                    [self.last_bytes[0], 0, 0, 0, 0, 0];
359                let varlen_offset = if let Ok(voffset) = detect_varlen_offset(
360                    &new_stream_pending.bytes_so_far
361                        [..usize::from(new_stream_pending.num_bytes_read)],
362                ) {
363                    voffset
364                } else {
365                    return BroCatliResult::BrotliFileNotCraftedForConcatenation;
366                };
367                let mut bytes_so_far = 0u64;
368                for index in 0..usize::from(new_stream_pending.num_bytes_read) {
369                    bytes_so_far |=
370                        u64::from(new_stream_pending.bytes_so_far[index]) << (index * 8);
371                }
372                bytes_so_far >>= window_offset; // mask out the window size
373                bytes_so_far &= (1u64 << (varlen_offset - window_offset)) - 1;
374                let var_len_bytes = (((varlen_offset - window_offset) + 7) / 8);
375                for byte_index in 0..var_len_bytes {
376                    let cur_byte = (bytes_so_far >> (byte_index * 8));
377                    realigned_header[byte_index] |=
378                        ((cur_byte & ((1 << (8 - self.last_byte_bit_offset)) - 1))
379                            << self.last_byte_bit_offset) as u8;
380                    realigned_header[byte_index + 1] =
381                        (cur_byte >> (8 - self.last_byte_bit_offset)) as u8;
382                }
383                let whole_byte_destination =
384                    ((usize::from(self.last_byte_bit_offset) + varlen_offset - window_offset) + 7)
385                        / 8;
386                let whole_byte_source = (varlen_offset + 7) / 8;
387                if whole_byte_source > usize::from(new_stream_pending.num_bytes_read) {
388                    return BroCatliResult::BrotliFileNotCraftedForConcatenation;
389                }
390                let num_whole_bytes_to_copy =
391                    usize::from(new_stream_pending.num_bytes_read) - whole_byte_source;
392                for aligned_index in 0..num_whole_bytes_to_copy {
393                    realigned_header[whole_byte_destination + aligned_index] =
394                        new_stream_pending.bytes_so_far[whole_byte_source + aligned_index];
395                }
396                out_bytes[*out_offset] = realigned_header[0];
397                self.any_bytes_emitted = true;
398                *out_offset += 1;
399                // subtract one since that has just been written out and we're only copying realigned_header[1..]
400                new_stream_pending.num_bytes_read =
401                    (whole_byte_destination + num_whole_bytes_to_copy) as u8 - 1;
402                new_stream_pending.num_bytes_written = Some(0);
403                new_stream_pending
404                    .bytes_so_far
405                    .clone_from_slice(&realigned_header[1..]);
406            }
407        } else {
408            assert_ne!(self.window_size, 0);
409        }
410        let to_copy = min(
411            out_bytes.len() - *out_offset,
412            usize::from(
413                new_stream_pending.num_bytes_read - new_stream_pending.num_bytes_written.unwrap(),
414            ),
415        );
416        out_bytes
417            .split_at_mut(*out_offset)
418            .1
419            .split_at_mut(to_copy)
420            .0
421            .clone_from_slice(
422                new_stream_pending
423                    .bytes_so_far
424                    .split_at(usize::from(new_stream_pending.num_bytes_written.unwrap()))
425                    .1
426                    .split_at(to_copy)
427                    .0,
428            );
429        *out_offset += to_copy;
430        if to_copy != 0 {
431            self.any_bytes_emitted = true;
432        }
433        new_stream_pending.num_bytes_written =
434            Some((new_stream_pending.num_bytes_written.unwrap() + to_copy as u8));
435        if new_stream_pending.num_bytes_written.unwrap() != new_stream_pending.num_bytes_read {
436            self.new_stream_pending = Some(new_stream_pending);
437            return BroCatliResult::NeedsMoreOutput;
438        }
439        self.new_stream_pending = None;
440        self.last_byte_sanitized = false;
441        self.last_byte_bit_offset = 0;
442        self.last_bytes_len = 0;
443        self.last_bytes = [0, 0];
444        //now unwrite from the stream, since the last byte may need to be adjusted to be EOF
445        *out_offset -= 1;
446        self.last_bytes[0] = out_bytes[*out_offset];
447        self.last_bytes_len = 1;
448        BroCatliResult::Success
449    }
450    pub fn stream(
451        &mut self,
452        in_bytes: &[u8],
453        in_offset: &mut usize,
454        out_bytes: &mut [u8],
455        out_offset: &mut usize,
456    ) -> BroCatliResult {
457        if let Some(mut new_stream_pending) = self.new_stream_pending {
458            let flush_result = self.flush_previous_stream(out_bytes, out_offset);
459            if let BroCatliResult::Success = flush_result {
460                if usize::from(new_stream_pending.num_bytes_read)
461                    < new_stream_pending.bytes_so_far.len()
462                {
463                    {
464                        let dst = &mut new_stream_pending.bytes_so_far
465                            [usize::from(new_stream_pending.num_bytes_read)..];
466                        let to_copy = min(dst.len(), in_bytes.len() - *in_offset);
467                        dst[..to_copy]
468                            .clone_from_slice(in_bytes.split_at(*in_offset).1.split_at(to_copy).0);
469                        *in_offset += to_copy;
470                        new_stream_pending.num_bytes_read += to_copy as u8;
471                    }
472                    self.new_stream_pending = Some(new_stream_pending); // write back changes
473                }
474                if !new_stream_pending.sufficient() {
475                    return BroCatliResult::NeedsMoreInput;
476                }
477                if out_bytes.len() == *out_offset {
478                    return BroCatliResult::NeedsMoreOutput;
479                }
480                let shift_result = self.shift_and_check_new_stream_header(
481                    new_stream_pending,
482                    out_bytes,
483                    out_offset,
484                );
485                if let BroCatliResult::Success = shift_result {
486                } else {
487                    return shift_result;
488                }
489            } else {
490                return flush_result;
491            }
492            if *out_offset == out_bytes.len() {
493                return BroCatliResult::NeedsMoreOutput; // need to be able to write at least one byte of data to make progress
494            }
495        }
496        assert!(self.new_stream_pending.is_none()); // this should have been handled above
497        if self.last_bytes_len != 2 {
498            if out_bytes.len() == *out_offset {
499                return BroCatliResult::NeedsMoreOutput;
500            }
501            if in_bytes.len() == *in_offset {
502                return BroCatliResult::NeedsMoreInput;
503            }
504            self.last_bytes[usize::from(self.last_bytes_len)] = in_bytes[*in_offset];
505            *in_offset += 1;
506            self.last_bytes_len += 1;
507            if self.last_bytes_len != 2 {
508                if out_bytes.len() == *out_offset {
509                    return BroCatliResult::NeedsMoreOutput;
510                }
511                if in_bytes.len() == *in_offset {
512                    return BroCatliResult::NeedsMoreInput;
513                }
514                self.last_bytes[usize::from(self.last_bytes_len)] = in_bytes[*in_offset];
515                self.last_bytes_len += 1;
516                *in_offset += 1;
517            }
518        }
519        if out_bytes.len() == *out_offset {
520            return BroCatliResult::NeedsMoreOutput;
521        }
522        if in_bytes.len() == *in_offset {
523            return BroCatliResult::NeedsMoreInput;
524        }
525        let mut to_copy = min(out_bytes.len() - *out_offset, in_bytes.len() - *in_offset);
526        assert_ne!(to_copy, 0);
527        if to_copy == 1 {
528            out_bytes[*out_offset] = self.last_bytes[0];
529            self.last_bytes[0] = self.last_bytes[1];
530            self.last_bytes[1] = in_bytes[*in_offset];
531            *in_offset += 1;
532            *out_offset += 1;
533            if *out_offset == out_bytes.len() {
534                return BroCatliResult::NeedsMoreOutput;
535            }
536            return BroCatliResult::NeedsMoreInput;
537        }
538        out_bytes
539            .split_at_mut(*out_offset)
540            .1
541            .split_at_mut(2)
542            .0
543            .clone_from_slice(&self.last_bytes[..]);
544        *out_offset += 2;
545        let (new_in_offset, last_two) = in_bytes
546            .split_at(*in_offset)
547            .1
548            .split_at(to_copy)
549            .0
550            .split_at(to_copy - 2);
551        self.last_bytes.clone_from_slice(last_two);
552        *in_offset += 2; // add this after the clone since we grab the last 2 bytes, not the first
553        to_copy -= 2;
554        out_bytes
555            .split_at_mut(*out_offset)
556            .1
557            .split_at_mut(to_copy)
558            .0
559            .clone_from_slice(new_in_offset);
560        *out_offset += to_copy;
561        *in_offset += to_copy;
562        if *out_offset == out_bytes.len() {
563            return BroCatliResult::NeedsMoreOutput;
564        }
565        BroCatliResult::NeedsMoreInput
566    }
567    fn append_eof_metablock_to_last_bytes(&mut self) {
568        assert!(self.last_byte_sanitized);
569        let mut last_bytes = self.last_bytes[0] as u16 | ((self.last_bytes[1] as u16) << 8);
570        let bit_end = (self.last_bytes_len - 1) * 8 + self.last_byte_bit_offset;
571        last_bytes |= 3 << bit_end;
572        self.last_bytes[0] = last_bytes as u8;
573        self.last_bytes[1] = (last_bytes >> 8) as u8;
574        self.last_byte_sanitized = false;
575        self.last_byte_bit_offset += 2;
576        if self.last_byte_bit_offset >= 8 {
577            self.last_byte_bit_offset -= 8;
578            self.last_bytes_len += 1;
579        }
580    }
581    pub fn finish(&mut self, out_bytes: &mut [u8], out_offset: &mut usize) -> BroCatliResult {
582        if self.last_byte_sanitized && self.last_bytes_len != 0 {
583            self.append_eof_metablock_to_last_bytes();
584        }
585        while self.last_bytes_len != 0 {
586            if *out_offset == out_bytes.len() {
587                return BroCatliResult::NeedsMoreOutput;
588            }
589            out_bytes[*out_offset] = self.last_bytes[0];
590            *out_offset += 1;
591            self.last_bytes_len -= 1;
592            self.last_bytes[0] = self.last_bytes[1];
593            self.any_bytes_emitted = true;
594        }
595        if !self.any_bytes_emitted {
596            if out_bytes.len() == *out_offset {
597                return BroCatliResult::NeedsMoreOutput;
598            }
599            self.any_bytes_emitted = true;
600            out_bytes[*out_offset] = b';';
601            *out_offset += 1;
602        }
603        BroCatliResult::Success
604    }
605}
606
607#[cfg(test)]
608mod test {
609    use super::BroCatli;
610
611    fn make_valid_serialized_buffer(buffer: &mut [u8]) {
612        buffer[8] = 2;
613        buffer[9] = (1 << 6) | (1 << 7);
614        buffer[10] = 7;
615        buffer[11] = 22;
616        buffer[12] = super::NUM_STREAM_HEADER_BYTES as u8;
617        buffer[13] = 3;
618    }
619
620    #[test]
621    fn test_deserialization() {
622        let broccoli = BroCatli {
623            new_stream_pending: Some(super::NewStreamData {
624                bytes_so_far: [0x33; super::NUM_STREAM_HEADER_BYTES],
625                num_bytes_read: super::NUM_STREAM_HEADER_BYTES as u8,
626                num_bytes_written: Some(3),
627            }),
628            last_bytes: [0x45, 0x46],
629            last_bytes_len: 1,
630            last_byte_sanitized: true,
631            any_bytes_emitted: false,
632            last_byte_bit_offset: 7,
633            window_size: 22,
634        };
635        let mut buffer = [0u8; 248];
636        broccoli.serialize_to_buffer(&mut buffer[..]).unwrap();
637        let bc = BroCatli::deserialize_from_buffer(&buffer[..]).unwrap();
638        assert_eq!(broccoli.last_bytes, bc.last_bytes);
639        assert_eq!(broccoli.last_bytes_len, bc.last_bytes_len);
640        assert_eq!(broccoli.last_byte_sanitized, bc.last_byte_sanitized);
641        assert_eq!(broccoli.last_byte_bit_offset, bc.last_byte_bit_offset);
642        assert_eq!(broccoli.window_size, bc.window_size);
643        assert_eq!(
644            broccoli.new_stream_pending.unwrap().bytes_so_far,
645            bc.new_stream_pending.unwrap().bytes_so_far
646        );
647        assert_eq!(
648            broccoli.new_stream_pending.unwrap().num_bytes_read,
649            bc.new_stream_pending.unwrap().num_bytes_read
650        );
651        assert_eq!(
652            broccoli.new_stream_pending.unwrap().num_bytes_written,
653            bc.new_stream_pending.unwrap().num_bytes_written
654        );
655    }
656    #[test]
657    fn test_deserialization_any_written() {
658        let broccoli = BroCatli {
659            new_stream_pending: Some(super::NewStreamData {
660                bytes_so_far: [0x33; super::NUM_STREAM_HEADER_BYTES],
661                num_bytes_read: super::NUM_STREAM_HEADER_BYTES as u8,
662                num_bytes_written: Some(3),
663            }),
664            last_bytes: [0x45, 0x46],
665            last_bytes_len: 1,
666            last_byte_sanitized: true,
667            any_bytes_emitted: true,
668            last_byte_bit_offset: 7,
669            window_size: 22,
670        };
671        let mut buffer = [0u8; 248];
672        broccoli.serialize_to_buffer(&mut buffer[..]).unwrap();
673        let bc = BroCatli::deserialize_from_buffer(&buffer[..]).unwrap();
674        assert_eq!(broccoli.last_bytes, bc.last_bytes);
675        assert_eq!(broccoli.last_bytes_len, bc.last_bytes_len);
676        assert_eq!(broccoli.last_byte_sanitized, bc.last_byte_sanitized);
677        assert_eq!(broccoli.last_byte_bit_offset, bc.last_byte_bit_offset);
678        assert_eq!(broccoli.window_size, bc.window_size);
679        assert_eq!(
680            broccoli.new_stream_pending.unwrap().bytes_so_far,
681            bc.new_stream_pending.unwrap().bytes_so_far
682        );
683        assert_eq!(
684            broccoli.new_stream_pending.unwrap().num_bytes_read,
685            bc.new_stream_pending.unwrap().num_bytes_read
686        );
687        assert_eq!(
688            broccoli.new_stream_pending.unwrap().num_bytes_written,
689            bc.new_stream_pending.unwrap().num_bytes_written
690        );
691    }
692    #[test]
693    fn test_serialization() {
694        let mut buffer = [0u8; 248];
695        let mut broccoli = BroCatli::deserialize_from_buffer(&buffer).unwrap();
696        let mut buffer2 = [0u8; 248];
697        broccoli.serialize_to_buffer(&mut buffer2[..]).unwrap();
698        assert_eq!(&buffer[..], &buffer2[..]);
699        for (index, item) in buffer.iter_mut().enumerate() {
700            *item = index as u8;
701        }
702        make_valid_serialized_buffer(&mut buffer[..]);
703        broccoli = BroCatli::deserialize_from_buffer(&buffer).unwrap();
704        broccoli.serialize_to_buffer(&mut buffer2[..]).unwrap();
705        broccoli = BroCatli::deserialize_from_buffer(&buffer2).unwrap();
706        for (_index, item) in buffer.iter_mut().enumerate() {
707            *item = 0;
708        }
709        broccoli.serialize_to_buffer(&mut buffer[..]).unwrap();
710        assert_eq!(&buffer[..], &buffer2[..]);
711        for (index, item) in buffer.iter_mut().enumerate() {
712            *item = 0xff ^ index as u8;
713        }
714        make_valid_serialized_buffer(&mut buffer[..]);
715        broccoli = BroCatli::deserialize_from_buffer(&buffer).unwrap();
716        broccoli.serialize_to_buffer(&mut buffer2[..]).unwrap();
717        broccoli = BroCatli::deserialize_from_buffer(&buffer2).unwrap();
718        for (_index, item) in buffer.iter_mut().enumerate() {
719            *item = 0;
720        }
721        broccoli.serialize_to_buffer(&mut buffer[..]).unwrap();
722        assert_eq!(&buffer[..], &buffer2[..]);
723    }
724    #[test]
725    fn test_deserialization_rejects_invalid_state_fields() {
726        let mut buffer = [0u8; 248];
727        make_valid_serialized_buffer(&mut buffer[..]);
728
729        let mut invalid = buffer;
730        invalid[8] = 3;
731        assert!(BroCatli::deserialize_from_buffer(&invalid[..]).is_err());
732
733        invalid = buffer;
734        invalid[10] = 8;
735        assert!(BroCatli::deserialize_from_buffer(&invalid[..]).is_err());
736
737        invalid = buffer;
738        invalid[11] = 9;
739        assert!(BroCatli::deserialize_from_buffer(&invalid[..]).is_err());
740
741        invalid = buffer;
742        invalid[12] = super::NUM_STREAM_HEADER_BYTES as u8 + 1;
743        assert!(BroCatli::deserialize_from_buffer(&invalid[..]).is_err());
744
745        invalid = buffer;
746        invalid[13] = buffer[12] + 1;
747        assert!(BroCatli::deserialize_from_buffer(&invalid[..]).is_err());
748    }
749    #[test]
750    fn test_cat_empty_stream() {
751        let empty_catable = [b';'];
752        let mut bcat = super::BroCatli::default();
753        let mut in_offset = 0usize;
754        let mut out_bytes = [0u8; 32];
755        let mut out_offset = 0usize;
756        bcat.new_brotli_file();
757        let mut res = bcat.stream(
758            &empty_catable[..],
759            &mut in_offset,
760            &mut out_bytes[..],
761            &mut out_offset,
762        );
763        assert_eq!(res, super::BroCatliResult::NeedsMoreInput);
764        bcat.new_brotli_file();
765        in_offset = 0;
766        res = bcat.stream(
767            &empty_catable[..],
768            &mut in_offset,
769            &mut out_bytes[..],
770            &mut out_offset,
771        );
772        assert_eq!(res, super::BroCatliResult::NeedsMoreInput);
773        res = bcat.finish(&mut out_bytes[..], &mut out_offset);
774        assert_eq!(res, super::BroCatliResult::Success);
775        assert_ne!(out_offset, 0);
776        assert_eq!(&out_bytes[..out_offset], &[b';']);
777    }
778    #[test]
779    fn test_cat_truncated_metadata_header_fails() {
780        let empty_catable = [b';'];
781        let mut bcat = super::BroCatli::new_with_window_size(22);
782        let mut in_offset = 0usize;
783        let mut out_bytes = [0u8; 32];
784        let mut out_offset = 0usize;
785        let mut res = bcat.stream(
786            &empty_catable[..],
787            &mut in_offset,
788            &mut out_bytes[..],
789            &mut out_offset,
790        );
791        assert_eq!(res, super::BroCatliResult::NeedsMoreInput);
792
793        let truncated_metadata = [0x71, 0x1b, 0, 0];
794        bcat.new_brotli_file();
795        in_offset = 0;
796        out_offset = 0;
797        res = bcat.stream(
798            &truncated_metadata[..],
799            &mut in_offset,
800            &mut out_bytes[..],
801            &mut out_offset,
802        );
803        assert_eq!(
804            res,
805            super::BroCatliResult::BrotliFileNotCraftedForConcatenation
806        );
807    }
808    #[test]
809    fn test_try_new_with_window_size_invalid_returns_error() {
810        use super::BroCatliResult;
811        // Values 0..=9 are invalid window sizes and must return an error, not panic.
812        for ws in 0u8..=9 {
813            match BroCatli::try_new_with_window_size(ws) {
814                Err(BroCatliResult::InvalidWindowSize) => {}
815                Err(_) => panic!("window_size {} returned wrong error variant", ws),
816                Ok(_) => panic!("window_size {} should be rejected", ws),
817            }
818            #[cfg(feature = "std")]
819            assert!(
820                std::panic::catch_unwind(|| BroCatli::new_with_window_size(ws)).is_err(),
821                "window_size {} should panic through the legacy constructor",
822                ws
823            );
824        }
825    }
826    #[test]
827    fn test_new_with_window_size_valid() {
828        // Values 10..=24 and >24 are valid window sizes.
829        for ws in 10u8..=30 {
830            let _ = BroCatli::new_with_window_size(ws);
831            assert!(
832                BroCatli::try_new_with_window_size(ws).is_ok(),
833                "window_size {} should be accepted",
834                ws
835            );
836        }
837    }
838}