1use std::borrow::Cow;
2use std::fmt;
3use std::net::SocketAddr;
4
5fn parse_socket_addr(address: &str) -> Option<SocketAddr> {
8 if let Ok(addr) = address.parse() {
9 return Some(addr);
10 }
11 let (ip, port) = address.strip_prefix('[')?.split_once("]:")?;
12 Some(SocketAddr::new(ip.parse().ok()?, port.parse().ok()?))
13}
14
15#[derive(Debug, Clone, Copy, PartialEq, Eq)]
20pub enum SkipReason {
21 PartialFirstFrame,
24 OversizedFrame,
27 MidStreamSkip,
29 ReplayedFrame,
32 IncompleteFrame,
34 InvalidHeader,
36}
37
38impl fmt::Display for SkipReason {
39 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
40 match self {
41 SkipReason::PartialFirstFrame => f.write_str("partial first frame"),
42 SkipReason::OversizedFrame => f.write_str("oversized frame"),
43 SkipReason::MidStreamSkip => f.write_str("mid-stream skip"),
44 SkipReason::ReplayedFrame => f.write_str("replayed frame (logrotate)"),
45 SkipReason::IncompleteFrame => f.write_str("incomplete frame"),
46 SkipReason::InvalidHeader => f.write_str("invalid header"),
47 }
48 }
49}
50
51#[derive(Debug, Clone, Copy, PartialEq, Eq)]
56pub enum SkipTracking {
57 CountOnly,
59 TrackRegions,
61 CaptureData,
63}
64
65#[derive(Debug, Clone)]
67pub struct UnparsedRegion {
68 pub offset: u64,
70 pub length: u64,
72 pub reason: SkipReason,
74 pub data: Option<Vec<u8>>,
76}
77
78#[derive(Debug, Default, Clone)]
84pub struct ParseStats {
85 pub bytes_read: u64,
87 pub bytes_skipped: u64,
89 pub unparsed_regions: Vec<UnparsedRegion>,
92}
93
94impl ParseStats {
95 pub fn drain_regions(&mut self) -> Vec<UnparsedRegion> {
97 std::mem::take(&mut self.unparsed_regions)
98 }
99}
100
101#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
103pub enum Direction {
104 Recv,
106 Sent,
108}
109
110impl fmt::Display for Direction {
111 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
112 match self {
113 Direction::Recv => f.write_str("recv"),
114 Direction::Sent => f.write_str("sent"),
115 }
116 }
117}
118
119impl Direction {
120 pub fn preposition(&self) -> &'static str {
122 match self {
123 Direction::Recv => "from",
124 Direction::Sent => "to",
125 }
126 }
127}
128
129#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
131pub enum Transport {
132 Tcp,
134 Udp,
136 Tls,
138 Wss,
140}
141
142impl fmt::Display for Transport {
143 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
144 match self {
145 Transport::Tcp => f.write_str("tcp"),
146 Transport::Udp => f.write_str("udp"),
147 Transport::Tls => f.write_str("tls"),
148 Transport::Wss => f.write_str("wss"),
149 }
150 }
151}
152
153#[derive(Debug, Clone, Copy, PartialEq, Eq)]
158pub enum Timestamp {
159 TimeOnly {
161 hour: u8,
163 min: u8,
165 sec: u8,
167 usec: u32,
169 },
170 DateTime {
172 year: u16,
174 month: u8,
176 day: u8,
178 hour: u8,
180 min: u8,
182 sec: u8,
184 usec: u32,
186 },
187}
188
189impl Timestamp {
190 pub fn time_of_day_secs(&self) -> u32 {
192 let (h, m, s) = match self {
193 Timestamp::TimeOnly { hour, min, sec, .. } => (*hour, *min, *sec),
194 Timestamp::DateTime { hour, min, sec, .. } => (*hour, *min, *sec),
195 };
196 h as u32 * 3600 + m as u32 * 60 + s as u32
197 }
198
199 pub fn sort_key(&self) -> (u16, u8, u8, u8, u8, u8, u32) {
202 match self {
203 Timestamp::TimeOnly {
204 hour,
205 min,
206 sec,
207 usec,
208 } => (0, 0, 0, *hour, *min, *sec, *usec),
209 Timestamp::DateTime {
210 year,
211 month,
212 day,
213 hour,
214 min,
215 sec,
216 usec,
217 } => (*year, *month, *day, *hour, *min, *sec, *usec),
218 }
219 }
220}
221
222impl fmt::Display for Timestamp {
223 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
224 match self {
225 Timestamp::TimeOnly {
226 hour,
227 min,
228 sec,
229 usec,
230 } => write!(f, "{hour:02}:{min:02}:{sec:02}.{usec:06}"),
231 Timestamp::DateTime {
232 year,
233 month,
234 day,
235 hour,
236 min,
237 sec,
238 usec,
239 } => write!(
240 f,
241 "{year:04}-{month:02}-{day:02} {hour:02}:{min:02}:{sec:02}.{usec:06}"
242 ),
243 }
244 }
245}
246
247#[derive(Debug, Clone)]
253pub struct Frame {
254 pub direction: Direction,
256 pub byte_count: usize,
258 pub transport: Transport,
260 pub address: String,
262 pub timestamp: Timestamp,
264 pub content: Vec<u8>,
266}
267
268impl Frame {
269 pub fn socket_addr(&self) -> Option<SocketAddr> {
273 parse_socket_addr(&self.address)
274 }
275}
276
277#[derive(Debug, Clone)]
282pub struct SipMessage {
283 pub direction: Direction,
285 pub transport: Transport,
287 pub address: String,
289 pub timestamp: Timestamp,
291 pub content: Vec<u8>,
293 pub frame_count: usize,
295}
296
297impl SipMessage {
298 pub fn socket_addr(&self) -> Option<SocketAddr> {
302 parse_socket_addr(&self.address)
303 }
304}
305
306#[derive(Debug, Clone, PartialEq, Eq)]
308pub enum SipMessageType {
309 Request {
311 method: String,
313 uri: String,
315 },
316 Response {
318 code: u16,
320 reason: String,
322 },
323}
324
325impl fmt::Display for SipMessageType {
326 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
327 match self {
328 SipMessageType::Request { method, uri } => write!(f, "{method} {uri}"),
329 SipMessageType::Response { code, reason } => write!(f, "{code} {reason}"),
330 }
331 }
332}
333
334impl SipMessageType {
335 pub fn summary(&self) -> Cow<'_, str> {
337 match self {
338 SipMessageType::Request { method, .. } => Cow::Borrowed(method),
339 SipMessageType::Response { code, reason } => Cow::Owned(format!("{code} {reason}")),
340 }
341 }
342}
343
344#[derive(Debug, Clone, Default, PartialEq, Eq)]
347pub struct Headers(pub Vec<(String, String)>);
348
349impl Headers {
350 pub fn value(&self, name: &str) -> Option<&str> {
354 self.0
355 .iter()
356 .find(|(k, _)| k.eq_ignore_ascii_case(name))
357 .map(|(_, v)| v.as_str())
358 }
359}
360
361impl std::ops::Deref for Headers {
362 type Target = [(String, String)];
363
364 fn deref(&self) -> &Self::Target {
365 &self.0
366 }
367}
368
369impl std::ops::DerefMut for Headers {
370 fn deref_mut(&mut self) -> &mut Self::Target {
371 &mut self.0
372 }
373}
374
375impl From<Vec<(String, String)>> for Headers {
376 fn from(headers: Vec<(String, String)>) -> Self {
377 Headers(headers)
378 }
379}
380
381impl FromIterator<(String, String)> for Headers {
382 fn from_iter<I: IntoIterator<Item = (String, String)>>(iter: I) -> Self {
383 Headers(iter.into_iter().collect())
384 }
385}
386
387impl<'a> IntoIterator for &'a Headers {
388 type Item = &'a (String, String);
389 type IntoIter = std::slice::Iter<'a, (String, String)>;
390
391 fn into_iter(self) -> Self::IntoIter {
392 self.0.iter()
393 }
394}
395
396#[derive(Debug, Clone)]
403pub struct ParsedSipMessage {
404 pub direction: Direction,
406 pub transport: Transport,
408 pub address: String,
410 pub timestamp: Timestamp,
412 pub message_type: SipMessageType,
414 pub headers: Headers,
416 pub body: Vec<u8>,
418 pub frame_count: usize,
420}
421
422#[derive(Debug, Clone, PartialEq, Eq)]
429pub struct SipFragment {
430 pub message_type: Option<SipMessageType>,
432 pub headers: Headers,
434 pub body: Vec<u8>,
436}
437
438impl SipFragment {
439 pub fn header_value(&self, name: &str) -> Option<&str> {
441 self.headers.value(name)
442 }
443
444 pub fn content_type(&self) -> Option<&str> {
447 self.header_value("Content-Type")
448 .or_else(|| self.header_value("c"))
449 }
450}
451
452#[derive(Debug, Clone, PartialEq, Eq)]
454pub struct MimePart {
455 pub headers: Headers,
457 pub body: Vec<u8>,
459}
460
461impl MimePart {
462 pub fn content_type(&self) -> Option<&str> {
464 self.headers.value("Content-Type")
465 }
466
467 pub fn header_value(&self, name: &str) -> Option<&str> {
469 self.headers.value(name)
470 }
471
472 pub fn content_id(&self) -> Option<&str> {
474 self.header_value("Content-ID")
475 }
476
477 pub fn content_disposition(&self) -> Option<&str> {
479 self.header_value("Content-Disposition")
480 }
481
482 pub fn content_transfer_encoding(&self) -> Option<&str> {
486 self.header_value("Content-Transfer-Encoding")
487 }
488}
489
490impl ParsedSipMessage {
491 pub fn socket_addr(&self) -> Option<SocketAddr> {
495 parse_socket_addr(&self.address)
496 }
497
498 pub fn call_id(&self) -> Option<&str> {
501 self.header_value("Call-ID")
502 .or_else(|| self.header_value("i"))
503 }
504
505 pub fn content_type(&self) -> Option<&str> {
508 self.header_value("Content-Type")
509 .or_else(|| self.header_value("c"))
510 }
511
512 pub fn content_length(&self) -> Option<usize> {
515 self.header_value("Content-Length")
516 .or_else(|| self.header_value("l"))
517 .and_then(|v| v.trim().parse().ok())
518 }
519
520 pub fn cseq(&self) -> Option<&str> {
522 self.header_value("CSeq")
523 }
524
525 pub fn method(&self) -> Option<&str> {
528 match &self.message_type {
529 SipMessageType::Request { method, .. } => Some(method),
530 SipMessageType::Response { .. } => {
531 self.cseq().and_then(|cs| cs.split_whitespace().nth(1))
532 }
533 }
534 }
535
536 pub fn body_data(&self) -> Cow<'_, str> {
539 String::from_utf8_lossy(&self.body)
540 }
541
542 pub fn to_bytes(&self) -> Vec<u8> {
544 let mut out = Vec::new();
545 match &self.message_type {
546 SipMessageType::Request { method, uri } => {
547 out.extend_from_slice(format!("{method} {uri} SIP/2.0\r\n").as_bytes());
548 }
549 SipMessageType::Response { code, reason } => {
550 out.extend_from_slice(format!("SIP/2.0 {code} {reason}\r\n").as_bytes());
551 }
552 }
553 for (name, value) in &self.headers {
554 out.extend_from_slice(format!("{name}: {value}\r\n").as_bytes());
555 }
556 out.extend_from_slice(b"\r\n");
557 if !self.body.is_empty() {
558 out.extend_from_slice(&self.body);
559 }
560 out
561 }
562
563 pub fn header_value(&self, name: &str) -> Option<&str> {
566 self.headers.value(name)
567 }
568}
569
570#[cfg(test)]
571mod tests {
572 use super::*;
573
574 fn make_parsed(
575 msg_type: SipMessageType,
576 headers: Vec<(&str, &str)>,
577 body: &[u8],
578 ) -> ParsedSipMessage {
579 ParsedSipMessage {
580 direction: Direction::Recv,
581 transport: Transport::Tcp,
582 address: "10.0.0.1:5060".into(),
583 timestamp: Timestamp::TimeOnly {
584 hour: 12,
585 min: 0,
586 sec: 0,
587 usec: 0,
588 },
589 message_type: msg_type,
590 headers: Headers(
591 headers
592 .iter()
593 .map(|(k, v)| (k.to_string(), v.to_string()))
594 .collect(),
595 ),
596 body: body.to_vec(),
597 frame_count: 1,
598 }
599 }
600
601 fn make_frame(address: &str) -> Frame {
602 Frame {
603 direction: Direction::Recv,
604 byte_count: 0,
605 transport: Transport::Tcp,
606 address: address.into(),
607 timestamp: Timestamp::TimeOnly {
608 hour: 0,
609 min: 0,
610 sec: 0,
611 usec: 0,
612 },
613 content: Vec::new(),
614 }
615 }
616
617 fn make_message(address: &str) -> SipMessage {
618 SipMessage {
619 direction: Direction::Recv,
620 transport: Transport::Tcp,
621 address: address.into(),
622 timestamp: Timestamp::TimeOnly {
623 hour: 0,
624 min: 0,
625 sec: 0,
626 usec: 0,
627 },
628 content: Vec::new(),
629 frame_count: 1,
630 }
631 }
632
633 fn parsed_with_address(address: &str) -> ParsedSipMessage {
634 let mut msg = make_parsed(
635 SipMessageType::Request {
636 method: "OPTIONS".into(),
637 uri: "sip:host".into(),
638 },
639 vec![],
640 b"",
641 );
642 msg.address = address.into();
643 msg
644 }
645
646 #[test]
647 fn socket_addr_ipv4() {
648 let addr = make_frame("10.0.0.1:5060").socket_addr().unwrap();
649 assert!(addr.is_ipv4());
650 assert_eq!(addr.port(), 5060);
651 assert_eq!(addr.ip().to_string(), "10.0.0.1");
652 }
653
654 #[test]
655 fn socket_addr_ipv6_bracketed() {
656 let addr = make_message("[2001:db8::1]:5061").socket_addr().unwrap();
657 assert!(addr.is_ipv6());
658 assert_eq!(addr.port(), 5061);
659 assert_eq!(addr.ip().to_string(), "2001:db8::1");
660 }
661
662 #[test]
663 fn socket_addr_ipv4_bracketed() {
664 let addr = make_frame("[198.51.100.7]:5060").socket_addr().unwrap();
665 assert!(addr.is_ipv4());
666 assert_eq!(addr.port(), 5060);
667 assert_eq!(addr.ip().to_string(), "198.51.100.7");
668 }
669
670 #[test]
671 fn socket_addr_on_parsed_message() {
672 let addr = parsed_with_address("192.0.2.4:5080").socket_addr().unwrap();
673 assert_eq!(addr.port(), 5080);
674 }
675
676 #[test]
677 fn socket_addr_rejects_non_addresses() {
678 for bad in [
679 "345.678.987.654:5060",
680 "10.0.0.1",
681 "host.example.test:5060",
682 "2001:db8::1:5060",
683 "",
684 ] {
685 assert!(
686 make_frame(bad).socket_addr().is_none(),
687 "should not parse: {bad}"
688 );
689 assert!(make_message(bad).socket_addr().is_none());
690 assert!(parsed_with_address(bad).socket_addr().is_none());
691 }
692 }
693
694 #[test]
695 fn to_bytes_request_no_body() {
696 let msg = make_parsed(
697 SipMessageType::Request {
698 method: "OPTIONS".into(),
699 uri: "sip:host".into(),
700 },
701 vec![("Call-ID", "test")],
702 b"",
703 );
704 let bytes = msg.to_bytes();
705 let text = String::from_utf8(bytes).unwrap();
706 assert!(text.starts_with("OPTIONS sip:host SIP/2.0\r\n"));
707 assert!(text.contains("Call-ID: test\r\n"));
708 assert!(text.ends_with("\r\n\r\n"));
709 }
710
711 #[test]
712 fn to_bytes_request_with_body() {
713 let body = b"v=0\r\ns=-\r\n";
714 let msg = make_parsed(
715 SipMessageType::Request {
716 method: "INVITE".into(),
717 uri: "sip:host".into(),
718 },
719 vec![("Call-ID", "test")],
720 body,
721 );
722 let bytes = msg.to_bytes();
723 assert!(bytes.ends_with(body));
724 }
725
726 #[test]
727 fn to_bytes_response() {
728 let msg = make_parsed(
729 SipMessageType::Response {
730 code: 200,
731 reason: "OK".into(),
732 },
733 vec![("Call-ID", "resp-test")],
734 b"",
735 );
736 let bytes = msg.to_bytes();
737 let text = String::from_utf8(bytes).unwrap();
738 assert!(text.starts_with("SIP/2.0 200 OK\r\n"));
739 }
740
741 #[test]
742 fn body_data_valid_utf8() {
743 let msg = make_parsed(
744 SipMessageType::Request {
745 method: "MESSAGE".into(),
746 uri: "sip:host".into(),
747 },
748 vec![],
749 b"hello world",
750 );
751 assert_eq!(&*msg.body_data(), "hello world");
752 }
753
754 #[test]
755 fn body_data_empty() {
756 let msg = make_parsed(
757 SipMessageType::Request {
758 method: "OPTIONS".into(),
759 uri: "sip:host".into(),
760 },
761 vec![],
762 b"",
763 );
764 assert_eq!(&*msg.body_data(), "");
765 }
766
767 #[test]
768 fn body_data_binary() {
769 let msg = make_parsed(
770 SipMessageType::Request {
771 method: "MESSAGE".into(),
772 uri: "sip:host".into(),
773 },
774 vec![],
775 &[0xFF, 0xFE],
776 );
777 assert!(msg.body_data().contains('\u{FFFD}'));
778 }
779
780 #[test]
781 fn body_text_non_json_passthrough() {
782 let msg = make_parsed(
783 SipMessageType::Request {
784 method: "INVITE".into(),
785 uri: "sip:host".into(),
786 },
787 vec![("Content-Type", "application/sdp")],
788 b"v=0\r\ns=-\r\n",
789 );
790 assert_eq!(msg.body_text().as_ref(), msg.body_data().as_ref());
791 }
792
793 #[test]
794 fn body_text_json_unescapes_newlines() {
795 let msg = make_parsed(
796 SipMessageType::Request {
797 method: "NOTIFY".into(),
798 uri: "sip:host".into(),
799 },
800 vec![("Content-Type", "application/json")],
801 br#"{"invite":"INVITE sip:host SIP/2.0\r\nTo: <sip:host>\r\n"}"#,
802 );
803 let text = msg.body_text();
804 assert!(
805 text.contains("INVITE sip:host SIP/2.0\r\nTo: <sip:host>\r\n"),
806 "JSON \\r\\n should be unescaped to actual CRLF, got: {text:?}"
807 );
808 }
809
810 #[test]
811 fn body_text_plus_json_content_type() {
812 let msg = make_parsed(
813 SipMessageType::Request {
814 method: "NOTIFY".into(),
815 uri: "sip:host".into(),
816 },
817 vec![(
818 "Content-Type",
819 "application/emergencyCallData.AbandonedCall+json",
820 )],
821 br#"{"invite":"line1\nline2"}"#,
822 );
823 let text = msg.body_text();
824 assert!(
825 text.contains("line1\nline2"),
826 "application/*+json should trigger unescaping, got: {text:?}"
827 );
828 }
829
830 #[test]
831 fn body_data_preserves_json_escapes() {
832 let raw = br#"{"key":"value\nwith\\escapes"}"#;
833 let msg = make_parsed(
834 SipMessageType::Request {
835 method: "NOTIFY".into(),
836 uri: "sip:host".into(),
837 },
838 vec![("Content-Type", "application/json")],
839 raw,
840 );
841 assert_eq!(
842 msg.body_data().as_ref(),
843 r#"{"key":"value\nwith\\escapes"}"#,
844 "body_data() must preserve raw escapes"
845 );
846 }
847}