diff --git a/Cargo.lock b/Cargo.lock index 894e82853..c5c1eebff 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1,5 +1,10 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. +[[package]] +name = "adler32" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "aho-corasick" version = "0.6.10" @@ -263,6 +268,14 @@ dependencies = [ "build_const 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "crc32fast" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "cfg-if 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "crossbeam-utils" version = "0.5.0" @@ -399,6 +412,17 @@ dependencies = [ "serde_json 1.0.40 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "flate2" +version = "1.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "crc32fast 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.60 (registry+https://github.com/rust-lang/crates.io-index)", + "libz-sys 1.0.25 (registry+https://github.com/rust-lang/crates.io-index)", + "miniz_oxide_c_api 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "fnv" version = "1.0.6" @@ -563,6 +587,17 @@ name = "libc" version = "0.2.60" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "libz-sys" +version = "1.0.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "cc 1.0.37 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.60 (registry+https://github.com/rust-lang/crates.io-index)", + "pkg-config 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)", + "vcpkg 0.2.7 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "log" version = "0.3.9" @@ -604,6 +639,25 @@ name = "memchr" version = "2.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "miniz_oxide" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "adler32 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "miniz_oxide_c_api" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "cc 1.0.37 (registry+https://github.com/rust-lang/crates.io-index)", + "crc32fast 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.60 (registry+https://github.com/rust-lang/crates.io-index)", + "miniz_oxide 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "new_debug_unreachable" version = "1.0.3" @@ -725,6 +779,11 @@ dependencies = [ "siphasher 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "pkg-config" +version = "0.3.15" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "precomputed-hash" version = "0.1.1" @@ -1045,6 +1104,7 @@ dependencies = [ name = "rustpython-vm" version = "0.1.0" dependencies = [ + "adler32 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)", "arr_macro 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", "bincode 1.1.4 (registry+https://github.com/rust-lang/crates.io-index)", "bitflags 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", @@ -1053,9 +1113,11 @@ dependencies = [ "caseless 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", "chrono 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", "crc 1.8.1 (registry+https://github.com/rust-lang/crates.io-index)", + "crc32fast 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)", "digest 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)", "flame 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", "flamer 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", + "flate2 1.0.9 (registry+https://github.com/rust-lang/crates.io-index)", "gethostname 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", "hex 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)", "hexf-parse 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", @@ -1064,6 +1126,7 @@ dependencies = [ "lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)", "lexical 2.2.1 (registry+https://github.com/rust-lang/crates.io-index)", "libc 0.2.60 (registry+https://github.com/rust-lang/crates.io-index)", + "libz-sys 1.0.25 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)", "maplit 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", "md-5 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)", @@ -1734,6 +1797,11 @@ name = "utf8parse" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "vcpkg" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "vec_map" version = "0.8.1" @@ -1899,6 +1967,7 @@ version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" [metadata] +"checksum adler32 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "7e522997b529f05601e05166c07ed17789691f562762c7f3b987263d2dedee5c" "checksum aho-corasick 0.6.10 (registry+https://github.com/rust-lang/crates.io-index)" = "81ce3d38065e618af2d7b77e10c5ad9a069859b4be3c2250f674af3840d9c8a5" "checksum aho-corasick 0.7.4 (registry+https://github.com/rust-lang/crates.io-index)" = "36b7aa1ccb7d7ea3f437cf025a2ab1c47cc6c1bc9fc84918ff449def12f5e282" "checksum ansi_term 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b" @@ -1932,6 +2001,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum constant_time_eq 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "8ff012e225ce166d4422e0e78419d901719760f62ae2b7969ca6b564d1b54a9e" "checksum cpython 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "b489034e723e7f5109fecd19b719e664f89ef925be785885252469e9822fa940" "checksum crc 1.8.1 (registry+https://github.com/rust-lang/crates.io-index)" = "d663548de7f5cca343f1e0a48d14dcfb0e9eb4e079ec58883b7251539fa10aeb" +"checksum crc32fast 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ba125de2af0df55319f41944744ad91c71113bf74a4646efff39afe1f6842db1" "checksum crossbeam-utils 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "677d453a17e8bd2b913fa38e8b9cf04bcdbb5be790aa294f2389661d72036015" "checksum crypto-mac 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "4434400df11d95d556bac068ddfedd482915eb18fe8bea89bc80b6e4b1c179e5" "checksum diff 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)" = "3c2b69f912779fbb121ceb775d74d51e915af17aaebc38d28a592843a2dd0a3a" @@ -1948,6 +2018,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum flame 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "1fc2706461e1ee94f55cab2ed2e3d34ae9536cfa830358ef80acff1a3dacab30" "checksum flamer 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "f2add1a5e84b1ed7b5d00cdc21789a28e0a8f4e427b677313c773880ba3c4dac" "checksum flamescope 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "6e3e6aee625a28b97be4308bccc2eb5f81d9ec606b3f29e13c0f459ce20dd136" +"checksum flate2 1.0.9 (registry+https://github.com/rust-lang/crates.io-index)" = "550934ad4808d5d39365e5d61727309bf18b3b02c6c56b729cb92e7dd84bc3d8" "checksum fnv 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)" = "2fad85553e09a6f881f739c29f0b00b0f01357c743266d478b68951ce23285f3" "checksum fuchsia-cprng 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "a06f77d526c1a601b7c4cdd98f54b5eaabffc14d5f2f0296febdc7f357c6d3ba" "checksum futures 0.1.28 (registry+https://github.com/rust-lang/crates.io-index)" = "45dc39533a6cae6da2b56da48edae506bb767ec07370f86f70fc062e9d435869" @@ -1970,12 +2041,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum lexical 2.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "93de1b2ed9c7f01aac327bf84542a053b6cd15761defdcfaceb27e1d1b871e74" "checksum lexical-core 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "3f8673fab7063c2cac37d299c8a1a7beb720e78f71500098e4a3c137fdf025bf" "checksum libc 0.2.60 (registry+https://github.com/rust-lang/crates.io-index)" = "d44e80633f007889c7eff624b709ab43c92d708caad982295768a7b13ca3b5eb" +"checksum libz-sys 1.0.25 (registry+https://github.com/rust-lang/crates.io-index)" = "2eb5e43362e38e2bca2fd5f5134c4d4564a23a5c28e9b95411652021a8675ebe" "checksum log 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)" = "e19e8d5c34a3e0e2223db8e060f9e8264aeeb5c5fc64a4ee9965c062211c024b" "checksum log 0.4.7 (registry+https://github.com/rust-lang/crates.io-index)" = "c275b6ad54070ac2d665eef9197db647b32239c9d244bfb6f041a766d00da5b3" "checksum maplit 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "08cbb6b4fef96b6d77bfc40ec491b1690c779e77b05cd9f07f787ed376fd4c43" "checksum matches 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)" = "7ffc5c5338469d4d3ea17d269fa8ea3512ad247247c30bd2df69e68309ed0a08" "checksum md-5 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "a18af3dcaf2b0219366cdb4e2af65a6101457b415c3d1a5c71dd9c2b7c77b9c8" "checksum memchr 2.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "88579771288728879b57485cc7d6b07d648c9f0141eb955f8ab7f9d45394468e" +"checksum miniz_oxide 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7108aff85b876d06f22503dcce091e29f76733b2bfdd91eebce81f5e68203a10" +"checksum miniz_oxide_c_api 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "6c675792957b0d19933816c4e1d56663c341dd9bfa31cb2140ff2267c1d8ecf4" "checksum new_debug_unreachable 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "f40f005c60db6e03bae699e414c58bf9aa7ea02a2d0b9bfbcf19286cc4c82b30" "checksum nix 0.13.1 (registry+https://github.com/rust-lang/crates.io-index)" = "4dbdc256eaac2e3bd236d93ad999d3479ef775c863dbda3068c4006a92eec51b" "checksum nix 0.14.1 (registry+https://github.com/rust-lang/crates.io-index)" = "6c722bee1037d430d0f8e687bbdbf222f27cc6e4e68d5caf630857bb2b6dbdce" @@ -1991,6 +2065,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum petgraph 0.4.13 (registry+https://github.com/rust-lang/crates.io-index)" = "9c3659d1ee90221741f65dd128d9998311b0e40c5d3c23a62445938214abce4f" "checksum phf_generator 0.7.24 (registry+https://github.com/rust-lang/crates.io-index)" = "09364cc93c159b8b06b1f4dd8a4398984503483891b0c26b867cf431fb132662" "checksum phf_shared 0.7.24 (registry+https://github.com/rust-lang/crates.io-index)" = "234f71a15de2288bcb7e3b6515828d22af7ec8598ee6d24c3b526fa0a80b67a0" +"checksum pkg-config 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)" = "a7c1d2cfa5a714db3b5f24f0915e74fcdf91d09d496ba61329705dda7774d2af" "checksum precomputed-hash 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c" "checksum proc-macro-hack 0.5.8 (registry+https://github.com/rust-lang/crates.io-index)" = "982a35d1194084ba319d65c4a68d24ca28f5fdb5b8bc20899e4eef8641ea5178" "checksum proc-macro2 0.4.30 (registry+https://github.com/rust-lang/crates.io-index)" = "cf3d2011ab5c909338f7887f4fc896d35932e29146c12c8d01da6b22a80ba759" @@ -2093,6 +2168,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum unreachable 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "382810877fe448991dfc7f0dd6e3ae5d58088fd0ea5e35189655f84e6814fa56" "checksum utf8-ranges 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "9d50aa7650df78abf942826607c62468ce18d9019673d4a2ebe1865dbb96ffde" "checksum utf8parse 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "8772a4ccbb4e89959023bc5b7cb8623a795caa7092d99f3aa9501b9484d4557d" +"checksum vcpkg 0.2.7 (registry+https://github.com/rust-lang/crates.io-index)" = "33dd455d0f96e90a75803cfeb7f948768c08d70a6de9a8d2362461935698bf95" "checksum vec_map 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)" = "05c78687fb1a80548ae3250346c3db86a80a7cdd77bda190189f2d0a0987c81a" "checksum version_check 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "914b1a6776c4c929a602fafd8bc742e06365d4bcbe48c30f9cca5824f70dc9dd" "checksum void 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d" diff --git a/tests/snippets/stdlib_zlib.py b/tests/snippets/stdlib_zlib.py new file mode 100644 index 000000000..42847704a --- /dev/null +++ b/tests/snippets/stdlib_zlib.py @@ -0,0 +1,62 @@ +import zlib +from testutils import assert_raises + +# checksum functions +assert zlib.crc32(b"123") == 2286445522 +assert zlib.crc32(b"123", 1) == 2307525093 +assert zlib.crc32(b"123", 2) == 2345449404 +assert zlib.crc32(b"123", 3) == 2316230027 +assert zlib.crc32(b"123", 4) == 2403453710 +assert zlib.crc32(b"123", 5) == 2390991161 +assert zlib.crc32(b"123", 6) == 2361728864 +assert zlib.crc32(b"123", -123) == 3515918521 +assert zlib.crc32(b"123", -122) == 3554023136 +assert zlib.crc32(b"123", -121) == 3524558039 +assert zlib.crc32(b"123", -120) == 3645389802 +assert zlib.crc32(b"123", -119) == 3632943581 +assert zlib.crc32(b"123", -118) == 3670863748 +assert zlib.crc32(b"123") == zlib.crc32(b"123", 0) + +assert zlib.adler32(b"456") == 20906144 +assert zlib.adler32(b"456", 1) == 20906144 +assert zlib.adler32(b"456", 2) == 21102753 +assert zlib.adler32(b"456", 3) == 21299362 +assert zlib.adler32(b"456", 4) == 21495971 +assert zlib.adler32(b"456", 5) == 21692580 +assert zlib.adler32(b"456", 6) == 21889189 +assert zlib.adler32(b"456", -123) == 393267 +assert zlib.adler32(b"456", -122) == 589876 +assert zlib.adler32(b"456", -121) == 786485 +assert zlib.adler32(b"456", -120) == 983094 +assert zlib.adler32(b"456", -119) == 1179703 +assert zlib.adler32(b"456", -118) == 1376312 +assert zlib.adler32(b"456") == zlib.adler32(b"456", 1) + +# compression +lorem = bytes("Lorem ipsum dolor sit amet", "utf-8") + +compressed_lorem_list = [ + b"x\x01\x01\x1a\x00\xe5\xffLorem ipsum dolor sit amet\x83\xd5\t\xc5", + b"x\x01\xf3\xc9/J\xcdU\xc8,(.\xcdUH\xc9\xcf\xc9/R(\xce,QH\xccM-\x01\x00\x83\xd5\t\xc5", + b"x^\xf3\xc9/J\xcdU\xc8,(.\xcdUH\xc9\xcf\xc9/R(\xce,QH\xccM-\x01\x00\x83\xd5\t\xc5", + b"x^\xf3\xc9/J\xcdU\xc8,(.\xcdUH\xc9\xcf\xc9/R(\xce,QH\xccM-\x01\x00\x83\xd5\t\xc5", + b"x^\xf3\xc9/J\xcdU\xc8,(.\xcdUH\xc9\xcf\xc9/R(\xce,QH\xccM-\x01\x00\x83\xd5\t\xc5", + b"x^\xf3\xc9/J\xcdU\xc8,(.\xcdUH\xc9\xcf\xc9/R(\xce,QH\xccM-\x01\x00\x83\xd5\t\xc5", + b"x\x9c\xf3\xc9/J\xcdU\xc8,(.\xcdUH\xc9\xcf\xc9/R(\xce,QH\xccM-\x01\x00\x83\xd5\t\xc5", + b"x\xda\xf3\xc9/J\xcdU\xc8,(.\xcdUH\xc9\xcf\xc9/R(\xce,QH\xccM-\x01\x00\x83\xd5\t\xc5", + b"x\xda\xf3\xc9/J\xcdU\xc8,(.\xcdUH\xc9\xcf\xc9/R(\xce,QH\xccM-\x01\x00\x83\xd5\t\xc5", + b"x\xda\xf3\xc9/J\xcdU\xc8,(.\xcdUH\xc9\xcf\xc9/R(\xce,QH\xccM-\x01\x00\x83\xd5\t\xc5", +] + +for level, text in enumerate(compressed_lorem_list): + assert zlib.compress(lorem, level) == text + +# default level +assert zlib.compress(lorem) == zlib.compress(lorem, -1) == zlib.compress(lorem, 6) + +# decompression +for text in compressed_lorem_list: + assert zlib.decompress(text) == lorem + +assert_raises(zlib.error, lambda: zlib.compress(b"123", -40)) +assert_raises(zlib.error, lambda: zlib.compress(b"123", 10)) diff --git a/vm/Cargo.toml b/vm/Cargo.toml index abe546732..0d7f12b50 100644 --- a/vm/Cargo.toml +++ b/vm/Cargo.toml @@ -72,5 +72,9 @@ flamer = { version = "0.3", optional = true } pwd = "1" [target.'cfg(not(target_arch = "wasm32"))'.dependencies] +crc32fast = "1.2.0" +adler32 = "1.0.3" +flate2 = { version = "1.0", features = ["zlib"], default-features = false } +libz-sys = "1.0.25" gethostname = "0.2.0" subprocess = "0.1.18" diff --git a/vm/src/stdlib/mod.rs b/vm/src/stdlib/mod.rs index 0ec7643c5..51ab96e52 100644 --- a/vm/src/stdlib/mod.rs +++ b/vm/src/stdlib/mod.rs @@ -43,6 +43,8 @@ mod pwd; pub mod signal; #[cfg(not(target_arch = "wasm32"))] mod subprocess; +#[cfg(not(target_arch = "wasm32"))] +mod zlib; use crate::pyobject::PyObjectRef; @@ -99,6 +101,7 @@ pub fn get_module_inits() -> HashMap { modules.insert("socket".to_string(), Box::new(socket::make_module)); modules.insert("signal".to_string(), Box::new(signal::make_module)); modules.insert("subprocess".to_string(), Box::new(subprocess::make_module)); + modules.insert("zlib".to_string(), Box::new(zlib::make_module)); } // Unix-only diff --git a/vm/src/stdlib/zlib.rs b/vm/src/stdlib/zlib.rs new file mode 100644 index 000000000..b54d82425 --- /dev/null +++ b/vm/src/stdlib/zlib.rs @@ -0,0 +1,153 @@ +use crate::function::OptionalArg; +use crate::obj::{objbytes::PyBytesRef, objint::PyIntRef}; +use crate::pyobject::{create_type, ItemProtocol, PyObjectRef, PyResult}; +use crate::vm::VirtualMachine; + +use adler32::RollingAdler32 as Adler32; +use crc32fast::Hasher as Crc32; +use flate2::{write::ZlibEncoder, Compression, Decompress, FlushDecompress, Status}; +use libz_sys as libz; +use num_traits::cast::ToPrimitive; + +use std::io::Write; + +// copied from zlibmodule.c (commit 530f506ac91338) +const MAX_WBITS: u8 = 15; +const DEF_BUF_SIZE: usize = 16 * 1024; + +pub fn make_module(vm: &VirtualMachine) -> PyObjectRef { + let ctx = &vm.ctx; + + let zlib_error = create_type("error", &ctx.type_type, &ctx.exceptions.exception_type); + + py_module!(vm, "zlib", { + "crc32" => ctx.new_rustfunc(zlib_crc32), + "adler32" => ctx.new_rustfunc(zlib_adler32), + "compress" => ctx.new_rustfunc(zlib_compress), + "decompress" => ctx.new_rustfunc(zlib_decompress), + "error" => zlib_error, + "Z_DEFAULT_COMPRESSION" => ctx.new_int(libz::Z_DEFAULT_COMPRESSION), + "Z_NO_COMPRESSION" => ctx.new_int(libz::Z_NO_COMPRESSION), + "Z_BEST_SPEED" => ctx.new_int(libz::Z_BEST_SPEED), + "Z_BEST_COMPRESSION" => ctx.new_int(libz::Z_BEST_COMPRESSION), + "DEF_BUF_SIZE" => ctx.new_int(DEF_BUF_SIZE), + "MAX_WBITS" => ctx.new_int(MAX_WBITS), + }) +} + +/// Compute an Adler-32 checksum of data. +fn zlib_adler32( + data: PyBytesRef, + begin_state: OptionalArg, + vm: &VirtualMachine, +) -> PyResult { + let data = data.get_value(); + + let begin_state = begin_state + .into_option() + .as_ref() + .map(|v| v.as_bigint().to_i32().unwrap()) + .unwrap_or(1); + + let mut hasher = Adler32::from_value(begin_state as u32); + hasher.update_buffer(data); + + let checksum: u32 = hasher.hash(); + + Ok(vm.new_int(checksum)) +} + +/// Compute a CRC-32 checksum of data. +fn zlib_crc32( + data: PyBytesRef, + begin_state: OptionalArg, + vm: &VirtualMachine, +) -> PyResult { + let data = data.get_value(); + + let begin_state = begin_state + .into_option() + .as_ref() + .map(|v| v.as_bigint().to_i32().unwrap()) + .unwrap_or(0); + + let mut hasher = Crc32::new_with_initial(begin_state as u32); + hasher.update(data); + + let checksum: u32 = hasher.finalize(); + + Ok(vm.new_int(checksum)) +} + +/// Returns a bytes object containing compressed data. +fn zlib_compress( + data: PyBytesRef, + level: OptionalArg, + vm: &VirtualMachine, +) -> PyResult { + let input_bytes = data.get_value(); + + let level = level + .into_option() + .as_ref() + .map(|v| v.as_bigint().to_i32().unwrap()) + .unwrap_or(libz::Z_DEFAULT_COMPRESSION); + + let compression = match level { + valid_level @ libz::Z_NO_COMPRESSION...libz::Z_BEST_COMPRESSION => { + Compression::new(valid_level as u32) + } + libz::Z_DEFAULT_COMPRESSION => Compression::default(), + _ => return Err(zlib_error("Bad compression level", vm)), + }; + + let mut encoder = ZlibEncoder::new(Vec::new(), compression); + encoder.write_all(input_bytes).unwrap(); + let encoded_bytes = encoder.finish().unwrap(); + + Ok(vm.ctx.new_bytes(encoded_bytes)) +} + +/// Returns a bytes object containing the uncompressed data. +fn zlib_decompress( + data: PyBytesRef, + wbits: OptionalArg, + bufsize: OptionalArg, + vm: &VirtualMachine, +) -> PyResult { + let encoded_bytes = data.get_value(); + + let wbits = wbits + .into_option() + .as_ref() + .map(|wbits| wbits.as_bigint().to_u8().unwrap()) + .unwrap_or(MAX_WBITS); + + let bufsize = bufsize + .into_option() + .as_ref() + .map(|bufsize| bufsize.as_bigint().to_usize().unwrap()) + .unwrap_or(DEF_BUF_SIZE); + + let mut decompressor = Decompress::new_with_window_bits(true, wbits); + let mut decoded_bytes = Vec::with_capacity(bufsize); + + match decompressor.decompress_vec(&encoded_bytes, &mut decoded_bytes, FlushDecompress::Finish) { + Ok(Status::BufError) => Err(zlib_error("inconsistent or truncated state", vm)), + Err(_) => Err(zlib_error("invalid input data", vm)), + _ => Ok(vm.ctx.new_bytes(decoded_bytes)), + } +} + +fn zlib_error(message: &str, vm: &VirtualMachine) -> PyObjectRef { + let module = vm + .get_attribute(vm.sys_module.clone(), "modules") + .unwrap() + .get_item("zlib", vm) + .unwrap(); + + let zlib_error = vm.get_attribute(module, "error").unwrap(); + let zlib_error = zlib_error.downcast().unwrap(); + + vm.new_exception(zlib_error, message.to_string()) +}