Skip to main content

canlink_hal/filter/
chain.rs

1//! Filter chain implementation (FR-006)
2//!
3//! Manages multiple filters with automatic hardware/software fallback.
4
5use crate::error::FilterError;
6use crate::message::CanMessage;
7
8use super::MessageFilter;
9
10/// Filter chain for managing multiple filters
11///
12/// The filter chain manages a collection of filters and automatically
13/// handles hardware filter limits by falling back to software filtering.
14///
15/// # Filter Evaluation
16///
17/// Filters are evaluated in order of priority (highest first).
18/// A message passes if ANY filter matches (OR logic).
19/// An empty chain passes all messages.
20///
21/// # Hardware Filter Management
22///
23/// The chain tracks hardware filter capacity. When the limit is reached,
24/// additional filters are automatically treated as software filters.
25///
26/// # Example
27///
28/// ```rust
29/// use canlink_hal::filter::{FilterChain, IdFilter, RangeFilter};
30///
31/// let mut chain = FilterChain::new(4); // Max 4 hardware filters
32///
33/// chain.add_filter(Box::new(IdFilter::new(0x123)));
34/// chain.add_filter(Box::new(RangeFilter::new(0x200, 0x2FF)));
35///
36/// // Check if a message passes the filter chain
37/// // let passes = chain.matches(&message);
38/// ```
39pub struct FilterChain {
40    /// All filters in the chain
41    filters: Vec<Box<dyn MessageFilter>>,
42    /// Maximum number of hardware filters supported
43    max_hardware_filters: usize,
44    /// Current number of hardware filters in use
45    hardware_filter_count: usize,
46}
47
48impl FilterChain {
49    /// Create a new filter chain
50    ///
51    /// # Arguments
52    ///
53    /// * `max_hardware_filters` - Maximum number of hardware filters supported
54    #[must_use]
55    pub fn new(max_hardware_filters: usize) -> Self {
56        Self {
57            filters: Vec::new(),
58            max_hardware_filters,
59            hardware_filter_count: 0,
60        }
61    }
62
63    /// Add a filter to the chain
64    ///
65    /// Filters are added in order. If the filter is a hardware filter
66    /// and the hardware limit has been reached, it will be treated as
67    /// a software filter.
68    ///
69    /// # Arguments
70    ///
71    /// * `filter` - The filter to add
72    pub fn add_filter(&mut self, filter: Box<dyn MessageFilter>) {
73        if filter.is_hardware() && self.hardware_filter_count < self.max_hardware_filters {
74            self.hardware_filter_count += 1;
75        }
76        self.filters.push(filter);
77    }
78
79    /// Remove a filter at the given index
80    ///
81    /// # Arguments
82    ///
83    /// * `index` - Index of the filter to remove
84    ///
85    /// # Errors
86    ///
87    /// Returns `FilterError::FilterNotFound` if the index is out of bounds.
88    pub fn remove_filter(&mut self, index: usize) -> Result<Box<dyn MessageFilter>, FilterError> {
89        if index >= self.filters.len() {
90            return Err(FilterError::FilterNotFound { index });
91        }
92
93        let filter = self.filters.remove(index);
94        if filter.is_hardware() && self.hardware_filter_count > 0 {
95            self.hardware_filter_count -= 1;
96        }
97        Ok(filter)
98    }
99
100    /// Clear all filters from the chain
101    pub fn clear(&mut self) {
102        self.filters.clear();
103        self.hardware_filter_count = 0;
104    }
105
106    /// Check if a message matches any filter in the chain
107    ///
108    /// Returns `true` if:
109    /// - The chain is empty (pass-through mode)
110    /// - Any filter in the chain matches the message
111    #[must_use]
112    pub fn matches(&self, message: &CanMessage) -> bool {
113        // Empty chain passes all messages
114        if self.filters.is_empty() {
115            return true;
116        }
117
118        // Check if any filter matches (OR logic)
119        self.filters.iter().any(|f| f.matches(message))
120    }
121
122    /// Get the total number of filters
123    #[must_use]
124    pub fn len(&self) -> usize {
125        self.filters.len()
126    }
127
128    /// Check if the chain is empty
129    #[must_use]
130    pub fn is_empty(&self) -> bool {
131        self.filters.is_empty()
132    }
133
134    /// Get the number of hardware filters in use
135    #[must_use]
136    pub fn hardware_filter_count(&self) -> usize {
137        self.hardware_filter_count
138    }
139
140    /// Get the number of software filters in use
141    #[must_use]
142    pub fn software_filter_count(&self) -> usize {
143        self.filters
144            .iter()
145            .filter(|f| !f.is_hardware() || self.hardware_filter_count >= self.max_hardware_filters)
146            .count()
147    }
148
149    /// Get the maximum hardware filter capacity
150    #[must_use]
151    pub fn max_hardware_filters(&self) -> usize {
152        self.max_hardware_filters
153    }
154
155    /// Check if hardware filter capacity is available
156    #[must_use]
157    pub fn has_hardware_capacity(&self) -> bool {
158        self.hardware_filter_count < self.max_hardware_filters
159    }
160
161    /// Get the total filter count (alias for len)
162    #[must_use]
163    pub fn total_filter_count(&self) -> usize {
164        self.len()
165    }
166
167    /// Iterate over filters
168    pub fn iter(&self) -> impl Iterator<Item = &dyn MessageFilter> {
169        self.filters.iter().map(std::convert::AsRef::as_ref)
170    }
171}
172
173impl Default for FilterChain {
174    fn default() -> Self {
175        Self::new(4) // Default to 4 hardware filters
176    }
177}
178
179#[cfg(test)]
180mod tests {
181    use super::*;
182    use crate::filter::{IdFilter, RangeFilter};
183    use crate::message::CanMessage;
184
185    fn make_message(id: u16) -> CanMessage {
186        CanMessage::new_standard(id, &[0u8; 8]).unwrap()
187    }
188
189    #[test]
190    fn test_empty_chain_passes_all() {
191        let chain = FilterChain::new(4);
192        assert!(chain.matches(&make_message(0x123)));
193        assert!(chain.matches(&make_message(0x456)));
194    }
195
196    #[test]
197    fn test_single_filter() {
198        let mut chain = FilterChain::new(4);
199        chain.add_filter(Box::new(IdFilter::new(0x123)));
200
201        assert!(chain.matches(&make_message(0x123)));
202        assert!(!chain.matches(&make_message(0x456)));
203    }
204
205    #[test]
206    fn test_multiple_filters_or_logic() {
207        let mut chain = FilterChain::new(4);
208        chain.add_filter(Box::new(IdFilter::new(0x123)));
209        chain.add_filter(Box::new(IdFilter::new(0x456)));
210
211        assert!(chain.matches(&make_message(0x123)));
212        assert!(chain.matches(&make_message(0x456)));
213        assert!(!chain.matches(&make_message(0x789)));
214    }
215
216    #[test]
217    fn test_mixed_filters() {
218        let mut chain = FilterChain::new(4);
219        chain.add_filter(Box::new(IdFilter::new(0x123)));
220        chain.add_filter(Box::new(RangeFilter::new(0x200, 0x2FF)));
221
222        assert!(chain.matches(&make_message(0x123)));
223        assert!(chain.matches(&make_message(0x250)));
224        assert!(!chain.matches(&make_message(0x300)));
225    }
226
227    #[test]
228    fn test_hardware_filter_count() {
229        let mut chain = FilterChain::new(2);
230
231        // IdFilter is hardware by default
232        chain.add_filter(Box::new(IdFilter::new(0x100)));
233        assert_eq!(chain.hardware_filter_count(), 1);
234
235        chain.add_filter(Box::new(IdFilter::new(0x200)));
236        assert_eq!(chain.hardware_filter_count(), 2);
237
238        // RangeFilter is software by default
239        chain.add_filter(Box::new(RangeFilter::new(0x300, 0x3FF)));
240        assert_eq!(chain.hardware_filter_count(), 2);
241    }
242
243    #[test]
244    fn test_remove_filter() {
245        let mut chain = FilterChain::new(4);
246        chain.add_filter(Box::new(IdFilter::new(0x123)));
247        chain.add_filter(Box::new(IdFilter::new(0x456)));
248
249        assert_eq!(chain.len(), 2);
250
251        chain.remove_filter(0).unwrap();
252        assert_eq!(chain.len(), 1);
253
254        // Now only 0x456 should match
255        assert!(!chain.matches(&make_message(0x123)));
256        assert!(chain.matches(&make_message(0x456)));
257    }
258
259    #[test]
260    fn test_remove_invalid_index() {
261        let mut chain = FilterChain::new(4);
262        let result = chain.remove_filter(0);
263        assert!(result.is_err());
264    }
265
266    #[test]
267    fn test_clear() {
268        let mut chain = FilterChain::new(4);
269        chain.add_filter(Box::new(IdFilter::new(0x123)));
270        chain.add_filter(Box::new(IdFilter::new(0x456)));
271
272        chain.clear();
273        assert!(chain.is_empty());
274        assert_eq!(chain.hardware_filter_count(), 0);
275    }
276}