roxify 1.12.7 → 1.12.9
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/Cargo.toml +98 -0
- package/dist/cli.js +104 -130
- package/dist/stub-progress.d.ts +5 -10
- package/dist/stub-progress.js +7 -20
- package/dist/utils/encoder.js +1 -1
- package/dist/utils/rust-cli-wrapper.d.ts +1 -4
- package/dist/utils/rust-cli-wrapper.js +3 -69
- package/native/archive.rs +220 -0
- package/native/audio.rs +151 -0
- package/native/bench_hybrid.rs +145 -0
- package/native/bwt.rs +56 -0
- package/native/context_mixing.rs +117 -0
- package/native/core.rs +382 -0
- package/native/crypto.rs +204 -0
- package/native/encoder.rs +629 -0
- package/native/gpu.rs +116 -0
- package/native/hybrid.rs +287 -0
- package/native/image_utils.rs +82 -0
- package/native/lib.rs +489 -0
- package/native/main.rs +534 -0
- package/native/mtf.rs +106 -0
- package/native/packer.rs +447 -0
- package/native/png_utils.rs +538 -0
- package/native/pool.rs +101 -0
- package/native/progress.rs +43 -0
- package/native/rans.rs +149 -0
- package/native/rans_byte.rs +286 -0
- package/native/reconstitution.rs +623 -0
- package/native/streaming.rs +212 -0
- package/native/streaming_decode.rs +338 -0
- package/native/streaming_encode.rs +495 -0
- package/native/test_small_bwt.rs +31 -0
- package/native/test_stages.rs +70 -0
- package/package.json +111 -113
- package/roxify_native-x86_64-pc-windows-msvc.node +0 -0
|
@@ -0,0 +1,495 @@
|
|
|
1
|
+
use std::io::{Write, BufWriter, Read};
|
|
2
|
+
use std::fs::File;
|
|
3
|
+
use std::path::Path;
|
|
4
|
+
use walkdir::WalkDir;
|
|
5
|
+
use tar::{Builder, Header};
|
|
6
|
+
|
|
7
|
+
const PNG_HEADER: &[u8] = &[137, 80, 78, 71, 13, 10, 26, 10];
|
|
8
|
+
const PIXEL_MAGIC: &[u8] = b"PXL1";
|
|
9
|
+
const MARKER_START: [(u8, u8, u8); 3] = [(255, 0, 0), (0, 255, 0), (0, 0, 255)];
|
|
10
|
+
const MARKER_END: [(u8, u8, u8); 3] = [(0, 0, 255), (0, 255, 0), (255, 0, 0)];
|
|
11
|
+
const MARKER_ZSTD: (u8, u8, u8) = (0, 255, 0);
|
|
12
|
+
const MAGIC: &[u8] = b"ROX1";
|
|
13
|
+
|
|
14
|
+
pub fn encode_dir_to_png(
|
|
15
|
+
dir_path: &Path,
|
|
16
|
+
output_path: &Path,
|
|
17
|
+
compression_level: i32,
|
|
18
|
+
name: Option<&str>,
|
|
19
|
+
) -> anyhow::Result<()> {
|
|
20
|
+
encode_dir_to_png_encrypted(dir_path, output_path, compression_level, name, None, None)
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
pub fn encode_dir_to_png_encrypted(
|
|
24
|
+
dir_path: &Path,
|
|
25
|
+
output_path: &Path,
|
|
26
|
+
compression_level: i32,
|
|
27
|
+
name: Option<&str>,
|
|
28
|
+
passphrase: Option<&str>,
|
|
29
|
+
encrypt_type: Option<&str>,
|
|
30
|
+
) -> anyhow::Result<()> {
|
|
31
|
+
let tmp_zst = output_path.with_extension("tmp.zst");
|
|
32
|
+
|
|
33
|
+
let file_list = compress_dir_to_zst(dir_path, &tmp_zst, compression_level)?;
|
|
34
|
+
let file_list_json = serde_json::to_string(&file_list)?;
|
|
35
|
+
|
|
36
|
+
let result = write_png_from_zst(
|
|
37
|
+
&tmp_zst, output_path, name, Some(&file_list_json),
|
|
38
|
+
passphrase, encrypt_type,
|
|
39
|
+
);
|
|
40
|
+
let _ = std::fs::remove_file(&tmp_zst);
|
|
41
|
+
result
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
fn compress_dir_to_zst(
|
|
45
|
+
dir_path: &Path,
|
|
46
|
+
zst_path: &Path,
|
|
47
|
+
compression_level: i32,
|
|
48
|
+
) -> anyhow::Result<Vec<serde_json::Value>> {
|
|
49
|
+
let base = dir_path;
|
|
50
|
+
|
|
51
|
+
let entries: Vec<_> = WalkDir::new(dir_path)
|
|
52
|
+
.follow_links(false)
|
|
53
|
+
.into_iter()
|
|
54
|
+
.filter_map(|e| e.ok())
|
|
55
|
+
.filter(|e| e.file_type().is_file())
|
|
56
|
+
.collect();
|
|
57
|
+
|
|
58
|
+
let zst_file = File::create(zst_path)?;
|
|
59
|
+
let buf_writer = BufWriter::with_capacity(16 * 1024 * 1024, zst_file);
|
|
60
|
+
|
|
61
|
+
let actual_level = compression_level.min(3);
|
|
62
|
+
let mut encoder = zstd::stream::Encoder::new(buf_writer, actual_level)
|
|
63
|
+
.map_err(|e| anyhow::anyhow!("zstd init: {}", e))?;
|
|
64
|
+
|
|
65
|
+
let threads = num_cpus::get() as u32;
|
|
66
|
+
if threads > 1 {
|
|
67
|
+
let _ = encoder.multithread(threads);
|
|
68
|
+
}
|
|
69
|
+
let _ = encoder.long_distance_matching(true);
|
|
70
|
+
let _ = encoder.window_log(30);
|
|
71
|
+
|
|
72
|
+
encoder.write_all(MAGIC)?;
|
|
73
|
+
|
|
74
|
+
let mut file_list = Vec::new();
|
|
75
|
+
{
|
|
76
|
+
let mut tar_builder = Builder::new(&mut encoder);
|
|
77
|
+
for entry in &entries {
|
|
78
|
+
let full = entry.path();
|
|
79
|
+
let rel = full.strip_prefix(base).unwrap_or(full);
|
|
80
|
+
let rel_str = rel.to_string_lossy().replace('\\', "/");
|
|
81
|
+
|
|
82
|
+
let metadata = match std::fs::metadata(full) {
|
|
83
|
+
Ok(m) => m,
|
|
84
|
+
Err(_) => continue,
|
|
85
|
+
};
|
|
86
|
+
let size = metadata.len();
|
|
87
|
+
|
|
88
|
+
let mut header = Header::new_gnu();
|
|
89
|
+
header.set_size(size);
|
|
90
|
+
header.set_mode(0o644);
|
|
91
|
+
header.set_cksum();
|
|
92
|
+
|
|
93
|
+
let file = match File::open(full) {
|
|
94
|
+
Ok(f) => f,
|
|
95
|
+
Err(_) => continue,
|
|
96
|
+
};
|
|
97
|
+
let buf_reader = std::io::BufReader::with_capacity(
|
|
98
|
+
(size as usize).min(4 * 1024 * 1024).max(8192),
|
|
99
|
+
file,
|
|
100
|
+
);
|
|
101
|
+
|
|
102
|
+
tar_builder.append_data(&mut header, &rel_str, buf_reader)
|
|
103
|
+
.map_err(|e| anyhow::anyhow!("tar append {}: {}", rel_str, e))?;
|
|
104
|
+
|
|
105
|
+
file_list.push(serde_json::json!({"name": rel_str, "size": size}));
|
|
106
|
+
}
|
|
107
|
+
tar_builder.finish().map_err(|e| anyhow::anyhow!("tar finish: {}", e))?;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
encoder.finish().map_err(|e| anyhow::anyhow!("zstd finish: {}", e))?;
|
|
111
|
+
|
|
112
|
+
Ok(file_list)
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
fn write_png_from_zst(
|
|
116
|
+
zst_path: &Path,
|
|
117
|
+
output_path: &Path,
|
|
118
|
+
name: Option<&str>,
|
|
119
|
+
file_list: Option<&str>,
|
|
120
|
+
passphrase: Option<&str>,
|
|
121
|
+
_encrypt_type: Option<&str>,
|
|
122
|
+
) -> anyhow::Result<()> {
|
|
123
|
+
let zst_size = std::fs::metadata(zst_path)?.len() as usize;
|
|
124
|
+
|
|
125
|
+
let mut encryptor = match passphrase {
|
|
126
|
+
Some(pass) if !pass.is_empty() => Some(crate::crypto::StreamingEncryptor::new(pass)?),
|
|
127
|
+
_ => None,
|
|
128
|
+
};
|
|
129
|
+
|
|
130
|
+
let enc_header_len = encryptor.as_ref().map(|e| e.header_len()).unwrap_or(1);
|
|
131
|
+
let hmac_trailer_len: usize = if encryptor.is_some() { 32 } else { 0 };
|
|
132
|
+
|
|
133
|
+
let encrypted_payload_len = enc_header_len + zst_size + hmac_trailer_len;
|
|
134
|
+
|
|
135
|
+
let version = 1u8;
|
|
136
|
+
let name_bytes = name.map(|n| n.as_bytes()).unwrap_or(&[]);
|
|
137
|
+
let name_len = name_bytes.len().min(255) as u8;
|
|
138
|
+
let payload_len_bytes = (encrypted_payload_len as u32).to_be_bytes();
|
|
139
|
+
|
|
140
|
+
let mut meta_header = Vec::with_capacity(1 + 1 + name_len as usize + 4);
|
|
141
|
+
meta_header.push(version);
|
|
142
|
+
meta_header.push(name_len);
|
|
143
|
+
if name_len > 0 {
|
|
144
|
+
meta_header.extend_from_slice(&name_bytes[..name_len as usize]);
|
|
145
|
+
}
|
|
146
|
+
meta_header.extend_from_slice(&payload_len_bytes);
|
|
147
|
+
|
|
148
|
+
let meta_header_len = meta_header.len();
|
|
149
|
+
|
|
150
|
+
let file_list_chunk = file_list.map(|fl| {
|
|
151
|
+
let json_bytes = fl.as_bytes();
|
|
152
|
+
let mut chunk = Vec::with_capacity(4 + 4 + json_bytes.len());
|
|
153
|
+
chunk.extend_from_slice(b"rXFL");
|
|
154
|
+
chunk.extend_from_slice(&(json_bytes.len() as u32).to_be_bytes());
|
|
155
|
+
chunk.extend_from_slice(json_bytes);
|
|
156
|
+
chunk
|
|
157
|
+
});
|
|
158
|
+
let file_list_inline_len = file_list_chunk.as_ref().map(|c| c.len()).unwrap_or(0);
|
|
159
|
+
|
|
160
|
+
let total_meta_pixel_len = meta_header_len + encrypted_payload_len + file_list_inline_len;
|
|
161
|
+
let raw_payload_len = PIXEL_MAGIC.len() + total_meta_pixel_len;
|
|
162
|
+
let padding_needed = (3 - (raw_payload_len % 3)) % 3;
|
|
163
|
+
let padded_len = raw_payload_len + padding_needed;
|
|
164
|
+
|
|
165
|
+
let marker_start_len = 12;
|
|
166
|
+
let marker_end_bytes = 9;
|
|
167
|
+
let data_with_markers_len = marker_start_len + padded_len;
|
|
168
|
+
let data_pixels = (data_with_markers_len + 2) / 3;
|
|
169
|
+
let end_marker_pixels = 3;
|
|
170
|
+
let total_pixels = data_pixels + end_marker_pixels;
|
|
171
|
+
|
|
172
|
+
let side = (total_pixels as f64).sqrt().ceil() as usize;
|
|
173
|
+
let side = side.max(end_marker_pixels);
|
|
174
|
+
let width = side;
|
|
175
|
+
let height = side;
|
|
176
|
+
let row_bytes = width * 3;
|
|
177
|
+
let total_data_bytes = width * height * 3;
|
|
178
|
+
let marker_end_pos = total_data_bytes - marker_end_bytes;
|
|
179
|
+
|
|
180
|
+
let enc_header_bytes = if let Some(ref enc) = encryptor {
|
|
181
|
+
enc.header.clone()
|
|
182
|
+
} else {
|
|
183
|
+
vec![0x00]
|
|
184
|
+
};
|
|
185
|
+
|
|
186
|
+
let header_bytes = build_header_bytes(&meta_header, &enc_header_bytes);
|
|
187
|
+
|
|
188
|
+
let stride = row_bytes + 1;
|
|
189
|
+
let scanlines_total = height * stride;
|
|
190
|
+
|
|
191
|
+
const MAX_BLOCK: usize = 65535;
|
|
192
|
+
let num_blocks = (scanlines_total + MAX_BLOCK - 1) / MAX_BLOCK;
|
|
193
|
+
let idat_len = 2 + num_blocks * 5 + scanlines_total + 4;
|
|
194
|
+
|
|
195
|
+
let out_file = File::create(output_path)?;
|
|
196
|
+
let mut w = BufWriter::with_capacity(16 * 1024 * 1024, out_file);
|
|
197
|
+
|
|
198
|
+
w.write_all(PNG_HEADER)?;
|
|
199
|
+
|
|
200
|
+
let mut ihdr = [0u8; 13];
|
|
201
|
+
ihdr[0..4].copy_from_slice(&(width as u32).to_be_bytes());
|
|
202
|
+
ihdr[4..8].copy_from_slice(&(height as u32).to_be_bytes());
|
|
203
|
+
ihdr[8] = 8;
|
|
204
|
+
ihdr[9] = 2;
|
|
205
|
+
write_chunk_hdr(&mut w, b"IHDR", &ihdr)?;
|
|
206
|
+
|
|
207
|
+
let mut zst_file = File::open(zst_path)?;
|
|
208
|
+
let mut zst_reader = std::io::BufReader::with_capacity(16 * 1024 * 1024, &mut zst_file);
|
|
209
|
+
|
|
210
|
+
write_idat_streaming(
|
|
211
|
+
&mut w,
|
|
212
|
+
&header_bytes,
|
|
213
|
+
&mut zst_reader,
|
|
214
|
+
zst_size,
|
|
215
|
+
file_list_chunk.as_deref(),
|
|
216
|
+
&mut encryptor,
|
|
217
|
+
hmac_trailer_len,
|
|
218
|
+
height,
|
|
219
|
+
row_bytes,
|
|
220
|
+
marker_end_pos,
|
|
221
|
+
idat_len,
|
|
222
|
+
total_data_bytes,
|
|
223
|
+
)?;
|
|
224
|
+
|
|
225
|
+
if let Some(fl) = file_list {
|
|
226
|
+
write_chunk_hdr(&mut w, b"rXFL", fl.as_bytes())?;
|
|
227
|
+
}
|
|
228
|
+
write_chunk_hdr(&mut w, b"IEND", &[])?;
|
|
229
|
+
w.flush()?;
|
|
230
|
+
|
|
231
|
+
Ok(())
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
fn build_header_bytes(meta_header: &[u8], enc_header: &[u8]) -> Vec<u8> {
|
|
235
|
+
let mut header = Vec::with_capacity(12 + PIXEL_MAGIC.len() + meta_header.len() + enc_header.len());
|
|
236
|
+
for m in &MARKER_START {
|
|
237
|
+
header.push(m.0); header.push(m.1); header.push(m.2);
|
|
238
|
+
}
|
|
239
|
+
header.push(MARKER_ZSTD.0); header.push(MARKER_ZSTD.1); header.push(MARKER_ZSTD.2);
|
|
240
|
+
header.extend_from_slice(PIXEL_MAGIC);
|
|
241
|
+
header.extend_from_slice(meta_header);
|
|
242
|
+
header.extend_from_slice(enc_header);
|
|
243
|
+
header
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
fn write_idat_streaming<W: Write, R: Read>(
|
|
247
|
+
w: &mut W,
|
|
248
|
+
header_bytes: &[u8],
|
|
249
|
+
zst_reader: &mut R,
|
|
250
|
+
zst_size: usize,
|
|
251
|
+
file_list_chunk: Option<&[u8]>,
|
|
252
|
+
encryptor: &mut Option<crate::crypto::StreamingEncryptor>,
|
|
253
|
+
hmac_trailer_len: usize,
|
|
254
|
+
height: usize,
|
|
255
|
+
row_bytes: usize,
|
|
256
|
+
marker_end_pos: usize,
|
|
257
|
+
idat_len: usize,
|
|
258
|
+
total_data_bytes: usize,
|
|
259
|
+
) -> anyhow::Result<()> {
|
|
260
|
+
w.write_all(&(idat_len as u32).to_be_bytes())?;
|
|
261
|
+
w.write_all(b"IDAT")?;
|
|
262
|
+
|
|
263
|
+
let mut crc = crc32fast::Hasher::new();
|
|
264
|
+
crc.update(b"IDAT");
|
|
265
|
+
|
|
266
|
+
let stride = row_bytes + 1;
|
|
267
|
+
let scanlines_total = height * stride;
|
|
268
|
+
|
|
269
|
+
let zlib = [0x78u8, 0x01];
|
|
270
|
+
w.write_all(&zlib)?;
|
|
271
|
+
crc.update(&zlib);
|
|
272
|
+
|
|
273
|
+
let fl_chunk_data = file_list_chunk.unwrap_or(&[]);
|
|
274
|
+
let payload_total = header_bytes.len() + zst_size + hmac_trailer_len + fl_chunk_data.len();
|
|
275
|
+
let padding_after = total_data_bytes - payload_total.min(total_data_bytes);
|
|
276
|
+
|
|
277
|
+
let marker_end_bytes = build_marker_end_bytes();
|
|
278
|
+
|
|
279
|
+
let mut flat_pos: usize = 0;
|
|
280
|
+
let mut scanline_pos: usize = 0;
|
|
281
|
+
let mut deflate_block_remaining: usize = 0;
|
|
282
|
+
|
|
283
|
+
let mut header_pos: usize = 0;
|
|
284
|
+
let mut zst_remaining = zst_size;
|
|
285
|
+
let mut hmac_pos: usize = 0;
|
|
286
|
+
let mut hmac_written = hmac_trailer_len == 0;
|
|
287
|
+
let mut hmac_finalized: Option<[u8; 32]> = None;
|
|
288
|
+
let mut fl_pos: usize = 0;
|
|
289
|
+
let mut zero_remaining = padding_after;
|
|
290
|
+
|
|
291
|
+
let mut adler_a: u32 = 1;
|
|
292
|
+
let mut adler_b: u32 = 0;
|
|
293
|
+
|
|
294
|
+
let buf_size = 1024 * 1024;
|
|
295
|
+
let mut transfer_buf = vec![0u8; buf_size];
|
|
296
|
+
|
|
297
|
+
for _row in 0..height {
|
|
298
|
+
if deflate_block_remaining == 0 {
|
|
299
|
+
let remaining_scanlines = scanlines_total - scanline_pos;
|
|
300
|
+
let block_size = remaining_scanlines.min(65535);
|
|
301
|
+
let is_last = scanline_pos + block_size >= scanlines_total;
|
|
302
|
+
let header = [
|
|
303
|
+
if is_last { 0x01 } else { 0x00 },
|
|
304
|
+
block_size as u8,
|
|
305
|
+
(block_size >> 8) as u8,
|
|
306
|
+
!block_size as u8,
|
|
307
|
+
(!(block_size >> 8)) as u8,
|
|
308
|
+
];
|
|
309
|
+
w.write_all(&header)?;
|
|
310
|
+
crc.update(&header);
|
|
311
|
+
deflate_block_remaining = block_size;
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
let filter_byte = [0u8];
|
|
315
|
+
w.write_all(&filter_byte)?;
|
|
316
|
+
crc.update(&filter_byte);
|
|
317
|
+
adler_a = (adler_a + 0) % 65521;
|
|
318
|
+
adler_b = (adler_b + adler_a) % 65521;
|
|
319
|
+
scanline_pos += 1;
|
|
320
|
+
deflate_block_remaining -= 1;
|
|
321
|
+
|
|
322
|
+
let mut cols_written = 0;
|
|
323
|
+
while cols_written < row_bytes {
|
|
324
|
+
if deflate_block_remaining == 0 {
|
|
325
|
+
let remaining_scanlines = scanlines_total - scanline_pos;
|
|
326
|
+
let block_size = remaining_scanlines.min(65535);
|
|
327
|
+
let is_last = scanline_pos + block_size >= scanlines_total;
|
|
328
|
+
let header = [
|
|
329
|
+
if is_last { 0x01 } else { 0x00 },
|
|
330
|
+
block_size as u8,
|
|
331
|
+
(block_size >> 8) as u8,
|
|
332
|
+
!block_size as u8,
|
|
333
|
+
(!(block_size >> 8)) as u8,
|
|
334
|
+
];
|
|
335
|
+
w.write_all(&header)?;
|
|
336
|
+
crc.update(&header);
|
|
337
|
+
deflate_block_remaining = block_size;
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
let can_write = (row_bytes - cols_written).min(deflate_block_remaining);
|
|
341
|
+
|
|
342
|
+
let mut chunk_written = 0;
|
|
343
|
+
while chunk_written < can_write {
|
|
344
|
+
let need = can_write - chunk_written;
|
|
345
|
+
|
|
346
|
+
let is_marker_end_region = flat_pos >= marker_end_pos && flat_pos < marker_end_pos + 9;
|
|
347
|
+
|
|
348
|
+
if is_marker_end_region {
|
|
349
|
+
let me_offset = flat_pos - marker_end_pos;
|
|
350
|
+
let me_remaining = 9 - me_offset;
|
|
351
|
+
let take = need.min(me_remaining);
|
|
352
|
+
let slice = &marker_end_bytes[me_offset..me_offset + take];
|
|
353
|
+
w.write_all(slice)?;
|
|
354
|
+
crc.update(slice);
|
|
355
|
+
for &b in slice {
|
|
356
|
+
adler_a = (adler_a + b as u32) % 65521;
|
|
357
|
+
adler_b = (adler_b + adler_a) % 65521;
|
|
358
|
+
}
|
|
359
|
+
flat_pos += take;
|
|
360
|
+
chunk_written += take;
|
|
361
|
+
scanline_pos += take;
|
|
362
|
+
deflate_block_remaining -= take;
|
|
363
|
+
cols_written += take;
|
|
364
|
+
continue;
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
if header_pos < header_bytes.len() {
|
|
368
|
+
let avail = header_bytes.len() - header_pos;
|
|
369
|
+
let take = need.min(avail);
|
|
370
|
+
let slice = &header_bytes[header_pos..header_pos + take];
|
|
371
|
+
w.write_all(slice)?;
|
|
372
|
+
crc.update(slice);
|
|
373
|
+
for &b in slice {
|
|
374
|
+
adler_a = (adler_a + b as u32) % 65521;
|
|
375
|
+
adler_b = (adler_b + adler_a) % 65521;
|
|
376
|
+
}
|
|
377
|
+
header_pos += take;
|
|
378
|
+
flat_pos += take;
|
|
379
|
+
chunk_written += take;
|
|
380
|
+
scanline_pos += take;
|
|
381
|
+
deflate_block_remaining -= take;
|
|
382
|
+
cols_written += take;
|
|
383
|
+
} else if zst_remaining > 0 {
|
|
384
|
+
let take = need.min(zst_remaining).min(buf_size);
|
|
385
|
+
let got = zst_reader.read(&mut transfer_buf[..take])
|
|
386
|
+
.map_err(|e| anyhow::anyhow!("read zst: {}", e))?;
|
|
387
|
+
if got == 0 { break; }
|
|
388
|
+
if let Some(ref mut enc) = encryptor {
|
|
389
|
+
enc.encrypt_chunk(&mut transfer_buf[..got]);
|
|
390
|
+
}
|
|
391
|
+
w.write_all(&transfer_buf[..got])?;
|
|
392
|
+
crc.update(&transfer_buf[..got]);
|
|
393
|
+
for &b in &transfer_buf[..got] {
|
|
394
|
+
adler_a = (adler_a + b as u32) % 65521;
|
|
395
|
+
adler_b = (adler_b + adler_a) % 65521;
|
|
396
|
+
}
|
|
397
|
+
zst_remaining -= got;
|
|
398
|
+
flat_pos += got;
|
|
399
|
+
chunk_written += got;
|
|
400
|
+
scanline_pos += got;
|
|
401
|
+
deflate_block_remaining -= got;
|
|
402
|
+
cols_written += got;
|
|
403
|
+
} else if !hmac_written {
|
|
404
|
+
if hmac_finalized.is_none() {
|
|
405
|
+
if let Some(enc) = encryptor.take() {
|
|
406
|
+
hmac_finalized = Some(enc.finalize_hmac());
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
if let Some(ref hmac_bytes) = hmac_finalized {
|
|
410
|
+
let avail = hmac_trailer_len - hmac_pos;
|
|
411
|
+
let take = need.min(avail);
|
|
412
|
+
let slice = &hmac_bytes[hmac_pos..hmac_pos + take];
|
|
413
|
+
w.write_all(slice)?;
|
|
414
|
+
crc.update(slice);
|
|
415
|
+
for &b in slice {
|
|
416
|
+
adler_a = (adler_a + b as u32) % 65521;
|
|
417
|
+
adler_b = (adler_b + adler_a) % 65521;
|
|
418
|
+
}
|
|
419
|
+
hmac_pos += take;
|
|
420
|
+
flat_pos += take;
|
|
421
|
+
chunk_written += take;
|
|
422
|
+
scanline_pos += take;
|
|
423
|
+
deflate_block_remaining -= take;
|
|
424
|
+
cols_written += take;
|
|
425
|
+
if hmac_pos >= hmac_trailer_len {
|
|
426
|
+
hmac_written = true;
|
|
427
|
+
}
|
|
428
|
+
} else {
|
|
429
|
+
hmac_written = true;
|
|
430
|
+
}
|
|
431
|
+
} else if fl_pos < fl_chunk_data.len() {
|
|
432
|
+
let avail = fl_chunk_data.len() - fl_pos;
|
|
433
|
+
let take = need.min(avail);
|
|
434
|
+
let slice = &fl_chunk_data[fl_pos..fl_pos + take];
|
|
435
|
+
w.write_all(slice)?;
|
|
436
|
+
crc.update(slice);
|
|
437
|
+
for &b in slice {
|
|
438
|
+
adler_a = (adler_a + b as u32) % 65521;
|
|
439
|
+
adler_b = (adler_b + adler_a) % 65521;
|
|
440
|
+
}
|
|
441
|
+
fl_pos += take;
|
|
442
|
+
flat_pos += take;
|
|
443
|
+
chunk_written += take;
|
|
444
|
+
scanline_pos += take;
|
|
445
|
+
deflate_block_remaining -= take;
|
|
446
|
+
cols_written += take;
|
|
447
|
+
} else {
|
|
448
|
+
let take = need.min(zero_remaining).min(buf_size);
|
|
449
|
+
if take == 0 { break; }
|
|
450
|
+
let zeros = vec![0u8; take];
|
|
451
|
+
w.write_all(&zeros)?;
|
|
452
|
+
crc.update(&zeros);
|
|
453
|
+
for _ in 0..take {
|
|
454
|
+
adler_b = (adler_b + adler_a) % 65521;
|
|
455
|
+
}
|
|
456
|
+
zero_remaining -= take;
|
|
457
|
+
flat_pos += take;
|
|
458
|
+
chunk_written += take;
|
|
459
|
+
scanline_pos += take;
|
|
460
|
+
deflate_block_remaining -= take;
|
|
461
|
+
cols_written += take;
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
let adler = (adler_b << 16) | adler_a;
|
|
468
|
+
let adler_bytes = adler.to_be_bytes();
|
|
469
|
+
w.write_all(&adler_bytes)?;
|
|
470
|
+
crc.update(&adler_bytes);
|
|
471
|
+
|
|
472
|
+
w.write_all(&crc.finalize().to_be_bytes())?;
|
|
473
|
+
Ok(())
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
fn build_marker_end_bytes() -> [u8; 9] {
|
|
477
|
+
let mut buf = [0u8; 9];
|
|
478
|
+
for (i, m) in MARKER_END.iter().enumerate() {
|
|
479
|
+
buf[i * 3] = m.0;
|
|
480
|
+
buf[i * 3 + 1] = m.1;
|
|
481
|
+
buf[i * 3 + 2] = m.2;
|
|
482
|
+
}
|
|
483
|
+
buf
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
fn write_chunk_hdr<W: Write>(w: &mut W, chunk_type: &[u8; 4], data: &[u8]) -> anyhow::Result<()> {
|
|
487
|
+
w.write_all(&(data.len() as u32).to_be_bytes())?;
|
|
488
|
+
w.write_all(chunk_type)?;
|
|
489
|
+
w.write_all(data)?;
|
|
490
|
+
let mut h = crc32fast::Hasher::new();
|
|
491
|
+
h.update(chunk_type);
|
|
492
|
+
h.update(data);
|
|
493
|
+
w.write_all(&h.finalize().to_be_bytes())?;
|
|
494
|
+
Ok(())
|
|
495
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
mod bwt;
|
|
2
|
+
mod mtf;
|
|
3
|
+
mod rans_byte;
|
|
4
|
+
mod context_mixing;
|
|
5
|
+
mod pool;
|
|
6
|
+
mod hybrid;
|
|
7
|
+
|
|
8
|
+
fn main() {
|
|
9
|
+
println!("Testing small inputs through full pipeline...");
|
|
10
|
+
|
|
11
|
+
for size in [1, 2, 3, 4, 5, 6, 10, 100] {
|
|
12
|
+
let data: Vec<u8> = (0..size).map(|i| (i % 256) as u8).collect();
|
|
13
|
+
print!("Size {}: ", size);
|
|
14
|
+
|
|
15
|
+
match hybrid::compress_high_performance(&data) {
|
|
16
|
+
Ok((compressed, _stats)) => {
|
|
17
|
+
match hybrid::decompress_high_performance(&compressed) {
|
|
18
|
+
Ok(decompressed) => {
|
|
19
|
+
if decompressed == data {
|
|
20
|
+
println!("OK (compressed {} -> {} bytes)", size, compressed.len());
|
|
21
|
+
} else {
|
|
22
|
+
println!("MISMATCH!");
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
Err(e) => println!("DECOMPRESS ERROR: {}", e),
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
Err(e) => println!("COMPRESS ERROR: {}", e),
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
mod bwt;
|
|
2
|
+
mod mtf;
|
|
3
|
+
mod rans_byte;
|
|
4
|
+
mod context_mixing;
|
|
5
|
+
mod pool;
|
|
6
|
+
|
|
7
|
+
fn main() {
|
|
8
|
+
let data = b"banana";
|
|
9
|
+
|
|
10
|
+
// Test BWT
|
|
11
|
+
let bwt_result = bwt::bwt_encode(data).unwrap();
|
|
12
|
+
println!("BWT of 'banana': {:?}", String::from_utf8_lossy(&bwt_result.transformed));
|
|
13
|
+
println!("Primary index: {}", bwt_result.primary_index);
|
|
14
|
+
|
|
15
|
+
let decoded = bwt::bwt_decode(&bwt_result.transformed, bwt_result.primary_index).unwrap();
|
|
16
|
+
println!("BWT decode: {:?}", String::from_utf8_lossy(&decoded));
|
|
17
|
+
assert_eq!(&decoded, data, "BWT round-trip failed");
|
|
18
|
+
println!("BWT round-trip OK!\n");
|
|
19
|
+
|
|
20
|
+
// Test MTF
|
|
21
|
+
let mtf_enc = mtf::mtf_encode(data);
|
|
22
|
+
println!("MTF of 'banana': {:?}", mtf_enc);
|
|
23
|
+
let mtf_dec = mtf::mtf_decode(&mtf_enc);
|
|
24
|
+
assert_eq!(mtf_dec, data, "MTF round-trip failed");
|
|
25
|
+
println!("MTF round-trip OK!\n");
|
|
26
|
+
|
|
27
|
+
// Test RLE0
|
|
28
|
+
let test_rle = vec![0, 0, 0, 5, 0, 3, 0, 0, 0, 0, 0, 7];
|
|
29
|
+
let rle_enc = mtf::rle0_encode(&test_rle);
|
|
30
|
+
println!("RLE0 of {:?}: {:?}", test_rle, rle_enc);
|
|
31
|
+
let rle_dec = mtf::rle0_decode(&rle_enc);
|
|
32
|
+
println!("RLE0 decode: {:?}", rle_dec);
|
|
33
|
+
assert_eq!(rle_dec, test_rle, "RLE0 round-trip failed");
|
|
34
|
+
println!("RLE0 round-trip OK!\n");
|
|
35
|
+
|
|
36
|
+
// Test rANS byte
|
|
37
|
+
let test_data = b"abracadabra";
|
|
38
|
+
let stats = rans_byte::SymbolStats::from_data(test_data);
|
|
39
|
+
let encoded = rans_byte::rans_encode_block(test_data, &stats);
|
|
40
|
+
println!("rANS encoded 'abracadabra': {} bytes -> {} bytes", test_data.len(), encoded.len());
|
|
41
|
+
let decoded = rans_byte::rans_decode_block(&encoded, &stats, test_data.len()).unwrap();
|
|
42
|
+
assert_eq!(&decoded, test_data, "rANS round-trip failed");
|
|
43
|
+
println!("rANS round-trip OK!\n");
|
|
44
|
+
|
|
45
|
+
// Test full pipeline on larger data
|
|
46
|
+
let big_data: Vec<u8> = "Hello World! Testing the full BWT+MTF+RLE+rANS pipeline. ".repeat(20).into_bytes();
|
|
47
|
+
let bwt_r = bwt::bwt_encode(&big_data).unwrap();
|
|
48
|
+
let mtf_r = mtf::mtf_encode(&bwt_r.transformed);
|
|
49
|
+
let rle_r = mtf::rle0_encode(&mtf_r);
|
|
50
|
+
let stats = rans_byte::SymbolStats::from_data(&rle_r);
|
|
51
|
+
let enc = rans_byte::rans_encode_block(&rle_r, &stats);
|
|
52
|
+
|
|
53
|
+
println!("Full pipeline: {} -> BWT {} -> MTF {} -> RLE {} -> rANS {}",
|
|
54
|
+
big_data.len(), bwt_r.transformed.len(), mtf_r.len(), rle_r.len(), enc.len());
|
|
55
|
+
|
|
56
|
+
let dec_rle = rans_byte::rans_decode_block(&enc, &stats, rle_r.len()).unwrap();
|
|
57
|
+
assert_eq!(dec_rle, rle_r, "rANS stage failed");
|
|
58
|
+
|
|
59
|
+
let dec_mtf = mtf::rle0_decode(&dec_rle);
|
|
60
|
+
assert_eq!(dec_mtf, mtf_r, "RLE0 stage failed");
|
|
61
|
+
|
|
62
|
+
let dec_bwt = mtf::mtf_decode(&dec_mtf);
|
|
63
|
+
assert_eq!(dec_bwt, bwt_r.transformed, "MTF stage failed");
|
|
64
|
+
|
|
65
|
+
let dec_orig = bwt::bwt_decode(&dec_bwt, bwt_r.primary_index).unwrap();
|
|
66
|
+
assert_eq!(dec_orig, big_data, "BWT stage failed");
|
|
67
|
+
|
|
68
|
+
println!("Full pipeline round-trip OK!");
|
|
69
|
+
println!("Ratio: {:.1}%", (enc.len() as f64 / big_data.len() as f64) * 100.0);
|
|
70
|
+
}
|