diff --git a/src/sip/message.rs b/src/sip/message.rs index 956fc73..e60f0ad 100644 --- a/src/sip/message.rs +++ b/src/sip/message.rs @@ -360,6 +360,29 @@ impl std::fmt::Display for Request { } } +impl Request { + /// Serialize the request to its on-the-wire byte representation. + /// + /// The request line and headers are ASCII; the body is appended verbatim. + /// A SIP message body is an opaque octet sequence (RFC 3261 §7.4) and may + /// contain arbitrary bytes — e.g. a binary ISUP part or an eCall MSD — so + /// it must not be forced through UTF-8. + /// + /// Prefer this over [`Display`](std::fmt::Display) / `to_string()` when + /// putting a message on the wire: `Display` renders the body with + /// [`String::from_utf8_lossy`], which is fine for logging but corrupts + /// non-UTF-8 bytes. + pub fn to_bytes(&self) -> Vec { + let head = format!( + "{} {} {}\r\n{}\r\n", + self.method, self.uri, self.version, self.headers + ); + let mut buf = head.into_bytes(); + buf.extend_from_slice(&self.body); + buf + } +} + impl std::convert::TryFrom> for Request { type Error = Error; fn try_from(bytes: Vec) -> Result { @@ -406,7 +429,7 @@ impl std::convert::From for String { impl std::convert::From for Vec { fn from(r: Request) -> Vec { - r.to_string().into_bytes() + r.to_bytes() } } @@ -507,6 +530,25 @@ impl std::fmt::Display for Response { } } +impl Response { + /// Serialize the response to its on-the-wire byte representation. + /// + /// See [`Request::to_bytes`]: the body is written verbatim because a SIP + /// body is an opaque octet sequence (RFC 3261 §7.4), not necessarily UTF-8. + pub fn to_bytes(&self) -> Vec { + let head = format!( + "{} {} {}\r\n{}\r\n", + self.version, + self.status_code.code(), + self.status_code.text(), + self.headers + ); + let mut buf = head.into_bytes(); + buf.extend_from_slice(&self.body); + buf + } +} + impl std::convert::TryFrom> for Response { type Error = Error; fn try_from(bytes: Vec) -> Result { @@ -553,7 +595,7 @@ impl std::convert::From for String { impl std::convert::From for Vec { fn from(r: Response) -> Vec { - r.to_string().into_bytes() + r.to_bytes() } } @@ -605,6 +647,19 @@ impl std::fmt::Display for SipMessage { } } +impl SipMessage { + /// Serialize the message to its on-the-wire byte representation. + /// + /// See [`Request::to_bytes`]. Transports should use this rather than + /// `to_string()` so binary bodies are preserved byte-for-byte. + pub fn to_bytes(&self) -> Vec { + match self { + SipMessage::Request(r) => r.to_bytes(), + SipMessage::Response(r) => r.to_bytes(), + } + } +} + impl std::convert::TryFrom> for SipMessage { type Error = Error; fn try_from(bytes: Vec) -> Result { @@ -673,7 +728,7 @@ impl std::convert::From for String { impl std::convert::From for Vec { fn from(m: SipMessage) -> Vec { - m.to_string().into_bytes() + m.to_bytes() } } @@ -767,6 +822,39 @@ mod tests { assert_eq!(resp.body, b"v=0\r\no=bob 2890844527 2890844527 IN IP4 192.0.2.4\r\ns=-\r\nc=IN IP4 192.0.2.4\r\nt=0 0\r\nm=audio 3456 RTP/AVP 0\r\na=rtpmap:0 PCMU/8000"); } + #[test] + fn request_binary_body_survives_serialization() { + // A SIP message body is an opaque octet payload (RFC 3261 §7.4), not + // necessarily UTF-8 text. e.g. an NG-eCall INVITE carries a binary + // ASN.1 PER-encoded MSD. Serialization to the wire must preserve every + // byte, including bytes that are invalid as UTF-8. + let mut req: Request = invite_request().try_into().unwrap(); + let binary_body: Vec = vec![0x00, 0x01, 0x80, 0xFF, 0xC0, 0xC1, 0xFE, 0x02, 0x7F, 0x90]; + req.body = binary_body.clone(); + + let wire: Vec = req.into(); + + assert!( + wire.ends_with(&binary_body), + "binary body corrupted during serialization: got tail {:?}", + &wire[wire.len().saturating_sub(binary_body.len() + 4)..] + ); + } + + #[test] + fn response_binary_body_survives_serialization() { + let mut resp: Response = ok_response().try_into().unwrap(); + let binary_body: Vec = vec![0x00, 0x80, 0xFF, 0xC0, 0xC1, 0xFE, 0x02]; + resp.body = binary_body.clone(); + + let wire: Vec = resp.into(); + + assert!( + wire.ends_with(&binary_body), + "binary body corrupted during serialization" + ); + } + #[test] fn response_has_multiple_via_headers() { let resp: Response = ok_response().try_into().unwrap(); diff --git a/src/transport/stream.rs b/src/transport/stream.rs index fdbeaea..4b2c556 100644 --- a/src/transport/stream.rs +++ b/src/transport/stream.rs @@ -136,8 +136,9 @@ impl Encoder for SipCodec { type Error = crate::Error; fn encode(&mut self, item: SipMessage, dst: &mut BytesMut) -> Result<()> { - let data = item.to_string(); - dst.extend_from_slice(data.as_bytes()); + // Use to_bytes() (not to_string()) so binary bodies are preserved + // byte-for-byte; a SIP body is opaque octets (RFC 3261 §7.4). + dst.extend_from_slice(&item.to_bytes()); Ok(()) } } @@ -264,7 +265,7 @@ pub async fn send_to_stream(write_half: &Mutex, msg: SipMessage) -> Result where W: AsyncWrite + Unpin + Send, { - send_raw_to_stream(write_half, msg.to_string().as_bytes()).await + send_raw_to_stream(write_half, &msg.to_bytes()).await } pub async fn send_raw_to_stream(write_half: &Mutex, data: &[u8]) -> Result<()> @@ -324,6 +325,29 @@ mod tests { .to_vec() } + // ── binary body ────────────────────────────────────────────────────────── + + #[test] + fn encode_preserves_binary_body() { + use crate::sip::{Request, SipMessage}; + // A SIP body is opaque octets (RFC 3261 §7.4); the stream codec must + // emit it byte-for-byte, including bytes that are invalid as UTF-8. + let mut req: Request = invite_bytes("").as_slice().try_into().unwrap(); + let binary_body: Vec = vec![0x00, 0x80, 0xFF, 0xC0, 0xC1, 0xFE, 0x01, 0x7F]; + req.body = binary_body.clone(); + + let mut codec = make_codec(); + let mut dst = BytesMut::new(); + codec + .encode(SipMessage::Request(req), &mut dst) + .expect("encode"); + + assert!( + dst.ends_with(&binary_body), + "stream codec corrupted binary body" + ); + } + // ── 基本解码 ────────────────────────────────────────────────────────────── #[test] diff --git a/src/transport/udp.rs b/src/transport/udp.rs index 74e3b92..d9bb95a 100644 --- a/src/transport/udp.rs +++ b/src/transport/udp.rs @@ -211,13 +211,15 @@ impl UdpConnection { Some(addr) => addr.get_socketaddr(), None => SipConnection::get_destination(&msg), }?; - let buf = msg.to_string(); + // Use to_bytes() (not to_string()) so binary bodies are preserved + // byte-for-byte; a SIP body is opaque octets (RFC 3261 §7.4). + let buf = msg.to_bytes(); - debug!(len=buf.len(), dest=%destination, src=%self.get_addr(), raw_message=buf, "udp send"); + debug!(len=buf.len(), dest=%destination, src=%self.get_addr(), raw_message=%msg, "udp send"); self.inner .conn - .send_to(buf.as_bytes(), destination) + .send_to(&buf, destination) .await .map_err(|e| { crate::Error::TransportLayerError(e.to_string(), self.get_addr().to_owned())