1use base64::{Engine, engine::general_purpose::STANDARD};
28use flate2::Compression;
29use flate2::read::{GzDecoder, GzEncoder};
30use seq_core::seqstring::global_string;
31use seq_core::stack::{Stack, pop, push};
32use seq_core::value::Value;
33use std::io::Read;
34
35#[unsafe(no_mangle)]
44pub unsafe extern "C" fn patch_seq_compress_gzip(stack: Stack) -> Stack {
45 assert!(!stack.is_null(), "compress.gzip: stack is null");
46
47 let (stack, data_val) = unsafe { pop(stack) };
48
49 match data_val {
50 Value::String(data) => {
51 match gzip_compress(data.as_str().as_bytes(), Compression::default()) {
52 Some(compressed) => {
53 let encoded = STANDARD.encode(&compressed);
54 let stack = unsafe { push(stack, Value::String(global_string(encoded))) };
55 unsafe { push(stack, Value::Bool(true)) }
56 }
57 None => {
58 let stack = unsafe { push(stack, Value::String(global_string(String::new()))) };
59 unsafe { push(stack, Value::Bool(false)) }
60 }
61 }
62 }
63 _ => panic!("compress.gzip: expected String on stack"),
64 }
65}
66
67#[unsafe(no_mangle)]
77pub unsafe extern "C" fn patch_seq_compress_gzip_level(stack: Stack) -> Stack {
78 assert!(!stack.is_null(), "compress.gzip-level: stack is null");
79
80 let (stack, level_val) = unsafe { pop(stack) };
81 let (stack, data_val) = unsafe { pop(stack) };
82
83 match (data_val, level_val) {
84 (Value::String(data), Value::Int(level)) => {
85 let level = level.clamp(1, 9) as u32;
86 match gzip_compress(data.as_str().as_bytes(), Compression::new(level)) {
87 Some(compressed) => {
88 let encoded = STANDARD.encode(&compressed);
89 let stack = unsafe { push(stack, Value::String(global_string(encoded))) };
90 unsafe { push(stack, Value::Bool(true)) }
91 }
92 None => {
93 let stack = unsafe { push(stack, Value::String(global_string(String::new()))) };
94 unsafe { push(stack, Value::Bool(false)) }
95 }
96 }
97 }
98 _ => panic!("compress.gzip-level: expected String and Int on stack"),
99 }
100}
101
102#[unsafe(no_mangle)]
112pub unsafe extern "C" fn patch_seq_compress_gunzip(stack: Stack) -> Stack {
113 assert!(!stack.is_null(), "compress.gunzip: stack is null");
114
115 let (stack, data_val) = unsafe { pop(stack) };
116
117 match data_val {
118 Value::String(data) => {
119 let decoded = match STANDARD.decode(data.as_str()) {
121 Ok(d) => d,
122 Err(_) => {
123 let stack = unsafe { push(stack, Value::String(global_string(String::new()))) };
124 return unsafe { push(stack, Value::Bool(false)) };
125 }
126 };
127
128 match gzip_decompress(&decoded) {
130 Some(decompressed) => {
131 let stack = unsafe { push(stack, Value::String(global_string(decompressed))) };
132 unsafe { push(stack, Value::Bool(true)) }
133 }
134 None => {
135 let stack = unsafe { push(stack, Value::String(global_string(String::new()))) };
136 unsafe { push(stack, Value::Bool(false)) }
137 }
138 }
139 }
140 _ => panic!("compress.gunzip: expected String on stack"),
141 }
142}
143
144#[unsafe(no_mangle)]
153pub unsafe extern "C" fn patch_seq_compress_zstd(stack: Stack) -> Stack {
154 assert!(!stack.is_null(), "compress.zstd: stack is null");
155
156 let (stack, data_val) = unsafe { pop(stack) };
157
158 match data_val {
159 Value::String(data) => match zstd::encode_all(data.as_str().as_bytes(), 3) {
160 Ok(compressed) => {
161 let encoded = STANDARD.encode(&compressed);
162 let stack = unsafe { push(stack, Value::String(global_string(encoded))) };
163 unsafe { push(stack, Value::Bool(true)) }
164 }
165 Err(_) => {
166 let stack = unsafe { push(stack, Value::String(global_string(String::new()))) };
167 unsafe { push(stack, Value::Bool(false)) }
168 }
169 },
170 _ => panic!("compress.zstd: expected String on stack"),
171 }
172}
173
174#[unsafe(no_mangle)]
184pub unsafe extern "C" fn patch_seq_compress_zstd_level(stack: Stack) -> Stack {
185 assert!(!stack.is_null(), "compress.zstd-level: stack is null");
186
187 let (stack, level_val) = unsafe { pop(stack) };
188 let (stack, data_val) = unsafe { pop(stack) };
189
190 match (data_val, level_val) {
191 (Value::String(data), Value::Int(level)) => {
192 let level = level.clamp(1, 22) as i32;
193 match zstd::encode_all(data.as_str().as_bytes(), level) {
194 Ok(compressed) => {
195 let encoded = STANDARD.encode(&compressed);
196 let stack = unsafe { push(stack, Value::String(global_string(encoded))) };
197 unsafe { push(stack, Value::Bool(true)) }
198 }
199 Err(_) => {
200 let stack = unsafe { push(stack, Value::String(global_string(String::new()))) };
201 unsafe { push(stack, Value::Bool(false)) }
202 }
203 }
204 }
205 _ => panic!("compress.zstd-level: expected String and Int on stack"),
206 }
207}
208
209#[unsafe(no_mangle)]
219pub unsafe extern "C" fn patch_seq_compress_unzstd(stack: Stack) -> Stack {
220 assert!(!stack.is_null(), "compress.unzstd: stack is null");
221
222 let (stack, data_val) = unsafe { pop(stack) };
223
224 match data_val {
225 Value::String(data) => {
226 let decoded = match STANDARD.decode(data.as_str()) {
228 Ok(d) => d,
229 Err(_) => {
230 let stack = unsafe { push(stack, Value::String(global_string(String::new()))) };
231 return unsafe { push(stack, Value::Bool(false)) };
232 }
233 };
234
235 match zstd::decode_all(decoded.as_slice()) {
237 Ok(decompressed) => match String::from_utf8(decompressed) {
238 Ok(s) => {
239 let stack = unsafe { push(stack, Value::String(global_string(s))) };
240 unsafe { push(stack, Value::Bool(true)) }
241 }
242 Err(_) => {
243 let stack =
244 unsafe { push(stack, Value::String(global_string(String::new()))) };
245 unsafe { push(stack, Value::Bool(false)) }
246 }
247 },
248 Err(_) => {
249 let stack = unsafe { push(stack, Value::String(global_string(String::new()))) };
250 unsafe { push(stack, Value::Bool(false)) }
251 }
252 }
253 }
254 _ => panic!("compress.unzstd: expected String on stack"),
255 }
256}
257
258fn gzip_compress(data: &[u8], level: Compression) -> Option<Vec<u8>> {
261 let mut encoder = GzEncoder::new(data, level);
262 let mut compressed = Vec::new();
263 match encoder.read_to_end(&mut compressed) {
264 Ok(_) => Some(compressed),
265 Err(_) => None,
266 }
267}
268
269fn gzip_decompress(data: &[u8]) -> Option<String> {
270 let mut decoder = GzDecoder::new(data);
271 let mut decompressed = String::new();
272 match decoder.read_to_string(&mut decompressed) {
273 Ok(_) => Some(decompressed),
274 Err(_) => None,
275 }
276}
277
278#[cfg(test)]
279mod tests {
280 use super::*;
281 use seq_core::stack::alloc_stack;
282
283 #[test]
284 fn test_gzip_roundtrip() {
285 let stack = alloc_stack();
286 let stack = unsafe {
287 push(
288 stack,
289 Value::String(global_string("hello world".to_string())),
290 )
291 };
292
293 let stack = unsafe { patch_seq_compress_gzip(stack) };
295
296 let (stack, compress_success) = unsafe { pop(stack) };
298 assert_eq!(compress_success, Value::Bool(true));
299
300 let stack = unsafe { patch_seq_compress_gunzip(stack) };
302
303 let (stack, success) = unsafe { pop(stack) };
305 assert_eq!(success, Value::Bool(true));
306
307 let (_, result) = unsafe { pop(stack) };
308 if let Value::String(s) = result {
309 assert_eq!(s.as_str(), "hello world");
310 } else {
311 panic!("expected string");
312 }
313 }
314
315 #[test]
316 fn test_gzip_level() {
317 let stack = alloc_stack();
318 let stack = unsafe {
319 push(
320 stack,
321 Value::String(global_string("hello world".to_string())),
322 )
323 };
324 let stack = unsafe { push(stack, Value::Int(9)) };
325
326 let stack = unsafe { patch_seq_compress_gzip_level(stack) };
328
329 let (stack, compress_success) = unsafe { pop(stack) };
331 assert_eq!(compress_success, Value::Bool(true));
332
333 let stack = unsafe { patch_seq_compress_gunzip(stack) };
335 let (stack, success) = unsafe { pop(stack) };
336 assert_eq!(success, Value::Bool(true));
337
338 let (_, result) = unsafe { pop(stack) };
339 if let Value::String(s) = result {
340 assert_eq!(s.as_str(), "hello world");
341 } else {
342 panic!("expected string");
343 }
344 }
345
346 #[test]
347 fn test_zstd_roundtrip() {
348 let stack = alloc_stack();
349 let stack = unsafe {
350 push(
351 stack,
352 Value::String(global_string("hello world".to_string())),
353 )
354 };
355
356 let stack = unsafe { patch_seq_compress_zstd(stack) };
358
359 let (stack, compress_success) = unsafe { pop(stack) };
361 assert_eq!(compress_success, Value::Bool(true));
362
363 let stack = unsafe { patch_seq_compress_unzstd(stack) };
365
366 let (stack, success) = unsafe { pop(stack) };
368 assert_eq!(success, Value::Bool(true));
369
370 let (_, result) = unsafe { pop(stack) };
371 if let Value::String(s) = result {
372 assert_eq!(s.as_str(), "hello world");
373 } else {
374 panic!("expected string");
375 }
376 }
377
378 #[test]
379 fn test_zstd_level() {
380 let stack = alloc_stack();
381 let stack = unsafe {
382 push(
383 stack,
384 Value::String(global_string("hello world".to_string())),
385 )
386 };
387 let stack = unsafe { push(stack, Value::Int(19)) };
388
389 let stack = unsafe { patch_seq_compress_zstd_level(stack) };
391
392 let (stack, compress_success) = unsafe { pop(stack) };
394 assert_eq!(compress_success, Value::Bool(true));
395
396 let stack = unsafe { patch_seq_compress_unzstd(stack) };
398 let (stack, success) = unsafe { pop(stack) };
399 assert_eq!(success, Value::Bool(true));
400
401 let (_, result) = unsafe { pop(stack) };
402 if let Value::String(s) = result {
403 assert_eq!(s.as_str(), "hello world");
404 } else {
405 panic!("expected string");
406 }
407 }
408
409 #[test]
410 fn test_gunzip_invalid_base64() {
411 let stack = alloc_stack();
412 let stack = unsafe {
413 push(
414 stack,
415 Value::String(global_string("not valid base64!!!".to_string())),
416 )
417 };
418
419 let stack = unsafe { patch_seq_compress_gunzip(stack) };
420 let (_, success) = unsafe { pop(stack) };
421 assert_eq!(success, Value::Bool(false));
422 }
423
424 #[test]
425 fn test_gunzip_invalid_gzip() {
426 let stack = alloc_stack();
427 let stack = unsafe {
429 push(
430 stack,
431 Value::String(global_string("aGVsbG8gd29ybGQ=".to_string())),
432 )
433 };
434
435 let stack = unsafe { patch_seq_compress_gunzip(stack) };
436 let (_, success) = unsafe { pop(stack) };
437 assert_eq!(success, Value::Bool(false));
438 }
439
440 #[test]
441 fn test_unzstd_invalid() {
442 let stack = alloc_stack();
443 let stack = unsafe {
445 push(
446 stack,
447 Value::String(global_string("aGVsbG8gd29ybGQ=".to_string())),
448 )
449 };
450
451 let stack = unsafe { patch_seq_compress_unzstd(stack) };
452 let (_, success) = unsafe { pop(stack) };
453 assert_eq!(success, Value::Bool(false));
454 }
455
456 #[test]
457 fn test_empty_string() {
458 let stack = alloc_stack();
459 let stack = unsafe { push(stack, Value::String(global_string(String::new()))) };
460
461 let stack = unsafe { patch_seq_compress_gzip(stack) };
463
464 let (stack, compress_success) = unsafe { pop(stack) };
466 assert_eq!(compress_success, Value::Bool(true));
467
468 let stack = unsafe { patch_seq_compress_gunzip(stack) };
470 let (stack, success) = unsafe { pop(stack) };
471 assert_eq!(success, Value::Bool(true));
472
473 let (_, result) = unsafe { pop(stack) };
474 if let Value::String(s) = result {
475 assert_eq!(s.as_str(), "");
476 } else {
477 panic!("expected string");
478 }
479 }
480
481 #[test]
482 fn test_large_data() {
483 let stack = alloc_stack();
484 let large_data = "x".repeat(10000);
485 let stack = unsafe { push(stack, Value::String(global_string(large_data.clone()))) };
486
487 let stack = unsafe { patch_seq_compress_zstd(stack) };
489
490 let (stack, compress_success) = unsafe { pop(stack) };
492 assert_eq!(compress_success, Value::Bool(true));
493
494 let stack = unsafe { patch_seq_compress_unzstd(stack) };
496 let (stack, success) = unsafe { pop(stack) };
497 assert_eq!(success, Value::Bool(true));
498
499 let (_, result) = unsafe { pop(stack) };
500 if let Value::String(s) = result {
501 assert_eq!(s.as_str(), large_data);
502 } else {
503 panic!("expected string");
504 }
505 }
506}