roxify 1.11.1 → 1.12.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/native/bwt.rs DELETED
@@ -1,56 +0,0 @@
1
- use anyhow::Result;
2
- use libsais::bwt::Bwt;
3
- use libsais::typestate::OwnedBuffer;
4
- use libsais::BwtConstruction;
5
- use rayon::prelude::*;
6
-
7
- pub struct BwtResult {
8
- pub transformed: Vec<u8>,
9
- pub primary_index: u32,
10
- }
11
-
12
- pub fn bwt_encode(data: &[u8]) -> Result<BwtResult> {
13
- let n = data.len();
14
- if n == 0 {
15
- return Ok(BwtResult { transformed: Vec::new(), primary_index: 0 });
16
- }
17
-
18
- let bwt_result = BwtConstruction::for_text(data)
19
- .with_owned_temporary_array_buffer32()
20
- .single_threaded()
21
- .run()
22
- .map_err(|e| anyhow::anyhow!("libsais BWT: {:?}", e))?;
23
-
24
- let primary_index = bwt_result.primary_index() as u32;
25
- let transformed = bwt_result.bwt().to_vec();
26
-
27
- Ok(BwtResult { transformed, primary_index })
28
- }
29
-
30
- pub fn bwt_decode(bwt_data: &[u8], primary_index: u32) -> Result<Vec<u8>> {
31
- if bwt_data.is_empty() {
32
- return Ok(Vec::new());
33
- }
34
-
35
- let bwt_obj: Bwt<'static, u8, OwnedBuffer> =
36
- unsafe { Bwt::from_parts(bwt_data.to_vec(), primary_index as usize) };
37
-
38
- let text = bwt_obj
39
- .unbwt()
40
- .with_owned_temporary_array_buffer32()
41
- .single_threaded()
42
- .run()
43
- .map_err(|e| anyhow::anyhow!("libsais UnBWT: {:?}", e))?;
44
-
45
- Ok(text.as_slice().to_vec())
46
- }
47
-
48
- pub fn bwt_encode_streaming(block_size: usize, data: &[u8]) -> Result<Vec<(BwtResult, usize)>> {
49
- data.par_chunks(block_size)
50
- .enumerate()
51
- .map(|(i, chunk)| {
52
- let result = bwt_encode(chunk)?;
53
- Ok((result, i * block_size))
54
- })
55
- .collect()
56
- }
@@ -1,117 +0,0 @@
1
- #[derive(Clone, Copy, Debug)]
2
- pub struct ProbabilityEstimate {
3
- pub p0: u32,
4
- pub p1: u32,
5
- pub total: u32,
6
- }
7
-
8
- impl ProbabilityEstimate {
9
- pub fn entropy_bits(&self) -> f32 {
10
- if self.total == 0 {
11
- return 0.0;
12
- }
13
- let p0 = (self.p0 as f32) / (self.total as f32);
14
- let p1 = (self.p1 as f32) / (self.total as f32);
15
-
16
- let mut bits = 0.0;
17
- if p0 > 0.0 {
18
- bits -= p0 * p0.log2();
19
- }
20
- if p1 > 0.0 {
21
- bits -= p1 * p1.log2();
22
- }
23
- bits
24
- }
25
- }
26
-
27
- pub struct ContextMixer {
28
- contexts_order0: Vec<ProbabilityEstimate>,
29
- contexts_order1: Vec<[ProbabilityEstimate; 256]>,
30
- contexts_order2: Vec<[[ProbabilityEstimate; 256]; 256]>,
31
- }
32
-
33
- impl ContextMixer {
34
- pub fn new() -> Self {
35
- ContextMixer {
36
- contexts_order0: vec![ProbabilityEstimate { p0: 1, p1: 1, total: 2 }; 1],
37
- contexts_order1: vec![
38
- [ProbabilityEstimate { p0: 1, p1: 1, total: 2 }; 256];
39
- 256
40
- ],
41
- contexts_order2: vec![
42
- [[ProbabilityEstimate { p0: 1, p1: 1, total: 2 }; 256]; 256];
43
- 256
44
- ],
45
- }
46
- }
47
-
48
- pub fn predict_order0(&self) -> ProbabilityEstimate {
49
- self.contexts_order0[0]
50
- }
51
-
52
- pub fn predict_order1(&self, context1: u8) -> ProbabilityEstimate {
53
- self.contexts_order1[context1 as usize][0]
54
- }
55
-
56
- pub fn predict_order2(&self, context1: u8, context2: u8) -> ProbabilityEstimate {
57
- self.contexts_order2[context1 as usize][context2 as usize][0]
58
- }
59
-
60
- pub fn update_order0(&mut self, bit: bool) {
61
- let ctx = &mut self.contexts_order0[0];
62
- if bit {
63
- ctx.p1 += 1;
64
- } else {
65
- ctx.p0 += 1;
66
- }
67
- ctx.total += 1;
68
- }
69
-
70
- pub fn update_order1(&mut self, context1: u8, bit: bool) {
71
- let ctx = &mut self.contexts_order1[context1 as usize][0];
72
- if bit {
73
- ctx.p1 += 1;
74
- } else {
75
- ctx.p0 += 1;
76
- }
77
- ctx.total += 1;
78
- }
79
-
80
- pub fn update_order2(&mut self, context1: u8, context2: u8, bit: bool) {
81
- let ctx = &mut self.contexts_order2[context1 as usize][context2 as usize][0];
82
- if bit {
83
- ctx.p1 += 1;
84
- } else {
85
- ctx.p0 += 1;
86
- }
87
- ctx.total += 1;
88
- }
89
- }
90
-
91
- pub fn analyze_entropy(data: &[u8]) -> f32 {
92
- let mut freq = [0u32; 256];
93
- for &byte in data {
94
- freq[byte as usize] += 1;
95
- }
96
-
97
- let total = data.len() as f32;
98
- if total == 0.0 {
99
- return 0.0;
100
- }
101
-
102
- let inv_total = 1.0 / total;
103
- let mut entropy = 0.0f32;
104
- for &f in &freq {
105
- if f > 0 {
106
- let p = f as f32 * inv_total;
107
- entropy -= p * p.log2();
108
- }
109
- }
110
- entropy
111
- }
112
-
113
- pub fn estimate_compression_gain(original: &[u8], entropy_bits: f32) -> f64 {
114
- let theoretical_min = (original.len() as f64) * (entropy_bits as f64) / 8.0;
115
- let ratio = theoretical_min / (original.len() as f64);
116
- (1.0 - ratio) * 100.0
117
- }
package/native/core.rs DELETED
@@ -1,382 +0,0 @@
1
- use rayon::prelude::*;
2
- use std::sync::Arc;
3
- use std::path::PathBuf;
4
- use anyhow::Result;
5
-
6
- pub struct PlainScanResult {
7
- pub marker_positions: Vec<u32>,
8
- pub magic_positions: Vec<u32>,
9
- }
10
-
11
- pub fn scan_pixels_bytes(buf: &[u8], channels: usize, marker_bytes: Option<&[u8]>) -> PlainScanResult {
12
- let magic = b"ROX1";
13
-
14
- let magic_positions: Vec<u32> = if buf.len() >= 4 {
15
- (0..(buf.len() - 3))
16
- .into_par_iter()
17
- .filter_map(|i| if &buf[i..i + 4] == magic { Some(i as u32) } else { None })
18
- .collect()
19
- } else {
20
- Vec::new()
21
- };
22
-
23
- let markers: Vec<[u8; 3]> = match marker_bytes {
24
- Some(bytes) if !bytes.is_empty() => {
25
- if bytes.len() % 3 != 0 {
26
- return PlainScanResult { marker_positions: Vec::new(), magic_positions };
27
- }
28
- bytes.chunks(3).map(|c| [c[0], c[1], c[2]]).collect()
29
- }
30
- _ => Vec::new(),
31
- };
32
-
33
- let marker_positions = if markers.is_empty() {
34
- Vec::new()
35
- } else {
36
- let markers = Arc::new(markers);
37
- let ch = channels as usize;
38
- if ch < 3 || buf.len() < 3 {
39
- Vec::new()
40
- } else {
41
- let pixel_count = buf.len() / ch;
42
- (0..pixel_count)
43
- .into_par_iter()
44
- .filter_map(|i| {
45
- let base = i * ch;
46
- if base + 3 > buf.len() {
47
- return None;
48
- }
49
- for m in markers.iter() {
50
- if buf[base] == m[0] && buf[base + 1] == m[1] && buf[base + 2] == m[2] {
51
- return Some(i as u32);
52
- }
53
- }
54
- None
55
- })
56
- .collect()
57
- }
58
- };
59
-
60
- PlainScanResult { marker_positions, magic_positions }
61
- }
62
-
63
- pub fn crc32_bytes(buf: &[u8]) -> u32 {
64
- // parallelize checksum on large buffers, since crc32fast::hash is single-threaded
65
- const PAR_THRESHOLD: usize = 4 * 1024 * 1024; // 4 MiB
66
- if buf.len() < PAR_THRESHOLD {
67
- crc32fast::hash(buf)
68
- } else {
69
- // compute per-chunk hasher in parallel then combine
70
- let chunk = PAR_THRESHOLD;
71
- let combined = buf
72
- .par_chunks(chunk)
73
- .map(|chunk| {
74
- let mut h = crc32fast::Hasher::new();
75
- h.update(chunk);
76
- h
77
- })
78
- .reduce(|| crc32fast::Hasher::new(), |mut a, b| {
79
- a.combine(&b);
80
- a
81
- });
82
- combined.finalize()
83
- }
84
- }
85
-
86
- pub fn adler32_bytes(buf: &[u8]) -> u32 {
87
- const MOD: u32 = 65521;
88
- const NMAX: usize = 5552;
89
-
90
- if buf.len() > 4 * 1024 * 1024 {
91
- return adler32_parallel(buf);
92
- }
93
-
94
- let mut a: u32 = 1;
95
- let mut b: u32 = 0;
96
-
97
- for chunk in buf.chunks(NMAX) {
98
- for &v in chunk {
99
- a += v as u32;
100
- b += a;
101
- }
102
- a %= MOD;
103
- b %= MOD;
104
- }
105
-
106
- (b << 16) | a
107
- }
108
-
109
- fn adler32_parallel(buf: &[u8]) -> u32 {
110
- use rayon::prelude::*;
111
- const MOD: u32 = 65521;
112
- const CHUNK: usize = 1024 * 1024;
113
-
114
- let chunks: Vec<&[u8]> = buf.chunks(CHUNK).collect();
115
- let partials: Vec<(u32, u32, usize)> = chunks.par_iter().map(|chunk| {
116
- let mut a: u32 = 0;
117
- let mut b: u32 = 0;
118
- for &v in *chunk {
119
- a += v as u32;
120
- b += a;
121
- }
122
- a %= MOD;
123
- b %= MOD;
124
- (a, b, chunk.len())
125
- }).collect();
126
-
127
- let mut a: u64 = 1;
128
- let mut b: u64 = 0;
129
- for (pa, pb, len) in partials {
130
- b = (b + pb as u64 + a * len as u64) % MOD as u64;
131
- a = (a + pa as u64) % MOD as u64;
132
- }
133
-
134
- ((b as u32) << 16) | (a as u32)
135
- }
136
-
137
- pub fn delta_encode_bytes(buf: &[u8]) -> Vec<u8> {
138
- let len = buf.len();
139
- if len == 0 {
140
- return Vec::new();
141
- }
142
- let mut out = vec![0u8; len];
143
- out[0] = buf[0];
144
- for i in 1..len {
145
- out[i] = buf[i].wrapping_sub(buf[i - 1]);
146
- }
147
- out
148
- }
149
-
150
- pub fn delta_decode_bytes(buf: &[u8]) -> Vec<u8> {
151
- let len = buf.len();
152
- if len == 0 {
153
- return Vec::new();
154
- }
155
- let mut out = vec![0u8; len];
156
- out[0] = buf[0];
157
- for i in 1..len {
158
- out[i] = out[i - 1].wrapping_add(buf[i]);
159
- }
160
- out
161
- }
162
-
163
- fn compress_with_chunk_size(buf: &[u8], level: i32, chunk_size: usize) -> std::result::Result<Vec<u8>, String> {
164
- use std::io::Write;
165
-
166
- // Allow ultra levels (20-22) for maximum compression
167
- let actual_level = level.min(22).max(1);
168
- let mut encoder = zstd::stream::Encoder::new(Vec::new(), actual_level)
169
- .map_err(|e| format!("zstd encoder init error: {}", e))?;
170
-
171
- let threads = num_cpus::get() as u32;
172
- if threads > 1 {
173
- // Ultra levels (>=20) use much more memory per thread; limit to 4
174
- let max_threads = if actual_level >= 20 { threads.min(4) } else { threads };
175
- let _ = encoder.multithread(max_threads);
176
- }
177
-
178
- if buf.len() > 1024 * 1024 {
179
- let _ = encoder.long_distance_matching(true);
180
- let wlog = if buf.len() > 512 * 1024 * 1024 { 28 }
181
- else if buf.len() > 64 * 1024 * 1024 { 27 }
182
- else { 26 };
183
- let _ = encoder.window_log(wlog);
184
- }
185
-
186
- let _ = encoder.set_pledged_src_size(Some(buf.len() as u64));
187
-
188
- for chunk in buf.chunks(chunk_size) {
189
- encoder.write_all(chunk).map_err(|e| format!("zstd write error: {}", e))?;
190
- }
191
-
192
- encoder.finish().map_err(|e| format!("zstd finish error: {}", e))
193
- }
194
-
195
- pub fn train_zstd_dictionary(sample_paths: &[PathBuf], dict_size: usize) -> Result<Vec<u8>> {
196
- // load all sample files contiguously
197
- let mut samples = Vec::new();
198
- let mut lengths = Vec::new();
199
- for path in sample_paths {
200
- let data = std::fs::read(path)?;
201
- lengths.push(data.len());
202
- samples.extend_from_slice(&data);
203
- }
204
- let dict = zstd::dict::from_continuous(&samples, &lengths, dict_size)?;
205
- Ok(dict)
206
- }
207
-
208
- /// Compress a slice with optional zstd dictionary.
209
- ///
210
- /// When `dict` is `Some`, the dictionary is passed to the encoder (same
211
- /// dict required for decompression). Pass `None` for normal compression.
212
- ///
213
- /// For large buffers (>50 MiB) without a dictionary, multiple chunk sizes
214
- /// are benchmarked on a sample and the best is selected automatically.
215
- pub fn zstd_compress_bytes(buf: &[u8], level: i32, dict: Option<&[u8]>) -> std::result::Result<Vec<u8>, String> {
216
- zstd_compress_with_prefix(buf, level, dict, &[])
217
- }
218
-
219
- pub fn zstd_compress_with_prefix(buf: &[u8], level: i32, dict: Option<&[u8]>, prefix: &[u8]) -> std::result::Result<Vec<u8>, String> {
220
- use std::io::Write;
221
-
222
- let actual_level = level.min(22).max(1);
223
- let total_len = prefix.len() + buf.len();
224
-
225
- let adaptive_level = if total_len > 2 * 1024 * 1024 * 1024 {
226
- actual_level.min(1)
227
- } else if total_len > 1024 * 1024 * 1024 {
228
- actual_level.min(3)
229
- } else if total_len > 256 * 1024 * 1024 {
230
- actual_level.min(6)
231
- } else if total_len > 64 * 1024 * 1024 {
232
- actual_level.min(12)
233
- } else {
234
- actual_level
235
- };
236
-
237
- if dict.is_none() && total_len < 4 * 1024 * 1024 {
238
- if prefix.is_empty() {
239
- return zstd::bulk::compress(buf, adaptive_level)
240
- .map_err(|e| format!("zstd bulk compress error: {}", e));
241
- }
242
- let mut combined = Vec::with_capacity(total_len);
243
- combined.extend_from_slice(prefix);
244
- combined.extend_from_slice(buf);
245
- return zstd::bulk::compress(&combined, adaptive_level)
246
- .map_err(|e| format!("zstd bulk compress error: {}", e));
247
- }
248
-
249
- let mut encoder = if let Some(d) = dict {
250
- zstd::stream::Encoder::with_dictionary(Vec::with_capacity(total_len / 2), adaptive_level, d)
251
- .map_err(|e| format!("zstd encoder init error: {}", e))?
252
- } else {
253
- zstd::stream::Encoder::new(Vec::with_capacity(total_len / 2), adaptive_level)
254
- .map_err(|e| format!("zstd encoder init error: {}", e))?
255
- };
256
-
257
- let threads = num_cpus::get() as u32;
258
- if threads > 1 {
259
- let max_threads = if adaptive_level >= 20 { threads.min(4) } else { threads };
260
- let _ = encoder.multithread(max_threads);
261
- }
262
-
263
- if total_len > 256 * 1024 && adaptive_level >= 3 {
264
- let _ = encoder.long_distance_matching(true);
265
- }
266
- if total_len > 256 * 1024 {
267
- let wlog = if total_len > 1024 * 1024 * 1024 { 30 }
268
- else if total_len > 512 * 1024 * 1024 { 29 }
269
- else if total_len > 64 * 1024 * 1024 { 28 }
270
- else if total_len > 8 * 1024 * 1024 { 27 }
271
- else { 26 };
272
- let _ = encoder.window_log(wlog);
273
- }
274
-
275
- let _ = encoder.set_pledged_src_size(Some(total_len as u64));
276
-
277
- if !prefix.is_empty() {
278
- encoder.write_all(prefix).map_err(|e| format!("zstd write prefix error: {}", e))?;
279
- }
280
-
281
- let chunk_size = if total_len > 256 * 1024 * 1024 { 16 * 1024 * 1024 }
282
- else if total_len > 64 * 1024 * 1024 { 8 * 1024 * 1024 }
283
- else { buf.len() };
284
-
285
- for chunk in buf.chunks(chunk_size) {
286
- encoder.write_all(chunk).map_err(|e| format!("zstd write error: {}", e))?;
287
- }
288
-
289
- encoder.finish().map_err(|e| format!("zstd finish error: {}", e))
290
- }
291
-
292
- pub fn zstd_decompress_bytes(buf: &[u8], dict: Option<&[u8]>) -> std::result::Result<Vec<u8>, String> {
293
- use std::io::Read;
294
- let mut out = Vec::with_capacity(buf.len() * 2);
295
- if let Some(d) = dict {
296
- let mut decoder = zstd::stream::Decoder::with_dictionary(std::io::Cursor::new(buf), d)
297
- .map_err(|e| format!("zstd decoder init error: {}", e))?;
298
- decoder.window_log_max(31).map_err(|e| format!("zstd window_log_max error: {}", e))?;
299
- decoder.read_to_end(&mut out).map_err(|e| format!("zstd decompress error: {}", e))?;
300
- } else {
301
- let mut decoder = zstd::stream::Decoder::new(std::io::Cursor::new(buf))
302
- .map_err(|e| format!("zstd decoder init error: {}", e))?;
303
- decoder.window_log_max(31).map_err(|e| format!("zstd window_log_max error: {}", e))?;
304
- decoder.read_to_end(&mut out).map_err(|e| format!("zstd decompress error: {}", e))?;
305
- }
306
- Ok(out)
307
- }
308
-
309
-
310
- #[cfg(test)]
311
- mod tests {
312
- use super::*;
313
-
314
- #[test]
315
- fn test_scan_magic() {
316
- let data = b"xxxxROX1yyyyROX1".to_vec();
317
- let res = scan_pixels_bytes(&data, 3, None);
318
- assert_eq!(res.magic_positions.len(), 2);
319
- }
320
-
321
- #[test]
322
- fn test_markers() {
323
- let pixels = vec![1u8,2,3, 4,5,6, 1,2,3];
324
- let markers_vec = vec![1u8,2,3];
325
- let res = scan_pixels_bytes(&pixels, 3, Some(&markers_vec));
326
- assert_eq!(res.marker_positions, vec![0,2]);
327
- }
328
-
329
- #[test]
330
- fn test_train_dictionary() {
331
- use std::fs::{write, create_dir_all};
332
- let td = std::env::temp_dir().join("rox_dict_test");
333
- let _ = create_dir_all(&td);
334
- let f1 = td.join("a.bin");
335
- let f2 = td.join("b.bin");
336
- // produce 1 MiB of repeated data per file
337
- let big = vec![0xABu8; 1024 * 1024];
338
- write(&f1, &big).unwrap();
339
- write(&f2, &big).unwrap();
340
- // choose dictionary size 16 KiB (far below total sample size ≈2 MiB)
341
- match train_zstd_dictionary(&[f1.clone(), f2.clone()], 16 * 1024) {
342
- Ok(dict) => {
343
- assert!(dict.len() <= 16 * 1024);
344
- assert!(!dict.is_empty());
345
- }
346
- Err(e) => {
347
- // dictionary training may fail due to insufficient or unsuitable samples;
348
- // ensure error string is nonempty to catch panics
349
- assert!(!e.to_string().is_empty());
350
- }
351
- }
352
- }
353
-
354
- #[test]
355
- fn test_delta_roundtrip() {
356
- let data = vec![10u8, 20, 30, 40, 250];
357
- let enc = delta_encode_bytes(&data);
358
- let dec = delta_decode_bytes(&enc);
359
- assert_eq!(dec, data);
360
- }
361
-
362
- #[test]
363
- fn test_crc_adler() {
364
- let data = b"hello".to_vec();
365
- assert_eq!(crc32_bytes(&data), crc32fast::hash(&data));
366
- assert_eq!(adler32_bytes(&data), adler32_bytes(&data));
367
-
368
- // also test large buffer triggers parallel branch
369
- let big = vec![0xAAu8; 5 * 1024 * 1024];
370
- assert_eq!(crc32_bytes(&big), crc32fast::hash(&big));
371
- }
372
-
373
- #[test]
374
- fn test_zstd_dict_roundtrip() {
375
- let data = b"this is some test data that repeats. ".repeat(1000);
376
- // simple dictionary containing a substring
377
- let dict = b"test data";
378
- let compressed = zstd_compress_bytes(&data, 3, Some(dict)).expect("compress");
379
- let decompressed = zstd_decompress_bytes(&compressed, Some(dict)).expect("decompress");
380
- assert_eq!(decompressed, data);
381
- }
382
- }
package/native/crypto.rs DELETED
@@ -1,119 +0,0 @@
1
- use anyhow::{anyhow, Result};
2
- use aes_gcm::{
3
- aead::{Aead, KeyInit},
4
- Aes256Gcm, Nonce,
5
- };
6
- use pbkdf2::pbkdf2_hmac;
7
- use rand::RngCore;
8
- use sha2::Sha256;
9
-
10
- const ENC_NONE: u8 = 0x00;
11
- const ENC_AES: u8 = 0x01;
12
- const ENC_XOR: u8 = 0x02;
13
- const PBKDF2_ITERS: u32 = 600_000;
14
-
15
- pub fn encrypt_xor(data: &[u8], passphrase: &str) -> Vec<u8> {
16
- let key = passphrase.as_bytes();
17
- let mut result = Vec::with_capacity(1 + data.len());
18
- result.push(ENC_XOR);
19
-
20
- for (i, &byte) in data.iter().enumerate() {
21
- result.push(byte ^ key[i % key.len()]);
22
- }
23
-
24
- result
25
- }
26
-
27
- pub fn encrypt_aes(data: &[u8], passphrase: &str) -> Result<Vec<u8>> {
28
- let mut salt = [0u8; 16];
29
- rand::thread_rng().fill_bytes(&mut salt);
30
-
31
- let mut key = [0u8; 32];
32
- pbkdf2_hmac::<Sha256>(passphrase.as_bytes(), &salt, PBKDF2_ITERS, &mut key);
33
-
34
- let cipher = Aes256Gcm::new_from_slice(&key)
35
- .map_err(|e| anyhow::anyhow!("Failed to create cipher: {}", e))?;
36
-
37
- let mut iv = [0u8; 12];
38
- rand::thread_rng().fill_bytes(&mut iv);
39
- let nonce = Nonce::from_slice(&iv);
40
-
41
- let ciphertext = cipher
42
- .encrypt(nonce, data)
43
- .map_err(|e| anyhow::anyhow!("Encryption failed: {}", e))?;
44
-
45
- let cipher_len = ciphertext.len();
46
- if cipher_len < 16 {
47
- return Err(anyhow::anyhow!("Ciphertext too short"));
48
- }
49
-
50
- let tag = &ciphertext[cipher_len - 16..];
51
- let encrypted_data = &ciphertext[..cipher_len - 16];
52
-
53
- let mut result = Vec::with_capacity(1 + 16 + 12 + 16 + encrypted_data.len());
54
- result.push(ENC_AES);
55
- result.extend_from_slice(&salt);
56
- result.extend_from_slice(&iv);
57
- result.extend_from_slice(tag);
58
- result.extend_from_slice(encrypted_data);
59
-
60
- Ok(result)
61
- }
62
-
63
- pub fn no_encryption(data: &[u8]) -> Vec<u8> {
64
- let mut result = Vec::with_capacity(1 + data.len());
65
- result.push(ENC_NONE);
66
- result.extend_from_slice(data);
67
- result
68
- }
69
-
70
- pub fn decrypt_xor(data: &[u8], passphrase: &str) -> Result<Vec<u8>> {
71
- if data.is_empty() { return Err(anyhow!("Empty xor payload")); }
72
- if passphrase.is_empty() { return Err(anyhow!("Passphrase required")); }
73
- let key = passphrase.as_bytes();
74
- let mut out = Vec::with_capacity(data.len());
75
- for (i, &b) in data.iter().enumerate() {
76
- out.push(b ^ key[i % key.len()]);
77
- }
78
- Ok(out)
79
- }
80
-
81
- pub fn decrypt_aes(data: &[u8], passphrase: &str) -> Result<Vec<u8>> {
82
- if data.len() < 1 + 16 + 12 + 16 { return Err(anyhow!("Invalid AES payload length")); }
83
- let salt = &data[1..17];
84
- let iv = &data[17..29];
85
- let tag = &data[29..45];
86
- let enc = &data[45..];
87
-
88
- let mut key = [0u8; 32];
89
- pbkdf2_hmac::<Sha256>(passphrase.as_bytes(), salt, PBKDF2_ITERS, &mut key);
90
-
91
- let cipher = Aes256Gcm::new_from_slice(&key)
92
- .map_err(|e| anyhow!("Failed to create cipher: {}", e))?;
93
-
94
- let mut combined = Vec::with_capacity(enc.len() + tag.len());
95
- combined.extend_from_slice(enc);
96
- combined.extend_from_slice(tag);
97
-
98
- let nonce = Nonce::from_slice(iv);
99
- let decrypted = cipher.decrypt(nonce, combined.as_ref())
100
- .map_err(|e| anyhow!("AES decryption failed: {}", e))?;
101
- Ok(decrypted)
102
- }
103
-
104
- pub fn try_decrypt(buf: &[u8], passphrase: Option<&str>) -> Result<Vec<u8>> {
105
- if buf.is_empty() { return Err(anyhow!("Empty buffer")); }
106
- let flag = buf[0];
107
- match flag {
108
- ENC_NONE => Ok(buf[1..].to_vec()),
109
- ENC_XOR => {
110
- let pass = passphrase.ok_or_else(|| anyhow!("Passphrase required for XOR decryption"))?;
111
- decrypt_xor(&buf[1..], pass)
112
- }
113
- ENC_AES => {
114
- let pass = passphrase.ok_or_else(|| anyhow!("Passphrase required for AES decryption"))?;
115
- decrypt_aes(buf, pass)
116
- }
117
- _ => Err(anyhow!("Unknown encryption flag: {}", flag)),
118
- }
119
- }