Skip to main content

canlink_hal/filter/
range_filter.rs

1//! Range filter implementation (FR-006)
2//!
3//! Provides ID range-based filtering.
4
5use crate::error::FilterError;
6use crate::message::CanMessage;
7
8use super::id_filter::{MAX_EXTENDED_ID, MAX_STANDARD_ID};
9use super::MessageFilter;
10
11/// Range filter for matching a range of CAN IDs
12///
13/// Matches any message with an ID between `start_id` and `end_id` (inclusive).
14///
15/// # Example
16///
17/// ```rust
18/// use canlink_hal::filter::RangeFilter;
19///
20/// // Match IDs 0x100-0x1FF
21/// let filter = RangeFilter::new(0x100, 0x1FF);
22/// ```
23#[derive(Debug, Clone)]
24pub struct RangeFilter {
25    /// Start of ID range (inclusive)
26    start_id: u32,
27    /// End of ID range (inclusive)
28    end_id: u32,
29    /// Whether this is for extended IDs
30    extended: bool,
31}
32
33impl RangeFilter {
34    /// Create a new range filter for standard frames
35    ///
36    /// # Arguments
37    ///
38    /// * `start_id` - Start of the ID range (inclusive)
39    /// * `end_id` - End of the ID range (inclusive)
40    ///
41    /// # Panics
42    ///
43    /// Panics if `start_id > end_id` or if IDs exceed maximum.
44    #[must_use]
45    pub fn new(start_id: u32, end_id: u32) -> Self {
46        assert!(start_id <= end_id, "start_id must be <= end_id");
47        assert!(end_id <= MAX_STANDARD_ID, "ID exceeds maximum standard ID");
48        Self {
49            start_id,
50            end_id,
51            extended: false,
52        }
53    }
54
55    /// Create a new range filter for extended frames
56    ///
57    /// # Arguments
58    ///
59    /// * `start_id` - Start of the ID range (inclusive)
60    /// * `end_id` - End of the ID range (inclusive)
61    ///
62    /// # Panics
63    ///
64    /// Panics if `start_id > end_id` or if IDs exceed maximum.
65    #[must_use]
66    pub fn new_extended(start_id: u32, end_id: u32) -> Self {
67        assert!(start_id <= end_id, "start_id must be <= end_id");
68        assert!(end_id <= MAX_EXTENDED_ID, "ID exceeds maximum extended ID");
69        Self {
70            start_id,
71            end_id,
72            extended: true,
73        }
74    }
75
76    /// Try to create a new range filter, returning error if invalid
77    ///
78    /// # Errors
79    ///
80    /// Returns `FilterError::InvalidRange` if `start_id > end_id`.
81    /// Returns `FilterError::IdOutOfRange` if `end_id` exceeds the maximum standard ID.
82    pub fn try_new(start_id: u32, end_id: u32) -> Result<Self, FilterError> {
83        if start_id > end_id {
84            return Err(FilterError::InvalidRange {
85                start: start_id,
86                end: end_id,
87            });
88        }
89        if end_id > MAX_STANDARD_ID {
90            return Err(FilterError::IdOutOfRange {
91                id: end_id,
92                max: MAX_STANDARD_ID,
93            });
94        }
95        Ok(Self::new(start_id, end_id))
96    }
97
98    /// Try to create a new extended range filter, returning error if invalid
99    ///
100    /// # Errors
101    ///
102    /// Returns `FilterError::InvalidRange` if `start_id > end_id`.
103    /// Returns `FilterError::IdOutOfRange` if `end_id` exceeds the maximum extended ID.
104    pub fn try_new_extended(start_id: u32, end_id: u32) -> Result<Self, FilterError> {
105        if start_id > end_id {
106            return Err(FilterError::InvalidRange {
107                start: start_id,
108                end: end_id,
109            });
110        }
111        if end_id > MAX_EXTENDED_ID {
112            return Err(FilterError::IdOutOfRange {
113                id: end_id,
114                max: MAX_EXTENDED_ID,
115            });
116        }
117        Ok(Self::new_extended(start_id, end_id))
118    }
119
120    /// Get the start ID
121    #[must_use]
122    pub fn start_id(&self) -> u32 {
123        self.start_id
124    }
125
126    /// Get the end ID
127    #[must_use]
128    pub fn end_id(&self) -> u32 {
129        self.end_id
130    }
131
132    /// Check if this filter is for extended frames
133    #[must_use]
134    pub fn is_extended(&self) -> bool {
135        self.extended
136    }
137
138    /// Get the range size
139    #[must_use]
140    pub fn range_size(&self) -> u32 {
141        self.end_id - self.start_id + 1
142    }
143}
144
145impl MessageFilter for RangeFilter {
146    fn matches(&self, message: &CanMessage) -> bool {
147        let msg_id = message.id();
148        let is_extended = msg_id.is_extended();
149
150        // Frame type must match
151        if is_extended != self.extended {
152            return false;
153        }
154
155        // Check if ID is in range
156        let raw_id = msg_id.raw();
157        raw_id >= self.start_id && raw_id <= self.end_id
158    }
159
160    fn priority(&self) -> u32 {
161        // Smaller ranges have higher priority
162        let range_size = self.end_id - self.start_id;
163        if range_size == 0 {
164            100 // Single ID, highest priority
165        } else if range_size < 16 {
166            75
167        } else if range_size < 256 {
168            50
169        } else {
170            25
171        }
172    }
173
174    fn is_hardware(&self) -> bool {
175        // Range filters are typically software-only
176        // unless the range can be expressed as a mask
177        false
178    }
179}
180
181#[cfg(test)]
182mod tests {
183    use super::*;
184    use crate::message::CanMessage;
185
186    #[test]
187    fn test_range_match() {
188        let filter = RangeFilter::new(0x100, 0x1FF);
189
190        let msg_100 = CanMessage::new_standard(0x100, &[0u8; 8]).unwrap();
191        let msg_150 = CanMessage::new_standard(0x150, &[0u8; 8]).unwrap();
192        let msg_1ff = CanMessage::new_standard(0x1FF, &[0u8; 8]).unwrap();
193        let msg_200 = CanMessage::new_standard(0x200, &[0u8; 8]).unwrap();
194        let msg_0ff = CanMessage::new_standard(0x0FF, &[0u8; 8]).unwrap();
195
196        assert!(filter.matches(&msg_100));
197        assert!(filter.matches(&msg_150));
198        assert!(filter.matches(&msg_1ff));
199        assert!(!filter.matches(&msg_200));
200        assert!(!filter.matches(&msg_0ff));
201    }
202
203    #[test]
204    fn test_single_id_range() {
205        let filter = RangeFilter::new(0x123, 0x123);
206
207        let msg_match = CanMessage::new_standard(0x123, &[0u8; 8]).unwrap();
208        let msg_no_match = CanMessage::new_standard(0x124, &[0u8; 8]).unwrap();
209
210        assert!(filter.matches(&msg_match));
211        assert!(!filter.matches(&msg_no_match));
212    }
213
214    #[test]
215    fn test_extended_range() {
216        let filter = RangeFilter::new_extended(0x10000, 0x1FFFF);
217
218        let msg_ext = CanMessage::new_extended(0x15000, &[0u8; 8]).unwrap();
219        let msg_std = CanMessage::new_standard(0x100, &[0u8; 8]).unwrap();
220
221        assert!(filter.matches(&msg_ext));
222        assert!(!filter.matches(&msg_std)); // Frame type mismatch
223    }
224
225    #[test]
226    fn test_try_new_invalid_range() {
227        let result = RangeFilter::try_new(0x200, 0x100);
228        assert!(result.is_err());
229    }
230
231    #[test]
232    fn test_range_size() {
233        let filter = RangeFilter::new(0x100, 0x1FF);
234        assert_eq!(filter.range_size(), 256);
235    }
236
237    #[test]
238    fn test_priority() {
239        let single = RangeFilter::new(0x100, 0x100);
240        let small = RangeFilter::new(0x100, 0x10F);
241        let large = RangeFilter::new(0x100, 0x1FF);
242
243        assert!(single.priority() > small.priority());
244        assert!(small.priority() > large.priority());
245    }
246}