@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.
- package/LICENSE +196 -0
- package/README.en.md +13 -0
- package/README.md +13 -0
- package/THIRD_PARTY_NOTICES.md +12 -0
- package/dist/chm.d.ts +2 -0
- package/dist/chm.js +593 -0
- package/dist/chm.worker.d.ts +1 -0
- package/dist/chm.worker.js +2 -0
- package/dist/chm_wasm.js +583 -0
- package/dist/chm_wasm_bg.wasm +0 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.js +16 -0
- package/dist/model.d.ts +78 -0
- package/dist/model.js +125 -0
- package/dist/security.d.ts +33 -0
- package/dist/security.js +850 -0
- package/dist/style.d.ts +1 -0
- package/dist/style.js +26 -0
- package/dist/workerClient.d.ts +32 -0
- package/dist/workerClient.js +157 -0
- package/dist/workerProtocol.d.ts +70 -0
- package/dist/workerProtocol.js +1 -0
- package/file-viewer.capability.json +38 -0
- package/package.json +92 -0
- package/rust/Cargo.lock +247 -0
- package/rust/Cargo.toml +29 -0
- package/rust/LICENSE +196 -0
- package/rust/NOTICE.md +47 -0
- package/rust/THIRD_PARTY_LICENSES.md +365 -0
- package/rust/src/chm.rs +702 -0
- package/rust/src/core.rs +745 -0
- package/rust/src/error.rs +64 -0
- package/rust/src/lib.rs +104 -0
- package/rust/src/lzx.rs +632 -0
- package/rust/src/metadata.rs +839 -0
- package/rust/src/sitemap.rs +442 -0
package/rust/src/lzx.rs
ADDED
|
@@ -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
|
+
}
|