Skip to content

Commit 128bd8c

Browse files
committed
Add zlib module
+ Implement following functions: * zlib.compress * zlib.decompress * zlib.crc32 * zlib.adler32 + Add compression level constants
1 parent 96bd5a6 commit 128bd8c

File tree

4 files changed

+232
-0
lines changed

4 files changed

+232
-0
lines changed

Cargo.lock

Lines changed: 76 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

vm/Cargo.toml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,10 @@ libc = "0.2"
6464
nix = "0.14.1"
6565
wtf8 = "0.0.3"
6666
arr_macro = "0.1.2"
67+
crc32fast = "1.2.0"
68+
adler32 = "1.0.3"
69+
flate2 = { version = "1.0", features = ["zlib"], default-features = false }
70+
libz-sys = "1.0.25"
6771

6872
flame = { version = "0.2", optional = true }
6973
flamer = { version = "0.3", optional = true }

vm/src/stdlib/mod.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ mod tokenize;
2929
mod unicodedata;
3030
mod warnings;
3131
mod weakref;
32+
mod zlib;
3233
use std::collections::HashMap;
3334

3435
use crate::vm::VirtualMachine;
@@ -72,6 +73,7 @@ pub fn get_module_inits() -> HashMap<String, StdlibInitFunc> {
7273
"_imp".to_string() => Box::new(imp::make_module),
7374
"unicodedata".to_string() => Box::new(unicodedata::make_module),
7475
"_warnings".to_string() => Box::new(warnings::make_module),
76+
"zlib".to_string() => Box::new(zlib::make_module),
7577
};
7678

7779
// Insert parser related modules:

vm/src/stdlib/zlib.rs

Lines changed: 150 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,150 @@
1+
use crate::function::OptionalArg;
2+
use crate::obj::{objbytes::PyBytesRef, objint::PyIntRef};
3+
use crate::pyobject::{create_type, ItemProtocol, PyObjectRef, PyResult};
4+
use crate::vm::VirtualMachine;
5+
6+
use adler32::RollingAdler32 as Adler32;
7+
use crc32fast::Hasher as Crc32;
8+
use flate2::{write::ZlibEncoder, Compression, Decompress, FlushDecompress, Status};
9+
use libz_sys as libz;
10+
use num_traits::cast::ToPrimitive;
11+
12+
use std::io::Write;
13+
14+
// copied from zlibmodule.c (commit 530f506ac91338)
15+
const MAX_WBITS: u8 = 15;
16+
const DEF_BUF_SIZE: usize = 16 * 1024;
17+
18+
pub fn make_module(vm: &VirtualMachine) -> PyObjectRef {
19+
let ctx = &vm.ctx;
20+
21+
let zlib_error = create_type("error", &ctx.type_type, &ctx.exceptions.exception_type);
22+
23+
py_module!(vm, "zlib", {
24+
"crc32" => ctx.new_rustfunc(zlib_crc32),
25+
"adler32" => ctx.new_rustfunc(zlib_adler32),
26+
"compress" => ctx.new_rustfunc(zlib_compress),
27+
"decompress" => ctx.new_rustfunc(zlib_decompress),
28+
"error" => zlib_error,
29+
"Z_DEFAULT_COMPRESSION" => ctx.new_int(libz::Z_DEFAULT_COMPRESSION),
30+
"Z_NO_COMPRESSION" => ctx.new_int(libz::Z_NO_COMPRESSION),
31+
"Z_BEST_SPEED" => ctx.new_int(libz::Z_BEST_SPEED),
32+
"Z_BEST_COMPRESSION" => ctx.new_int(libz::Z_BEST_COMPRESSION),
33+
"DEF_BUF_SIZE" => ctx.new_int(DEF_BUF_SIZE),
34+
"MAX_WBITS" => ctx.new_int(MAX_WBITS),
35+
})
36+
}
37+
38+
macro_rules! checksum_fn {
39+
($fn_name: ident, $h_new: expr, $h_update: expr, $h_compute: expr, $begin_state: expr) => {
40+
fn $fn_name(
41+
data: PyBytesRef,
42+
begin_state: OptionalArg<PyIntRef>,
43+
vm: &VirtualMachine,
44+
) -> PyResult<PyObjectRef> {
45+
let data = data.get_value();
46+
47+
let begin_state = begin_state
48+
.into_option()
49+
.as_ref()
50+
.map(|v| v.as_bigint().to_i32().unwrap())
51+
.unwrap_or($begin_state);
52+
53+
let mut hasher = $h_new(begin_state as u32);
54+
$h_update(&mut hasher, &data);
55+
56+
let checksum: u32 = $h_compute(hasher);
57+
58+
Ok(vm.new_int(checksum))
59+
}
60+
};
61+
}
62+
63+
checksum_fn!(
64+
zlib_adler32,
65+
|value| Adler32::from_value(value),
66+
|hasher: &mut Adler32, buffer| hasher.update_buffer(buffer),
67+
|hasher: Adler32| hasher.hash(),
68+
1
69+
);
70+
71+
checksum_fn!(
72+
zlib_crc32,
73+
|value| Crc32::new_with_initial(value),
74+
|hasher: &mut Crc32, buffer| hasher.update(buffer),
75+
|hasher: Crc32| hasher.finalize(),
76+
0
77+
);
78+
79+
/// Returns a bytes object containing compressed data.
80+
fn zlib_compress(
81+
data: PyBytesRef,
82+
level: OptionalArg<PyIntRef>,
83+
vm: &VirtualMachine,
84+
) -> PyResult<PyObjectRef> {
85+
let input_bytes = data.get_value();
86+
87+
let level = level
88+
.into_option()
89+
.as_ref()
90+
.map(|v| v.as_bigint().to_i32().unwrap())
91+
.unwrap_or(libz::Z_DEFAULT_COMPRESSION);
92+
93+
let compression = match level {
94+
valid_level @ libz::Z_NO_COMPRESSION...libz::Z_BEST_COMPRESSION => {
95+
Compression::new(valid_level as u32)
96+
}
97+
libz::Z_DEFAULT_COMPRESSION => Compression::default(),
98+
_ => return Err(zlib_error("Bad compression level", vm)),
99+
};
100+
101+
let mut encoder = ZlibEncoder::new(Vec::new(), compression);
102+
encoder.write_all(input_bytes).unwrap();
103+
let encoded_bytes = encoder.finish().unwrap();
104+
105+
Ok(vm.ctx.new_bytes(encoded_bytes))
106+
}
107+
108+
/// Returns a bytes object containing the uncompressed data.
109+
fn zlib_decompress(
110+
data: PyBytesRef,
111+
wbits: OptionalArg<PyIntRef>,
112+
bufsize: OptionalArg<PyIntRef>,
113+
vm: &VirtualMachine,
114+
) -> PyResult<PyObjectRef> {
115+
let encoded_bytes = data.get_value();
116+
117+
let wbits = wbits
118+
.into_option()
119+
.as_ref()
120+
.map(|wbits| wbits.as_bigint().to_u8().unwrap())
121+
.unwrap_or(MAX_WBITS);
122+
123+
let bufsize = bufsize
124+
.into_option()
125+
.as_ref()
126+
.map(|bufsize| bufsize.as_bigint().to_usize().unwrap())
127+
.unwrap_or(DEF_BUF_SIZE);
128+
129+
let mut decompressor = Decompress::new_with_window_bits(true, wbits);
130+
let mut decoded_bytes = Vec::with_capacity(bufsize);
131+
132+
match decompressor.decompress_vec(&encoded_bytes, &mut decoded_bytes, FlushDecompress::Finish) {
133+
Ok(Status::BufError) => Err(zlib_error("inconsistent or truncated state", vm)),
134+
Err(_) => Err(zlib_error("invalid input data", vm)),
135+
_ => Ok(vm.ctx.new_bytes(decoded_bytes)),
136+
}
137+
}
138+
139+
fn zlib_error(message: &str, vm: &VirtualMachine) -> PyObjectRef {
140+
let module = vm
141+
.get_attribute(vm.sys_module.clone(), "modules")
142+
.unwrap()
143+
.get_item("zlib", vm)
144+
.unwrap();
145+
146+
let zlib_error = vm.get_attribute(module, "error").unwrap();
147+
let zlib_error = zlib_error.downcast().unwrap();
148+
149+
vm.new_exception(zlib_error, message.to_string())
150+
}

0 commit comments

Comments
 (0)