dash7/app/
query.rs

1use deku::prelude::*;
2
3use super::operand::{FileOffset, Length};
4
5#[derive(DekuRead, DekuWrite, Debug, Clone, PartialEq)]
6#[deku(bits = 3, type = "u8")]
7pub enum ArithmeticComparisonType {
8    #[deku(id = "0")]
9    Inequal,
10    #[deku(id = "1")]
11    Equal,
12    #[deku(id = "2")]
13    LessThan,
14    #[deku(id = "3")]
15    LessThanOrEqual,
16    #[deku(id = "4")]
17    GreaterThan,
18    #[deku(id = "5")]
19    GreaterThanOrEqual,
20}
21
22#[derive(DekuRead, DekuWrite, Debug, Clone, PartialEq)]
23pub struct ArithmeticQueryParams {
24    #[deku(bits = 1)]
25    pub signed: bool,
26    pub comparison_type: ArithmeticComparisonType,
27}
28
29#[derive(DekuRead, DekuWrite, Debug, Clone, PartialEq)]
30#[deku(bits = 3, type = "u8")]
31pub enum RangeComparisonType {
32    #[deku(id = "0")]
33    NotInRange,
34    #[deku(id = "1")]
35    InRange,
36}
37
38#[derive(DekuRead, DekuWrite, Debug, Clone, PartialEq)]
39pub struct RangeQueryParams {
40    #[deku(bits = 1)]
41    pub signed: bool,
42    pub comparison_type: RangeComparisonType,
43}
44
45#[derive(DekuRead, DekuWrite, Debug, Clone, PartialEq)]
46#[deku(bits = 3, type = "u8")]
47pub enum Query {
48    #[deku(id = "0x00")]
49    NonVoid(NonVoid),
50    #[deku(id = "0x01")]
51    ComparisonWithZero(ComparisonWithZero),
52    #[deku(id = "0x02")]
53    ComparisonWithValue(ComparisonWithValue),
54    #[deku(id = "0x03")]
55    ComparisonWithOtherFile(ComparisonWithOtherFile),
56    #[deku(id = "0x04")]
57    BitmapRangeComparison(BitmapRangeComparison),
58    #[deku(id = "0x07")]
59    StringTokenSearch(StringTokenSearch),
60}
61
62// ALP_SPEC Does this fail if the content overflows the file?
63/// Checks if the file content exists.
64#[derive(DekuRead, DekuWrite, Debug, Clone, PartialEq)]
65pub struct NonVoid {
66    #[deku(pad_bits_before = "5")]
67    pub length: Length,
68    pub file: FileOffset,
69}
70
71#[derive(DekuRead, DekuWrite, Debug, Clone, PartialEq)]
72pub struct ComparisonWithZero {
73    #[deku(bits = 1, update = "self.mask.len() > 0")]
74    mask_present: bool,
75
76    pub params: ArithmeticQueryParams,
77
78    #[deku(update = "self.mask.len()")]
79    length: Length,
80
81    #[deku(cond = "*mask_present", count = "length", endian = "big")]
82    pub mask: Vec<u8>,
83    pub file: FileOffset,
84}
85
86impl ComparisonWithZero {
87    pub fn new(params: ArithmeticQueryParams, mask: Vec<u8>, file: FileOffset) -> Self {
88        Self {
89            mask_present: mask.len() > 0,
90            params,
91            length: mask.len().into(),
92            mask,
93            file,
94        }
95    }
96}
97
98/// Compare some file content optionally masked, with a value
99#[derive(DekuRead, DekuWrite, Debug, Clone, PartialEq)]
100pub struct ComparisonWithValue {
101    #[deku(bits = 1, update = "self.mask.len() > 0")]
102    mask_present: bool,
103
104    pub params: ArithmeticQueryParams,
105
106    #[deku(update = "self.value.len()")]
107    length: Length,
108
109    #[deku(cond = "*mask_present", count = "length", endian = "big")]
110    pub mask: Vec<u8>,
111
112    #[deku(count = "length", endian = "big")]
113    pub value: Vec<u8>,
114
115    pub file: FileOffset,
116}
117
118impl ComparisonWithValue {
119    pub fn new(
120        params: ArithmeticQueryParams,
121        mask: Vec<u8>,
122        value: Vec<u8>,
123        file: FileOffset,
124    ) -> Self {
125        Self {
126            mask_present: mask.len() > 0,
127            params,
128            length: value.len().into(),
129            mask,
130            value,
131            file,
132        }
133    }
134}
135
136/// Compare content of 2 files optionally masked
137#[derive(DekuRead, DekuWrite, Debug, Clone, PartialEq)]
138pub struct ComparisonWithOtherFile {
139    #[deku(bits = 1, update = "self.mask.len() > 0")]
140    mask_present: bool,
141    pub params: ArithmeticQueryParams,
142
143    pub length: Length,
144    #[deku(cond = "*mask_present", count = "length", endian = "big")]
145    pub mask: Vec<u8>,
146
147    pub file1: FileOffset,
148    pub file2: FileOffset,
149}
150
151impl ComparisonWithOtherFile {
152    pub fn new(
153        params: ArithmeticQueryParams,
154        mask: Vec<u8>,
155        file1: FileOffset,
156        file2: FileOffset,
157    ) -> Self {
158        Self {
159            mask_present: mask.len() > 0,
160            params,
161            length: mask.len().into(),
162            mask,
163            file1,
164            file2,
165        }
166    }
167}
168
169/// Check if the content of a file is (not) contained in the sent bitmap values
170#[derive(DekuRead, DekuWrite, Debug, Clone, PartialEq)]
171pub struct BitmapRangeComparison {
172    #[deku(bits = 1, update = "self.mask.len() > 0")]
173    mask_present: bool,
174    pub params: RangeQueryParams,
175    pub length: Length,
176    // ALP SPEC: In theory, start and stop can be huge array thus impossible to cast into any trivial
177    // number. How do we deal with this.
178    // If the max size is ever settled by the spec, replace the buffer by the max size. This may take up more
179    // memory, but would be way easier to use. Also it would avoid having to specify the ".size"
180    // field.
181    pub start: Length,
182    pub stop: Length,
183
184    #[deku(count = "length", endian = "big")]
185    pub mask: Vec<u8>,
186    pub file: FileOffset,
187}
188
189impl BitmapRangeComparison {
190    pub fn new(
191        params: RangeQueryParams,
192        start: u32,
193        stop: u32,
194        mask: Vec<u8>,
195        file: FileOffset,
196    ) -> Self {
197        Self {
198            mask_present: mask.len() > 0,
199            params,
200            length: mask.len().into(),
201            start: start.into(),
202            stop: stop.into(),
203            mask,
204            file,
205        }
206    }
207}
208
209/// Compare some file content, optional masked, with an array of bytes and up to a certain number
210/// of errors.
211#[derive(DekuRead, DekuWrite, Debug, Clone, PartialEq)]
212pub struct StringTokenSearch {
213    #[deku(bits = 1, update = "self.mask.len() > 0", pad_bits_after = "1")]
214    mask_present: bool,
215
216    // TODO: is this bitsize correct?
217    #[deku(bits = 3)]
218    pub max_errors: u8,
219
220    #[deku(update = "self.value.len()")]
221    pub length: Length,
222
223    #[deku(count = "length", endian = "big")]
224    pub mask: Vec<u8>,
225
226    #[deku(count = "length", endian = "big")]
227    pub value: Vec<u8>,
228
229    pub file: FileOffset,
230}
231
232impl StringTokenSearch {
233    pub fn new(max_errors: u8, mask: Vec<u8>, value: Vec<u8>, file: FileOffset) -> Self {
234        Self {
235            mask_present: mask.len() > 0,
236            max_errors,
237            length: value.len().into(),
238            mask,
239            value,
240            file,
241        }
242    }
243}
244
245#[cfg(test)]
246mod test {
247    use hex_literal::hex;
248
249    use crate::test_tools::test_item;
250
251    use super::*;
252
253    #[test]
254    fn test_query_non_void() {
255        test_item(
256            Query::NonVoid(NonVoid {
257                length: 4u32.into(),
258                file: FileOffset {
259                    file_id: 5,
260                    offset: 6u32.into(),
261                },
262            }),
263            &hex!("00 04  05 06"),
264        )
265    }
266
267    #[test]
268    fn test_query_comparison_with_zero() {
269        test_item(
270            Query::ComparisonWithZero(ComparisonWithZero::new(
271                ArithmeticQueryParams {
272                    signed: true,
273                    comparison_type: ArithmeticComparisonType::Inequal,
274                },
275                vec![0, 1, 2],
276                FileOffset {
277                    file_id: 4,
278                    offset: 5u32.into(),
279                },
280            )),
281            &hex!("38 03 000102 04 05"),
282        )
283    }
284
285    #[test]
286    fn test_query_comparison_with_value() {
287        test_item(
288            Query::ComparisonWithValue(ComparisonWithValue::new(
289                ArithmeticQueryParams {
290                    signed: false,
291                    comparison_type: ArithmeticComparisonType::Equal,
292                },
293                vec![],
294                vec![9, 9, 9],
295                FileOffset {
296                    file_id: 4,
297                    offset: 5u32.into(),
298                },
299            )),
300            &hex!("41 03 090909 04 05"),
301        )
302    }
303
304    #[test]
305    fn test_query_comparison_with_other_file() {
306        test_item(
307            Query::ComparisonWithOtherFile(ComparisonWithOtherFile::new(
308                ArithmeticQueryParams {
309                    signed: false,
310                    comparison_type: ArithmeticComparisonType::GreaterThan,
311                },
312                vec![0xFF, 0xFF],
313                FileOffset {
314                    file_id: 4,
315                    offset: 5u32.into(),
316                },
317                FileOffset {
318                    file_id: 8,
319                    offset: 9u32.into(),
320                },
321            )),
322            &hex!("74 02 FFFF 04 05 08 09"),
323        )
324    }
325
326    #[test]
327    fn test_query_bitmap_range_comparison() {
328        test_item(
329            Query::BitmapRangeComparison(BitmapRangeComparison::new(
330                RangeQueryParams {
331                    signed: false,
332                    comparison_type: RangeComparisonType::InRange,
333                },
334                3,
335                32,
336                hex!("01020304").to_vec(),
337                FileOffset {
338                    file_id: 0,
339                    offset: 4u32.into(),
340                },
341            )),
342            &hex!("91 04 03  20  01020304  00 04"),
343        )
344    }
345
346    #[test]
347    fn test_query_string_token_search() {
348        test_item(
349            Query::StringTokenSearch(StringTokenSearch::new(
350                2,
351                hex!("FF00FF00").to_vec(),
352                hex!("01020304").to_vec(),
353                FileOffset {
354                    file_id: 0,
355                    offset: 4u32.into(),
356                },
357            )),
358            &hex!("F2 04 FF00FF00  01020304  00 04"),
359        )
360    }
361}