Skip to main content

sip_core/
message.rs

1use crate::header::{HeaderName, Headers};
2use std::fmt;
3use thiserror::Error;
4
5#[derive(Debug, Clone, PartialEq, Eq, Hash)]
6pub enum SipMethod {
7    Register,
8    Invite,
9    Ack,
10    Bye,
11    Cancel,
12    Options,
13    Info,
14    Refer,
15    Notify,
16    Update,
17    Prack,
18    Other(String),
19}
20
21impl SipMethod {
22    pub fn from_str(s: &str) -> Self {
23        match s.to_uppercase().as_str() {
24            "REGISTER" => SipMethod::Register,
25            "INVITE" => SipMethod::Invite,
26            "ACK" => SipMethod::Ack,
27            "BYE" => SipMethod::Bye,
28            "CANCEL" => SipMethod::Cancel,
29            "OPTIONS" => SipMethod::Options,
30            "INFO" => SipMethod::Info,
31            "REFER" => SipMethod::Refer,
32            "NOTIFY" => SipMethod::Notify,
33            "UPDATE" => SipMethod::Update,
34            "PRACK" => SipMethod::Prack,
35            other => SipMethod::Other(other.to_string()),
36        }
37    }
38
39    pub fn as_str(&self) -> &str {
40        match self {
41            SipMethod::Register => "REGISTER",
42            SipMethod::Invite => "INVITE",
43            SipMethod::Ack => "ACK",
44            SipMethod::Bye => "BYE",
45            SipMethod::Cancel => "CANCEL",
46            SipMethod::Options => "OPTIONS",
47            SipMethod::Info => "INFO",
48            SipMethod::Refer => "REFER",
49            SipMethod::Notify => "NOTIFY",
50            SipMethod::Update => "UPDATE",
51            SipMethod::Prack => "PRACK",
52            SipMethod::Other(s) => s.as_str(),
53        }
54    }
55}
56
57impl fmt::Display for SipMethod {
58    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
59        write!(f, "{}", self.as_str())
60    }
61}
62
63#[derive(Debug, Clone, Copy, PartialEq, Eq)]
64pub struct StatusCode(pub u16);
65
66impl StatusCode {
67    pub const TRYING: Self = Self(100);
68    pub const RINGING: Self = Self(180);
69    pub const SESSION_PROGRESS: Self = Self(183);
70    pub const OK: Self = Self(200);
71    pub const ACCEPTED: Self = Self(202);
72    pub const BAD_REQUEST: Self = Self(400);
73    pub const UNAUTHORIZED: Self = Self(401);
74    pub const FORBIDDEN: Self = Self(403);
75    pub const NOT_FOUND: Self = Self(404);
76    pub const METHOD_NOT_ALLOWED: Self = Self(405);
77    pub const PROXY_AUTH_REQUIRED: Self = Self(407);
78    pub const REQUEST_TIMEOUT: Self = Self(408);
79    pub const REQUEST_PENDING: Self = Self(491);
80    pub const BUSY_HERE: Self = Self(486);
81    pub const SERVER_ERROR: Self = Self(500);
82    pub const NOT_IMPLEMENTED: Self = Self(501);
83
84    pub fn reason_phrase(&self) -> &'static str {
85        match self.0 {
86            100 => "Trying",
87            180 => "Ringing",
88            183 => "Session Progress",
89            200 => "OK",
90            202 => "Accepted",
91            400 => "Bad Request",
92            401 => "Unauthorized",
93            403 => "Forbidden",
94            404 => "Not Found",
95            405 => "Method Not Allowed",
96            407 => "Proxy Authentication Required",
97            408 => "Request Timeout",
98            486 => "Busy Here",
99            491 => "Request Pending",
100            500 => "Server Internal Error",
101            501 => "Not Implemented",
102            _ => "Unknown",
103        }
104    }
105
106    pub fn is_provisional(&self) -> bool {
107        self.0 >= 100 && self.0 < 200
108    }
109
110    pub fn is_success(&self) -> bool {
111        self.0 >= 200 && self.0 < 300
112    }
113
114    pub fn is_redirect(&self) -> bool {
115        self.0 >= 300 && self.0 < 400
116    }
117
118    pub fn is_error(&self) -> bool {
119        self.0 >= 400
120    }
121}
122
123impl fmt::Display for StatusCode {
124    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
125        write!(f, "{}", self.0)
126    }
127}
128
129#[derive(Debug, Clone)]
130pub struct SipRequest {
131    pub method: SipMethod,
132    pub uri: String,
133    pub version: String,
134    pub headers: Headers,
135    pub body: Option<String>,
136}
137
138#[derive(Debug, Clone)]
139pub struct SipResponse {
140    pub version: String,
141    pub status: StatusCode,
142    pub reason: String,
143    pub headers: Headers,
144    pub body: Option<String>,
145}
146
147#[derive(Debug, Clone)]
148pub enum SipMessage {
149    Request(SipRequest),
150    Response(SipResponse),
151}
152
153#[derive(Debug, Error)]
154pub enum ParseError {
155    #[error("invalid start line: {0}")]
156    InvalidStartLine(String),
157    #[error("invalid header: {0}")]
158    InvalidHeader(String),
159    #[error("invalid status code: {0}")]
160    InvalidStatusCode(String),
161    #[error("incomplete message")]
162    Incomplete,
163}
164
165impl SipMessage {
166    pub fn parse(input: &str) -> Result<Self, ParseError> {
167        let mut lines = input.lines();
168        let start_line = lines.next().ok_or(ParseError::Incomplete)?;
169        let start_line = start_line.trim();
170
171        // Determine if request or response
172        if start_line.starts_with("SIP/") {
173            // Response: SIP/2.0 200 OK
174            Self::parse_response(start_line, &mut lines)
175        } else {
176            // Request: INVITE sip:bob@biloxi.com SIP/2.0
177            Self::parse_request(start_line, &mut lines)
178        }
179    }
180
181    fn parse_request<'a>(
182        start_line: &str,
183        lines: &mut impl Iterator<Item = &'a str>,
184    ) -> Result<Self, ParseError> {
185        let parts: Vec<&str> = start_line.splitn(3, ' ').collect();
186        if parts.len() != 3 {
187            return Err(ParseError::InvalidStartLine(start_line.to_string()));
188        }
189
190        let method = SipMethod::from_str(parts[0]);
191        let uri = parts[1].to_string();
192        let version = parts[2].to_string();
193
194        let (headers, body) = Self::parse_headers_and_body(lines)?;
195
196        Ok(SipMessage::Request(SipRequest {
197            method,
198            uri,
199            version,
200            headers,
201            body,
202        }))
203    }
204
205    fn parse_response<'a>(
206        start_line: &str,
207        lines: &mut impl Iterator<Item = &'a str>,
208    ) -> Result<Self, ParseError> {
209        let parts: Vec<&str> = start_line.splitn(3, ' ').collect();
210        if parts.len() < 2 {
211            return Err(ParseError::InvalidStartLine(start_line.to_string()));
212        }
213
214        let version = parts[0].to_string();
215        let status_code: u16 = parts[1]
216            .parse()
217            .map_err(|_| ParseError::InvalidStatusCode(parts[1].to_string()))?;
218        let reason = if parts.len() > 2 {
219            parts[2].to_string()
220        } else {
221            StatusCode(status_code).reason_phrase().to_string()
222        };
223
224        let (headers, body) = Self::parse_headers_and_body(lines)?;
225
226        Ok(SipMessage::Response(SipResponse {
227            version,
228            status: StatusCode(status_code),
229            reason,
230            headers,
231            body,
232        }))
233    }
234
235    fn parse_headers_and_body<'a>(
236        lines: &mut impl Iterator<Item = &'a str>,
237    ) -> Result<(Headers, Option<String>), ParseError> {
238        let mut headers = Headers::new();
239        let mut body_lines = Vec::new();
240        let mut in_body = false;
241
242        for line in lines {
243            if in_body {
244                body_lines.push(line);
245                continue;
246            }
247
248            if line.trim().is_empty() {
249                in_body = true;
250                continue;
251            }
252
253            // Handle header continuation (folding)
254            if line.starts_with(' ') || line.starts_with('\t') {
255                // This is a continuation of the previous header
256                // For simplicity, skip folding support in this implementation
257                continue;
258            }
259
260            if let Some((name, value)) = line.split_once(':') {
261                let name = HeaderName::from_str(name.trim());
262                let value = value.trim().to_string();
263                headers.add(name, value);
264            } else {
265                return Err(ParseError::InvalidHeader(line.to_string()));
266            }
267        }
268
269        let body = if body_lines.is_empty() {
270            None
271        } else {
272            let b = body_lines.join("\r\n");
273            if b.trim().is_empty() {
274                None
275            } else {
276                Some(b)
277            }
278        };
279
280        Ok((headers, body))
281    }
282
283    pub fn to_bytes(&self) -> Vec<u8> {
284        self.to_string().into_bytes()
285    }
286
287    pub fn headers(&self) -> &Headers {
288        match self {
289            SipMessage::Request(req) => &req.headers,
290            SipMessage::Response(res) => &res.headers,
291        }
292    }
293
294    pub fn headers_mut(&mut self) -> &mut Headers {
295        match self {
296            SipMessage::Request(req) => &mut req.headers,
297            SipMessage::Response(res) => &mut res.headers,
298        }
299    }
300
301    pub fn body(&self) -> Option<&str> {
302        match self {
303            SipMessage::Request(req) => req.body.as_deref(),
304            SipMessage::Response(res) => res.body.as_deref(),
305        }
306    }
307
308    pub fn call_id(&self) -> Option<String> {
309        self.headers()
310            .get(&HeaderName::CallId)
311            .map(|v| v.0.clone())
312    }
313
314    pub fn cseq(&self) -> Option<(u32, SipMethod)> {
315        let cseq_val = self.headers().get(&HeaderName::CSeq)?;
316        let parts: Vec<&str> = cseq_val.as_str().splitn(2, ' ').collect();
317        if parts.len() != 2 {
318            return None;
319        }
320        let seq: u32 = parts[0].parse().ok()?;
321        let method = SipMethod::from_str(parts[1]);
322        Some((seq, method))
323    }
324
325    pub fn is_request(&self) -> bool {
326        matches!(self, SipMessage::Request(_))
327    }
328
329    pub fn is_response(&self) -> bool {
330        matches!(self, SipMessage::Response(_))
331    }
332
333    pub fn method(&self) -> Option<&SipMethod> {
334        match self {
335            SipMessage::Request(req) => Some(&req.method),
336            SipMessage::Response(_) => None,
337        }
338    }
339
340    pub fn status(&self) -> Option<&StatusCode> {
341        match self {
342            SipMessage::Response(res) => Some(&res.status),
343            SipMessage::Request(_) => None,
344        }
345    }
346}
347
348impl fmt::Display for SipMessage {
349    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
350        match self {
351            SipMessage::Request(req) => {
352                writeln!(f, "{} {} {}\r", req.method, req.uri, req.version)?;
353                for header in req.headers.iter() {
354                    writeln!(f, "{}\r", header)?;
355                }
356                writeln!(f, "\r")?;
357                if let Some(body) = &req.body {
358                    write!(f, "{}", body)?;
359                }
360            }
361            SipMessage::Response(res) => {
362                writeln!(f, "{} {} {}\r", res.version, res.status, res.reason)?;
363                for header in res.headers.iter() {
364                    writeln!(f, "{}\r", header)?;
365                }
366                writeln!(f, "\r")?;
367                if let Some(body) = &res.body {
368                    write!(f, "{}", body)?;
369                }
370            }
371        }
372        Ok(())
373    }
374}
375
376/// Builder for creating SIP requests
377pub struct RequestBuilder {
378    method: SipMethod,
379    uri: String,
380    headers: Headers,
381    body: Option<String>,
382}
383
384impl RequestBuilder {
385    pub fn new(method: SipMethod, uri: impl Into<String>) -> Self {
386        Self {
387            method,
388            uri: uri.into(),
389            headers: Headers::new(),
390            body: None,
391        }
392    }
393
394    pub fn header(mut self, name: HeaderName, value: impl Into<String>) -> Self {
395        self.headers.add(name, value);
396        self
397    }
398
399    pub fn body(mut self, body: impl Into<String>) -> Self {
400        self.body = Some(body.into());
401        self
402    }
403
404    pub fn build(mut self) -> SipMessage {
405        // Set Content-Length
406        let content_length = self.body.as_ref().map_or(0, |b| b.len());
407        self.headers
408            .set(HeaderName::ContentLength, content_length.to_string());
409
410        SipMessage::Request(SipRequest {
411            method: self.method,
412            uri: self.uri,
413            version: "SIP/2.0".to_string(),
414            headers: self.headers,
415            body: self.body,
416        })
417    }
418}
419
420/// Builder for creating SIP responses
421pub struct ResponseBuilder {
422    status: StatusCode,
423    headers: Headers,
424    body: Option<String>,
425}
426
427impl ResponseBuilder {
428    pub fn new(status: StatusCode) -> Self {
429        Self {
430            status,
431            headers: Headers::new(),
432            body: None,
433        }
434    }
435
436    /// Set a header, replacing any existing value for that header name.
437    pub fn header(mut self, name: HeaderName, value: impl Into<String>) -> Self {
438        self.headers.set(name, value);
439        self
440    }
441
442    pub fn body(mut self, body: impl Into<String>) -> Self {
443        self.body = Some(body.into());
444        self
445    }
446
447    /// Build a response from an incoming request, copying Via, From, To, Call-ID, CSeq headers.
448    pub fn from_request(request: &SipRequest, status: StatusCode) -> Self {
449        let mut headers = Headers::new();
450
451        // Copy Via headers
452        for via in request.headers.get_all(&HeaderName::Via) {
453            headers.add(HeaderName::Via, via.as_str());
454        }
455
456        // Copy From
457        if let Some(from) = request.headers.get(&HeaderName::From) {
458            headers.add(HeaderName::From, from.as_str());
459        }
460
461        // Copy To
462        if let Some(to) = request.headers.get(&HeaderName::To) {
463            headers.add(HeaderName::To, to.as_str());
464        }
465
466        // Copy Call-ID
467        if let Some(call_id) = request.headers.get(&HeaderName::CallId) {
468            headers.add(HeaderName::CallId, call_id.as_str());
469        }
470
471        // Copy CSeq
472        if let Some(cseq) = request.headers.get(&HeaderName::CSeq) {
473            headers.add(HeaderName::CSeq, cseq.as_str());
474        }
475
476        Self {
477            status,
478            headers,
479            body: None,
480        }
481    }
482
483    pub fn build(mut self) -> SipMessage {
484        let content_length = self.body.as_ref().map_or(0, |b| b.len());
485        self.headers
486            .set(HeaderName::ContentLength, content_length.to_string());
487
488        SipMessage::Response(SipResponse {
489            version: "SIP/2.0".to_string(),
490            status: self.status,
491            reason: self.status.reason_phrase().to_string(),
492            headers: self.headers,
493            body: self.body,
494        })
495    }
496}
497
498#[cfg(test)]
499mod tests {
500    use super::*;
501
502    const INVITE_REQUEST: &str = "INVITE sip:bob@biloxi.com SIP/2.0\r\n\
503        Via: SIP/2.0/UDP pc33.atlanta.com;branch=z9hG4bK776asdhds\r\n\
504        Max-Forwards: 70\r\n\
505        To: Bob <sip:bob@biloxi.com>\r\n\
506        From: Alice <sip:alice@atlanta.com>;tag=1928301774\r\n\
507        Call-ID: a84b4c76e66710@pc33.atlanta.com\r\n\
508        CSeq: 314159 INVITE\r\n\
509        Contact: <sip:alice@pc33.atlanta.com>\r\n\
510        Content-Type: application/sdp\r\n\
511        Content-Length: 4\r\n\
512        \r\n\
513        test";
514
515    const OK_RESPONSE: &str = "SIP/2.0 200 OK\r\n\
516        Via: SIP/2.0/UDP pc33.atlanta.com;branch=z9hG4bK776asdhds\r\n\
517        To: Bob <sip:bob@biloxi.com>;tag=a6c85cf\r\n\
518        From: Alice <sip:alice@atlanta.com>;tag=1928301774\r\n\
519        Call-ID: a84b4c76e66710@pc33.atlanta.com\r\n\
520        CSeq: 314159 INVITE\r\n\
521        Contact: <sip:bob@192.0.2.4>\r\n\
522        Content-Length: 0\r\n\
523        \r\n";
524
525    #[test]
526    fn test_parse_invite_request() {
527        let msg = SipMessage::parse(INVITE_REQUEST).unwrap();
528        assert!(msg.is_request());
529
530        if let SipMessage::Request(req) = &msg {
531            assert_eq!(req.method, SipMethod::Invite);
532            assert_eq!(req.uri, "sip:bob@biloxi.com");
533            assert_eq!(req.version, "SIP/2.0");
534            assert_eq!(
535                req.headers.get(&HeaderName::CallId).unwrap().as_str(),
536                "a84b4c76e66710@pc33.atlanta.com"
537            );
538            assert_eq!(req.body.as_deref(), Some("test"));
539        }
540    }
541
542    #[test]
543    fn test_parse_ok_response() {
544        let msg = SipMessage::parse(OK_RESPONSE).unwrap();
545        assert!(msg.is_response());
546
547        if let SipMessage::Response(res) = &msg {
548            assert_eq!(res.status, StatusCode::OK);
549            assert_eq!(res.reason, "OK");
550            assert_eq!(res.version, "SIP/2.0");
551        }
552    }
553
554    #[test]
555    fn test_parse_register_request() {
556        let input = "REGISTER sip:registrar.biloxi.com SIP/2.0\r\n\
557            Via: SIP/2.0/UDP bobspc.biloxi.com:5060;branch=z9hG4bKnashds7\r\n\
558            Max-Forwards: 70\r\n\
559            To: Bob <sip:bob@biloxi.com>\r\n\
560            From: Bob <sip:bob@biloxi.com>;tag=456248\r\n\
561            Call-ID: 843817637684230@998sdasdh09\r\n\
562            CSeq: 1826 REGISTER\r\n\
563            Contact: <sip:bob@192.0.2.4>\r\n\
564            Expires: 7200\r\n\
565            Content-Length: 0\r\n\
566            \r\n";
567
568        let msg = SipMessage::parse(input).unwrap();
569        if let SipMessage::Request(req) = &msg {
570            assert_eq!(req.method, SipMethod::Register);
571            assert_eq!(req.uri, "sip:registrar.biloxi.com");
572        } else {
573            panic!("Expected request");
574        }
575    }
576
577    #[test]
578    fn test_cseq_parsing() {
579        let msg = SipMessage::parse(INVITE_REQUEST).unwrap();
580        let (seq, method) = msg.cseq().unwrap();
581        assert_eq!(seq, 314159);
582        assert_eq!(method, SipMethod::Invite);
583    }
584
585    #[test]
586    fn test_call_id() {
587        let msg = SipMessage::parse(INVITE_REQUEST).unwrap();
588        assert_eq!(
589            msg.call_id().unwrap(),
590            "a84b4c76e66710@pc33.atlanta.com"
591        );
592    }
593
594    #[test]
595    fn test_method_enum() {
596        assert_eq!(SipMethod::from_str("INVITE"), SipMethod::Invite);
597        assert_eq!(SipMethod::from_str("invite"), SipMethod::Invite);
598        assert_eq!(SipMethod::from_str("BYE"), SipMethod::Bye);
599        assert_eq!(SipMethod::from_str("REGISTER"), SipMethod::Register);
600        assert_eq!(SipMethod::from_str("INFO"), SipMethod::Info);
601        assert_eq!(
602            SipMethod::from_str("SUBSCRIBE"),
603            SipMethod::Other("SUBSCRIBE".to_string())
604        );
605    }
606
607    #[test]
608    fn test_status_code() {
609        assert!(StatusCode::TRYING.is_provisional());
610        assert!(StatusCode::RINGING.is_provisional());
611        assert!(StatusCode::SESSION_PROGRESS.is_provisional());
612        assert!(!StatusCode::SESSION_PROGRESS.is_success());
613        assert!(!StatusCode::SESSION_PROGRESS.is_error());
614        assert_eq!(StatusCode::SESSION_PROGRESS.0, 183);
615        assert_eq!(StatusCode::SESSION_PROGRESS.reason_phrase(), "Session Progress");
616        assert!(StatusCode::OK.is_success());
617        assert!(StatusCode::UNAUTHORIZED.is_error());
618        assert!(StatusCode::NOT_FOUND.is_error());
619    }
620
621    #[test]
622    fn test_parse_183_session_progress() {
623        let raw = "SIP/2.0 183 Session Progress\r\n\
624                   Via: SIP/2.0/UDP 10.0.0.1:5060;branch=z9hG4bK776\r\n\
625                   From: <sip:alice@example.com>;tag=abc123\r\n\
626                   To: <sip:bob@example.com>;tag=xyz789\r\n\
627                   Call-ID: early-media-test@10.0.0.1\r\n\
628                   CSeq: 1 INVITE\r\n\
629                   Content-Type: application/sdp\r\n\
630                   Content-Length: 0\r\n\
631                   \r\n";
632        let msg = SipMessage::parse(raw).expect("should parse 183");
633        assert!(msg.is_response());
634        let status = msg.status().expect("should have status");
635        assert_eq!(status.0, 183);
636        assert!(status.is_provisional());
637        assert!(!status.is_success());
638        assert_eq!(status.reason_phrase(), "Session Progress");
639    }
640
641    #[test]
642    fn test_request_builder() {
643        let msg = RequestBuilder::new(SipMethod::Register, "sip:registrar.example.com")
644            .header(
645                HeaderName::Via,
646                "SIP/2.0/UDP 10.0.0.1:5060;branch=z9hG4bK776",
647            )
648            .header(HeaderName::From, "<sip:alice@example.com>;tag=abc123")
649            .header(HeaderName::To, "<sip:alice@example.com>")
650            .header(HeaderName::CallId, "unique-call-id@10.0.0.1")
651            .header(HeaderName::CSeq, "1 REGISTER")
652            .build();
653
654        assert!(msg.is_request());
655        if let SipMessage::Request(req) = &msg {
656            assert_eq!(req.method, SipMethod::Register);
657            assert_eq!(
658                req.headers.get(&HeaderName::ContentLength).unwrap().as_str(),
659                "0"
660            );
661        }
662    }
663
664    #[test]
665    fn test_response_builder_from_request() {
666        let invite = SipMessage::parse(INVITE_REQUEST).unwrap();
667        if let SipMessage::Request(req) = &invite {
668            let response = ResponseBuilder::from_request(req, StatusCode::OK).build();
669            if let SipMessage::Response(res) = &response {
670                assert_eq!(res.status, StatusCode::OK);
671                assert_eq!(
672                    res.headers.get(&HeaderName::CallId).unwrap().as_str(),
673                    "a84b4c76e66710@pc33.atlanta.com"
674                );
675                assert_eq!(
676                    res.headers.get(&HeaderName::CSeq).unwrap().as_str(),
677                    "314159 INVITE"
678                );
679            } else {
680                panic!("Expected response");
681            }
682        }
683    }
684
685    #[test]
686    fn test_message_serialization_roundtrip() {
687        let msg = RequestBuilder::new(SipMethod::Invite, "sip:bob@biloxi.com")
688            .header(
689                HeaderName::Via,
690                "SIP/2.0/UDP pc33.atlanta.com;branch=z9hG4bK776",
691            )
692            .header(HeaderName::From, "<sip:alice@atlanta.com>;tag=123")
693            .header(HeaderName::To, "<sip:bob@biloxi.com>")
694            .header(HeaderName::CallId, "test-call-id@pc33")
695            .header(HeaderName::CSeq, "1 INVITE")
696            .build();
697
698        let serialized = msg.to_string();
699        let parsed = SipMessage::parse(&serialized).unwrap();
700        assert!(parsed.is_request());
701        assert_eq!(parsed.call_id().unwrap(), "test-call-id@pc33");
702    }
703
704    #[test]
705    fn test_parse_error_invalid_start_line() {
706        let result = SipMessage::parse("NOT A VALID SIP MESSAGE");
707        // This should parse as a request with method "NOT", uri "A", version "VALID SIP MESSAGE"
708        // Actually "NOT A VALID SIP MESSAGE" splits into 3 parts with splitn(3, ' ')
709        assert!(result.is_ok()); // it parses as a (weird) request
710    }
711
712    #[test]
713    fn test_parse_error_empty() {
714        let result = SipMessage::parse("");
715        assert!(result.is_err());
716    }
717
718    #[test]
719    fn test_parse_401_response() {
720        let input = "SIP/2.0 401 Unauthorized\r\n\
721            Via: SIP/2.0/UDP 10.0.0.1:5060;branch=z9hG4bK776\r\n\
722            From: <sip:alice@example.com>;tag=123\r\n\
723            To: <sip:alice@example.com>;tag=456\r\n\
724            Call-ID: test-call-id\r\n\
725            CSeq: 1 REGISTER\r\n\
726            WWW-Authenticate: Digest realm=\"example.com\", nonce=\"abc123\"\r\n\
727            Content-Length: 0\r\n\
728            \r\n";
729
730        let msg = SipMessage::parse(input).unwrap();
731        if let SipMessage::Response(res) = &msg {
732            assert_eq!(res.status, StatusCode::UNAUTHORIZED);
733            assert!(res
734                .headers
735                .get(&HeaderName::WwwAuthenticate)
736                .is_some());
737        } else {
738            panic!("Expected response");
739        }
740    }
741}