From 795e6c3090dd2dc2c4ff2b69e90df8c31afea3f4 Mon Sep 17 00:00:00 2001 From: Dennis Ranke Date: Sun, 12 Mar 2023 13:45:23 +0100 Subject: [PATCH] add basic example for compiling upkr to a c library --- c_library/.gitignore | 2 + c_library/Cargo.lock | 127 +++++++++++++++++++++++++++++++++++++++++++ c_library/Cargo.toml | 17 ++++++ c_library/Makefile | 8 +++ c_library/Readme.md | 11 ++++ c_library/src/lib.rs | 42 ++++++++++++++ c_library/upkr.c | 99 +++++++++++++++++++++++++++++++++ c_library/upkr.h | 25 +++++++++ 8 files changed, 331 insertions(+) create mode 100644 c_library/.gitignore create mode 100644 c_library/Cargo.lock create mode 100644 c_library/Cargo.toml create mode 100644 c_library/Makefile create mode 100644 c_library/Readme.md create mode 100644 c_library/src/lib.rs create mode 100644 c_library/upkr.c create mode 100644 c_library/upkr.h diff --git a/c_library/.gitignore b/c_library/.gitignore new file mode 100644 index 0000000..e0b7adb --- /dev/null +++ b/c_library/.gitignore @@ -0,0 +1,2 @@ +/target/ +/upkr \ No newline at end of file diff --git a/c_library/Cargo.lock b/c_library/Cargo.lock new file mode 100644 index 0000000..d6b4f41 --- /dev/null +++ b/c_library/Cargo.lock @@ -0,0 +1,127 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "anyhow" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "224afbd727c3d6e4b90103ece64b8d1b67fbb1973b1046c2281eed3f3803f800" + +[[package]] +name = "autocfg" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" + +[[package]] +name = "cc" +version = "1.0.79" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f" + +[[package]] +name = "cdivsufsort" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edefce019197609da416762da75bb000bbd2224b2d89a7e722c2296cbff79b8c" +dependencies = [ + "cc", + "sacabase", +] + +[[package]] +name = "lexopt" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "478ee9e62aaeaf5b140bd4138753d1f109765488581444218d3ddda43234f3e8" + +[[package]] +name = "num-traits" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd" +dependencies = [ + "autocfg", +] + +[[package]] +name = "proc-macro2" +version = "1.0.51" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d727cae5b39d21da60fa540906919ad737832fe0b1c165da3a34d6548c849d6" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8856d8364d252a14d474036ea1358d63c9e6965c8e5c1885c18f73d70bff9c7b" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "sacabase" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9883fc3d6ce3d78bb54d908602f8bc1f7b5f983afe601dabe083009d86267a84" +dependencies = [ + "num-traits", +] + +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "thiserror" +version = "1.0.39" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5ab016db510546d856297882807df8da66a16fb8c4101cb8b30054b0d5b2d9c" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.39" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5420d42e90af0c38c3290abcca25b9b3bdf379fc9f55c528f53a269d9c9a267e" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "unicode-ident" +version = "1.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5464a87b239f13a63a501f2701565754bae92d243d4bb7eb12f6d57d2269bf4" + +[[package]] +name = "upkr" +version = "0.2.1" +dependencies = [ + "anyhow", + "cdivsufsort", + "lexopt", + "thiserror", +] + +[[package]] +name = "upkr_c" +version = "0.0.1" +dependencies = [ + "upkr", +] diff --git a/c_library/Cargo.toml b/c_library/Cargo.toml new file mode 100644 index 0000000..9a0e748 --- /dev/null +++ b/c_library/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "upkr_c" +version = "0.0.1" +edition = "2021" + +[lib] +name = "upkr" +crate-type = ["staticlib"] + +[profile.release] +opt-level = "s" +strip = "debuginfo" +lto = true +panic = "abort" + +[dependencies] +upkr = { path="..", default-features=false } diff --git a/c_library/Makefile b/c_library/Makefile new file mode 100644 index 0000000..7ccd43a --- /dev/null +++ b/c_library/Makefile @@ -0,0 +1,8 @@ +upkr: upkr.c upkr.h target/release/libupkr.a + gcc -O2 -Ltarget/release -o upkr upkr.c -lupkr -lm + strip upkr + +target/release/libupkr.a: cargo + cargo build --release + +.PHONY: cargo \ No newline at end of file diff --git a/c_library/Readme.md b/c_library/Readme.md new file mode 100644 index 0000000..e177225 --- /dev/null +++ b/c_library/Readme.md @@ -0,0 +1,11 @@ +This is a simple example of compiling upkr to a library that can be linked in a +c program. It consists of a small rust crate which implements the c api and +compiles to a static library and a matching c header file. As is, the rust +crate offers two simple functions to compress/uncompress data with the default +upkr config. + +The provided makefile will only work on linux. Building the example upkr.c on +other platforms is left as an exercise for the reader ;) + +On Windows you might have to make sure to install and use the correct rust +toolchain version (mingw vs. msvc) to match your c compiler. \ No newline at end of file diff --git a/c_library/src/lib.rs b/c_library/src/lib.rs new file mode 100644 index 0000000..9fb4b15 --- /dev/null +++ b/c_library/src/lib.rs @@ -0,0 +1,42 @@ +use std::ffi::c_int; + +// the upkr config to use, this can be modified to use other configs +fn config() -> upkr::Config { + upkr::Config::default() +} + +#[no_mangle] +pub extern "C" fn upkr_compress( + output_buffer: *mut u8, + output_buffer_size: usize, + input_buffer: *const u8, + input_size: usize, + compression_level: c_int, +) -> usize { + let output_buffer = unsafe { std::slice::from_raw_parts_mut(output_buffer, output_buffer_size) }; + let input_buffer = unsafe { std::slice::from_raw_parts(input_buffer, input_size) }; + + let packed_data = upkr::pack(input_buffer, compression_level.max(0).min(9) as u8, &config(), None); + let copy_size = packed_data.len().min(output_buffer.len()); + output_buffer[..copy_size].copy_from_slice(&packed_data[..copy_size]); + + packed_data.len() +} + +#[no_mangle] +pub extern "C" fn upkr_uncompress(output_buffer: *mut u8, output_buffer_size: usize, input_buffer: *const u8, input_size: usize) -> isize { + let output_buffer = unsafe { std::slice::from_raw_parts_mut(output_buffer, output_buffer_size)}; + let input_buffer = unsafe { std::slice::from_raw_parts(input_buffer, input_size)}; + + match upkr::unpack(input_buffer, &config(), output_buffer.len()) { + Ok(unpacked_data) => { + output_buffer[..unpacked_data.len()].copy_from_slice(&unpacked_data); + unpacked_data.len() as isize + } + Err(upkr::UnpackError::OverSize { size, .. }) => size as isize, + Err(other) => { + eprintln!("[upkr] compressed data corrupt: {}", other); + -1 + } + } +} \ No newline at end of file diff --git a/c_library/upkr.c b/c_library/upkr.c new file mode 100644 index 0000000..2350c7d --- /dev/null +++ b/c_library/upkr.c @@ -0,0 +1,99 @@ +#include "upkr.h" +#include +#include +#include + +int main(int argc, char** argv) { + if(argc < 2) { + fprintf(stdout, "Usage:\n upkr [compress] [-0 .. -9] []\n upkr [uncompress] []\n"); + return 1; + } + + int argi = 1; + int uncompress = 0; + int compression_level = 4; + if(strcmp(argv[argi], "compress") == 0) { + ++argi; + } else if(strcmp(argv[argi], "uncompress") == 0) { + uncompress = 1; + ++argi; + } + + if(argi < argc && argv[argi][0] == '-') { + compression_level = atoi(argv[argi] + 1); + ++argi; + } + + if(argi == argc) { + fprintf(stdout, "intput filename missing\n"); + return 1; + } + + const char* input_name = argv[argi++]; + char* output_name; + if(argi < argc) { + output_name = argv[argi]; + } else { + output_name = malloc(strlen(input_name) + 5); + strcpy(output_name, input_name); + strcat(output_name, uncompress ? ".unp" : ".upk"); + } + + FILE* file = fopen(input_name, "rb"); + if(file == 0) { + fprintf(stdout, "failed to open input file '%s'\n", file); + return 1; + } + fseek(file, 0, SEEK_END); + long input_size = ftell(file); + rewind(file); + + char* input_buffer = (char*)malloc(input_size); + long offset = 0; + while(offset < input_size) { + long read_size = fread(input_buffer + offset, 1, input_size - offset, file); + if(read_size <= 0) { + fprintf(stdout, "error reading input file\n"); + return 1; + } + offset += read_size; + } + fclose(file); + + long output_buffer_size = input_size * 8; + long output_size; + char* output_buffer = (char*)malloc(output_buffer_size); + for(;;) { + if(uncompress) { + output_size = upkr_uncompress(output_buffer, output_buffer_size, input_buffer, input_size); + } else { + output_size = upkr_compress(output_buffer, output_buffer_size, input_buffer, input_size, compression_level); + } + if(output_size < 0) { + return 1; + } + if(output_size <= output_buffer_size) { + break; + } + output_buffer = (char*)realloc(output_buffer, output_size); + output_buffer_size = output_size; + } + + file = fopen(output_name, "wb"); + if(file == 0) { + fprintf(stdout, "failed to open output file '%s'\n", output_name); + return 1; + } + offset = 0; + while(offset < output_size) { + long written_size = fwrite(output_buffer + offset, 1, output_size - offset, file); + if(written_size <= 0) { + fprintf(stdout, "error writing output file\n"); + return 1; + } + offset += written_size; + } + fclose(file); + + return 0; +} \ No newline at end of file diff --git a/c_library/upkr.h b/c_library/upkr.h new file mode 100644 index 0000000..cfb4997 --- /dev/null +++ b/c_library/upkr.h @@ -0,0 +1,25 @@ +#ifndef UPKR_H_INCLUDED + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +// input_buffer/input_size: input data to compress +// output_buffer/output_buffer_size: buffer to compress into +// compression_level: 0-9 +// returns the size of the compressed data, even if it didn't fit into the output buffer +size_t upkr_compress(void* output_buffer, size_t output_buffer_size, void* input_buffer, size_t input_size, int compression_level); + +// input_buffer/input_size: compressed data +// output_buffer/output_buffer_size: buffer to uncompress into +// return value: +// >= 0 : size of uncompressed data, even if it didn't fit into the output buffer +// < 0 : input data corrupt, unable to decompress +ptrdiff_t upkr_uncompress(void* output_buffer, size_t output_buffer_size, void* input_buffer, size_t input_size); + +#ifdef __cplusplus +} +#endif +#endif \ No newline at end of file