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) => match gzip_compress(data.as_bytes(), Compression::default()) {
51 Some(compressed) => {
52 let encoded = STANDARD.encode(&compressed);
53 let stack = unsafe { push(stack, Value::String(global_string(encoded))) };
54 unsafe { push(stack, Value::Bool(true)) }
55 }
56 None => {
57 let stack = unsafe { push(stack, Value::String(global_string(String::new()))) };
58 unsafe { push(stack, Value::Bool(false)) }
59 }
60 },
61 _ => panic!("compress.gzip: expected String on stack"),
62 }
63}
64
65#[unsafe(no_mangle)]
75pub unsafe extern "C" fn patch_seq_compress_gzip_level(stack: Stack) -> Stack {
76 assert!(!stack.is_null(), "compress.gzip-level: stack is null");
77
78 let (stack, level_val) = unsafe { pop(stack) };
79 let (stack, data_val) = unsafe { pop(stack) };
80
81 match (data_val, level_val) {
82 (Value::String(data), Value::Int(level)) => {
83 let level = level.clamp(1, 9) as u32;
84 match gzip_compress(data.as_bytes(), Compression::new(level)) {
85 Some(compressed) => {
86 let encoded = STANDARD.encode(&compressed);
87 let stack = unsafe { push(stack, Value::String(global_string(encoded))) };
88 unsafe { push(stack, Value::Bool(true)) }
89 }
90 None => {
91 let stack = unsafe { push(stack, Value::String(global_string(String::new()))) };
92 unsafe { push(stack, Value::Bool(false)) }
93 }
94 }
95 }
96 _ => panic!("compress.gzip-level: expected String and Int on stack"),
97 }
98}
99
100#[unsafe(no_mangle)]
110pub unsafe extern "C" fn patch_seq_compress_gunzip(stack: Stack) -> Stack {
111 assert!(!stack.is_null(), "compress.gunzip: stack is null");
112
113 let (stack, data_val) = unsafe { pop(stack) };
114
115 match data_val {
116 Value::String(data) => {
117 let decoded = match STANDARD.decode(data.as_str_or_empty()) {
119 Ok(d) => d,
120 Err(_) => {
121 let stack = unsafe { push(stack, Value::String(global_string(String::new()))) };
122 return unsafe { push(stack, Value::Bool(false)) };
123 }
124 };
125
126 match gzip_decompress(&decoded) {
128 Some(decompressed) => {
129 let stack = unsafe { push(stack, Value::String(global_string(decompressed))) };
130 unsafe { push(stack, Value::Bool(true)) }
131 }
132 None => {
133 let stack = unsafe { push(stack, Value::String(global_string(String::new()))) };
134 unsafe { push(stack, Value::Bool(false)) }
135 }
136 }
137 }
138 _ => panic!("compress.gunzip: expected String on stack"),
139 }
140}
141
142#[unsafe(no_mangle)]
151pub unsafe extern "C" fn patch_seq_compress_zstd(stack: Stack) -> Stack {
152 assert!(!stack.is_null(), "compress.zstd: stack is null");
153
154 let (stack, data_val) = unsafe { pop(stack) };
155
156 match data_val {
157 Value::String(data) => match zstd::encode_all(data.as_bytes(), 3) {
158 Ok(compressed) => {
159 let encoded = STANDARD.encode(&compressed);
160 let stack = unsafe { push(stack, Value::String(global_string(encoded))) };
161 unsafe { push(stack, Value::Bool(true)) }
162 }
163 Err(_) => {
164 let stack = unsafe { push(stack, Value::String(global_string(String::new()))) };
165 unsafe { push(stack, Value::Bool(false)) }
166 }
167 },
168 _ => panic!("compress.zstd: expected String on stack"),
169 }
170}
171
172#[unsafe(no_mangle)]
182pub unsafe extern "C" fn patch_seq_compress_zstd_level(stack: Stack) -> Stack {
183 assert!(!stack.is_null(), "compress.zstd-level: stack is null");
184
185 let (stack, level_val) = unsafe { pop(stack) };
186 let (stack, data_val) = unsafe { pop(stack) };
187
188 match (data_val, level_val) {
189 (Value::String(data), Value::Int(level)) => {
190 let level = level.clamp(1, 22) as i32;
191 match zstd::encode_all(data.as_bytes(), level) {
192 Ok(compressed) => {
193 let encoded = STANDARD.encode(&compressed);
194 let stack = unsafe { push(stack, Value::String(global_string(encoded))) };
195 unsafe { push(stack, Value::Bool(true)) }
196 }
197 Err(_) => {
198 let stack = unsafe { push(stack, Value::String(global_string(String::new()))) };
199 unsafe { push(stack, Value::Bool(false)) }
200 }
201 }
202 }
203 _ => panic!("compress.zstd-level: expected String and Int on stack"),
204 }
205}
206
207#[unsafe(no_mangle)]
217pub unsafe extern "C" fn patch_seq_compress_unzstd(stack: Stack) -> Stack {
218 assert!(!stack.is_null(), "compress.unzstd: stack is null");
219
220 let (stack, data_val) = unsafe { pop(stack) };
221
222 match data_val {
223 Value::String(data) => {
224 let decoded = match STANDARD.decode(data.as_str_or_empty()) {
226 Ok(d) => d,
227 Err(_) => {
228 let stack = unsafe { push(stack, Value::String(global_string(String::new()))) };
229 return unsafe { push(stack, Value::Bool(false)) };
230 }
231 };
232
233 match zstd::decode_all(decoded.as_slice()) {
235 Ok(decompressed) => match String::from_utf8(decompressed) {
236 Ok(s) => {
237 let stack = unsafe { push(stack, Value::String(global_string(s))) };
238 unsafe { push(stack, Value::Bool(true)) }
239 }
240 Err(_) => {
241 let stack =
242 unsafe { push(stack, Value::String(global_string(String::new()))) };
243 unsafe { push(stack, Value::Bool(false)) }
244 }
245 },
246 Err(_) => {
247 let stack = unsafe { push(stack, Value::String(global_string(String::new()))) };
248 unsafe { push(stack, Value::Bool(false)) }
249 }
250 }
251 }
252 _ => panic!("compress.unzstd: expected String on stack"),
253 }
254}
255
256fn gzip_compress(data: &[u8], level: Compression) -> Option<Vec<u8>> {
259 let mut encoder = GzEncoder::new(data, level);
260 let mut compressed = Vec::new();
261 match encoder.read_to_end(&mut compressed) {
262 Ok(_) => Some(compressed),
263 Err(_) => None,
264 }
265}
266
267fn gzip_decompress(data: &[u8]) -> Option<String> {
268 let mut decoder = GzDecoder::new(data);
269 let mut decompressed = String::new();
270 match decoder.read_to_string(&mut decompressed) {
271 Ok(_) => Some(decompressed),
272 Err(_) => None,
273 }
274}
275
276#[cfg(test)]
277mod tests;