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/engine/src/types.rs b/engine/src/types.rs index 2399404a..6486d684 100644 --- a/engine/src/types.rs +++ b/engine/src/types.rs @@ -54,7 +54,7 @@ macro_rules! declare_types { ($($(# $attrs:tt)* $name:ident ( $(# $lhs_attrs:tt)* $lhs_ty:ty | $rhs_ty:ty | $multi_rhs_ty:ty ) , )*) => { /// Enumeration of supported types for field values. #[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize)] - #[repr(u8)] + #[repr(C)] pub enum Type { $($(# $attrs)* $name,)* } diff --git a/ffi/Cargo.toml b/ffi/Cargo.toml index 5a2f6956..b3389698 100644 --- a/ffi/Cargo.toml +++ b/ffi/Cargo.toml @@ -9,7 +9,7 @@ publish = false assets = [["target/release/libwirefilter_ffi.so", "usr/local/lib/libwirefilter.so", "644"]] [lib] -crate-type = ["cdylib"] +crate-type = ["cdylib", "rlib"] # Avoid duplicate compilation error messages as we don't have doctests anyway doctest = false bench = false @@ -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..ee2f77be --- /dev/null +++ b/ffi/include/wirefilter.h @@ -0,0 +1,127 @@ +#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 { + 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..ca08a73d --- /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") + .warnings(true) + .extra_warnings(true) + .warnings_into_errors(true) + .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..350f3ac5 --- /dev/null +++ b/ffi/tests/ctests/src/lib.rs @@ -0,0 +1,41 @@ +extern crate wirefilter_ffi as _; + +#[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() { + 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..e9eb1ac5 --- /dev/null +++ b/ffi/tests/ctests/src/tests.c @@ -0,0 +1,310 @@ +#include +#include +#include + +#include + +extern void rust_assert(bool check, const char *msg); + +static wirefilter_externally_allocated_str_t wirefilter_string(const char *s) { + wirefilter_externally_allocated_str_t str; + str.data = s; + str.length = strlen(s); + return str; +} + +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); +}