1use crate::dto::CanFrameDto;
6use crate::state::AppState;
7use serde::{Deserialize, Serialize};
8use std::collections::{HashMap, HashSet};
9use std::sync::Arc;
10
11#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
17#[serde(rename_all = "lowercase")]
18pub enum MatchStatus {
19 #[default]
20 All,
21 Matched,
22 Unmatched,
23}
24
25#[derive(Debug, Clone, Serialize, Deserialize, Default)]
27#[serde(rename_all = "camelCase")]
28pub struct FilterConfig {
29 pub time_min: Option<f64>,
31 pub time_max: Option<f64>,
33 #[serde(default)]
35 pub can_ids: Vec<u32>,
36 #[serde(default)]
38 pub messages: Vec<String>,
39 #[serde(default)]
41 pub signals: Vec<String>,
42 pub data_pattern: Option<String>,
44 pub channel: Option<String>,
46 #[serde(default)]
48 pub match_status: MatchStatus,
49}
50
51#[derive(Debug, Clone, Serialize, Deserialize)]
53#[serde(rename_all = "camelCase")]
54pub struct FilterResult {
55 pub frames: Vec<CanFrameDto>,
57 pub total_count: usize,
59 pub filtered_count: usize,
61}
62
63#[derive(Debug, Clone, Serialize, Deserialize)]
65#[serde(rename_all = "camelCase")]
66pub struct FrameStats {
67 pub unique_messages: usize,
69 pub frame_rate: f64,
71 pub avg_delta_ms: f64,
73 pub bus_load: f64,
75}
76
77#[derive(Debug, Clone, Serialize, Deserialize)]
79#[serde(rename_all = "camelCase")]
80pub struct MessageCount {
81 pub can_id: u32,
82 pub is_extended: bool,
83 pub count: u64,
84}
85
86#[derive(Debug, Clone, Serialize, Deserialize)]
88#[serde(rename_all = "camelCase")]
89pub struct DlcDetectionResult {
90 pub detected_dlc: u8,
91 pub confidence: f64,
92 pub sample_count: usize,
93}
94
95pub fn parse_data_pattern(pattern: &str) -> Vec<Option<u8>> {
102 pattern
103 .split_whitespace()
104 .map(|s| {
105 let s = s.to_uppercase();
106 if s == "??" || s == "XX" {
107 None } else {
109 u8::from_str_radix(&s, 16).ok()
110 }
111 })
112 .collect()
113}
114
115pub fn match_data_pattern(data: &[u8], pattern: &[Option<u8>]) -> bool {
117 if pattern.len() > data.len() {
118 return false;
119 }
120
121 for (i, expected) in pattern.iter().enumerate() {
122 if let Some(expected_byte) = expected {
123 if data[i] != *expected_byte {
124 return false;
125 }
126 }
127 }
129 true
130}
131
132pub struct DbcMessageInfo {
139 pub name: String,
140 pub signal_names: Vec<String>,
141}
142
143pub type DbcMessageCache = HashMap<(u32, bool), DbcMessageInfo>;
145
146pub fn build_message_cache_from_dbc(dbc: &dbc_rs::Dbc) -> DbcMessageCache {
149 let mut cache = HashMap::new();
150
151 for msg in dbc.messages().iter() {
152 let is_extended = msg.id() > 0x7FF;
153 let signal_names: Vec<String> = msg
154 .signals()
155 .iter()
156 .map(|s| s.name().to_lowercase())
157 .collect();
158
159 cache.insert(
160 (msg.id(), is_extended),
161 DbcMessageInfo {
162 name: msg.name().to_lowercase(),
163 signal_names,
164 },
165 );
166 }
167
168 cache
169}
170
171fn build_message_cache(state: &AppState) -> DbcMessageCache {
173 let dbc_guard = state.dbc.lock();
174 match *dbc_guard {
175 Some(ref dbc) => build_message_cache_from_dbc(dbc),
176 None => HashMap::new(),
177 }
178}
179
180pub fn filter_frames_with_cache(
187 frames: Vec<CanFrameDto>,
188 filters: &FilterConfig,
189 msg_cache: &DbcMessageCache,
190) -> FilterResult {
191 let total_count = frames.len();
192
193 let can_id_set: HashSet<u32> = filters.can_ids.iter().copied().collect();
195 let has_can_id_filter = !can_id_set.is_empty();
196
197 let data_pattern = filters.data_pattern.as_ref().map(|p| parse_data_pattern(p));
199 let has_data_pattern = data_pattern.as_ref().is_some_and(|p| !p.is_empty());
200
201 let message_filters: Vec<String> = filters.messages.iter().map(|s| s.to_lowercase()).collect();
203 let has_message_filter = !message_filters.is_empty();
204
205 let signal_filters: Vec<String> = filters.signals.iter().map(|s| s.to_lowercase()).collect();
206 let has_signal_filter = !signal_filters.is_empty();
207
208 let needs_dbc =
210 filters.match_status != MatchStatus::All || has_message_filter || has_signal_filter;
211
212 let filtered: Vec<CanFrameDto> = frames
214 .into_iter()
215 .filter(|frame| {
216 if let Some(min) = filters.time_min {
218 if frame.timestamp < min {
219 return false;
220 }
221 }
222 if let Some(max) = filters.time_max {
223 if frame.timestamp > max {
224 return false;
225 }
226 }
227
228 if has_can_id_filter && !can_id_set.contains(&frame.can_id) {
230 return false;
231 }
232
233 if let Some(ref ch) = filters.channel {
235 if frame.channel != *ch {
236 return false;
237 }
238 }
239
240 if has_data_pattern {
242 if let Some(ref pattern) = data_pattern {
243 if !match_data_pattern(&frame.data, pattern) {
244 return false;
245 }
246 }
247 }
248
249 if needs_dbc {
251 let key = (frame.can_id, frame.is_extended);
252 let msg_info = msg_cache.get(&key);
253 let has_match = msg_info.is_some();
254
255 match filters.match_status {
257 MatchStatus::All => {}
258 MatchStatus::Matched => {
259 if !has_match {
260 return false;
261 }
262 }
263 MatchStatus::Unmatched => {
264 if has_match {
265 return false;
266 }
267 }
268 }
269
270 if has_message_filter {
272 let Some(info) = msg_info else {
273 return false;
274 };
275 if !message_filters.iter().any(|m| info.name.contains(m)) {
276 return false;
277 }
278 }
279
280 if has_signal_filter {
282 let Some(info) = msg_info else {
283 return false;
284 };
285 if !signal_filters
286 .iter()
287 .any(|s| info.signal_names.iter().any(|sn| sn.contains(s)))
288 {
289 return false;
290 }
291 }
292 }
293
294 true
295 })
296 .collect();
297
298 let filtered_count = filtered.len();
299
300 FilterResult {
301 frames: filtered,
302 total_count,
303 filtered_count,
304 }
305}
306
307#[tauri::command]
314pub fn filter_frames(
315 frames: Vec<CanFrameDto>,
316 filters: FilterConfig,
317 state: tauri::State<'_, Arc<AppState>>,
318) -> FilterResult {
319 let needs_dbc = filters.match_status != MatchStatus::All
321 || !filters.messages.is_empty()
322 || !filters.signals.is_empty();
323
324 let msg_cache = if needs_dbc {
325 build_message_cache(&state)
326 } else {
327 HashMap::new()
328 };
329
330 filter_frames_with_cache(frames, &filters, &msg_cache)
331}
332
333#[tauri::command]
336pub fn calculate_frame_stats(frames: Vec<CanFrameDto>) -> FrameStats {
337 if frames.is_empty() {
338 return FrameStats {
339 unique_messages: 0,
340 frame_rate: 0.0,
341 avg_delta_ms: 0.0,
342 bus_load: 0.0,
343 };
344 }
345
346 let unique_ids: HashSet<u32> = frames.iter().map(|f| f.can_id).collect();
348 let unique_messages = unique_ids.len();
349
350 let recent_count = frames.len().min(100);
352 let recent_frames = &frames[frames.len() - recent_count..];
353
354 let mut frame_rate = 0.0;
355 let mut avg_delta_ms = 0.0;
356
357 if recent_frames.len() >= 2 {
358 let first_ts = recent_frames[0].timestamp;
359 let last_ts = recent_frames[recent_frames.len() - 1].timestamp;
360 let duration = last_ts - first_ts;
361
362 if duration > 0.0 {
363 frame_rate = (recent_frames.len() - 1) as f64 / duration;
364 avg_delta_ms = (duration / (recent_frames.len() - 1) as f64) * 1000.0;
365 }
366 }
367
368 let max_frames_per_sec = 5000.0;
372 let bus_load = (frame_rate / max_frames_per_sec * 100.0).min(100.0);
373
374 FrameStats {
375 unique_messages,
376 frame_rate,
377 avg_delta_ms,
378 bus_load,
379 }
380}
381
382#[tauri::command]
385pub fn get_message_counts(frames: Vec<CanFrameDto>) -> Vec<MessageCount> {
386 let mut counts: HashMap<(u32, bool), u64> = HashMap::new();
387
388 for frame in &frames {
389 *counts.entry((frame.can_id, frame.is_extended)).or_default() += 1;
390 }
391
392 let mut result: Vec<MessageCount> = counts
393 .into_iter()
394 .map(|((can_id, is_extended), count)| MessageCount {
395 can_id,
396 is_extended,
397 count,
398 })
399 .collect();
400
401 result.sort_by(|a, b| b.count.cmp(&a.count));
403
404 result
405}
406
407#[tauri::command]
410pub fn detect_dlc(frames: Vec<CanFrameDto>, can_id: u32, is_extended: bool) -> DlcDetectionResult {
411 let matching: Vec<&CanFrameDto> = frames
413 .iter()
414 .filter(|f| f.can_id == can_id && f.is_extended == is_extended)
415 .collect();
416
417 if matching.is_empty() {
418 return DlcDetectionResult {
419 detected_dlc: 8,
420 confidence: 0.0,
421 sample_count: 0,
422 };
423 }
424
425 let mut dlc_counts: HashMap<u8, usize> = HashMap::new();
427 for frame in &matching {
428 *dlc_counts.entry(frame.dlc).or_default() += 1;
429 }
430
431 let (detected_dlc, max_count) = dlc_counts
433 .iter()
434 .max_by_key(|(_, count)| *count)
435 .map(|(dlc, count)| (*dlc, *count))
436 .unwrap_or((8, 0));
437
438 let confidence = if matching.is_empty() {
439 0.0
440 } else {
441 max_count as f64 / matching.len() as f64
442 };
443
444 DlcDetectionResult {
445 detected_dlc,
446 confidence,
447 sample_count: matching.len(),
448 }
449}
450
451#[cfg(test)]
456mod tests {
457 use super::*;
458
459 fn make_frame(id: u32, timestamp: f64, data: Vec<u8>) -> CanFrameDto {
460 CanFrameDto {
461 timestamp,
462 channel: "vcan0".to_string(),
463 can_id: id,
464 is_extended: false,
465 is_fd: false,
466 brs: false,
467 esi: false,
468 dlc: data.len() as u8,
469 data,
470 }
471 }
472
473 #[test]
474 fn test_parse_data_pattern() {
475 let pattern = parse_data_pattern("01 ?? FF");
476 assert_eq!(pattern.len(), 3);
477 assert_eq!(pattern[0], Some(0x01));
478 assert_eq!(pattern[1], None); assert_eq!(pattern[2], Some(0xFF));
480 }
481
482 #[test]
483 fn test_match_data_pattern() {
484 let pattern = parse_data_pattern("01 ?? FF");
485 assert!(match_data_pattern(&[0x01, 0x00, 0xFF], &pattern));
486 assert!(match_data_pattern(&[0x01, 0xAB, 0xFF], &pattern));
487 assert!(!match_data_pattern(&[0x01, 0xAB, 0xFE], &pattern));
488 assert!(!match_data_pattern(&[0x02, 0xAB, 0xFF], &pattern));
489 }
490
491 #[test]
492 fn test_calculate_frame_stats_empty() {
493 let stats = calculate_frame_stats(vec![]);
494 assert_eq!(stats.unique_messages, 0);
495 assert_eq!(stats.frame_rate, 0.0);
496 }
497
498 #[test]
499 fn test_calculate_frame_stats() {
500 let frames = vec![
501 make_frame(0x100, 0.0, vec![0, 1, 2, 3, 4, 5, 6, 7]),
502 make_frame(0x100, 0.001, vec![0, 1, 2, 3, 4, 5, 6, 7]),
503 make_frame(0x200, 0.002, vec![0, 1, 2, 3, 4, 5, 6, 7]),
504 ];
505 let stats = calculate_frame_stats(frames);
506 assert_eq!(stats.unique_messages, 2);
507 assert!(stats.frame_rate > 0.0);
508 }
509
510 #[test]
511 fn test_get_message_counts() {
512 let frames = vec![
513 make_frame(0x100, 0.0, vec![]),
514 make_frame(0x100, 0.001, vec![]),
515 make_frame(0x200, 0.002, vec![]),
516 ];
517 let counts = get_message_counts(frames);
518 assert_eq!(counts.len(), 2);
519 assert_eq!(counts[0].can_id, 0x100); assert_eq!(counts[0].count, 2);
521 assert_eq!(counts[1].can_id, 0x200);
522 assert_eq!(counts[1].count, 1);
523 }
524
525 #[test]
526 fn test_detect_dlc() {
527 let frames = vec![
528 make_frame(0x100, 0.0, vec![0; 8]),
529 make_frame(0x100, 0.001, vec![0; 8]),
530 make_frame(0x100, 0.002, vec![0; 4]),
531 make_frame(0x200, 0.003, vec![0; 2]),
532 ];
533 let result = detect_dlc(frames, 0x100, false);
534 assert_eq!(result.detected_dlc, 8);
535 assert!(result.confidence > 0.6); assert_eq!(result.sample_count, 3);
537 }
538}