1use std::time::{SystemTime, UNIX_EPOCH};
34
35use crate::protocol::{
36 EEG_EVENT_ID, EEG_SCALING_FACTOR, NUM_EEG_CHANNELS, PACKET_SIZE, SYNC_BYTE,
37};
38use crate::types::{ChecksumStats, EegPacket, Mw75Event};
39
40fn now_secs() -> f64 {
44 SystemTime::now()
45 .duration_since(UNIX_EPOCH)
46 .expect("system clock is before Unix epoch")
47 .as_secs_f64()
48}
49
50pub fn validate_checksum(packet: &[u8]) -> (bool, u16, u16) {
78 if packet.len() < PACKET_SIZE {
79 return (false, 0, 0);
80 }
81 let calculated: u16 = packet[..61].iter().map(|&b| b as u16).sum::<u16>() & 0xFFFF;
82 let received: u16 = packet[61] as u16 | ((packet[62] as u16) << 8);
83 (calculated == received, calculated, received)
84}
85
86pub fn parse_eeg_packet(packet: &[u8]) -> Option<EegPacket> {
113 if packet.len() != PACKET_SIZE || packet[0] != SYNC_BYTE {
114 return None;
115 }
116
117 let (is_valid, _calc, _recv) = validate_checksum(packet);
118 if !is_valid {
119 return None;
120 }
121
122 let event_id = packet[1];
123 let counter = packet[3];
124 let timestamp = now_secs();
125
126 let ref_value = f32::from_le_bytes([packet[4], packet[5], packet[6], packet[7]]);
128 let drl = f32::from_le_bytes([packet[8], packet[9], packet[10], packet[11]]);
129
130 let mut channels = Vec::with_capacity(NUM_EEG_CHANNELS);
132 for ch in 0..NUM_EEG_CHANNELS {
133 let offset = 12 + ch * 4;
134 if offset + 4 <= packet.len() {
135 let raw = f32::from_le_bytes([
136 packet[offset],
137 packet[offset + 1],
138 packet[offset + 2],
139 packet[offset + 3],
140 ]);
141 channels.push(raw * EEG_SCALING_FACTOR);
142 }
143 }
144
145 let feature_status = if packet.len() > 60 { packet[60] } else { 0 };
146
147 Some(EegPacket {
148 timestamp,
149 event_id,
150 counter,
151 ref_value,
152 drl,
153 channels,
154 feature_status,
155 checksum_valid: true,
156 })
157}
158
159pub struct PacketProcessor {
190 buffer: Vec<u8>,
192 pub stats: ChecksumStats,
194 pub verbose: bool,
196}
197
198impl PacketProcessor {
199 pub fn new(verbose: bool) -> Self {
203 Self {
204 buffer: Vec::with_capacity(PACKET_SIZE * 10),
205 stats: ChecksumStats::default(),
206 verbose,
207 }
208 }
209
210 pub fn process_data(&mut self, data: &[u8]) -> Vec<Mw75Event> {
219 self.buffer.extend_from_slice(data);
220 let mut events = Vec::new();
221
222 let mut i = 0;
223 while i < self.buffer.len() {
224 if self.buffer[i] == SYNC_BYTE {
225 if i + PACKET_SIZE <= self.buffer.len() {
227 let packet = &self.buffer[i..i + PACKET_SIZE];
228
229 let (is_valid, calc, recv) = validate_checksum(packet);
231 self.stats.total_packets += 1;
232
233 if !is_valid {
234 self.stats.invalid_packets += 1;
235 if self.verbose {
236 log::warn!(
237 "Checksum mismatch: calc=0x{:04x} recv=0x{:04x} (event={}, counter={})",
238 calc, recv, packet[1], packet[3]
239 );
240 }
241 i += 1;
243 continue;
244 }
245
246 self.stats.valid_packets += 1;
247
248 if packet[1] == EEG_EVENT_ID {
249 if let Some(eeg) = parse_eeg_packet(packet) {
250 events.push(Mw75Event::Eeg(eeg));
251 }
252 } else {
253 events.push(Mw75Event::OtherEvent {
254 event_id: packet[1],
255 counter: packet[3],
256 raw: packet.to_vec(),
257 });
258 }
259
260 i += PACKET_SIZE;
261 } else {
262 break;
264 }
265 } else {
266 i += 1;
267 }
268 }
269
270 if i > 0 {
272 self.buffer.drain(..i);
273 }
274
275 let max_buf = PACKET_SIZE * 10;
277 if self.buffer.len() > max_buf {
278 if let Some(pos) = self.buffer.iter().rposition(|&b| b == SYNC_BYTE) {
280 self.buffer.drain(..pos);
281 log::debug!("Buffer overflow — recovered sync at {pos}");
282 } else {
283 self.buffer.clear();
284 log::warn!("Buffer overflow — no sync byte found, cleared");
285 }
286 }
287
288 events
289 }
290
291 pub fn buffered_len(&self) -> usize {
293 self.buffer.len()
294 }
295
296 pub fn get_stats(&self) -> ChecksumStats {
298 self.stats.clone()
299 }
300
301 pub fn reset(&mut self) {
303 self.buffer.clear();
304 self.stats = ChecksumStats::default();
305 }
306}
307
308#[cfg(test)]
309mod tests {
310 use super::*;
311
312 fn make_packet(event_id: u8, counter: u8) -> Vec<u8> {
316 let mut pkt = vec![0u8; PACKET_SIZE];
317 pkt[0] = SYNC_BYTE;
318 pkt[1] = event_id;
319 pkt[2] = 58; pkt[3] = counter;
321 fix_checksum(&mut pkt);
323 pkt
324 }
325
326 fn make_packet_with_channels(counter: u8, raw_values: &[f32; 12]) -> Vec<u8> {
328 let mut pkt = vec![0u8; PACKET_SIZE];
329 pkt[0] = SYNC_BYTE;
330 pkt[1] = EEG_EVENT_ID;
331 pkt[2] = 58;
332 pkt[3] = counter;
333 pkt[4..8].copy_from_slice(&42.0_f32.to_le_bytes());
335 pkt[8..12].copy_from_slice(&(-7.5_f32).to_le_bytes());
336 for (i, &val) in raw_values.iter().enumerate() {
338 let off = 12 + i * 4;
339 pkt[off..off + 4].copy_from_slice(&val.to_le_bytes());
340 }
341 pkt[60] = 0x01; fix_checksum(&mut pkt);
343 pkt
344 }
345
346 fn fix_checksum(pkt: &mut [u8]) {
348 let sum: u16 = pkt[..61].iter().map(|&b| b as u16).sum::<u16>() & 0xFFFF;
349 pkt[61] = (sum & 0xFF) as u8;
350 pkt[62] = (sum >> 8) as u8;
351 }
352
353 #[test]
356 fn checksum_valid_packet() {
357 let pkt = make_packet(EEG_EVENT_ID, 0);
358 let (valid, calc, recv) = validate_checksum(&pkt);
359 assert!(valid);
360 assert_eq!(calc, recv);
361 }
362
363 #[test]
364 fn checksum_invalid_corrupted_byte() {
365 let mut pkt = make_packet(EEG_EVENT_ID, 0);
366 pkt[62] ^= 0xFF; let (valid, _, _) = validate_checksum(&pkt);
368 assert!(!valid);
369 }
370
371 #[test]
372 fn checksum_invalid_corrupted_payload() {
373 let mut pkt = make_packet(EEG_EVENT_ID, 10);
374 pkt[30] = 0xFF; let (valid, _, _) = validate_checksum(&pkt);
376 assert!(!valid);
377 }
378
379 #[test]
380 fn checksum_too_short() {
381 let (valid, calc, recv) = validate_checksum(&[0xAA, 0x00]);
382 assert!(!valid);
383 assert_eq!(calc, 0);
384 assert_eq!(recv, 0);
385 }
386
387 #[test]
388 fn checksum_empty() {
389 let (valid, _, _) = validate_checksum(&[]);
390 assert!(!valid);
391 }
392
393 #[test]
394 fn checksum_exact_minimum_length() {
395 let pkt = make_packet(EEG_EVENT_ID, 0);
397 assert_eq!(pkt.len(), 63);
398 let (valid, _, _) = validate_checksum(&pkt);
399 assert!(valid);
400 }
401
402 #[test]
403 fn checksum_longer_than_packet_still_valid() {
404 let mut pkt = make_packet(EEG_EVENT_ID, 0);
406 pkt.extend_from_slice(&[0xFF, 0xFF, 0xFF]);
407 let (valid, _, _) = validate_checksum(&pkt);
408 assert!(valid);
409 }
410
411 #[test]
414 fn parse_basic_eeg_packet() {
415 let pkt = make_packet(EEG_EVENT_ID, 42);
416 let eeg = parse_eeg_packet(&pkt).expect("should parse");
417 assert_eq!(eeg.event_id, EEG_EVENT_ID);
418 assert_eq!(eeg.counter, 42);
419 assert_eq!(eeg.channels.len(), NUM_EEG_CHANNELS);
420 assert!(eeg.checksum_valid);
421 assert!(eeg.timestamp > 0.0);
422 }
423
424 #[test]
425 fn parse_rejects_wrong_sync_byte() {
426 let mut pkt = make_packet(EEG_EVENT_ID, 0);
427 pkt[0] = 0xBB; fix_checksum(&mut pkt);
429 assert!(parse_eeg_packet(&pkt).is_none());
430 }
431
432 #[test]
433 fn parse_rejects_short_packet() {
434 assert!(parse_eeg_packet(&[0xAA]).is_none());
435 assert!(parse_eeg_packet(&[0xAA; 62]).is_none());
436 }
437
438 #[test]
439 fn parse_rejects_invalid_checksum() {
440 let mut pkt = make_packet(EEG_EVENT_ID, 0);
441 pkt[61] = 0; pkt[62] = 0;
443 assert!(parse_eeg_packet(&pkt).is_none());
444 }
445
446 #[test]
447 fn parse_channel_values_scaled() {
448 let raw = [1000.0_f32; 12];
451 let pkt = make_packet_with_channels(0, &raw);
452 let eeg = parse_eeg_packet(&pkt).unwrap();
453 for &ch in &eeg.channels {
454 assert!((ch - 23.842).abs() < 0.01, "Expected ~23.842, got {ch}");
455 }
456 }
457
458 #[test]
459 fn parse_negative_channel_values() {
460 let raw = [-5000.0_f32; 12];
461 let pkt = make_packet_with_channels(0, &raw);
462 let eeg = parse_eeg_packet(&pkt).unwrap();
463 for &ch in &eeg.channels {
464 let expected = -5000.0 * EEG_SCALING_FACTOR;
465 assert!((ch - expected).abs() < 0.1, "Expected ~{expected}, got {ch}");
466 }
467 }
468
469 #[test]
470 fn parse_ref_and_drl() {
471 let raw = [0.0_f32; 12];
472 let pkt = make_packet_with_channels(0, &raw);
473 let eeg = parse_eeg_packet(&pkt).unwrap();
474 assert!((eeg.ref_value - 42.0).abs() < 0.001);
475 assert!((eeg.drl - (-7.5)).abs() < 0.001);
476 }
477
478 #[test]
479 fn parse_feature_status() {
480 let raw = [0.0_f32; 12];
481 let pkt = make_packet_with_channels(0, &raw);
482 let eeg = parse_eeg_packet(&pkt).unwrap();
483 assert_eq!(eeg.feature_status, 0x01);
484 }
485
486 #[test]
487 fn parse_all_counter_values() {
488 for c in 0..=255u8 {
489 let pkt = make_packet(EEG_EVENT_ID, c);
490 let eeg = parse_eeg_packet(&pkt).unwrap();
491 assert_eq!(eeg.counter, c);
492 }
493 }
494
495 #[test]
498 fn processor_basic_single_packet() {
499 let mut proc = PacketProcessor::new(false);
500 let pkt = make_packet(EEG_EVENT_ID, 1);
501 let events = proc.process_data(&pkt);
502 assert_eq!(events.len(), 1);
503 assert!(matches!(&events[0], Mw75Event::Eeg(e) if e.counter == 1));
504 assert_eq!(proc.stats.valid_packets, 1);
505 assert_eq!(proc.stats.total_packets, 1);
506 assert_eq!(proc.stats.invalid_packets, 0);
507 }
508
509 #[test]
510 fn processor_multiple_packets_in_one_call() {
511 let mut proc = PacketProcessor::new(false);
512 let mut data = Vec::new();
513 for i in 0..5 {
514 data.extend_from_slice(&make_packet(EEG_EVENT_ID, i));
515 }
516 let events = proc.process_data(&data);
517 assert_eq!(events.len(), 5);
518 assert_eq!(proc.stats.valid_packets, 5);
519 }
520
521 #[test]
522 fn processor_split_delivery_across_two_calls() {
523 let mut proc = PacketProcessor::new(false);
524 let pkt = make_packet(EEG_EVENT_ID, 1);
525
526 let events1 = proc.process_data(&pkt[..30]);
528 assert!(events1.is_empty());
529 assert_eq!(proc.buffered_len(), 30);
530
531 let events2 = proc.process_data(&pkt[30..]);
533 assert_eq!(events2.len(), 1);
534 assert_eq!(proc.buffered_len(), 0);
535 }
536
537 #[test]
538 fn processor_split_at_every_byte() {
539 let mut proc = PacketProcessor::new(false);
541 let pkt = make_packet(EEG_EVENT_ID, 99);
542
543 let mut total_events = 0;
544 for &byte in &pkt {
545 let events = proc.process_data(&[byte]);
546 total_events += events.len();
547 }
548 assert_eq!(total_events, 1);
549 }
550
551 #[test]
552 fn processor_garbage_prefix_skipped() {
553 let mut proc = PacketProcessor::new(false);
554 let pkt = make_packet(EEG_EVENT_ID, 5);
555
556 let mut data = vec![0x01, 0x02, 0x03, 0x04, 0x05];
558 data.extend_from_slice(&pkt);
559
560 let events = proc.process_data(&data);
561 assert_eq!(events.len(), 1);
562 assert!(matches!(&events[0], Mw75Event::Eeg(e) if e.counter == 5));
563 }
564
565 #[test]
566 fn processor_garbage_between_packets() {
567 let mut proc = PacketProcessor::new(false);
568 let mut data = Vec::new();
569 data.extend_from_slice(&make_packet(EEG_EVENT_ID, 1));
570 data.extend_from_slice(&[0x01, 0x02, 0x03]); data.extend_from_slice(&make_packet(EEG_EVENT_ID, 2));
572
573 let events = proc.process_data(&data);
574 assert_eq!(events.len(), 2);
575 }
576
577 #[test]
578 fn processor_other_event_type() {
579 let mut proc = PacketProcessor::new(false);
580 let pkt = make_packet(100, 7); let events = proc.process_data(&pkt);
582 assert_eq!(events.len(), 1);
583 assert!(matches!(
584 &events[0],
585 Mw75Event::OtherEvent { event_id: 100, counter: 7, .. }
586 ));
587 }
588
589 #[test]
590 fn processor_invalid_checksum_skips_and_counts() {
591 let mut proc = PacketProcessor::new(false);
592 let mut pkt = make_packet(EEG_EVENT_ID, 1);
593 pkt[30] = 0xFF; let events = proc.process_data(&pkt);
596 assert!(events.is_empty());
597 assert!(proc.stats.invalid_packets > 0);
598 }
599
600 #[test]
601 fn processor_invalid_then_valid() {
602 let mut proc = PacketProcessor::new(false);
603
604 let mut bad = make_packet(EEG_EVENT_ID, 1);
606 bad[30] = 0xFF;
607
608 let good = make_packet(EEG_EVENT_ID, 2);
610
611 let mut data = Vec::new();
612 data.extend_from_slice(&bad);
613 data.extend_from_slice(&good);
614
615 let events = proc.process_data(&data);
616 assert!(!events.is_empty());
618 assert!(proc.stats.valid_packets >= 1);
619 }
620
621 #[test]
622 fn processor_sync_byte_in_payload() {
623 let mut pkt = vec![0u8; PACKET_SIZE];
625 pkt[0] = SYNC_BYTE;
626 pkt[1] = EEG_EVENT_ID;
627 pkt[2] = 58;
628 pkt[3] = 10;
629 pkt[15] = 0xAA;
631 pkt[20] = 0xAA;
632 pkt[40] = 0xAA;
633 fix_checksum(&mut pkt);
634
635 let mut proc = PacketProcessor::new(false);
636 let events = proc.process_data(&pkt);
637 assert_eq!(events.len(), 1);
638 assert!(matches!(&events[0], Mw75Event::Eeg(e) if e.counter == 10));
639 }
640
641 #[test]
642 fn processor_reset_clears_state() {
643 let mut proc = PacketProcessor::new(false);
644 let pkt = make_packet(EEG_EVENT_ID, 1);
645 proc.process_data(&pkt);
646 assert_eq!(proc.stats.valid_packets, 1);
647
648 proc.reset();
649 assert_eq!(proc.stats.valid_packets, 0);
650 assert_eq!(proc.stats.total_packets, 0);
651 assert_eq!(proc.buffered_len(), 0);
652 }
653
654 #[test]
655 fn processor_partial_packet_retained() {
656 let mut proc = PacketProcessor::new(false);
657 let pkt = make_packet(EEG_EVENT_ID, 1);
658
659 let events = proc.process_data(&pkt[..40]);
661 assert!(events.is_empty());
662 assert_eq!(proc.buffered_len(), 40);
663
664 let events = proc.process_data(&pkt[40..]);
666 assert_eq!(events.len(), 1);
667 assert_eq!(proc.buffered_len(), 0);
668 }
669
670 #[test]
671 fn processor_buffer_overflow_protection() {
672 let mut proc = PacketProcessor::new(false);
673 let garbage = vec![0x01; PACKET_SIZE * 15];
675 let events = proc.process_data(&garbage);
676 assert!(events.is_empty());
677 assert!(proc.buffered_len() < PACKET_SIZE * 11);
679 }
680
681 #[test]
682 fn processor_stats_error_rate() {
683 let mut proc = PacketProcessor::new(false);
684
685 for i in 0..3 {
687 let pkt = make_packet(EEG_EVENT_ID, i);
688 proc.process_data(&pkt);
689 }
690
691 assert_eq!(proc.stats.valid_packets, 3);
692 assert_eq!(proc.stats.total_packets, 3);
693 assert!((proc.stats.error_rate() - 0.0).abs() < f64::EPSILON);
694 }
695
696 #[test]
697 fn processor_two_packets_in_64_byte_chunk() {
698 let mut proc = PacketProcessor::new(false);
701
702 let pkt1 = make_packet(EEG_EVENT_ID, 1);
703 let pkt2 = make_packet(EEG_EVENT_ID, 2);
704
705 let mut combined = Vec::new();
707 combined.extend_from_slice(&pkt1);
708 combined.extend_from_slice(&pkt2);
709
710 let events1 = proc.process_data(&combined[..64]);
712 assert_eq!(events1.len(), 1);
714
715 let events2 = proc.process_data(&combined[64..]);
716 assert_eq!(events2.len(), 1);
717 }
718
719 #[test]
720 fn processor_empty_input() {
721 let mut proc = PacketProcessor::new(false);
722 let events = proc.process_data(&[]);
723 assert!(events.is_empty());
724 assert_eq!(proc.buffered_len(), 0);
725 }
726
727 #[test]
728 fn processor_verbose_mode() {
729 let mut proc = PacketProcessor::new(true);
730 assert!(proc.verbose);
731 let mut bad = make_packet(EEG_EVENT_ID, 0);
733 bad[50] = 0xFF;
734 proc.process_data(&bad);
735 assert!(proc.stats.invalid_packets > 0);
736 }
737
738 #[test]
741 fn stats_default() {
742 let stats = ChecksumStats::default();
743 assert_eq!(stats.valid_packets, 0);
744 assert_eq!(stats.invalid_packets, 0);
745 assert_eq!(stats.total_packets, 0);
746 assert_eq!(stats.error_rate(), 0.0);
747 }
748
749 #[test]
750 fn stats_error_rate_calculation() {
751 let stats = ChecksumStats {
752 valid_packets: 90,
753 invalid_packets: 10,
754 total_packets: 100,
755 };
756 assert!((stats.error_rate() - 10.0).abs() < 0.01);
757 }
758
759 #[test]
760 fn stats_error_rate_zero_packets() {
761 let stats = ChecksumStats::default();
762 assert_eq!(stats.error_rate(), 0.0); }
764
765 #[test]
766 fn stats_error_rate_all_invalid() {
767 let stats = ChecksumStats {
768 valid_packets: 0,
769 invalid_packets: 50,
770 total_packets: 50,
771 };
772 assert!((stats.error_rate() - 100.0).abs() < 0.01);
773 }
774}