From 649e029bd81eede0044f5fab1395438e933b7840 Mon Sep 17 00:00:00 2001 From: marmeladema Date: Wed, 27 Mar 2019 10:58:09 +0000 Subject: [PATCH] FW-1087: provide wirefilter.h C header file and add ffi/tests/ctests crate This new crate is here to provide a set of C based tests that will serve two purposes: * Provide some examples how to use the FFI bindings * Test that those bindings are actually working as intended Internally, it relies on ffi/tests/ctests/src/tests.c file which contains tests written in C and that is compiled at cargo configuration time through the use of a build.rs file. This produces a wirefilter_ffi_ctests.a static library that is later used in the ffi/tests/ctests/src/lib.rs file to call the different tests functions. All of this is done in order to try to integrate somehow properly with cargo test. --- Cargo.lock | 9 + ffi/Cargo.toml | 1 + ffi/include/wirefilter.h | 129 +++++++++++++++ ffi/src/lib.rs | 5 + ffi/tests/ctests/Cargo.toml | 12 ++ ffi/tests/ctests/build.rs | 11 ++ ffi/tests/ctests/src/lib.rs | 40 +++++ ffi/tests/ctests/src/tests.c | 307 +++++++++++++++++++++++++++++++++++ 8 files changed, 514 insertions(+) create mode 100644 ffi/include/wirefilter.h create mode 100644 ffi/tests/ctests/Cargo.toml create mode 100644 ffi/tests/ctests/build.rs create mode 100644 ffi/tests/ctests/src/lib.rs create mode 100644 ffi/tests/ctests/src/tests.c diff --git a/Cargo.lock b/Cargo.lock index 6160d576..0469951f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -774,6 +774,15 @@ dependencies = [ "regex 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", "serde_json 1.0.27 (registry+https://github.com/rust-lang/crates.io-index)", "wirefilter-engine 0.6.1", + "wirefilter-ffi-ctests 0.1.0", +] + +[[package]] +name = "wirefilter-ffi-ctests" +version = "0.1.0" +dependencies = [ + "cc 1.0.18 (registry+https://github.com/rust-lang/crates.io-index)", + "wirefilter-ffi 0.6.1", ] [[package]] diff --git a/ffi/Cargo.toml b/ffi/Cargo.toml index 5a2f6956..c270b42c 100644 --- a/ffi/Cargo.toml +++ b/ffi/Cargo.toml @@ -25,3 +25,4 @@ path = "../engine" [dev-dependencies] regex = "1.0.1" indoc = "0.3.0" +wirefilter-ffi-ctests = {path = "tests/ctests"} diff --git a/ffi/include/wirefilter.h b/ffi/include/wirefilter.h new file mode 100644 index 00000000..de037e8b --- /dev/null +++ b/ffi/include/wirefilter.h @@ -0,0 +1,129 @@ +#ifndef _WIREFILTER_H_ +#define _WIREFILTER_H_ + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct wirefilter_scheme wirefilter_scheme_t; +typedef struct wirefilter_execution_context wirefilter_execution_context_t; +typedef struct wirefilter_filter_ast wirefilter_filter_ast_t; +typedef struct wirefilter_filter wirefilter_filter_t; + +typedef struct { + const char *data; + size_t length; +} wirefilter_rust_allocated_str_t; + +typedef struct { + const char *data; + size_t length; +} wirefilter_static_rust_allocated_str_t; + +typedef struct { + const char *data; + size_t length; +} wirefilter_externally_allocated_str_t; + +typedef struct { + const unsigned char *data; + size_t length; +} wirefilter_externally_allocated_byte_arr_t; + +typedef union { + union { + uint8_t success; + struct { + uint8_t _res1; + wirefilter_rust_allocated_str_t msg; + } err; + struct { + uint8_t _res2; + wirefilter_filter_ast_t *ast; + } ok; + }; +} wirefilter_parsing_result_t; + +typedef enum { + WIREFILTER_TYPE_IP, + WIREFILTER_TYPE_BYTES, + WIREFILTER_TYPE_INT, + WIREFILTER_TYPE_BOOL, +} wirefilter_type_t; + +wirefilter_scheme_t *wirefilter_create_scheme(); +void wirefilter_free_scheme(wirefilter_scheme_t *scheme); + +void wirefilter_add_type_field_to_scheme( + wirefilter_scheme_t *scheme, + wirefilter_externally_allocated_str_t name, + wirefilter_type_t type +); + +wirefilter_parsing_result_t wirefilter_parse_filter( + wirefilter_scheme_t *scheme, + wirefilter_externally_allocated_str_t input +); + +void wirefilter_free_parsing_result(wirefilter_parsing_result_t result); + +wirefilter_filter_t *wirefilter_compile_filter(wirefilter_filter_ast_t *ast); +void wirefilter_free_compiled_filter(wirefilter_filter_t *filter); + +wirefilter_execution_context_t *wirefilter_create_execution_context( + wirefilter_scheme_t *scheme +); +void wirefilter_free_execution_context( + wirefilter_execution_context_t *exec_ctx +); + +void wirefilter_add_int_value_to_execution_context( + wirefilter_execution_context_t *exec_ctx, + wirefilter_externally_allocated_str_t name, + int32_t value +); + +void wirefilter_add_bytes_value_to_execution_context( + wirefilter_execution_context_t *exec_ctx, + wirefilter_externally_allocated_str_t name, + wirefilter_externally_allocated_byte_arr_t value +); + +void wirefilter_add_ipv6_value_to_execution_context( + wirefilter_execution_context_t *exec_ctx, + wirefilter_externally_allocated_str_t name, + uint8_t value[16] +); + +void wirefilter_add_ipv4_value_to_execution_context( + wirefilter_execution_context_t *exec_ctx, + wirefilter_externally_allocated_str_t name, + uint8_t value[4] +); + +void wirefilter_add_bool_value_to_execution_context( + wirefilter_execution_context_t *exec_ctx, + wirefilter_externally_allocated_str_t name, + bool value +); + +bool wirefilter_match( + wirefilter_filter_t *filter, + wirefilter_execution_context_t *exec_ctx +); + +bool wirefilter_filter_uses( + wirefilter_filter_t *filter, + wirefilter_externally_allocated_str_t field_name +); + +wirefilter_static_rust_allocated_str_t wirefilter_get_version(); + +#ifdef __cplusplus +} +#endif + +#endif // _WIREFILTER_H_ diff --git a/ffi/src/lib.rs b/ffi/src/lib.rs index e4f9fa33..1686fe54 100644 --- a/ffi/src/lib.rs +++ b/ffi/src/lib.rs @@ -92,6 +92,11 @@ pub extern "C" fn wirefilter_parse_filter<'s, 'i>( } } +#[no_mangle] +pub extern "C" fn wirefilter_free_parsing_result(r: ParsingResult<'_>) { + drop(r); +} + /// Wrapper for Hasher that allows using Write API (e.g. with serializer). #[derive(Default)] struct HasherWrite(H); diff --git a/ffi/tests/ctests/Cargo.toml b/ffi/tests/ctests/Cargo.toml new file mode 100644 index 00000000..5e9dba5a --- /dev/null +++ b/ffi/tests/ctests/Cargo.toml @@ -0,0 +1,12 @@ +[package] +authors = ["Elie ROUDNINSKI "] +name = "wirefilter-ffi-ctests" +version = "0.1.0" +description = "C based tests for FFI bindings of the Wirefilter engine" +publish = false + +[dependencies] +wirefilter-ffi = {path = "../.."} + +[build-dependencies] +cc = "1.0" diff --git a/ffi/tests/ctests/build.rs b/ffi/tests/ctests/build.rs new file mode 100644 index 00000000..a0f64b6d --- /dev/null +++ b/ffi/tests/ctests/build.rs @@ -0,0 +1,11 @@ +extern crate cc; + +fn main() { + cc::Build::new() + .include("../../include") + .file("src/tests.c") + .flag("-Wall") + .flag("-Wextra") + .flag("-Werror") + .compile("wirefilter_ffi_ctests"); +} diff --git a/ffi/tests/ctests/src/lib.rs b/ffi/tests/ctests/src/lib.rs new file mode 100644 index 00000000..40eea9ff --- /dev/null +++ b/ffi/tests/ctests/src/lib.rs @@ -0,0 +1,40 @@ +#[no_mangle] +unsafe extern "C" fn rust_assert(check: bool, msg: *const std::os::raw::c_char) { + assert!(check, "{}", std::ffi::CStr::from_ptr(msg).to_str().unwrap()); +} + +macro_rules! ffi_ctest { + (@inner $($name:ident => $link_name:expr,)*) => { + $( + #[test] + pub fn $name() { + #[link(name = "wirefilter_ffi")] + extern "C" { + #[link_name = $link_name] + fn ctest(); + } + + unsafe { ctest() } + } + )* + }; + + ($($name:ident,)*) => { + ffi_ctest! { @inner + $($name => concat!("wirefilter_ffi_ctest_", stringify!($name)),)* + } + }; +} + +mod ffi_ctest { + ffi_ctest!( + create_scheme, + add_fields_to_scheme, + parse_good_filter, + parse_bad_filter, + compile_filter, + create_execution_context, + add_values_to_execution_context, + match_filter, + ); +} diff --git a/ffi/tests/ctests/src/tests.c b/ffi/tests/ctests/src/tests.c new file mode 100644 index 00000000..0edb8c65 --- /dev/null +++ b/ffi/tests/ctests/src/tests.c @@ -0,0 +1,307 @@ +#include +#include +#include + +#include + +extern void rust_assert(bool check, const char *msg); + +static wirefilter_externally_allocated_str_t wirefilter_string(const char *s) { + return (wirefilter_externally_allocated_str_t){s, strlen(s)}; +} + +void wirefilter_ffi_ctest_create_scheme() { + wirefilter_scheme_t *scheme = wirefilter_create_scheme(); + rust_assert(scheme != NULL, "could not create scheme"); + wirefilter_free_scheme(scheme); +} + +void wirefilter_ffi_ctest_add_fields_to_scheme() { + wirefilter_scheme_t *scheme = wirefilter_create_scheme(); + rust_assert(scheme != NULL, "could not create scheme"); + + wirefilter_add_type_field_to_scheme( + scheme, + wirefilter_string("http.host"), + WIREFILTER_TYPE_BYTES + ); + wirefilter_add_type_field_to_scheme( + scheme, + wirefilter_string("ip.addr"), + WIREFILTER_TYPE_IP + ); + wirefilter_add_type_field_to_scheme( + scheme, + wirefilter_string("ssl"), + WIREFILTER_TYPE_BOOL + ); + wirefilter_add_type_field_to_scheme( + scheme, + wirefilter_string("tcp.port"), + WIREFILTER_TYPE_INT + ); + + wirefilter_free_scheme(scheme); +} + +void wirefilter_ffi_ctest_parse_good_filter() { + wirefilter_scheme_t *scheme = wirefilter_create_scheme(); + rust_assert(scheme != NULL, "could not create scheme"); + + wirefilter_add_type_field_to_scheme( + scheme, + wirefilter_string("http.host"), + WIREFILTER_TYPE_BYTES + ); + wirefilter_add_type_field_to_scheme( + scheme, + wirefilter_string("ip.addr"), + WIREFILTER_TYPE_IP + ); + wirefilter_add_type_field_to_scheme( + scheme, + wirefilter_string("ssl"), + WIREFILTER_TYPE_BOOL + ); + wirefilter_add_type_field_to_scheme( + scheme, + wirefilter_string("tcp.port"), + WIREFILTER_TYPE_INT + ); + + wirefilter_parsing_result_t result = wirefilter_parse_filter( + scheme, + wirefilter_string("tcp.port == 80") + ); + rust_assert(result.success == 1, "could not parse good filter"); + rust_assert(result.ok.ast != NULL, "could not parse good filter"); + + wirefilter_free_parsing_result(result); + + wirefilter_free_scheme(scheme); +} + +void wirefilter_ffi_ctest_parse_bad_filter() { + wirefilter_scheme_t *scheme = wirefilter_create_scheme(); + rust_assert(scheme != NULL, "could not create scheme"); + + wirefilter_add_type_field_to_scheme( + scheme, + wirefilter_string("http.host"), + WIREFILTER_TYPE_BYTES + ); + wirefilter_add_type_field_to_scheme( + scheme, + wirefilter_string("ip.addr"), + WIREFILTER_TYPE_IP + ); + wirefilter_add_type_field_to_scheme( + scheme, + wirefilter_string("ssl"), + WIREFILTER_TYPE_BOOL + ); + wirefilter_add_type_field_to_scheme( + scheme, + wirefilter_string("tcp.port"), + WIREFILTER_TYPE_INT + ); + + wirefilter_parsing_result_t result = wirefilter_parse_filter( + scheme, + wirefilter_string("tcp.port == \"wirefilter\"") + ); + rust_assert(result.success == false, "should not parse bad filter"); + rust_assert(result.err.msg.data && result.err.msg.length > 0, "missing error message"); + + wirefilter_free_parsing_result(result); + + wirefilter_free_scheme(scheme); +} + +void wirefilter_ffi_ctest_compile_filter() { + wirefilter_scheme_t *scheme = wirefilter_create_scheme(); + rust_assert(scheme != NULL, "could not create scheme"); + + wirefilter_add_type_field_to_scheme( + scheme, + wirefilter_string("http.host"), + WIREFILTER_TYPE_BYTES + ); + wirefilter_add_type_field_to_scheme( + scheme, + wirefilter_string("ip.addr"), + WIREFILTER_TYPE_IP + ); + wirefilter_add_type_field_to_scheme( + scheme, + wirefilter_string("ssl"), + WIREFILTER_TYPE_BOOL + ); + wirefilter_add_type_field_to_scheme( + scheme, + wirefilter_string("tcp.port"), + WIREFILTER_TYPE_INT + ); + + wirefilter_parsing_result_t result = wirefilter_parse_filter( + scheme, + wirefilter_string("tcp.port == 80") + ); + rust_assert(result.success == true, "could not parse good filter"); + rust_assert(result.ok.ast != NULL, "could not parse good filter"); + + wirefilter_filter_t *filter = wirefilter_compile_filter(result.ok.ast); + rust_assert(filter != NULL, "could not compile filter"); + + wirefilter_free_compiled_filter(filter); + + wirefilter_free_scheme(scheme); +} + +void wirefilter_ffi_ctest_create_execution_context() { + wirefilter_scheme_t *scheme = wirefilter_create_scheme(); + rust_assert(scheme != NULL, "could not create scheme"); + + wirefilter_execution_context_t *exec_ctx = wirefilter_create_execution_context(scheme); + rust_assert(exec_ctx != NULL, "could not create execution context"); + + wirefilter_free_execution_context(exec_ctx); + + wirefilter_free_scheme(scheme); +} + +void wirefilter_ffi_ctest_add_values_to_execution_context() { + wirefilter_scheme_t *scheme = wirefilter_create_scheme(); + rust_assert(scheme != NULL, "could not create scheme"); + + wirefilter_add_type_field_to_scheme( + scheme, + wirefilter_string("http.host"), + WIREFILTER_TYPE_BYTES + ); + wirefilter_add_type_field_to_scheme( + scheme, + wirefilter_string("ip.addr"), + WIREFILTER_TYPE_IP + ); + wirefilter_add_type_field_to_scheme( + scheme, + wirefilter_string("ssl"), + WIREFILTER_TYPE_BOOL + ); + wirefilter_add_type_field_to_scheme( + scheme, + wirefilter_string("tcp.port"), + WIREFILTER_TYPE_INT + ); + + wirefilter_execution_context_t *exec_ctx = wirefilter_create_execution_context(scheme); + rust_assert(exec_ctx != NULL, "could not create execution context"); + + wirefilter_externally_allocated_byte_arr_t http_host; + http_host.data = (unsigned char *)"www.cloudflare.com"; + http_host.length = strlen((char *)http_host.data); + wirefilter_add_bytes_value_to_execution_context( + exec_ctx, + wirefilter_string("http.host"), + http_host + ); + + uint8_t ip_addr[4] = {192, 168, 0, 1}; + wirefilter_add_ipv4_value_to_execution_context( + exec_ctx, + wirefilter_string("ip.addr"), + ip_addr + ); + + wirefilter_add_bool_value_to_execution_context( + exec_ctx, + wirefilter_string("ssl"), + false + ); + + wirefilter_add_int_value_to_execution_context( + exec_ctx, + wirefilter_string("tcp.port"), + 80 + ); + + wirefilter_free_execution_context(exec_ctx); + + wirefilter_free_scheme(scheme); +} + +void wirefilter_ffi_ctest_match_filter() { + wirefilter_scheme_t *scheme = wirefilter_create_scheme(); + rust_assert(scheme != NULL, "could not create scheme"); + + wirefilter_add_type_field_to_scheme( + scheme, + wirefilter_string("http.host"), + WIREFILTER_TYPE_BYTES + ); + wirefilter_add_type_field_to_scheme( + scheme, + wirefilter_string("ip.addr"), + WIREFILTER_TYPE_IP + ); + wirefilter_add_type_field_to_scheme( + scheme, + wirefilter_string("ssl"), + WIREFILTER_TYPE_BOOL + ); + wirefilter_add_type_field_to_scheme( + scheme, + wirefilter_string("tcp.port"), + WIREFILTER_TYPE_INT + ); + + wirefilter_parsing_result_t result = wirefilter_parse_filter( + scheme, + wirefilter_string("tcp.port == 80") + ); + rust_assert(result.success == true, "could not parse good filter"); + rust_assert(result.ok.ast != NULL, "could not parse good filter"); + + wirefilter_filter_t *filter = wirefilter_compile_filter(result.ok.ast); + rust_assert(filter != NULL, "could not compile filter"); + + wirefilter_execution_context_t *exec_ctx = wirefilter_create_execution_context(scheme); + rust_assert(exec_ctx != NULL, "could not create execution context"); + + wirefilter_externally_allocated_byte_arr_t http_host; + http_host.data = (unsigned char *)"www.cloudflare.com"; + http_host.length = strlen((char *)http_host.data); + wirefilter_add_bytes_value_to_execution_context( + exec_ctx, + wirefilter_string("http.host"), + http_host + ); + + uint8_t ip_addr[4] = {192, 168, 0, 1}; + wirefilter_add_ipv4_value_to_execution_context( + exec_ctx, + wirefilter_string("ip.addr"), + ip_addr + ); + + wirefilter_add_bool_value_to_execution_context( + exec_ctx, + wirefilter_string("ssl"), + false + ); + + wirefilter_add_int_value_to_execution_context( + exec_ctx, + wirefilter_string("tcp.port"), + 80 + ); + + rust_assert(wirefilter_match(filter, exec_ctx) == true, "could not match filter"); + + wirefilter_free_execution_context(exec_ctx); + + wirefilter_free_compiled_filter(filter); + + wirefilter_free_scheme(scheme); +}