@file-viewer/renderer-chm 3.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,632 @@
1
+ //! Bounded CHM LZX decoder.
2
+ //!
3
+ //! Adapted from the MIT clean-room implementation in RustChm/FastChm. This version
4
+ //! turns every bitstream boundary into a checked result and never allocates more than
5
+ //! the caller-requested reset window.
6
+
7
+ use crate::error::{ParseError, ParseResult};
8
+
9
+ const NUM_CHARS: usize = 256;
10
+ const PRETREE_SYMS: usize = 20;
11
+ const ALIGNED_SYMS: usize = 8;
12
+ const LEN_SYMS: usize = 249;
13
+ const MIN_MATCH: usize = 2;
14
+ const NUM_PRIMARY_LENGTHS: usize = 7;
15
+ const FRAME: u64 = 0x8000;
16
+ const INTEL_TRANSFORM_LIMIT: u64 = FRAME * 32_768;
17
+
18
+ const EXTRA_BITS: [u8; 51] = [
19
+ 0, 0, 0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8, 9, 9, 10, 10, 11, 11, 12, 12, 13,
20
+ 13, 14, 14, 15, 15, 16, 16, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17,
21
+ ];
22
+ const POS_BASE: [u32; 51] = [
23
+ 0, 1, 2, 3, 4, 6, 8, 12, 16, 24, 32, 48, 64, 96, 128, 192, 256, 384, 512, 768, 1024, 1536,
24
+ 2048, 3072, 4096, 6144, 8192, 12288, 16384, 24576, 32768, 49152, 65536, 98304, 131072, 196608,
25
+ 262144, 393216, 524288, 655360, 786432, 917504, 1048576, 1179648, 1310720, 1441792, 1572864,
26
+ 1703936, 1835008, 1966080, 2097152,
27
+ ];
28
+
29
+ struct BitReader<'a> {
30
+ input: &'a [u8],
31
+ position: usize,
32
+ buffer: u64,
33
+ count: u32,
34
+ }
35
+
36
+ impl<'a> BitReader<'a> {
37
+ fn new(input: &'a [u8]) -> Self {
38
+ Self {
39
+ input,
40
+ position: 0,
41
+ buffer: 0,
42
+ count: 0,
43
+ }
44
+ }
45
+
46
+ fn ensure(&mut self, bits: u32) -> ParseResult<()> {
47
+ if bits > 32 {
48
+ return Err(ParseError::Lzx("bit read is wider than 32 bits"));
49
+ }
50
+ while self.count < bits {
51
+ let Some(&low) = self.input.get(self.position) else {
52
+ return Err(ParseError::Lzx("unexpected end of compressed input"));
53
+ };
54
+ let high = self.input.get(self.position + 1).copied().unwrap_or(0);
55
+ self.position = (self.position + 2).min(self.input.len());
56
+ let word = u64::from(low) | (u64::from(high) << 8);
57
+ self.buffer = (self.buffer << 16) | word;
58
+ self.count += 16;
59
+ }
60
+ Ok(())
61
+ }
62
+
63
+ fn read(&mut self, bits: u32) -> ParseResult<u32> {
64
+ if bits == 0 {
65
+ return Ok(0);
66
+ }
67
+ self.ensure(bits)?;
68
+ let mask = if bits == 32 {
69
+ u64::from(u32::MAX)
70
+ } else {
71
+ (1u64 << bits) - 1
72
+ };
73
+ let value = (self.buffer >> (self.count - bits)) & mask;
74
+ self.count -= bits;
75
+ Ok(value as u32)
76
+ }
77
+
78
+ fn align_to_word(&mut self) {
79
+ self.count -= self.count % 16;
80
+ }
81
+
82
+ fn read_bytes(&mut self, output: &mut [u8]) -> ParseResult<()> {
83
+ let mut index = 0usize;
84
+ while index < output.len() && self.count >= 8 {
85
+ output[index] = ((self.buffer >> (self.count - 8)) & 0xff) as u8;
86
+ self.count -= 8;
87
+ index += 1;
88
+ }
89
+ let remaining = output.len() - index;
90
+ let end = self
91
+ .position
92
+ .checked_add(remaining)
93
+ .ok_or(ParseError::Overflow)?;
94
+ let source = self
95
+ .input
96
+ .get(self.position..end)
97
+ .ok_or(ParseError::Lzx("uncompressed block exceeds input"))?;
98
+ output[index..].copy_from_slice(source);
99
+ self.position = end;
100
+ Ok(())
101
+ }
102
+ }
103
+
104
+ struct HuffDecoder {
105
+ count: [u32; 18],
106
+ first_code: [u32; 18],
107
+ first_index: [u32; 18],
108
+ max_exclusive: [u32; 18],
109
+ symbols: Vec<u16>,
110
+ }
111
+
112
+ impl HuffDecoder {
113
+ fn new() -> Self {
114
+ Self {
115
+ count: [0; 18],
116
+ first_code: [0; 18],
117
+ first_index: [0; 18],
118
+ max_exclusive: [0; 18],
119
+ symbols: Vec::new(),
120
+ }
121
+ }
122
+
123
+ fn build(&mut self, lengths: &[u8], symbol_count: usize) -> ParseResult<()> {
124
+ let lengths = lengths
125
+ .get(..symbol_count)
126
+ .ok_or(ParseError::Lzx("Huffman length table is truncated"))?;
127
+ self.count = [0; 18];
128
+ for &length in lengths {
129
+ if length > 16 {
130
+ return Err(ParseError::Lzx("Huffman code length exceeds 16"));
131
+ }
132
+ self.count[usize::from(length)] += 1;
133
+ }
134
+ let mut code = 0u32;
135
+ let mut index = 0u32;
136
+ for length in 1..=16 {
137
+ let available = 1u32 << length;
138
+ if code
139
+ .checked_add(self.count[length])
140
+ .is_none_or(|end| end > available)
141
+ {
142
+ return Err(ParseError::Lzx("oversubscribed Huffman tree"));
143
+ }
144
+ self.first_code[length] = code;
145
+ self.first_index[length] = index;
146
+ index = index
147
+ .checked_add(self.count[length])
148
+ .ok_or(ParseError::Overflow)?;
149
+ self.max_exclusive[length] = code + self.count[length];
150
+ code = (code + self.count[length]) << 1;
151
+ }
152
+ self.symbols = vec![0; usize::try_from(index).map_err(|_| ParseError::Overflow)?];
153
+ let mut next = self.first_index;
154
+ for (symbol, &length) in lengths.iter().enumerate() {
155
+ if length != 0 {
156
+ let length_index = usize::from(length);
157
+ let target =
158
+ usize::try_from(next[length_index]).map_err(|_| ParseError::Overflow)?;
159
+ *self
160
+ .symbols
161
+ .get_mut(target)
162
+ .ok_or(ParseError::Lzx("Huffman symbol table overflow"))? =
163
+ u16::try_from(symbol).map_err(|_| ParseError::Overflow)?;
164
+ next[length_index] += 1;
165
+ }
166
+ }
167
+ Ok(())
168
+ }
169
+
170
+ fn decode(&self, reader: &mut BitReader<'_>) -> ParseResult<usize> {
171
+ let mut code = 0u32;
172
+ for length in 1..=16 {
173
+ code = (code << 1) | reader.read(1)?;
174
+ if self.count[length] != 0
175
+ && code >= self.first_code[length]
176
+ && code < self.max_exclusive[length]
177
+ {
178
+ let index = self.first_index[length] + (code - self.first_code[length]);
179
+ return self
180
+ .symbols
181
+ .get(usize::try_from(index).map_err(|_| ParseError::Overflow)?)
182
+ .copied()
183
+ .map(usize::from)
184
+ .ok_or(ParseError::Lzx("Huffman symbol is out of range"));
185
+ }
186
+ }
187
+ Err(ParseError::Lzx("invalid Huffman code"))
188
+ }
189
+ }
190
+
191
+ fn read_lengths(reader: &mut BitReader<'_>, lengths: &mut [u8], size: usize) -> ParseResult<()> {
192
+ if size > lengths.len() {
193
+ return Err(ParseError::Lzx("code length output is truncated"));
194
+ }
195
+ let mut pretree_lengths = [0u8; PRETREE_SYMS];
196
+ for length in &mut pretree_lengths {
197
+ *length = reader.read(4)? as u8;
198
+ }
199
+ let mut pretree = HuffDecoder::new();
200
+ pretree.build(&pretree_lengths, PRETREE_SYMS)?;
201
+ let mut index = 0usize;
202
+ while index < size {
203
+ let symbol = pretree.decode(reader)?;
204
+ match symbol {
205
+ 17 | 18 => {
206
+ let extra = if symbol == 17 {
207
+ reader.read(4)? + 4
208
+ } else {
209
+ reader.read(5)? + 20
210
+ };
211
+ let run = usize::try_from(extra).map_err(|_| ParseError::Overflow)?;
212
+ let end = index.checked_add(run).ok_or(ParseError::Overflow)?;
213
+ if end > size {
214
+ return Err(ParseError::Lzx("zero code-length run exceeds table"));
215
+ }
216
+ lengths[index..end].fill(0);
217
+ index = end;
218
+ }
219
+ 19 => {
220
+ let run = usize::try_from(reader.read(1)? + 4).map_err(|_| ParseError::Overflow)?;
221
+ let delta = pretree.decode(reader)?;
222
+ let end = index.checked_add(run).ok_or(ParseError::Overflow)?;
223
+ if end > size {
224
+ return Err(ParseError::Lzx("repeated code-length run exceeds table"));
225
+ }
226
+ let new_length = ((i32::from(lengths[index]) - delta as i32 + 17) % 17) as u8;
227
+ lengths[index..end].fill(new_length);
228
+ index = end;
229
+ }
230
+ delta if delta <= 16 => {
231
+ lengths[index] = ((i32::from(lengths[index]) - delta as i32 + 17) % 17) as u8;
232
+ index += 1;
233
+ }
234
+ _ => return Err(ParseError::Lzx("invalid pretree symbol")),
235
+ }
236
+ }
237
+ Ok(())
238
+ }
239
+
240
+ pub fn decompress_reset_window(
241
+ compressed: &[u8],
242
+ uncompressed_size: u64,
243
+ reset_interval: u32,
244
+ window_bits: u32,
245
+ absolute_output_start: u64,
246
+ ) -> ParseResult<Vec<u8>> {
247
+ static SLOTS: [usize; 7] = [30, 32, 34, 36, 38, 42, 50];
248
+ if !(15..=21).contains(&window_bits) {
249
+ return Err(ParseError::Lzx("unsupported window size"));
250
+ }
251
+ if reset_interval == 0 || u64::from(reset_interval) < FRAME {
252
+ return Err(ParseError::Lzx("invalid reset interval"));
253
+ }
254
+ let target = usize::try_from(uncompressed_size).map_err(|_| ParseError::Overflow)?;
255
+ if uncompressed_size > u64::from(reset_interval) {
256
+ return Err(ParseError::Lzx("requested output exceeds one reset window"));
257
+ }
258
+ let slot_count = SLOTS[usize::try_from(window_bits - 15).map_err(|_| ParseError::Overflow)?];
259
+ let main_symbols = NUM_CHARS + 8 * slot_count;
260
+ let mut output = Vec::with_capacity(target);
261
+ let mut reader = BitReader::new(compressed);
262
+ let (mut r0, mut r1, mut r2) = (1i32, 1i32, 1i32);
263
+ let mut main_lengths = vec![0u8; main_symbols];
264
+ let mut length_lengths = vec![0u8; LEN_SYMS];
265
+ let mut aligned_lengths = [0u8; ALIGNED_SYMS];
266
+ let mut main_tree = HuffDecoder::new();
267
+ let mut length_tree = HuffDecoder::new();
268
+ let mut aligned_tree = HuffDecoder::new();
269
+ let mut produced = 0u64;
270
+ let mut block_remaining = 0u64;
271
+ let mut frame_remaining = FRAME;
272
+ let mut block_type = 0u32;
273
+ let mut uncompressed_block_needs_pad = false;
274
+
275
+ let intel_filesize = if reader.read(1)? != 0 {
276
+ let high = reader.read(16)?;
277
+ let low = reader.read(16)?;
278
+ Some((high << 16) | low)
279
+ } else {
280
+ None
281
+ };
282
+ while produced < uncompressed_size {
283
+ if block_remaining == 0 {
284
+ block_type = reader.read(3)?;
285
+ block_remaining = u64::from(reader.read(24)?);
286
+ if block_remaining == 0 {
287
+ return Err(ParseError::Lzx("zero-length block"));
288
+ }
289
+ match block_type {
290
+ 1 | 2 => {
291
+ if block_type == 2 {
292
+ for length in &mut aligned_lengths {
293
+ *length = reader.read(3)? as u8;
294
+ }
295
+ }
296
+ read_lengths(&mut reader, &mut main_lengths, NUM_CHARS)?;
297
+ let tail_len = main_symbols - NUM_CHARS;
298
+ read_lengths(&mut reader, &mut main_lengths[NUM_CHARS..], tail_len)?;
299
+ read_lengths(&mut reader, &mut length_lengths, LEN_SYMS)?;
300
+ main_tree.build(&main_lengths, main_symbols)?;
301
+ length_tree.build(&length_lengths, LEN_SYMS)?;
302
+ if block_type == 2 {
303
+ aligned_tree.build(&aligned_lengths, ALIGNED_SYMS)?;
304
+ }
305
+ }
306
+ 3 => {
307
+ uncompressed_block_needs_pad = block_remaining % 2 != 0;
308
+ reader.align_to_word();
309
+ let mut recent = [0u8; 12];
310
+ reader.read_bytes(&mut recent)?;
311
+ r0 = i32::from_le_bytes(recent[0..4].try_into().expect("fixed slice"));
312
+ r1 = i32::from_le_bytes(recent[4..8].try_into().expect("fixed slice"));
313
+ r2 = i32::from_le_bytes(recent[8..12].try_into().expect("fixed slice"));
314
+ }
315
+ _ => return Err(ParseError::Lzx("unsupported block type")),
316
+ }
317
+ }
318
+
319
+ if block_type == 3 {
320
+ let chunk = block_remaining
321
+ .min(frame_remaining)
322
+ .min(uncompressed_size - produced);
323
+ let chunk = usize::try_from(chunk).map_err(|_| ParseError::Overflow)?;
324
+ let start = output.len();
325
+ output.resize(start + chunk, 0);
326
+ reader.read_bytes(&mut output[start..])?;
327
+ produced += chunk as u64;
328
+ block_remaining -= chunk as u64;
329
+ frame_remaining -= chunk as u64;
330
+ if block_remaining == 0 && uncompressed_block_needs_pad && produced < uncompressed_size
331
+ {
332
+ let mut padding = [0u8; 1];
333
+ reader.read_bytes(&mut padding)?;
334
+ uncompressed_block_needs_pad = false;
335
+ }
336
+ } else {
337
+ let symbol = main_tree.decode(&mut reader)?;
338
+ if symbol < NUM_CHARS {
339
+ if block_remaining == 0 || frame_remaining == 0 {
340
+ return Err(ParseError::Lzx("literal crosses a block or frame boundary"));
341
+ }
342
+ output.push(symbol as u8);
343
+ produced += 1;
344
+ block_remaining -= 1;
345
+ frame_remaining -= 1;
346
+ } else {
347
+ let slot = (symbol - NUM_CHARS) >> 3;
348
+ let length_header = (symbol - NUM_CHARS) & 7;
349
+ let mut match_length = length_header + MIN_MATCH;
350
+ if length_header == NUM_PRIMARY_LENGTHS {
351
+ match_length =
352
+ length_tree.decode(&mut reader)? + NUM_PRIMARY_LENGTHS + MIN_MATCH;
353
+ }
354
+ let match_offset = match slot {
355
+ 0 => r0,
356
+ 1 => {
357
+ std::mem::swap(&mut r0, &mut r1);
358
+ r0
359
+ }
360
+ 2 => {
361
+ std::mem::swap(&mut r0, &mut r2);
362
+ r0
363
+ }
364
+ _ => {
365
+ let extra_bits = *EXTRA_BITS
366
+ .get(slot)
367
+ .ok_or(ParseError::Lzx("match slot is out of range"))?;
368
+ let footer = if block_type == 2 && extra_bits >= 3 {
369
+ let verbatim = if extra_bits > 3 {
370
+ reader.read(u32::from(extra_bits - 3))?
371
+ } else {
372
+ 0
373
+ };
374
+ (verbatim << 3)
375
+ | u32::try_from(aligned_tree.decode(&mut reader)?)
376
+ .map_err(|_| ParseError::Overflow)?
377
+ } else {
378
+ reader.read(u32::from(extra_bits))?
379
+ };
380
+ let base = *POS_BASE
381
+ .get(slot)
382
+ .ok_or(ParseError::Lzx("match position base is out of range"))?;
383
+ let value = i64::from(base) + i64::from(footer) - 2;
384
+ let value = i32::try_from(value)
385
+ .map_err(|_| ParseError::Lzx("match offset overflows"))?;
386
+ r2 = r1;
387
+ r1 = r0;
388
+ r0 = value;
389
+ value
390
+ }
391
+ };
392
+ if match_offset <= 0
393
+ || usize::try_from(match_offset).map_or(true, |value| value > output.len())
394
+ {
395
+ return Err(ParseError::Lzx("match offset is outside the reset window"));
396
+ }
397
+ let match_length_u64 =
398
+ u64::try_from(match_length).map_err(|_| ParseError::Overflow)?;
399
+ if match_length_u64 > block_remaining || match_length_u64 > frame_remaining {
400
+ return Err(ParseError::Lzx("match crosses a block or frame boundary"));
401
+ }
402
+ let offset = usize::try_from(match_offset).map_err(|_| ParseError::Overflow)?;
403
+ let source = output.len() - offset;
404
+ for index in 0..match_length {
405
+ let byte = *output
406
+ .get(source + index)
407
+ .ok_or(ParseError::Lzx("overlapping match is out of range"))?;
408
+ output.push(byte);
409
+ }
410
+ produced = produced
411
+ .checked_add(match_length_u64)
412
+ .ok_or(ParseError::Overflow)?;
413
+ block_remaining -= match_length_u64;
414
+ frame_remaining -= match_length_u64;
415
+ }
416
+ }
417
+
418
+ if frame_remaining == 0 {
419
+ reader.align_to_word();
420
+ frame_remaining = FRAME;
421
+ }
422
+ if produced > uncompressed_size + 257 {
423
+ return Err(ParseError::Lzx("decoded output exceeded its target"));
424
+ }
425
+ }
426
+ output.truncate(target);
427
+ if let Some(filesize) = intel_filesize.filter(|filesize| *filesize != 0) {
428
+ undo_e8_transform(&mut output, absolute_output_start, filesize);
429
+ }
430
+ Ok(output)
431
+ }
432
+
433
+ /// Reverse LZX's optional Intel x86 CALL-address preprocessing. The transform is
434
+ /// deliberately applied only after successful bounded decompression, and the caller
435
+ /// supplies the absolute section offset so random reset-window reads match sequential
436
+ /// decoding.
437
+ fn undo_e8_transform(output: &mut [u8], absolute_start: u64, filesize: u32) {
438
+ if output.len() <= 10 || absolute_start >= INTEL_TRANSFORM_LIMIT {
439
+ return;
440
+ }
441
+ let mut output_offset = 0usize;
442
+ while output_offset < output.len() {
443
+ let Some(frame_start) = absolute_start.checked_add(output_offset as u64) else {
444
+ return;
445
+ };
446
+ if frame_start >= INTEL_TRANSFORM_LIMIT {
447
+ return;
448
+ }
449
+ let remaining_in_frame = FRAME - frame_start % FRAME;
450
+ let active_before_cutoff = INTEL_TRANSFORM_LIMIT - frame_start;
451
+ let frame_len = usize::try_from(
452
+ remaining_in_frame
453
+ .min(active_before_cutoff)
454
+ .min((output.len() - output_offset) as u64),
455
+ )
456
+ .expect("frame length fits usize");
457
+ undo_e8_frame(
458
+ &mut output[output_offset..output_offset + frame_len],
459
+ frame_start,
460
+ filesize,
461
+ );
462
+ output_offset += frame_len;
463
+ }
464
+ }
465
+
466
+ fn undo_e8_frame(output: &mut [u8], absolute_start: u64, filesize: u32) {
467
+ if output.len() <= 10 {
468
+ return;
469
+ }
470
+ let filesize = i64::from(filesize);
471
+ let absolute_start = absolute_start as i64;
472
+ let mut index = 0usize;
473
+ while index + 10 < output.len() {
474
+ if output[index] != 0xe8 {
475
+ index += 1;
476
+ continue;
477
+ }
478
+ let value_start = index + 1;
479
+ let absolute = i64::from(i32::from_le_bytes(
480
+ output[value_start..value_start + 4]
481
+ .try_into()
482
+ .expect("fixed slice"),
483
+ ));
484
+ let current = absolute_start.saturating_add(index as i64);
485
+ if absolute >= -current && absolute < filesize {
486
+ let relative = if absolute >= 0 {
487
+ absolute - current
488
+ } else {
489
+ absolute + filesize
490
+ };
491
+ if let Ok(relative) = i32::try_from(relative) {
492
+ output[value_start..value_start + 4].copy_from_slice(&relative.to_le_bytes());
493
+ }
494
+ }
495
+ index += 5;
496
+ }
497
+ }
498
+
499
+ #[cfg(test)]
500
+ mod tests {
501
+ use super::*;
502
+
503
+ fn push_bits(bits: &mut Vec<bool>, width: u32, value: u32) {
504
+ for shift in (0..width).rev() {
505
+ bits.push(value & (1 << shift) != 0);
506
+ }
507
+ }
508
+
509
+ fn append_word_aligned_bits(stream: &mut Vec<u8>, bits: &mut Vec<bool>) {
510
+ while !bits.len().is_multiple_of(16) {
511
+ bits.push(false);
512
+ }
513
+ for word_bits in bits.chunks_exact(16) {
514
+ let word = word_bits
515
+ .iter()
516
+ .fold(0u16, |value, bit| (value << 1) | u16::from(*bit));
517
+ stream.extend_from_slice(&word.to_le_bytes());
518
+ }
519
+ bits.clear();
520
+ }
521
+
522
+ fn append_recent_offsets(stream: &mut Vec<u8>) {
523
+ for recent in [1i32, 1, 1] {
524
+ stream.extend_from_slice(&recent.to_le_bytes());
525
+ }
526
+ }
527
+
528
+ fn uncompressed_stream_with_e8(payload: &[u8], filesize: u32) -> Vec<u8> {
529
+ let mut bits = Vec::new();
530
+ push_bits(&mut bits, 1, 1);
531
+ push_bits(&mut bits, 16, filesize >> 16);
532
+ push_bits(&mut bits, 16, filesize & 0xffff);
533
+ push_bits(&mut bits, 3, 3);
534
+ push_bits(&mut bits, 24, payload.len() as u32);
535
+ let mut stream = Vec::new();
536
+ append_word_aligned_bits(&mut stream, &mut bits);
537
+ append_recent_offsets(&mut stream);
538
+ stream.extend_from_slice(payload);
539
+ stream
540
+ }
541
+
542
+ #[test]
543
+ fn rejects_empty_and_unbounded_streams() {
544
+ assert!(decompress_reset_window(&[], 1, 0x10000, 16, 0).is_err());
545
+ assert!(decompress_reset_window(&[0; 8], 0x10001, 0x10000, 16, 0).is_err());
546
+ assert!(decompress_reset_window(&[0; 8], 1, 0x10000, 22, 0).is_err());
547
+ }
548
+
549
+ #[test]
550
+ fn reverses_e8_addresses_with_absolute_window_position() {
551
+ let mut bytes = vec![0x90, 0x90, 0xe8];
552
+ bytes.extend_from_slice(&1000i32.to_le_bytes());
553
+ bytes.extend_from_slice(&[0x90; 16]);
554
+ undo_e8_transform(&mut bytes, 100, 4096);
555
+ assert_eq!(i32::from_le_bytes(bytes[3..7].try_into().unwrap()), 898);
556
+
557
+ let mut negative = vec![0xe8];
558
+ negative.extend_from_slice(&(-100i32).to_le_bytes());
559
+ negative.extend_from_slice(&[0x90; 16]);
560
+ undo_e8_transform(&mut negative, 100, 4096);
561
+ assert_eq!(i32::from_le_bytes(negative[1..5].try_into().unwrap()), 3996);
562
+ }
563
+
564
+ #[test]
565
+ fn consumes_e8_filesize_header_and_decodes_uncompressed_block() {
566
+ let mut transformed = vec![0x90; 24];
567
+ transformed[2] = 0xe8;
568
+ transformed[3..7].copy_from_slice(&1000i32.to_le_bytes());
569
+ let stream = uncompressed_stream_with_e8(&transformed, 4096);
570
+ let output = decompress_reset_window(&stream, 24, 0x10000, 16, 100).unwrap();
571
+ assert_eq!(i32::from_le_bytes(output[3..7].try_into().unwrap()), 898);
572
+ }
573
+
574
+ #[test]
575
+ fn consumes_odd_uncompressed_block_padding_before_next_block() {
576
+ let first = b"abc";
577
+ let mut second = vec![0x90; 16];
578
+ second[2] = 0xe8;
579
+ second[3..7].copy_from_slice(&1000i32.to_le_bytes());
580
+
581
+ let mut stream = Vec::new();
582
+ let mut bits = Vec::new();
583
+ push_bits(&mut bits, 1, 1);
584
+ push_bits(&mut bits, 16, 0);
585
+ push_bits(&mut bits, 16, 4096);
586
+ push_bits(&mut bits, 3, 3);
587
+ push_bits(&mut bits, 24, first.len() as u32);
588
+ append_word_aligned_bits(&mut stream, &mut bits);
589
+ append_recent_offsets(&mut stream);
590
+ stream.extend_from_slice(first);
591
+ stream.push(0xa5); // required word-alignment padding for the odd raw block
592
+
593
+ push_bits(&mut bits, 3, 3);
594
+ push_bits(&mut bits, 24, second.len() as u32);
595
+ append_word_aligned_bits(&mut stream, &mut bits);
596
+ append_recent_offsets(&mut stream);
597
+ stream.extend_from_slice(&second);
598
+
599
+ let output = decompress_reset_window(&stream, 19, 0x10000, 16, 100).unwrap();
600
+ assert_eq!(&output[..3], first);
601
+ assert_eq!(i32::from_le_bytes(output[6..10].try_into().unwrap()), 895);
602
+ }
603
+
604
+ #[test]
605
+ fn e8_transform_respects_frame_edges_and_one_gib_cutoff() {
606
+ let mut bytes = vec![0x90; FRAME as usize + 24];
607
+ let trailing = FRAME as usize - 8;
608
+ bytes[trailing] = 0xe8;
609
+ bytes[trailing + 1..trailing + 5].copy_from_slice(&1000i32.to_le_bytes());
610
+ let next_frame = FRAME as usize + 2;
611
+ bytes[next_frame] = 0xe8;
612
+ bytes[next_frame + 1..next_frame + 5].copy_from_slice(&50_000i32.to_le_bytes());
613
+ undo_e8_transform(&mut bytes, 0, 100_000);
614
+ assert_eq!(
615
+ i32::from_le_bytes(bytes[trailing + 1..trailing + 5].try_into().unwrap()),
616
+ 1000
617
+ );
618
+ assert_eq!(
619
+ i32::from_le_bytes(bytes[next_frame + 1..next_frame + 5].try_into().unwrap()),
620
+ 50_000 - next_frame as i32
621
+ );
622
+
623
+ let mut after_cutoff = vec![0xe8];
624
+ after_cutoff.extend_from_slice(&1000i32.to_le_bytes());
625
+ after_cutoff.extend_from_slice(&[0x90; 16]);
626
+ undo_e8_transform(&mut after_cutoff, INTEL_TRANSFORM_LIMIT, 4096);
627
+ assert_eq!(
628
+ i32::from_le_bytes(after_cutoff[1..5].try_into().unwrap()),
629
+ 1000
630
+ );
631
+ }
632
+ }