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;