use base64::{Engine, engine::general_purpose::STANDARD};
use flate2::Compression;
use flate2::read::{GzDecoder, GzEncoder};
use seq_core::seqstring::global_string;
use seq_core::stack::{Stack, pop, push};
use seq_core::value::Value;
use std::io::Read;
#[unsafe(no_mangle)]
pub unsafe extern "C" fn patch_seq_compress_gzip(stack: Stack) -> Stack {
assert!(!stack.is_null(), "compress.gzip: stack is null");
let (stack, data_val) = unsafe { pop(stack) };
match data_val {
Value::String(data) => {
match gzip_compress(data.as_str().as_bytes(), Compression::default()) {
Some(compressed) => {
let encoded = STANDARD.encode(&compressed);
let stack = unsafe { push(stack, Value::String(global_string(encoded))) };
unsafe { push(stack, Value::Bool(true)) }
}
None => {
let stack = unsafe { push(stack, Value::String(global_string(String::new()))) };
unsafe { push(stack, Value::Bool(false)) }
}
}
}
_ => panic!("compress.gzip: expected String on stack"),
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn patch_seq_compress_gzip_level(stack: Stack) -> Stack {
assert!(!stack.is_null(), "compress.gzip-level: stack is null");
let (stack, level_val) = unsafe { pop(stack) };
let (stack, data_val) = unsafe { pop(stack) };
match (data_val, level_val) {
(Value::String(data), Value::Int(level)) => {
let level = level.clamp(1, 9) as u32;
match gzip_compress(data.as_str().as_bytes(), Compression::new(level)) {
Some(compressed) => {
let encoded = STANDARD.encode(&compressed);
let stack = unsafe { push(stack, Value::String(global_string(encoded))) };
unsafe { push(stack, Value::Bool(true)) }
}
None => {
let stack = unsafe { push(stack, Value::String(global_string(String::new()))) };
unsafe { push(stack, Value::Bool(false)) }
}
}
}
_ => panic!("compress.gzip-level: expected String and Int on stack"),
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn patch_seq_compress_gunzip(stack: Stack) -> Stack {
assert!(!stack.is_null(), "compress.gunzip: stack is null");
let (stack, data_val) = unsafe { pop(stack) };
match data_val {
Value::String(data) => {
let decoded = match STANDARD.decode(data.as_str()) {
Ok(d) => d,
Err(_) => {
let stack = unsafe { push(stack, Value::String(global_string(String::new()))) };
return unsafe { push(stack, Value::Bool(false)) };
}
};
match gzip_decompress(&decoded) {
Some(decompressed) => {
let stack = unsafe { push(stack, Value::String(global_string(decompressed))) };
unsafe { push(stack, Value::Bool(true)) }
}
None => {
let stack = unsafe { push(stack, Value::String(global_string(String::new()))) };
unsafe { push(stack, Value::Bool(false)) }
}
}
}
_ => panic!("compress.gunzip: expected String on stack"),
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn patch_seq_compress_zstd(stack: Stack) -> Stack {
assert!(!stack.is_null(), "compress.zstd: stack is null");
let (stack, data_val) = unsafe { pop(stack) };
match data_val {
Value::String(data) => match zstd::encode_all(data.as_str().as_bytes(), 3) {
Ok(compressed) => {
let encoded = STANDARD.encode(&compressed);
let stack = unsafe { push(stack, Value::String(global_string(encoded))) };
unsafe { push(stack, Value::Bool(true)) }
}
Err(_) => {
let stack = unsafe { push(stack, Value::String(global_string(String::new()))) };
unsafe { push(stack, Value::Bool(false)) }
}
},
_ => panic!("compress.zstd: expected String on stack"),
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn patch_seq_compress_zstd_level(stack: Stack) -> Stack {
assert!(!stack.is_null(), "compress.zstd-level: stack is null");
let (stack, level_val) = unsafe { pop(stack) };
let (stack, data_val) = unsafe { pop(stack) };
match (data_val, level_val) {
(Value::String(data), Value::Int(level)) => {
let level = level.clamp(1, 22) as i32;
match zstd::encode_all(data.as_str().as_bytes(), level) {
Ok(compressed) => {
let encoded = STANDARD.encode(&compressed);
let stack = unsafe { push(stack, Value::String(global_string(encoded))) };
unsafe { push(stack, Value::Bool(true)) }
}
Err(_) => {
let stack = unsafe { push(stack, Value::String(global_string(String::new()))) };
unsafe { push(stack, Value::Bool(false)) }
}
}
}
_ => panic!("compress.zstd-level: expected String and Int on stack"),
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn patch_seq_compress_unzstd(stack: Stack) -> Stack {
assert!(!stack.is_null(), "compress.unzstd: stack is null");
let (stack, data_val) = unsafe { pop(stack) };
match data_val {
Value::String(data) => {
let decoded = match STANDARD.decode(data.as_str()) {
Ok(d) => d,
Err(_) => {
let stack = unsafe { push(stack, Value::String(global_string(String::new()))) };
return unsafe { push(stack, Value::Bool(false)) };
}
};
match zstd::decode_all(decoded.as_slice()) {
Ok(decompressed) => match String::from_utf8(decompressed) {
Ok(s) => {
let stack = unsafe { push(stack, Value::String(global_string(s))) };
unsafe { push(stack, Value::Bool(true)) }
}
Err(_) => {
let stack =
unsafe { push(stack, Value::String(global_string(String::new()))) };
unsafe { push(stack, Value::Bool(false)) }
}
},
Err(_) => {
let stack = unsafe { push(stack, Value::String(global_string(String::new()))) };
unsafe { push(stack, Value::Bool(false)) }
}
}
}
_ => panic!("compress.unzstd: expected String on stack"),
}
}
fn gzip_compress(data: &[u8], level: Compression) -> Option<Vec<u8>> {
let mut encoder = GzEncoder::new(data, level);
let mut compressed = Vec::new();
match encoder.read_to_end(&mut compressed) {
Ok(_) => Some(compressed),
Err(_) => None,
}
}
fn gzip_decompress(data: &[u8]) -> Option<String> {
let mut decoder = GzDecoder::new(data);
let mut decompressed = String::new();
match decoder.read_to_string(&mut decompressed) {
Ok(_) => Some(decompressed),
Err(_) => None,
}
}
#[cfg(test)]
mod tests {
use super::*;
use seq_core::stack::alloc_stack;
#[test]
fn test_gzip_roundtrip() {
let stack = alloc_stack();
let stack = unsafe {
push(
stack,
Value::String(global_string("hello world".to_string())),
)
};
let stack = unsafe { patch_seq_compress_gzip(stack) };
let (stack, compress_success) = unsafe { pop(stack) };
assert_eq!(compress_success, Value::Bool(true));
let stack = unsafe { patch_seq_compress_gunzip(stack) };
let (stack, success) = unsafe { pop(stack) };
assert_eq!(success, Value::Bool(true));
let (_, result) = unsafe { pop(stack) };
if let Value::String(s) = result {
assert_eq!(s.as_str(), "hello world");
} else {
panic!("expected string");
}
}
#[test]
fn test_gzip_level() {
let stack = alloc_stack();
let stack = unsafe {
push(
stack,
Value::String(global_string("hello world".to_string())),
)
};
let stack = unsafe { push(stack, Value::Int(9)) };
let stack = unsafe { patch_seq_compress_gzip_level(stack) };
let (stack, compress_success) = unsafe { pop(stack) };
assert_eq!(compress_success, Value::Bool(true));
let stack = unsafe { patch_seq_compress_gunzip(stack) };
let (stack, success) = unsafe { pop(stack) };
assert_eq!(success, Value::Bool(true));
let (_, result) = unsafe { pop(stack) };
if let Value::String(s) = result {
assert_eq!(s.as_str(), "hello world");
} else {
panic!("expected string");
}
}
#[test]
fn test_zstd_roundtrip() {
let stack = alloc_stack();
let stack = unsafe {
push(
stack,
Value::String(global_string("hello world".to_string())),
)
};
let stack = unsafe { patch_seq_compress_zstd(stack) };
let (stack, compress_success) = unsafe { pop(stack) };
assert_eq!(compress_success, Value::Bool(true));
let stack = unsafe { patch_seq_compress_unzstd(stack) };
let (stack, success) = unsafe { pop(stack) };
assert_eq!(success, Value::Bool(true));
let (_, result) = unsafe { pop(stack) };
if let Value::String(s) = result {
assert_eq!(s.as_str(), "hello world");
} else {
panic!("expected string");
}
}
#[test]
fn test_zstd_level() {
let stack = alloc_stack();
let stack = unsafe {
push(
stack,
Value::String(global_string("hello world".to_string())),
)
};
let stack = unsafe { push(stack, Value::Int(19)) };
let stack = unsafe { patch_seq_compress_zstd_level(stack) };
let (stack, compress_success) = unsafe { pop(stack) };
assert_eq!(compress_success, Value::Bool(true));
let stack = unsafe { patch_seq_compress_unzstd(stack) };
let (stack, success) = unsafe { pop(stack) };
assert_eq!(success, Value::Bool(true));
let (_, result) = unsafe { pop(stack) };
if let Value::String(s) = result {
assert_eq!(s.as_str(), "hello world");
} else {
panic!("expected string");
}
}
#[test]
fn test_gunzip_invalid_base64() {
let stack = alloc_stack();
let stack = unsafe {
push(
stack,
Value::String(global_string("not valid base64!!!".to_string())),
)
};
let stack = unsafe { patch_seq_compress_gunzip(stack) };
let (_, success) = unsafe { pop(stack) };
assert_eq!(success, Value::Bool(false));
}
#[test]
fn test_gunzip_invalid_gzip() {
let stack = alloc_stack();
let stack = unsafe {
push(
stack,
Value::String(global_string("aGVsbG8gd29ybGQ=".to_string())),
)
};
let stack = unsafe { patch_seq_compress_gunzip(stack) };
let (_, success) = unsafe { pop(stack) };
assert_eq!(success, Value::Bool(false));
}
#[test]
fn test_unzstd_invalid() {
let stack = alloc_stack();
let stack = unsafe {
push(
stack,
Value::String(global_string("aGVsbG8gd29ybGQ=".to_string())),
)
};
let stack = unsafe { patch_seq_compress_unzstd(stack) };
let (_, success) = unsafe { pop(stack) };
assert_eq!(success, Value::Bool(false));
}
#[test]
fn test_empty_string() {
let stack = alloc_stack();
let stack = unsafe { push(stack, Value::String(global_string(String::new()))) };
let stack = unsafe { patch_seq_compress_gzip(stack) };
let (stack, compress_success) = unsafe { pop(stack) };
assert_eq!(compress_success, Value::Bool(true));
let stack = unsafe { patch_seq_compress_gunzip(stack) };
let (stack, success) = unsafe { pop(stack) };
assert_eq!(success, Value::Bool(true));
let (_, result) = unsafe { pop(stack) };
if let Value::String(s) = result {
assert_eq!(s.as_str(), "");
} else {
panic!("expected string");
}
}
#[test]
fn test_large_data() {
let stack = alloc_stack();
let large_data = "x".repeat(10000);
let stack = unsafe { push(stack, Value::String(global_string(large_data.clone()))) };
let stack = unsafe { patch_seq_compress_zstd(stack) };
let (stack, compress_success) = unsafe { pop(stack) };
assert_eq!(compress_success, Value::Bool(true));
let stack = unsafe { patch_seq_compress_unzstd(stack) };
let (stack, success) = unsafe { pop(stack) };
assert_eq!(success, Value::Bool(true));
let (_, result) = unsafe { pop(stack) };
if let Value::String(s) = result {
assert_eq!(s.as_str(), large_data);
} else {
panic!("expected string");
}
}
}