Skip to main content

canlink_hal/filter/
id_filter.rs

1//! ID filter implementation (FR-006)
2//!
3//! Provides single ID and mask-based filtering.
4
5use crate::error::FilterError;
6use crate::message::CanMessage;
7
8use super::MessageFilter;
9
10/// Maximum standard CAN ID (11-bit)
11pub const MAX_STANDARD_ID: u32 = 0x7FF;
12
13/// Maximum extended CAN ID (29-bit)
14pub const MAX_EXTENDED_ID: u32 = 0x1FFF_FFFF;
15
16/// ID filter for single ID or mask-based matching
17///
18/// Supports both exact ID matching and mask-based matching.
19///
20/// # Example
21///
22/// ```rust
23/// use canlink_hal::filter::IdFilter;
24///
25/// // Exact match for ID 0x123
26/// let filter = IdFilter::new(0x123);
27///
28/// // Mask match: matches 0x120-0x12F
29/// let filter = IdFilter::with_mask(0x120, 0x7F0);
30/// ```
31#[derive(Debug, Clone)]
32pub struct IdFilter {
33    /// Target ID
34    id: u32,
35    /// Mask for matching (0xFFFFFFFF for exact match)
36    mask: u32,
37    /// Whether this is for extended IDs
38    extended: bool,
39    /// Whether this can be a hardware filter
40    hardware: bool,
41}
42
43impl IdFilter {
44    /// Create a new filter for exact ID match (standard frame)
45    ///
46    /// # Arguments
47    ///
48    /// * `id` - The CAN ID to match (must be <= 0x7FF)
49    ///
50    /// # Panics
51    ///
52    /// Panics if `id` exceeds the maximum standard ID.
53    #[must_use]
54    pub fn new(id: u32) -> Self {
55        assert!(id <= MAX_STANDARD_ID, "ID exceeds maximum standard ID");
56        Self {
57            id,
58            mask: MAX_STANDARD_ID,
59            extended: false,
60            hardware: true,
61        }
62    }
63
64    /// Create a new filter with mask (standard frame)
65    ///
66    /// # Arguments
67    ///
68    /// * `id` - The target ID
69    /// * `mask` - Bits set to 1 must match, bits set to 0 are ignored
70    ///
71    /// # Example
72    ///
73    /// ```rust
74    /// use canlink_hal::filter::IdFilter;
75    ///
76    /// // Match IDs 0x120-0x12F (mask ignores lower 4 bits)
77    /// let filter = IdFilter::with_mask(0x120, 0x7F0);
78    /// ```
79    #[must_use]
80    pub fn with_mask(id: u32, mask: u32) -> Self {
81        Self {
82            id: id & mask,
83            mask,
84            extended: false,
85            hardware: true,
86        }
87    }
88
89    /// Create a new filter for extended frame
90    ///
91    /// # Arguments
92    ///
93    /// * `id` - The CAN ID to match (must be <= 0x1FFFFFFF)
94    ///
95    /// # Panics
96    ///
97    /// Panics if `id` exceeds the maximum extended ID.
98    #[must_use]
99    pub fn new_extended(id: u32) -> Self {
100        assert!(id <= MAX_EXTENDED_ID, "ID exceeds maximum extended ID");
101        Self {
102            id,
103            mask: MAX_EXTENDED_ID,
104            extended: true,
105            hardware: true,
106        }
107    }
108
109    /// Create a new filter with mask for extended frame
110    #[must_use]
111    pub fn with_mask_extended(id: u32, mask: u32) -> Self {
112        Self {
113            id: id & mask,
114            mask,
115            extended: true,
116            hardware: true,
117        }
118    }
119
120    /// Try to create a new filter, returning error if ID is invalid
121    ///
122    /// # Errors
123    ///
124    /// Returns `FilterError::IdOutOfRange` if `id` exceeds the maximum standard ID.
125    pub fn try_new(id: u32) -> Result<Self, FilterError> {
126        if id > MAX_STANDARD_ID {
127            return Err(FilterError::IdOutOfRange {
128                id,
129                max: MAX_STANDARD_ID,
130            });
131        }
132        Ok(Self::new(id))
133    }
134
135    /// Try to create a new extended filter, returning error if ID is invalid
136    ///
137    /// # Errors
138    ///
139    /// Returns `FilterError::IdOutOfRange` if `id` exceeds the maximum extended ID.
140    pub fn try_new_extended(id: u32) -> Result<Self, FilterError> {
141        if id > MAX_EXTENDED_ID {
142            return Err(FilterError::IdOutOfRange {
143                id,
144                max: MAX_EXTENDED_ID,
145            });
146        }
147        Ok(Self::new_extended(id))
148    }
149
150    /// Set whether this filter can use hardware acceleration
151    pub fn set_hardware(&mut self, hardware: bool) {
152        self.hardware = hardware;
153    }
154
155    /// Get the target ID
156    #[must_use]
157    pub fn id(&self) -> u32 {
158        self.id
159    }
160
161    /// Get the mask
162    #[must_use]
163    pub fn mask(&self) -> u32 {
164        self.mask
165    }
166
167    /// Check if this filter is for extended frames
168    #[must_use]
169    pub fn is_extended(&self) -> bool {
170        self.extended
171    }
172}
173
174impl MessageFilter for IdFilter {
175    fn matches(&self, message: &CanMessage) -> bool {
176        let msg_id = message.id();
177        let is_extended = msg_id.is_extended();
178
179        // Frame type must match
180        if is_extended != self.extended {
181            return false;
182        }
183
184        // Apply mask and compare
185        let raw_id = msg_id.raw();
186        (raw_id & self.mask) == (self.id & self.mask)
187    }
188
189    fn priority(&self) -> u32 {
190        // Exact match (full mask) has higher priority
191        if self.mask == MAX_STANDARD_ID || self.mask == MAX_EXTENDED_ID {
192            100
193        } else {
194            50
195        }
196    }
197
198    fn is_hardware(&self) -> bool {
199        self.hardware
200    }
201}
202
203#[cfg(test)]
204mod tests {
205    use super::*;
206    use crate::message::CanMessage;
207
208    #[test]
209    fn test_exact_match() {
210        let filter = IdFilter::new(0x123);
211
212        let msg_match = CanMessage::new_standard(0x123, &[0u8; 8]).unwrap();
213        let msg_no_match = CanMessage::new_standard(0x456, &[0u8; 8]).unwrap();
214
215        assert!(filter.matches(&msg_match));
216        assert!(!filter.matches(&msg_no_match));
217    }
218
219    #[test]
220    fn test_mask_match() {
221        // Match 0x120-0x12F
222        let filter = IdFilter::with_mask(0x120, 0x7F0);
223
224        let msg_120 = CanMessage::new_standard(0x120, &[0u8; 8]).unwrap();
225        let msg_12f_id = CanMessage::new_standard(0x12F, &[0u8; 8]).unwrap();
226        let msg_130 = CanMessage::new_standard(0x130, &[0u8; 8]).unwrap();
227
228        assert!(filter.matches(&msg_120));
229        assert!(filter.matches(&msg_12f_id));
230        assert!(!filter.matches(&msg_130));
231    }
232
233    #[test]
234    fn test_extended_filter() {
235        let filter = IdFilter::new_extended(0x1234_5678);
236
237        let msg_ext = CanMessage::new_extended(0x1234_5678, &[0u8; 8]).unwrap();
238        let msg_std = CanMessage::new_standard(0x123, &[0u8; 8]).unwrap();
239
240        assert!(filter.matches(&msg_ext));
241        assert!(!filter.matches(&msg_std)); // Frame type mismatch
242    }
243
244    #[test]
245    fn test_try_new_invalid() {
246        let result = IdFilter::try_new(0x800);
247        assert!(result.is_err());
248    }
249
250    #[test]
251    fn test_priority() {
252        let exact = IdFilter::new(0x123);
253        let masked = IdFilter::with_mask(0x120, 0x7F0);
254
255        assert!(exact.priority() > masked.priority());
256    }
257}