1use std::fmt;
2use std::io::Read;
3
4use memchr::memmem;
5use tracing::{debug, info, trace, warn};
6
7use crate::finders::BOUNDARY;
8use crate::types::{
9 Direction, Frame, ParseStats, SkipReason, SkipTracking, Timestamp, Transport, UnparsedRegion,
10};
11
12const RECV_PREFIX: &[u8] = b"recv ";
13const SENT_PREFIX: &[u8] = b"sent ";
14const MAX_PARTIAL_FRAME: usize = 65537;
17
18#[derive(Debug)]
23pub enum ParseError {
24 InvalidHeader(String),
26 InvalidMessage(String),
28 TransportNoise {
31 bytes: usize,
33 transport: Transport,
35 address: String,
37 },
38 Io(std::io::Error),
40}
41
42impl fmt::Display for ParseError {
43 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
44 match self {
45 ParseError::InvalidHeader(msg) => write!(f, "invalid frame header: {msg}"),
46 ParseError::InvalidMessage(msg) => write!(f, "invalid SIP message: {msg}"),
47 ParseError::TransportNoise {
48 bytes,
49 transport,
50 address,
51 } => write!(
52 f,
53 "transport noise: {bytes} bytes of non-SIP data from {transport}/{address}"
54 ),
55 ParseError::Io(e) => write!(f, "I/O error: {e}"),
56 }
57 }
58}
59
60impl std::error::Error for ParseError {
61 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
62 match self {
63 ParseError::Io(e) => Some(e),
64 _ => None,
65 }
66 }
67}
68
69impl From<std::io::Error> for ParseError {
70 fn from(e: std::io::Error) -> Self {
71 ParseError::Io(e)
72 }
73}
74
75fn digit(b: u8) -> Option<u8> {
76 match b {
77 b'0'..=b'9' => Some(b - b'0'),
78 _ => None,
79 }
80}
81
82fn parse_digits<const MAX_DIGITS: usize, T: TryFrom<u64>>(bytes: &[u8]) -> Option<T> {
85 if bytes.is_empty() || bytes.len() > MAX_DIGITS {
86 return None;
87 }
88 let mut val: u64 = 0;
89 for &b in bytes {
90 val = val.checked_mul(10)?.checked_add(u64::from(digit(b)?))?;
91 }
92 T::try_from(val).ok()
93}
94
95fn parse_timestamp(bytes: &[u8]) -> Option<Timestamp> {
97 if bytes.len() >= 26 && bytes[4] == b'-' && bytes[7] == b'-' && bytes[10] == b' ' {
99 let year = parse_digits::<5, u16>(&bytes[0..4])?;
100 let month = parse_digits::<3, u8>(&bytes[5..7])?;
101 let day = parse_digits::<3, u8>(&bytes[8..10])?;
102 let ts = parse_time_part(&bytes[11..])?;
103 return Some(Timestamp::DateTime {
104 year,
105 month,
106 day,
107 hour: ts.0,
108 min: ts.1,
109 sec: ts.2,
110 usec: ts.3,
111 });
112 }
113 let (hour, min, sec, usec) = parse_time_part(bytes)?;
115 Some(Timestamp::TimeOnly {
116 hour,
117 min,
118 sec,
119 usec,
120 })
121}
122
123fn parse_time_part(bytes: &[u8]) -> Option<(u8, u8, u8, u32)> {
125 if bytes.len() < 15 {
126 return None;
127 }
128 if bytes[2] != b':' || bytes[5] != b':' || bytes[8] != b'.' {
129 return None;
130 }
131 let hour = parse_digits::<3, u8>(&bytes[0..2])?;
132 let min = parse_digits::<3, u8>(&bytes[3..5])?;
133 let sec = parse_digits::<3, u8>(&bytes[6..8])?;
134 let usec = parse_digits::<10, u32>(&bytes[9..15])?;
135 Some((hour, min, sec, usec))
136}
137
138#[derive(Debug)]
140pub struct FrameHeader {
141 pub direction: Direction,
143 pub byte_count: usize,
145 pub transport: Transport,
147 pub address: String,
149 pub timestamp: Timestamp,
151 pub header_len: usize,
153}
154
155pub fn parse_frame_header(data: &[u8]) -> Result<FrameHeader, ParseError> {
160 let newline_pos = memchr::memchr(b'\n', data)
161 .ok_or_else(|| ParseError::InvalidHeader("no newline in header".into()))?;
162 let line = &data[..newline_pos];
163 let line = line.strip_suffix(b"\r").unwrap_or(line);
165 let line = line
167 .strip_suffix(b":")
168 .ok_or_else(|| ParseError::InvalidHeader("header does not end with ':'".into()))?;
169
170 let direction = if line.starts_with(RECV_PREFIX) {
172 Direction::Recv
173 } else if line.starts_with(SENT_PREFIX) {
174 Direction::Sent
175 } else {
176 return Err(ParseError::InvalidHeader(
177 "expected 'recv' or 'sent'".into(),
178 ));
179 };
180 let mut pos = 5;
181
182 let space = memchr::memchr(b' ', &line[pos..])
184 .ok_or_else(|| ParseError::InvalidHeader("no space after byte count".into()))?;
185 let byte_count = parse_digits::<10, usize>(&line[pos..pos + space])
186 .ok_or_else(|| ParseError::InvalidHeader("invalid byte count".into()))?;
187 pos += space + 1;
188
189 let expected_recv = b"bytes from ";
191 let expected_sent = b"bytes to ";
192 if direction == Direction::Recv {
193 if !line[pos..].starts_with(expected_recv) {
194 return Err(ParseError::InvalidHeader("expected 'bytes from '".into()));
195 }
196 pos += expected_recv.len();
197 } else {
198 if !line[pos..].starts_with(expected_sent) {
199 return Err(ParseError::InvalidHeader("expected 'bytes to '".into()));
200 }
201 pos += expected_sent.len();
202 }
203
204 let transport = if line[pos..].starts_with(b"tcp/") {
206 pos += 4;
207 Transport::Tcp
208 } else if line[pos..].starts_with(b"udp/") {
209 pos += 4;
210 Transport::Udp
211 } else if line[pos..].starts_with(b"tls/") {
212 pos += 4;
213 Transport::Tls
214 } else if line[pos..].starts_with(b"wss/") {
215 pos += 4;
216 Transport::Wss
217 } else {
218 return Err(ParseError::InvalidHeader("unknown transport".into()));
219 };
220
221 let at_marker = b" at ";
223 let at_pos = memmem::find(&line[pos..], at_marker)
224 .ok_or_else(|| ParseError::InvalidHeader("no ' at ' in header".into()))?;
225 let address = String::from_utf8_lossy(&line[pos..pos + at_pos]).into_owned();
226 pos += at_pos + at_marker.len();
227
228 let timestamp = parse_timestamp(&line[pos..])
230 .ok_or_else(|| ParseError::InvalidHeader("invalid timestamp".into()))?;
231
232 Ok(FrameHeader {
233 direction,
234 byte_count,
235 transport,
236 address,
237 timestamp,
238 header_len: newline_pos + 1,
239 })
240}
241
242enum HeaderParse {
243 Ok(FrameHeader),
244 NeedMore,
245 Invalid(ParseError),
246}
247
248enum SyncStep {
249 Ready,
250 End,
251 Failed(ParseError),
252}
253
254enum HeaderStep {
255 Got(FrameHeader),
256 Restart,
257 End,
258 Failed(ParseError),
259}
260
261fn classify_header(data: &[u8]) -> HeaderParse {
263 match parse_frame_header(data) {
264 Ok(header) => HeaderParse::Ok(header),
265 Err(e) => {
266 if memchr::memchr(b'\n', data).is_none() {
267 HeaderParse::NeedMore
268 } else {
269 HeaderParse::Invalid(e)
270 }
271 }
272 }
273}
274
275pub fn is_frame_header(data: &[u8]) -> bool {
278 if data.len() < 20 {
279 return false;
280 }
281 let starts_valid = data.starts_with(RECV_PREFIX) || data.starts_with(SENT_PREFIX);
282 if !starts_valid {
283 return false;
284 }
285 let rest = &data[5..];
287 let space = match memchr::memchr(b' ', rest) {
288 Some(p) => p,
289 None => return false,
290 };
291 if space == 0 || space > 10 {
292 return false;
293 }
294 for &b in &rest[..space] {
295 if !b.is_ascii_digit() {
296 return false;
297 }
298 }
299 rest[space..].starts_with(b" bytes ")
300}
301
302const READ_BUF_SIZE: usize = 32 * 1024;
303
304pub struct FrameIterator<R> {
324 reader: R,
325 buf: Vec<u8>,
326 eof: bool,
327 frame_count: u64,
328 offset: u64,
329 stats: ParseStats,
330 skip_tracking: SkipTracking,
331}
332
333impl<R: Read> FrameIterator<R> {
334 pub fn new(reader: R) -> Self {
336 FrameIterator {
337 reader,
338 buf: Vec::with_capacity(READ_BUF_SIZE * 2),
339 eof: false,
340 frame_count: 0,
341 offset: 0,
342 stats: ParseStats::default(),
343 skip_tracking: SkipTracking::CountOnly,
344 }
345 }
346
347 pub fn capture_skipped(mut self, enable: bool) -> Self {
352 self.skip_tracking = if enable {
353 SkipTracking::CaptureData
354 } else {
355 SkipTracking::CountOnly
356 };
357 self
358 }
359
360 pub fn skip_tracking(mut self, tracking: SkipTracking) -> Self {
362 self.skip_tracking = tracking;
363 self
364 }
365
366 pub fn stats(&self) -> &ParseStats {
368 &self.stats
369 }
370
371 pub fn drain_unparsed(&mut self) -> Vec<UnparsedRegion> {
373 self.stats.drain_regions()
374 }
375
376 fn consume(&mut self, n: usize) {
377 self.buf.drain(..n);
378 self.offset += n as u64;
379 }
380
381 fn consume_skipped(&mut self, n: usize, reason: SkipReason) {
382 if self.skip_tracking != SkipTracking::CountOnly {
383 let data = if self.skip_tracking == SkipTracking::CaptureData {
384 Some(self.buf[..n].to_vec())
385 } else {
386 None
387 };
388 self.stats.unparsed_regions.push(UnparsedRegion {
389 offset: self.offset,
390 length: n as u64,
391 reason,
392 data,
393 });
394 }
395 self.stats.bytes_skipped += n as u64;
396 self.consume(n);
397 }
398
399 fn fill_buf(&mut self) -> Result<bool, std::io::Error> {
400 if self.eof {
401 return Ok(false);
402 }
403 let old_len = self.buf.len();
404 self.buf.resize(old_len + READ_BUF_SIZE, 0);
405 let n = self.reader.read(&mut self.buf[old_len..])?;
406 self.buf.truncate(old_len + n);
407 if n == 0 {
408 self.eof = true;
409 return Ok(false);
410 }
411 self.stats.bytes_read += n as u64;
412 Ok(true)
413 }
414
415 fn is_replay(&self, skipped: &[u8]) -> bool {
418 if self.frame_count == 0 {
419 return false;
420 }
421 skipped.ends_with(b"\r\n\r\n\x0B\n")
422 }
423
424 fn find_boundary(&self, start: usize) -> Option<usize> {
426 let mut search_from = start;
427 loop {
428 let pos = BOUNDARY.find(&self.buf[search_from..])?;
429 let abs_pos = search_from + pos;
430 let after = abs_pos + 2;
431 if after >= self.buf.len() {
432 if self.eof {
435 return Some(abs_pos);
436 }
437 return None; }
439 if is_frame_header(&self.buf[after..]) {
440 return Some(abs_pos);
441 }
442 trace!(
444 offset = abs_pos,
445 "found \\x0B\\n in content (not a boundary), skipping"
446 );
447 search_from = abs_pos + 2;
448 }
449 }
450
451 fn skip_to_first_header(&mut self) -> Option<usize> {
453 if is_frame_header(&self.buf) {
454 return Some(0);
455 }
456 let mut search_from = 0;
458 loop {
459 let pos = BOUNDARY.find(&self.buf[search_from..])?;
460 let abs_pos = search_from + pos;
461 let after = abs_pos + 2;
462 if after < self.buf.len() && is_frame_header(&self.buf[after..]) {
463 info!(skipped_bytes = after, "skipped partial first frame");
464 return Some(after);
465 }
466 search_from = abs_pos + 2;
467 }
468 }
469
470 fn sync_to_first_header(&mut self) -> SyncStep {
472 loop {
473 match self.skip_to_first_header() {
474 Some(offset) => {
475 if offset > 0 {
476 let reason = if offset <= MAX_PARTIAL_FRAME {
477 SkipReason::PartialFirstFrame
478 } else {
479 SkipReason::OversizedFrame
480 };
481 self.consume_skipped(offset, reason);
482 }
483 return SyncStep::Ready;
484 }
485 None => {
486 if self.eof {
487 debug!("no valid frame header found in entire input");
488 let remaining = self.buf.len();
489 if remaining > 0 {
490 self.consume_skipped(remaining, SkipReason::InvalidHeader);
491 }
492 return SyncStep::End;
493 }
494 if let Err(e) = self.fill_buf() {
495 return SyncStep::Failed(ParseError::Io(e));
496 }
497 }
498 }
499 }
500 }
501
502 fn strip_padding(&mut self) {
504 let mut strip = 0;
505 while strip < self.buf.len() {
506 if self.buf[strip] == b'\n' {
507 strip += 1;
508 } else if strip + 1 < self.buf.len()
509 && self.buf[strip] == b'\r'
510 && self.buf[strip + 1] == b'\n'
511 {
512 strip += 2;
513 } else {
514 break;
515 }
516 }
517 if strip > 0 {
518 self.consume(strip);
519 }
520 }
521
522 fn read_header(&mut self) -> HeaderStep {
524 loop {
525 match classify_header(&self.buf) {
526 HeaderParse::Ok(h) => return HeaderStep::Got(h),
527 HeaderParse::NeedMore => {
528 if self.eof {
529 debug!("truncated frame header at EOF");
530 let remaining = self.buf.len();
531 if remaining > 0 {
532 self.consume_skipped(remaining, SkipReason::InvalidHeader);
533 }
534 return HeaderStep::End;
535 }
536 if let Err(e) = self.fill_buf() {
537 return HeaderStep::Failed(ParseError::Io(e));
538 }
539 }
540 HeaderParse::Invalid(e) => {
541 let header_preview: String = self
542 .buf
543 .iter()
544 .take(200)
545 .take_while(|&&b| b != b'\n')
546 .map(|&b| {
547 if b.is_ascii_graphic() || b == b' ' {
548 b as char
549 } else {
550 '.'
551 }
552 })
553 .collect();
554 if header_preview.starts_with("dump started at ") {
555 let skip = memchr::memchr(b'\n', &self.buf)
556 .map(|p| {
557 let mut end = p + 1;
558 while end < self.buf.len() && self.buf[end] == b'\n' {
559 end += 1;
560 }
561 end
562 })
563 .unwrap_or(self.buf.len());
564 debug!(
565 header = %header_preview,
566 skipped_bytes = skip,
567 "skipped dump restart marker",
568 );
569 self.consume(skip);
570 return HeaderStep::Restart;
571 }
572 let skip = if let Some(b) = self.find_boundary(0) {
573 b + 2
574 } else {
575 memchr::memchr(b'\n', &self.buf)
576 .map(|p| p + 1)
577 .unwrap_or(self.buf.len())
578 };
579 let reason = self.classify_skip(skip);
580 self.consume_skipped(skip, reason);
581 return HeaderStep::Failed(e);
582 }
583 }
584 }
585 }
586
587 fn classify_skip(&self, skip: usize) -> SkipReason {
589 if self.buf.starts_with(RECV_PREFIX) || self.buf.starts_with(SENT_PREFIX) {
590 SkipReason::InvalidHeader
591 } else if skip > MAX_PARTIAL_FRAME {
592 SkipReason::OversizedFrame
593 } else if self.frame_count == 0 {
594 SkipReason::PartialFirstFrame
595 } else if self.is_replay(&self.buf[..skip]) {
596 SkipReason::ReplayedFrame
597 } else {
598 SkipReason::MidStreamSkip
599 }
600 }
601
602 fn read_content(&mut self, header: FrameHeader) -> Option<Result<Frame, ParseError>> {
604 let FrameHeader {
605 direction,
606 byte_count,
607 transport,
608 address,
609 timestamp,
610 header_len,
611 } = header;
612 let content_start = header_len;
613 let expected_end = content_start + byte_count;
614
615 let content = loop {
620 let fill_to = (expected_end + 1).min(content_start + MAX_PARTIAL_FRAME);
624 while self.buf.len() <= fill_to && !self.eof {
625 if let Err(e) = self.fill_buf() {
626 return Some(Err(ParseError::Io(e)));
627 }
628 }
629
630 if expected_end < self.buf.len() && self.buf[expected_end] == 0x0B {
632 let has_newline =
633 expected_end + 1 < self.buf.len() && self.buf[expected_end + 1] == b'\n';
634 let at_eof = expected_end + 1 >= self.buf.len() && self.eof;
635
636 if has_newline || at_eof {
637 let content = self.buf[content_start..expected_end].to_vec();
638 let drain_to = if has_newline {
639 expected_end + 2
640 } else {
641 expected_end + 1
642 };
643 self.consume(drain_to);
644 self.frame_count += 1;
645 break content;
646 }
647 }
648
649 if let Some(boundary_pos) = self.find_boundary(content_start) {
651 let content = self.buf[content_start..boundary_pos].to_vec();
652 let drain_to = boundary_pos + 2;
653 self.consume(drain_to);
654 self.frame_count += 1;
655
656 if content.len() != byte_count {
657 debug!(
658 frame = self.frame_count,
659 expected = byte_count,
660 actual = content.len(),
661 "frame content size mismatch"
662 );
663 }
664
665 break content;
666 }
667
668 if self.eof {
669 let end = if self.buf.last() == Some(&0x0B) {
671 self.buf.len() - 1
672 } else {
673 self.buf.len()
674 };
675 let content = self.buf[content_start..end].to_vec();
676 let len = self.buf.len();
677 self.consume(len);
678 self.frame_count += 1;
679
680 if content.len() < byte_count {
681 let missing = byte_count - content.len();
682 warn!(
683 frame = self.frame_count,
684 expected = byte_count,
685 actual = content.len(),
686 missing,
687 "incomplete frame at EOF"
688 );
689 if self.skip_tracking != SkipTracking::CountOnly {
690 self.stats.unparsed_regions.push(UnparsedRegion {
691 offset: self.offset,
692 length: missing as u64,
693 reason: SkipReason::IncompleteFrame,
694 data: None,
695 });
696 }
697 } else if content.len() != byte_count {
698 debug!(
699 frame = self.frame_count,
700 expected = byte_count,
701 actual = content.len(),
702 "last frame content size mismatch"
703 );
704 }
705
706 break content;
707 }
708
709 if let Err(e) = self.fill_buf() {
710 return Some(Err(ParseError::Io(e)));
711 }
712 };
713
714 Some(Ok(Frame {
715 direction,
716 byte_count,
717 transport,
718 address,
719 timestamp,
720 content,
721 }))
722 }
723}
724
725impl<R: Read> Iterator for FrameIterator<R> {
726 type Item = Result<Frame, ParseError>;
727
728 fn next(&mut self) -> Option<Self::Item> {
729 let header = loop {
730 if self.buf.is_empty() && !self.eof {
731 if let Err(e) = self.fill_buf() {
732 return Some(Err(ParseError::Io(e)));
733 }
734 }
735 if self.buf.is_empty() {
736 return None;
737 }
738
739 if self.frame_count == 0 {
740 match self.sync_to_first_header() {
741 SyncStep::Ready => {}
742 SyncStep::End => return None,
743 SyncStep::Failed(e) => return Some(Err(e)),
744 }
745 }
746
747 self.strip_padding();
748 if self.buf.is_empty() {
749 continue;
750 }
751
752 match self.read_header() {
753 HeaderStep::Got(header) => break header,
754 HeaderStep::Restart => continue,
755 HeaderStep::End => return None,
756 HeaderStep::Failed(e) => return Some(Err(e)),
757 }
758 };
759
760 self.read_content(header)
761 }
762}
763
764#[cfg(test)]
765mod tests {
766 use super::*;
767 use crate::types::SkipTracking;
768
769 #[test]
770 fn parse_recv_ipv4_tcp() {
771 let header = b"recv 100 bytes from tcp/192.168.1.1:5060 at 00:00:01.350874:\n";
772 let h = parse_frame_header(header).unwrap();
773 assert_eq!(h.direction, Direction::Recv);
774 assert_eq!(h.byte_count, 100);
775 assert_eq!(h.transport, Transport::Tcp);
776 assert_eq!(h.address, "192.168.1.1:5060");
777 assert_eq!(
778 h.timestamp,
779 Timestamp::TimeOnly {
780 hour: 0,
781 min: 0,
782 sec: 1,
783 usec: 350874
784 }
785 );
786 assert_eq!(h.header_len, header.len());
787 }
788
789 #[test]
790 fn parse_recv_ipv6_tcp() {
791 let header = b"recv 1440 bytes from tcp/[2001:4958:10:14::4]:30046 at 13:03:21.674883:\n";
792 let h = parse_frame_header(header).unwrap();
793 assert_eq!(h.direction, Direction::Recv);
794 assert_eq!(h.byte_count, 1440);
795 assert_eq!(h.transport, Transport::Tcp);
796 assert_eq!(h.address, "[2001:4958:10:14::4]:30046");
797 assert_eq!(
798 h.timestamp,
799 Timestamp::TimeOnly {
800 hour: 13,
801 min: 3,
802 sec: 21,
803 usec: 674883
804 }
805 );
806 }
807
808 #[test]
809 fn parse_sent_ipv6_tcp() {
810 let header = b"sent 681 bytes to tcp/[2001:4958:10:14::4]:30046 at 13:03:21.675500:\n";
811 let h = parse_frame_header(header).unwrap();
812 assert_eq!(h.direction, Direction::Sent);
813 assert_eq!(h.byte_count, 681);
814 assert_eq!(h.transport, Transport::Tcp);
815 assert_eq!(h.address, "[2001:4958:10:14::4]:30046");
816 }
817
818 #[test]
819 fn parse_recv_udp() {
820 let header = b"recv 457 bytes from udp/10.0.0.1:5060 at 00:19:47.123456:\n";
821 let h = parse_frame_header(header).unwrap();
822 assert_eq!(h.direction, Direction::Recv);
823 assert_eq!(h.transport, Transport::Udp);
824 }
825
826 #[test]
827 fn parse_sent_tls() {
828 let header = b"sent 500 bytes to tls/10.0.0.1:5061 at 12:00:00.000000:\n";
829 let h = parse_frame_header(header).unwrap();
830 assert_eq!(h.direction, Direction::Sent);
831 assert_eq!(h.byte_count, 500);
832 assert_eq!(h.transport, Transport::Tls);
833 }
834
835 #[test]
836 fn parse_full_datetime_timestamp() {
837 let header = b"recv 100 bytes from tcp/192.168.1.1:5060 at 2026-02-01 10:00:00.000000:\n";
838 let h = parse_frame_header(header).unwrap();
839 assert_eq!(
840 h.timestamp,
841 Timestamp::DateTime {
842 year: 2026,
843 month: 2,
844 day: 1,
845 hour: 10,
846 min: 0,
847 sec: 0,
848 usec: 0
849 }
850 );
851 }
852
853 #[test]
854 fn parse_invalid_header() {
855 assert!(parse_frame_header(b"invalid header\n").is_err());
856 assert!(
857 parse_frame_header(b"recv abc bytes from tcp/1.1.1.1:5060 at 00:00:00.000000:\n")
858 .is_err()
859 );
860 }
861
862 #[test]
863 fn is_frame_header_valid() {
864 assert!(is_frame_header(
865 b"recv 100 bytes from tcp/1.1.1.1:5060 at 00:00:00.000000:\n"
866 ));
867 assert!(is_frame_header(
868 b"sent 681 bytes to tcp/[::1]:5060 at 00:00:00.000000:\n"
869 ));
870 assert!(!is_frame_header(b"not a header"));
871 assert!(!is_frame_header(b"recv abc bytes"));
872 assert!(!is_frame_header(b""));
873 }
874
875 #[test]
876 fn frame_iterator_single_frame() {
877 let data = b"recv 5 bytes from tcp/1.1.1.1:5060 at 00:00:00.000000:\nhello\x0B\n";
878 let frames: Vec<Frame> = FrameIterator::new(&data[..])
879 .collect::<Result<Vec<_>, _>>()
880 .unwrap();
881 assert_eq!(frames.len(), 1);
882 assert_eq!(frames[0].content, b"hello");
883 assert_eq!(frames[0].byte_count, 5);
884 }
885
886 #[test]
887 fn frame_iterator_multiple_frames() {
888 let data = b"recv 5 bytes from tcp/1.1.1.1:5060 at 00:00:00.000000:\nhello\x0B\nsent 5 bytes to tcp/1.1.1.1:5060 at 00:00:00.000001:\nworld\x0B\n";
889 let frames: Vec<Frame> = FrameIterator::new(&data[..])
890 .collect::<Result<Vec<_>, _>>()
891 .unwrap();
892 assert_eq!(frames.len(), 2);
893 assert_eq!(frames[0].content, b"hello");
894 assert_eq!(frames[0].direction, Direction::Recv);
895 assert_eq!(frames[1].content, b"world");
896 assert_eq!(frames[1].direction, Direction::Sent);
897 }
898
899 #[test]
900 fn frame_iterator_vt_in_content() {
901 let mut data = Vec::new();
903 data.extend_from_slice(b"recv 15 bytes from tcp/1.1.1.1:5060 at 00:00:00.000000:\n");
904 data.extend_from_slice(b"he\x0B\nllo world!!");
905 data.extend_from_slice(b"\x0B\n");
906 let frames: Vec<Frame> = FrameIterator::new(&data[..])
907 .collect::<Result<Vec<_>, _>>()
908 .unwrap();
909 assert_eq!(frames.len(), 1);
910 assert_eq!(frames[0].content, b"he\x0B\nllo world!!");
911 }
912
913 #[test]
914 fn frame_iterator_eof_without_boundary() {
915 let data = b"recv 5 bytes from tcp/1.1.1.1:5060 at 00:00:00.000000:\nhello";
916 let frames: Vec<Frame> = FrameIterator::new(&data[..])
917 .collect::<Result<Vec<_>, _>>()
918 .unwrap();
919 assert_eq!(frames.len(), 1);
920 assert_eq!(frames[0].content, b"hello");
921 }
922
923 #[test]
924 fn frame_iterator_eof_with_lone_vt() {
925 let data = b"recv 5 bytes from tcp/1.1.1.1:5060 at 00:00:00.000000:\nhello\x0B";
926 let frames: Vec<Frame> = FrameIterator::new(&data[..])
927 .collect::<Result<Vec<_>, _>>()
928 .unwrap();
929 assert_eq!(frames.len(), 1);
930 assert_eq!(frames[0].content, b"hello");
931 }
932
933 #[test]
934 fn frame_iterator_partial_first_frame() {
935 let mut data = Vec::new();
937 data.extend_from_slice(b"partial garbage data");
938 data.extend_from_slice(b"\x0B\n");
939 data.extend_from_slice(
940 b"recv 5 bytes from tcp/1.1.1.1:5060 at 00:00:00.000000:\nhello\x0B\n",
941 );
942 let frames: Vec<Frame> = FrameIterator::new(&data[..])
943 .collect::<Result<Vec<_>, _>>()
944 .unwrap();
945 assert_eq!(frames.len(), 1);
946 assert_eq!(frames[0].content, b"hello");
947 }
948
949 #[test]
950 fn frame_iterator_truncated_last_frame() {
951 let mut data = Vec::new();
953 data.extend_from_slice(
954 b"recv 5 bytes from tcp/1.1.1.1:5060 at 00:00:00.000000:\nhello\x0B\n",
955 );
956 data.extend_from_slice(b"sent 3 bytes to tcp/1.1.1.1:5060 at 00:00:01.000000:\nbye");
957 let frames: Vec<Frame> = FrameIterator::new(&data[..])
958 .collect::<Result<Vec<_>, _>>()
959 .unwrap();
960 assert_eq!(frames.len(), 2);
961 assert_eq!(frames[0].content, b"hello");
962 assert_eq!(frames[1].content, b"bye");
963 }
964
965 #[test]
966 fn frame_iterator_file_concatenation() {
967 let mut data = Vec::new();
971
972 data.extend_from_slice(
974 b"recv 5 bytes from tcp/1.1.1.1:5060 at 00:00:00.000000:\nhello\x0B\n",
975 );
976 data.extend_from_slice(
977 b"sent 5 bytes to tcp/1.1.1.1:5060 at 00:00:00.000001:\nworld\x0B\n",
978 );
979
980 data.extend_from_slice(b"some truncated SIP content from previous rotation\r\n\r\n");
982 data.extend_from_slice(b"\x0B\n");
983 data.extend_from_slice(
984 b"recv 3 bytes from tcp/2.2.2.2:5060 at 01:00:00.000000:\nfoo\x0B\n",
985 );
986
987 let items: Vec<Result<Frame, ParseError>> = FrameIterator::new(&data[..]).collect();
988 let frames: Vec<Frame> = items.into_iter().filter_map(Result::ok).collect();
989 assert_eq!(frames.len(), 3);
990 assert_eq!(frames[0].content, b"hello");
991 assert_eq!(frames[1].content, b"world");
992 assert_eq!(frames[2].content, b"foo");
993 assert_eq!(frames[2].address, "2.2.2.2:5060");
994 }
995
996 #[test]
997 fn frame_iterator_file_concatenation_mid_stream_garbage() {
998 let mut data = Vec::new();
1002
1003 data.extend_from_slice(
1005 b"recv 5 bytes from tcp/1.1.1.1:5060 at 00:00:00.000000:\nhello\x0B\n",
1006 );
1007
1008 data.extend_from_slice(b"Content-Type: application/sdp\r\n\r\nv=0\r\n");
1010 data.extend_from_slice(b"\x0B\n");
1011
1012 data.extend_from_slice(b"sent 3 bytes to tcp/3.3.3.3:5060 at 02:00:00.000000:\nbar\x0B\n");
1014
1015 let items: Vec<Result<Frame, ParseError>> = FrameIterator::new(&data[..]).collect();
1016 let frames: Vec<Frame> = items.into_iter().filter_map(Result::ok).collect();
1017 assert_eq!(frames.len(), 2);
1018 assert_eq!(frames[0].content, b"hello");
1019 assert_eq!(frames[1].content, b"bar");
1020 }
1021
1022 #[test]
1023 fn frame_iterator_empty_input() {
1024 let data: &[u8] = b"";
1025 let frames: Vec<Result<Frame, ParseError>> = FrameIterator::new(data).collect();
1026 assert!(frames.is_empty());
1027 }
1028
1029 #[test]
1030 fn frame_iterator_only_garbage() {
1031 let data = b"this is not a SIP trace dump at all, just garbage text";
1032 let mut iter = FrameIterator::new(&data[..]).skip_tracking(SkipTracking::TrackRegions);
1033 let frames: Vec<Result<Frame, ParseError>> = iter.by_ref().collect();
1034 assert!(frames.is_empty());
1035 let stats = iter.stats();
1036 assert_eq!(stats.bytes_read, data.len() as u64);
1037 assert_eq!(stats.bytes_skipped, data.len() as u64);
1038 assert_eq!(stats.unparsed_regions.len(), 1);
1039 assert_eq!(stats.unparsed_regions[0].reason, SkipReason::InvalidHeader);
1040 }
1041
1042 #[test]
1043 fn frame_iterator_truncated_header_at_eof() {
1044 let data = b"recv 5 bytes from tcp/1.1.1.1:5060";
1045 let mut iter = FrameIterator::new(&data[..]).skip_tracking(SkipTracking::TrackRegions);
1046 let frames: Vec<Result<Frame, ParseError>> = iter.by_ref().collect();
1047 assert!(frames.is_empty());
1048 let stats = iter.stats();
1049 assert_eq!(stats.bytes_read, data.len() as u64);
1050 assert_eq!(stats.bytes_skipped, data.len() as u64);
1051 }
1052
1053 #[test]
1054 fn frame_iterator_dump_marker_at_eof() {
1055 let mut data = Vec::new();
1058 data.extend_from_slice(
1059 b"recv 5 bytes from tcp/1.1.1.1:5060 at 00:00:00.000000:\nhello\x0B\n",
1060 );
1061 data.extend_from_slice(b"dump started at Thu Aug 22 11:38:11 2024\n\n\n");
1062
1063 let frames: Vec<Result<Frame, ParseError>> = FrameIterator::new(&data[..]).collect();
1064 assert_eq!(frames.len(), 1);
1065 assert!(frames[0].is_ok());
1066 assert_eq!(frames[0].as_ref().unwrap().content, b"hello");
1067 }
1068
1069 #[test]
1070 fn frame_iterator_dump_marker_mid_stream() {
1071 let mut data = Vec::new();
1074 data.extend_from_slice(
1075 b"recv 5 bytes from tcp/1.1.1.1:5060 at 00:00:00.000000:\nhello\x0B\n",
1076 );
1077 data.extend_from_slice(b"dump started at Thu Aug 22 11:38:11 2024\n\n\n");
1078 data.extend_from_slice(b"sent 3 bytes to tcp/2.2.2.2:5060 at 00:00:01.000000:\nbye\x0B\n");
1079
1080 let frames: Vec<Result<Frame, ParseError>> = FrameIterator::new(&data[..]).collect();
1081 assert_eq!(frames.len(), 2);
1082 assert_eq!(frames[0].as_ref().unwrap().content, b"hello");
1083 assert_eq!(frames[1].as_ref().unwrap().content, b"bye");
1084 }
1085
1086 #[test]
1087 fn frame_iterator_multiple_newlines_after_boundary() {
1088 let mut data = Vec::new();
1090 data.extend_from_slice(
1091 b"recv 5 bytes from tcp/1.1.1.1:5060 at 00:00:00.000000:\nhello\x0B\n",
1092 );
1093 data.extend_from_slice(b"\n\r\n\n");
1094 data.extend_from_slice(
1095 b"sent 5 bytes to tcp/1.1.1.1:5060 at 00:00:00.000001:\nworld\x0B\n",
1096 );
1097
1098 let frames: Vec<Frame> = FrameIterator::new(&data[..])
1099 .collect::<Result<Vec<_>, _>>()
1100 .unwrap();
1101 assert_eq!(frames.len(), 2);
1102 assert_eq!(frames[0].content, b"hello");
1103 assert_eq!(frames[1].content, b"world");
1104 }
1105
1106 #[test]
1107 fn stats_clean_input() {
1108 let data = b"recv 5 bytes from tcp/1.1.1.1:5060 at 00:00:00.000000:\nhello\x0B\n";
1109 let mut iter = FrameIterator::new(&data[..]);
1110 let frames: Vec<Frame> = iter.by_ref().collect::<Result<Vec<_>, _>>().unwrap();
1111 assert_eq!(frames.len(), 1);
1112 let stats = iter.stats();
1113 assert_eq!(stats.bytes_read, data.len() as u64);
1114 assert_eq!(stats.bytes_skipped, 0);
1115 assert!(stats.unparsed_regions.is_empty());
1116 }
1117
1118 #[test]
1119 fn stats_multiple_frames() {
1120 let data = b"recv 5 bytes from tcp/1.1.1.1:5060 at 00:00:00.000000:\nhello\x0B\nsent 5 bytes to tcp/1.1.1.1:5060 at 00:00:00.000001:\nworld\x0B\n";
1121 let mut iter = FrameIterator::new(&data[..]);
1122 let frames: Vec<Frame> = iter.by_ref().collect::<Result<Vec<_>, _>>().unwrap();
1123 assert_eq!(frames.len(), 2);
1124 let stats = iter.stats();
1125 assert_eq!(stats.bytes_read, data.len() as u64);
1126 assert_eq!(stats.bytes_skipped, 0);
1127 assert!(stats.unparsed_regions.is_empty());
1128 }
1129
1130 #[test]
1131 fn stats_partial_first_frame() {
1132 let mut data = Vec::new();
1133 data.extend_from_slice(b"partial garbage data");
1134 data.extend_from_slice(b"\x0B\n");
1135 data.extend_from_slice(
1136 b"recv 5 bytes from tcp/1.1.1.1:5060 at 00:00:00.000000:\nhello\x0B\n",
1137 );
1138 let mut iter = FrameIterator::new(&data[..]).skip_tracking(SkipTracking::TrackRegions);
1139 let frames: Vec<Frame> = iter.by_ref().collect::<Result<Vec<_>, _>>().unwrap();
1140 assert_eq!(frames.len(), 1);
1141 let stats = iter.stats();
1142 assert_eq!(stats.bytes_read, data.len() as u64);
1143 let skipped = b"partial garbage data\x0B\n".len() as u64;
1145 assert_eq!(stats.bytes_skipped, skipped);
1146 assert_eq!(stats.unparsed_regions.len(), 1);
1147 assert_eq!(stats.unparsed_regions[0].offset, 0);
1148 assert_eq!(stats.unparsed_regions[0].length, skipped);
1149 assert_eq!(
1150 stats.unparsed_regions[0].reason,
1151 crate::types::SkipReason::PartialFirstFrame
1152 );
1153 assert!(stats.unparsed_regions[0].data.is_none());
1154 }
1155
1156 #[test]
1157 fn stats_partial_first_frame_capture() {
1158 let mut data = Vec::new();
1159 data.extend_from_slice(b"partial garbage data");
1160 data.extend_from_slice(b"\x0B\n");
1161 data.extend_from_slice(
1162 b"recv 5 bytes from tcp/1.1.1.1:5060 at 00:00:00.000000:\nhello\x0B\n",
1163 );
1164 let mut iter = FrameIterator::new(&data[..]).capture_skipped(true);
1165 let frames: Vec<Frame> = iter.by_ref().collect::<Result<Vec<_>, _>>().unwrap();
1166 assert_eq!(frames.len(), 1);
1167 let stats = iter.stats();
1168 assert_eq!(stats.unparsed_regions.len(), 1);
1169 let region = &stats.unparsed_regions[0];
1170 assert_eq!(
1171 region.data.as_deref(),
1172 Some(b"partial garbage data\x0B\n".as_slice())
1173 );
1174 }
1175
1176 #[test]
1177 fn stats_mid_stream_partial_frame() {
1178 let mut data = Vec::new();
1180 data.extend_from_slice(
1181 b"recv 5 bytes from tcp/1.1.1.1:5060 at 00:00:00.000000:\nhello\x0B\n",
1182 );
1183 data.extend_from_slice(b"Content-Type: application/sdp\r\n\r\nv=0\r\n");
1184 data.extend_from_slice(b"\x0B\n");
1185 data.extend_from_slice(b"sent 3 bytes to tcp/3.3.3.3:5060 at 02:00:00.000000:\nbar\x0B\n");
1186
1187 let mut iter = FrameIterator::new(&data[..]).skip_tracking(SkipTracking::TrackRegions);
1188 let items: Vec<Result<Frame, ParseError>> = iter.by_ref().collect();
1189 let frames: Vec<Frame> = items.into_iter().filter_map(Result::ok).collect();
1190 assert_eq!(frames.len(), 2);
1191 let stats = iter.stats();
1192 assert!(stats.bytes_skipped > 0);
1193 assert_eq!(stats.unparsed_regions.len(), 1);
1194 assert_eq!(
1195 stats.unparsed_regions[0].reason,
1196 crate::types::SkipReason::MidStreamSkip
1197 );
1198 }
1199
1200 #[test]
1201 fn stats_replayed_frame() {
1202 let frame1 = b"recv 5 bytes from tcp/1.1.1.1:5060 at 00:00:00.000000:\nhello\x0B\n";
1205 let replay = b"Route: <sip:10.0.0.1:5060;lr>\r\nContent-Length: 0\r\n\r\n\x0B\n";
1206 let frame2 = b"sent 3 bytes to tcp/3.3.3.3:5060 at 02:00:00.000000:\nbar\x0B\n";
1207
1208 let mut data = Vec::new();
1209 data.extend_from_slice(frame1);
1210 data.extend_from_slice(replay);
1211 data.extend_from_slice(frame2);
1212
1213 let mut iter = FrameIterator::new(&data[..]).skip_tracking(SkipTracking::TrackRegions);
1214 let items: Vec<Result<Frame, ParseError>> = iter.by_ref().collect();
1215 let frames: Vec<Frame> = items.into_iter().filter_map(Result::ok).collect();
1216 assert_eq!(frames.len(), 2);
1217 let stats = iter.stats();
1218 assert_eq!(stats.unparsed_regions.len(), 1);
1219 assert_eq!(
1220 stats.unparsed_regions[0].reason,
1221 crate::types::SkipReason::ReplayedFrame
1222 );
1223 }
1224
1225 #[test]
1226 fn stats_incomplete_frame_at_eof() {
1227 let mut data = Vec::new();
1229 data.extend_from_slice(
1230 b"recv 5 bytes from tcp/1.1.1.1:5060 at 00:00:00.000000:\nhello\x0B\n",
1231 );
1232 data.extend_from_slice(b"recv 100 bytes from tcp/2.2.2.2:5060 at 01:00:00.000000:\n");
1233 data.extend_from_slice(b"partial content only");
1234 let mut iter = FrameIterator::new(&data[..]).skip_tracking(SkipTracking::TrackRegions);
1237 let items: Vec<Result<Frame, ParseError>> = iter.by_ref().collect();
1238 let frames: Vec<Frame> = items.into_iter().filter_map(Result::ok).collect();
1239 assert_eq!(frames.len(), 2, "truncated frame should still be returned");
1240 assert_eq!(frames[1].content, b"partial content only");
1241 assert_eq!(frames[1].byte_count, 100);
1242 let stats = iter.stats();
1243 assert_eq!(stats.unparsed_regions.len(), 1);
1244 assert_eq!(
1245 stats.unparsed_regions[0].reason,
1246 crate::types::SkipReason::IncompleteFrame
1247 );
1248 }
1249
1250 #[test]
1251 fn stats_invalid_header_skip() {
1252 let mut data = Vec::new();
1254 data.extend_from_slice(
1255 b"recv 5 bytes from tcp/1.1.1.1:5060 at 00:00:00.000000:\nhello\x0B\n",
1256 );
1257 data.extend_from_slice(b"recv CORRUPT HEADER garbage\n");
1258 data.extend_from_slice(b"\x0B\n");
1259 data.extend_from_slice(b"sent 3 bytes to tcp/3.3.3.3:5060 at 02:00:00.000000:\nbar\x0B\n");
1260
1261 let mut iter = FrameIterator::new(&data[..]).skip_tracking(SkipTracking::TrackRegions);
1262 let items: Vec<Result<Frame, ParseError>> = iter.by_ref().collect();
1263 let frames: Vec<Frame> = items.into_iter().filter_map(Result::ok).collect();
1264 assert_eq!(frames.len(), 2);
1265 let stats = iter.stats();
1266 assert!(stats.bytes_skipped > 0);
1267 assert_eq!(stats.unparsed_regions.len(), 1);
1268 assert_eq!(
1269 stats.unparsed_regions[0].reason,
1270 crate::types::SkipReason::InvalidHeader
1271 );
1272 }
1273
1274 #[test]
1275 fn stats_oversized_frame_at_start() {
1276 let mut data = Vec::new();
1277 data.resize(MAX_PARTIAL_FRAME + 1, b'x');
1278 data.extend_from_slice(b"\x0B\n");
1279 data.extend_from_slice(
1280 b"recv 5 bytes from tcp/1.1.1.1:5060 at 00:00:00.000000:\nhello\x0B\n",
1281 );
1282 let mut iter = FrameIterator::new(&data[..]).skip_tracking(SkipTracking::TrackRegions);
1283 let items: Vec<Result<Frame, ParseError>> = iter.by_ref().collect();
1284 let frames: Vec<Frame> = items.into_iter().filter_map(Result::ok).collect();
1285 assert_eq!(frames.len(), 1);
1286 let stats = iter.stats();
1287 assert_eq!(stats.unparsed_regions.len(), 1);
1288 assert_eq!(
1289 stats.unparsed_regions[0].reason,
1290 crate::types::SkipReason::OversizedFrame
1291 );
1292 }
1293
1294 #[test]
1295 fn stats_oversized_frame_mid_stream() {
1296 let mut data = Vec::new();
1297 data.extend_from_slice(
1298 b"recv 5 bytes from tcp/1.1.1.1:5060 at 00:00:00.000000:\nhello\x0B\n",
1299 );
1300 let garbage_len = MAX_PARTIAL_FRAME + 1;
1301 data.resize(data.len() + garbage_len, b'x');
1302 data.extend_from_slice(b"\x0B\n");
1303 data.extend_from_slice(b"sent 3 bytes to tcp/3.3.3.3:5060 at 02:00:00.000000:\nbar\x0B\n");
1304 let mut iter = FrameIterator::new(&data[..]).skip_tracking(SkipTracking::TrackRegions);
1305 let items: Vec<Result<Frame, ParseError>> = iter.by_ref().collect();
1306 let frames: Vec<Frame> = items.into_iter().filter_map(Result::ok).collect();
1307 assert_eq!(frames.len(), 2);
1308 let stats = iter.stats();
1309 assert_eq!(stats.unparsed_regions.len(), 1);
1310 assert_eq!(
1311 stats.unparsed_regions[0].reason,
1312 crate::types::SkipReason::OversizedFrame
1313 );
1314 }
1315
1316 #[test]
1317 fn overlong_byte_count_does_not_buffer_ahead() {
1318 let mut data = Vec::new();
1319 data.extend_from_slice(
1320 format!(
1321 "recv {} bytes from tcp/1.1.1.1:5060 at 00:00:00.000000:\n",
1322 MAX_PARTIAL_FRAME * 10
1323 )
1324 .as_bytes(),
1325 );
1326 data.extend_from_slice(b"hello\x0B\n");
1327 while data.len() < MAX_PARTIAL_FRAME * 10 {
1328 data.extend_from_slice(
1329 b"sent 3 bytes to tcp/3.3.3.3:5060 at 02:00:00.000000:\nbar\x0B\n",
1330 );
1331 }
1332
1333 let mut iter = FrameIterator::new(&data[..]);
1334 let first = iter.next().unwrap().unwrap();
1335 assert_eq!(first.content, b"hello");
1336 assert!(
1337 iter.buf.len() <= MAX_PARTIAL_FRAME + READ_BUF_SIZE,
1338 "buffered {} bytes on a byte_count ten times the frame",
1339 iter.buf.len()
1340 );
1341 let second = iter.next().unwrap().unwrap();
1342 assert_eq!(second.content, b"bar");
1343 }
1344
1345 #[test]
1346 fn stats_partial_first_frame_within_limit() {
1347 let mut data = Vec::new();
1349 data.resize(MAX_PARTIAL_FRAME - 2, b'x');
1350 data.extend_from_slice(b"\x0B\n");
1351 data.extend_from_slice(
1352 b"recv 5 bytes from tcp/1.1.1.1:5060 at 00:00:00.000000:\nhello\x0B\n",
1353 );
1354 let mut iter = FrameIterator::new(&data[..]).skip_tracking(SkipTracking::TrackRegions);
1355 let frames: Vec<Frame> = iter.by_ref().collect::<Result<Vec<_>, _>>().unwrap();
1356 assert_eq!(frames.len(), 1);
1357 let stats = iter.stats();
1358 assert_eq!(stats.unparsed_regions.len(), 1);
1359 assert_eq!(
1360 stats.unparsed_regions[0].reason,
1361 crate::types::SkipReason::PartialFirstFrame
1362 );
1363 }
1364
1365 #[test]
1366 fn stats_dump_restart_marker() {
1367 let mut data = Vec::new();
1368 data.extend_from_slice(
1369 b"recv 5 bytes from tcp/1.1.1.1:5060 at 00:00:00.000000:\nhello\x0B\n",
1370 );
1371 data.extend_from_slice(b"dump started at Thu Aug 22 11:38:11 2024\n\n\n");
1372 data.extend_from_slice(b"sent 3 bytes to tcp/2.2.2.2:5060 at 00:00:01.000000:\nbye\x0B\n");
1373
1374 let mut iter = FrameIterator::new(&data[..]);
1375 let frames: Vec<Frame> = iter.by_ref().collect::<Result<Vec<_>, _>>().unwrap();
1376 assert_eq!(frames.len(), 2);
1377 let stats = iter.stats();
1378 assert_eq!(stats.bytes_skipped, 0);
1380 assert!(stats.unparsed_regions.is_empty());
1381 }
1382
1383 #[test]
1384 fn stats_count_only_no_regions() {
1385 let mut data = Vec::new();
1386 data.extend_from_slice(b"partial garbage data");
1387 data.extend_from_slice(b"\x0B\n");
1388 data.extend_from_slice(
1389 b"recv 5 bytes from tcp/1.1.1.1:5060 at 00:00:00.000000:\nhello\x0B\n",
1390 );
1391 let mut iter = FrameIterator::new(&data[..]);
1392 let frames: Vec<_> = iter.by_ref().collect::<Result<Vec<_>, _>>().unwrap();
1393 assert_eq!(frames.len(), 1);
1394 let stats = iter.stats();
1395 let skipped = b"partial garbage data\x0B\n".len() as u64;
1396 assert_eq!(stats.bytes_skipped, skipped);
1397 assert!(
1398 stats.unparsed_regions.is_empty(),
1399 "CountOnly should not accumulate regions"
1400 );
1401 }
1402
1403 #[test]
1404 fn frame_iterator_trailing_newlines_at_eof() {
1405 let mut data = Vec::new();
1407 data.extend_from_slice(
1408 b"recv 5 bytes from tcp/1.1.1.1:5060 at 00:00:00.000000:\nhello\x0B\n",
1409 );
1410 data.extend_from_slice(b"\n\n");
1411
1412 let frames: Vec<Frame> = FrameIterator::new(&data[..])
1413 .collect::<Result<Vec<_>, _>>()
1414 .unwrap();
1415 assert_eq!(frames.len(), 1);
1416 assert_eq!(frames[0].content, b"hello");
1417 }
1418}