wirefilter
517 строк · 13.7 Кб
1pub mod transfer_types;
2
3use crate::transfer_types::{
4ExternallyAllocatedByteArr, ExternallyAllocatedStr, RustAllocatedString, RustBox,
5StaticRustAllocatedString,
6};
7use fnv::FnvHasher;
8use std::{
9hash::Hasher,
10io::{self, Write},
11net::IpAddr,
12};
13use wirefilter::{ExecutionContext, Filter, FilterAst, ParseError, Scheme, Type};
14
15const VERSION: &str = env!("CARGO_PKG_VERSION");
16
17#[repr(u8)]
18pub enum ParsingResult<'s> {
19Err(RustAllocatedString),
20Ok(RustBox<FilterAst<'s>>),
21}
22
23impl<'s> From<FilterAst<'s>> for ParsingResult<'s> {
24fn from(filter_ast: FilterAst<'s>) -> Self {
25ParsingResult::Ok(filter_ast.into())
26}
27}
28
29impl<'s, 'a> From<ParseError<'a>> for ParsingResult<'s> {
30fn from(err: ParseError<'a>) -> Self {
31ParsingResult::Err(RustAllocatedString::from(err.to_string()))
32}
33}
34
35impl<'s> ParsingResult<'s> {
36pub fn unwrap(self) -> RustBox<FilterAst<'s>> {
37match self {
38ParsingResult::Err(err) => panic!("{}", &err as &str),
39ParsingResult::Ok(filter) => filter,
40}
41}
42}
43
44#[no_mangle]
45pub extern "C" fn wirefilter_create_scheme() -> RustBox<Scheme> {
46Default::default()
47}
48
49#[no_mangle]
50pub extern "C" fn wirefilter_free_scheme(scheme: RustBox<Scheme>) {
51drop(scheme);
52}
53
54#[no_mangle]
55pub extern "C" fn wirefilter_add_type_field_to_scheme(
56scheme: &mut Scheme,
57name: ExternallyAllocatedStr<'_>,
58ty: Type,
59) {
60scheme.add_field(name.into_ref().to_owned(), ty).unwrap();
61}
62
63#[no_mangle]
64pub extern "C" fn wirefilter_free_parsed_filter(filter_ast: RustBox<FilterAst<'_>>) {
65drop(filter_ast);
66}
67
68#[no_mangle]
69pub extern "C" fn wirefilter_free_string(s: RustAllocatedString) {
70drop(s);
71}
72
73#[no_mangle]
74pub extern "C" fn wirefilter_parse_filter<'s, 'i>(
75scheme: &'s Scheme,
76input: ExternallyAllocatedStr<'i>,
77) -> ParsingResult<'s> {
78match scheme.parse(input.into_ref()) {
79Ok(filter) => ParsingResult::from(filter),
80Err(err) => ParsingResult::from(err),
81}
82}
83
84#[no_mangle]
85pub extern "C" fn wirefilter_free_parsing_result(r: ParsingResult<'_>) {
86drop(r);
87}
88
89/// Wrapper for Hasher that allows using Write API (e.g. with serializer).
90#[derive(Default)]
91struct HasherWrite<H: Hasher>(H);
92
93impl<H: Hasher> Write for HasherWrite<H> {
94fn write_all(&mut self, buf: &[u8]) -> io::Result<()> {
95self.0.write(buf);
96Ok(())
97}
98
99fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
100self.write_all(buf)?;
101Ok(buf.len())
102}
103
104fn flush(&mut self) -> io::Result<()> {
105Ok(())
106}
107}
108
109fn unwrap_json_result<T>(filter_ast: &FilterAst<'_>, result: serde_json::Result<T>) -> T {
110// Filter serialisation must never fail.
111result.unwrap_or_else(|err| panic!("{} while serializing filter {:#?}", err, filter_ast))
112}
113
114#[no_mangle]
115pub extern "C" fn wirefilter_get_filter_hash(filter_ast: &FilterAst<'_>) -> u64 {
116let mut hasher = FnvHasher::default();
117// Serialize JSON to our Write-compatible wrapper around FnvHasher,
118// effectively calculating a hash for our filter in a streaming fashion
119// that is as stable as the JSON representation itself
120// (instead of relying on #[derive(Hash)] which would be tied to impl details).
121let result = serde_json::to_writer(HasherWrite(&mut hasher), filter_ast);
122unwrap_json_result(filter_ast, result);
123hasher.finish()
124}
125
126#[no_mangle]
127pub extern "C" fn wirefilter_serialize_filter_to_json(
128filter_ast: &FilterAst<'_>,
129) -> RustAllocatedString {
130let result = serde_json::to_string(filter_ast);
131unwrap_json_result(filter_ast, result).into()
132}
133
134#[no_mangle]
135pub extern "C" fn wirefilter_create_execution_context<'e, 's: 'e>(
136scheme: &'s Scheme,
137) -> RustBox<ExecutionContext<'e>> {
138ExecutionContext::new(scheme).into()
139}
140
141#[no_mangle]
142pub extern "C" fn wirefilter_free_execution_context(exec_context: RustBox<ExecutionContext<'_>>) {
143drop(exec_context);
144}
145
146#[no_mangle]
147pub extern "C" fn wirefilter_add_int_value_to_execution_context<'a>(
148exec_context: &mut ExecutionContext<'a>,
149name: ExternallyAllocatedStr<'_>,
150value: i32,
151) {
152exec_context
153.set_field_value(name.into_ref(), value)
154.unwrap();
155}
156
157#[no_mangle]
158pub extern "C" fn wirefilter_add_bytes_value_to_execution_context<'a>(
159exec_context: &mut ExecutionContext<'a>,
160name: ExternallyAllocatedStr<'_>,
161value: ExternallyAllocatedByteArr<'a>,
162) {
163let slice: &[u8] = value.into_ref();
164exec_context
165.set_field_value(name.into_ref(), slice)
166.unwrap();
167}
168
169#[no_mangle]
170pub extern "C" fn wirefilter_add_ipv6_value_to_execution_context(
171exec_context: &mut ExecutionContext<'_>,
172name: ExternallyAllocatedStr<'_>,
173value: &[u8; 16],
174) {
175exec_context
176.set_field_value(name.into_ref(), IpAddr::from(*value))
177.unwrap();
178}
179
180#[no_mangle]
181pub extern "C" fn wirefilter_add_ipv4_value_to_execution_context(
182exec_context: &mut ExecutionContext<'_>,
183name: ExternallyAllocatedStr<'_>,
184value: &[u8; 4],
185) {
186exec_context
187.set_field_value(name.into_ref(), IpAddr::from(*value))
188.unwrap();
189}
190
191#[no_mangle]
192pub extern "C" fn wirefilter_add_bool_value_to_execution_context(
193exec_context: &mut ExecutionContext<'_>,
194name: ExternallyAllocatedStr<'_>,
195value: bool,
196) {
197exec_context
198.set_field_value(name.into_ref(), value)
199.unwrap();
200}
201
202#[no_mangle]
203pub extern "C" fn wirefilter_compile_filter<'s>(
204filter_ast: RustBox<FilterAst<'s>>,
205) -> RustBox<Filter<'s>> {
206let filter_ast = filter_ast.into_real_box();
207filter_ast.compile().into()
208}
209
210#[no_mangle]
211pub extern "C" fn wirefilter_match<'s>(
212filter: &Filter<'s>,
213exec_context: &ExecutionContext<'s>,
214) -> bool {
215filter.execute(exec_context).unwrap()
216}
217
218#[no_mangle]
219pub extern "C" fn wirefilter_free_compiled_filter(filter: RustBox<Filter<'_>>) {
220drop(filter);
221}
222
223#[no_mangle]
224pub extern "C" fn wirefilter_filter_uses(
225filter_ast: &FilterAst<'_>,
226field_name: ExternallyAllocatedStr<'_>,
227) -> bool {
228filter_ast.uses(field_name.into_ref()).unwrap()
229}
230
231#[no_mangle]
232pub extern "C" fn wirefilter_get_version() -> StaticRustAllocatedString {
233StaticRustAllocatedString::from(VERSION)
234}
235
236#[cfg(test)]
237mod ffi_test {
238use super::*;
239use regex::Regex;
240
241fn create_scheme() -> RustBox<Scheme> {
242let mut scheme = wirefilter_create_scheme();
243
244wirefilter_add_type_field_to_scheme(
245&mut scheme,
246ExternallyAllocatedStr::from("ip1"),
247Type::Ip,
248);
249wirefilter_add_type_field_to_scheme(
250&mut scheme,
251ExternallyAllocatedStr::from("ip2"),
252Type::Ip,
253);
254
255wirefilter_add_type_field_to_scheme(
256&mut scheme,
257ExternallyAllocatedStr::from("str1"),
258Type::Bytes,
259);
260wirefilter_add_type_field_to_scheme(
261&mut scheme,
262ExternallyAllocatedStr::from("str2"),
263Type::Bytes,
264);
265
266wirefilter_add_type_field_to_scheme(
267&mut scheme,
268ExternallyAllocatedStr::from("num1"),
269Type::Int,
270);
271wirefilter_add_type_field_to_scheme(
272&mut scheme,
273ExternallyAllocatedStr::from("num2"),
274Type::Int,
275);
276
277scheme
278}
279
280fn create_execution_context<'e, 's: 'e>(scheme: &'s Scheme) -> RustBox<ExecutionContext<'e>> {
281let mut exec_context = wirefilter_create_execution_context(scheme);
282
283wirefilter_add_ipv4_value_to_execution_context(
284&mut exec_context,
285ExternallyAllocatedStr::from("ip1"),
286&[127, 0, 0, 1],
287);
288
289wirefilter_add_ipv6_value_to_execution_context(
290&mut exec_context,
291ExternallyAllocatedStr::from("ip2"),
292b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xFF\xFF\xC0\xA8\x00\x01",
293);
294
295wirefilter_add_bytes_value_to_execution_context(
296&mut exec_context,
297ExternallyAllocatedStr::from("str1"),
298ExternallyAllocatedByteArr::from("Hey"),
299);
300
301wirefilter_add_bytes_value_to_execution_context(
302&mut exec_context,
303ExternallyAllocatedStr::from("str2"),
304ExternallyAllocatedByteArr::from("yo123"),
305);
306
307wirefilter_add_int_value_to_execution_context(
308&mut exec_context,
309ExternallyAllocatedStr::from("num1"),
31042,
311);
312
313wirefilter_add_int_value_to_execution_context(
314&mut exec_context,
315ExternallyAllocatedStr::from("num2"),
3161337,
317);
318
319exec_context
320}
321
322fn parse_filter<'s>(scheme: &'s Scheme, input: &'static str) -> ParsingResult<'s> {
323wirefilter_parse_filter(scheme, ExternallyAllocatedStr::from(input))
324}
325
326fn match_filter(
327input: &'static str,
328scheme: &Scheme,
329exec_context: &ExecutionContext<'_>,
330) -> bool {
331let filter = parse_filter(scheme, input).unwrap();
332let filter = wirefilter_compile_filter(filter);
333
334let result = wirefilter_match(&filter, exec_context);
335
336wirefilter_free_compiled_filter(filter);
337
338result
339}
340
341#[test]
342fn parse_error() {
343use indoc::indoc;
344
345let src = indoc!(
346r#"
347(
348num1 == 42
349or
350num1 == "abc"
351)
352"#
353);
354
355let scheme = create_scheme();
356
357{
358let result = parse_filter(&scheme, src);
359
360match result {
361ParsingResult::Ok(_) => panic!("Error expected"),
362ParsingResult::Err(err) => {
363assert_eq!(
364&err as &str,
365indoc!(
366r#"
367Filter parsing error (4:13):
368num1 == "abc"
369^^^^^ expected digit
370"#
371)
372);
373wirefilter_free_string(err);
374}
375}
376}
377
378wirefilter_free_scheme(scheme);
379}
380
381#[test]
382fn filter_parsing() {
383let scheme = create_scheme();
384
385{
386let filter = parse_filter(&scheme, r#"num1 > 3 && str2 == "abc""#).unwrap();
387
388let json = wirefilter_serialize_filter_to_json(&filter);
389
390assert_eq!(
391&json as &str,
392r#"{"op":"And","items":[{"lhs":"num1","op":"GreaterThan","rhs":3},{"lhs":"str2","op":"Equal","rhs":"abc"}]}"#
393);
394
395wirefilter_free_string(json);
396
397wirefilter_free_parsed_filter(filter);
398}
399
400wirefilter_free_scheme(scheme);
401}
402
403#[test]
404fn filter_matching() {
405let scheme = create_scheme();
406
407{
408let exec_context = create_execution_context(&scheme);
409
410assert!(match_filter(
411r#"num1 > 41 && num2 == 1337 && ip1 != 192.168.0.1 && str2 ~ "yo\d+""#,
412&scheme,
413&exec_context
414));
415
416assert!(match_filter(
417r#"ip2 == 0:0:0:0:0:ffff:c0a8:1 && (str1 == "Hey" || str2 == "ya")"#,
418&scheme,
419&exec_context
420));
421
422assert!(!match_filter(
423"ip1 == 127.0.0.1 && ip2 == 0:0:0:0:0:ffff:c0a8:2",
424&scheme,
425&exec_context
426));
427
428wirefilter_free_execution_context(exec_context);
429}
430
431wirefilter_free_scheme(scheme);
432}
433
434#[test]
435fn filter_hash() {
436let scheme = create_scheme();
437
438{
439let filter1 = parse_filter(
440&scheme,
441r#"num1 > 41 && num2 == 1337 && ip1 != 192.168.0.1 && str2 ~ "yo\d+""#,
442)
443.unwrap();
444
445let filter2 = parse_filter(
446&scheme,
447r#"num1 > 41 && num2 == 1337 && ip1 != 192.168.0.1 and str2 ~ "yo\d+""#,
448)
449.unwrap();
450
451let filter3 = parse_filter(&scheme, r#"num1 > 41 && num2 == 1337"#).unwrap();
452
453let hash1 = wirefilter_get_filter_hash(&filter1);
454let hash2 = wirefilter_get_filter_hash(&filter2);
455let hash3 = wirefilter_get_filter_hash(&filter3);
456
457assert_eq!(hash1, hash2);
458assert_ne!(hash2, hash3);
459
460wirefilter_free_parsed_filter(filter1);
461wirefilter_free_parsed_filter(filter2);
462wirefilter_free_parsed_filter(filter3);
463}
464
465wirefilter_free_scheme(scheme);
466}
467
468#[test]
469fn get_version() {
470let version = wirefilter_get_version();
471let re = Regex::new(r"(?-u)^\d+\.\d+\.\d+$").unwrap();
472
473assert!(re.is_match(version.into_ref()));
474}
475
476#[test]
477fn filter_uses() {
478let scheme = create_scheme();
479
480{
481let filter = parse_filter(
482&scheme,
483r#"num1 > 41 && num2 == 1337 && ip1 != 192.168.0.1 && str2 ~ "yo\d+""#,
484)
485.unwrap();
486
487assert!(wirefilter_filter_uses(
488&filter,
489ExternallyAllocatedStr::from("num1")
490));
491
492assert!(wirefilter_filter_uses(
493&filter,
494ExternallyAllocatedStr::from("ip1")
495));
496
497assert!(wirefilter_filter_uses(
498&filter,
499ExternallyAllocatedStr::from("str2")
500));
501
502assert!(!wirefilter_filter_uses(
503&filter,
504ExternallyAllocatedStr::from("str1")
505));
506
507assert!(!wirefilter_filter_uses(
508&filter,
509ExternallyAllocatedStr::from("ip2")
510));
511
512wirefilter_free_parsed_filter(filter);
513}
514
515wirefilter_free_scheme(scheme);
516}
517}
518