1use std::net::SocketAddr;
21use std::sync::Arc;
22use std::time::Duration;
23
24use bytes::Bytes;
25use futures_util::{SinkExt as _, StreamExt as _};
26use sipx_sip::error::{FramingError, ParseError};
27use sipx_sip::{Limits, Message, StreamParser, parse_datagram};
28use tokio::io::{AsyncRead, AsyncWrite};
29use tokio::sync::mpsc;
30use tokio_tungstenite::tungstenite::Message as Frame;
31use tokio_tungstenite::tungstenite::handshake::server::{ErrorResponse, Request, Response};
32use tokio_tungstenite::tungstenite::http::{HeaderMap, HeaderValue, StatusCode};
33use tokio_tungstenite::tungstenite::protocol::WebSocketConfig;
34use tokio_tungstenite::{WebSocketStream, accept_hdr_async_with_config, client_async_with_config};
35
36use crate::target::{ConnectionKey, TransportKind};
37use crate::tcp::Event;
38use crate::{ConnectionState, policy::ObservationHub};
39
40pub const SUBPROTOCOL: &str = "sip";
42
43const PROTOCOL_HEADER: &str = "sec-websocket-protocol";
45
46pub type Socket<S> = WebSocketStream<S>;
48
49#[derive(Debug, thiserror::Error)]
51#[non_exhaustive]
52pub enum WsError {
53 #[error("{peer} did not agree to the sip subprotocol (RFC 7118 §4.2)")]
59 Subprotocol {
60 peer: String,
62 },
63 #[error("websocket handshake with {peer}: {detail}")]
65 Handshake {
66 peer: String,
68 detail: String,
70 },
71}
72
73pub async fn connect<S>(
79 stream: S,
80 authority: &str,
81 path: &str,
82 secure: bool,
83) -> Result<Socket<S>, WsError>
84where
85 S: AsyncRead + AsyncWrite + Unpin,
86{
87 connect_with_limits(stream, authority, path, secure, &Limits::stream()).await
88}
89
90pub(crate) async fn connect_with_limits<S>(
92 stream: S,
93 authority: &str,
94 path: &str,
95 secure: bool,
96 limits: &Limits,
97) -> Result<Socket<S>, WsError>
98where
99 S: AsyncRead + AsyncWrite + Unpin,
100{
101 let failed = |detail: String| WsError::Handshake {
102 peer: authority.to_owned(),
103 detail,
104 };
105
106 let request =
107 upgrade_request(authority, path, secure).map_err(|error| failed(error.to_string()))?;
108
109 let (socket, response) =
110 client_async_with_config(request, stream, Some(websocket_config(limits)))
111 .await
112 .map_err(|error| failed(error.to_string()))?;
113
114 if !offers_sip(response.headers()) {
122 return Err(WsError::Subprotocol {
123 peer: authority.to_owned(),
124 });
125 }
126
127 Ok(socket)
128}
129
130fn upgrade_request(
144 authority: &str,
145 path: &str,
146 secure: bool,
147) -> Result<
148 tokio_tungstenite::tungstenite::http::Request<()>,
149 tokio_tungstenite::tungstenite::http::Error,
150> {
151 let scheme = if secure { "wss" } else { "ws" };
152 tokio_tungstenite::tungstenite::http::Request::builder()
153 .method("GET")
154 .uri(format!("{scheme}://{authority}{path}"))
155 .header("Host", authority)
156 .header("Connection", "Upgrade")
157 .header("Upgrade", "websocket")
158 .header("Sec-WebSocket-Version", "13")
159 .header(
160 "Sec-WebSocket-Key",
161 tokio_tungstenite::tungstenite::handshake::client::generate_key(),
162 )
163 .header(PROTOCOL_HEADER, SUBPROTOCOL)
164 .body(())
165}
166
167#[allow(clippy::result_large_err)]
171pub async fn accept<S>(stream: S, peer: SocketAddr) -> Result<Socket<S>, WsError>
172where
173 S: AsyncRead + AsyncWrite + Unpin,
174{
175 accept_with_limits(stream, peer, &Limits::stream()).await
176}
177
178#[allow(clippy::result_large_err)]
182pub(crate) async fn accept_with_limits<S>(
183 stream: S,
184 peer: SocketAddr,
185 limits: &Limits,
186) -> Result<Socket<S>, WsError>
187where
188 S: AsyncRead + AsyncWrite + Unpin,
189{
190 accept_hdr_async_with_config(
191 stream,
192 |request: &Request, mut response: Response| {
193 if !offers_sip(request.headers()) {
194 let mut refusal = ErrorResponse::new(Some(format!(
195 "this endpoint speaks the {SUBPROTOCOL} subprotocol only (RFC 7118 §4.2)"
196 )));
197 *refusal.status_mut() = StatusCode::BAD_REQUEST;
198 return Err(refusal);
199 }
200 response
203 .headers_mut()
204 .insert(PROTOCOL_HEADER, HeaderValue::from_static(SUBPROTOCOL));
205 Ok(response)
206 },
207 Some(websocket_config(limits)),
208 )
209 .await
210 .map_err(|error| WsError::Handshake {
211 peer: peer.to_string(),
212 detail: error.to_string(),
213 })
214}
215
216fn websocket_config(limits: &Limits) -> WebSocketConfig {
218 WebSocketConfig::default()
219 .max_message_size(Some(limits.max_message_bytes))
220 .max_frame_size(Some(limits.max_message_bytes))
221}
222
223#[must_use]
230pub fn invented_sent_by() -> String {
231 use rand::Rng;
232 let value: u64 = rand::rng().random();
233 format!("{value:016x}.invalid")
234}
235
236fn offers_sip(headers: &HeaderMap) -> bool {
240 headers
241 .get_all(PROTOCOL_HEADER)
242 .iter()
243 .filter_map(|value| value.to_str().ok())
244 .flat_map(|value| value.split(','))
245 .any(|token| token.trim().eq_ignore_ascii_case(SUBPROTOCOL))
246}
247
248#[allow(
251 clippy::too_many_arguments,
252 reason = "the generation travels beside the existing connection identity and pump policy"
253)]
254pub(crate) async fn dial<S>(
255 stream: S,
256 authority: &str,
257 key: ConnectionKey,
258 id: u64,
259 outgoing: mpsc::Receiver<Bytes>,
260 events: mpsc::Sender<Event>,
261 limits: Limits,
262 keepalive: Duration,
263 observations: Option<Arc<ObservationHub>>,
264 admission_generation: Option<u64>,
265 authenticated: bool,
266) where
267 S: AsyncRead + AsyncWrite + Unpin + Send + 'static,
268{
269 let secure = key.transport == TransportKind::Wss;
270 match connect_with_limits(stream, authority, key.ws_path(), secure, &limits).await {
273 Ok(socket) => {
274 crate::tcp::observe_ready(
275 observations.as_ref(),
276 &key,
277 id,
278 admission_generation,
279 authenticated,
280 );
281 pump(socket, key, id, outgoing, events, limits, keepalive).await;
282 }
283 Err(error) => {
284 tracing::warn!(%error, peer = %key.peer, "websocket handshake failed");
285 crate::tcp::observe_state(
286 observations.as_ref(),
287 &key,
288 id,
289 admission_generation,
290 ConnectionState::Failed,
291 );
292 }
293 }
294}
295
296pub(crate) async fn pump<S>(
298 socket: Socket<S>,
299 key: ConnectionKey,
300 id: u64,
301 mut outgoing: mpsc::Receiver<Bytes>,
302 events: mpsc::Sender<Event>,
303 limits: Limits,
304 keepalive: Duration,
305) where
306 S: AsyncRead + AsyncWrite + Unpin + Send + 'static,
307{
308 let (peer, transport) = (key.peer, key.transport);
309 let (mut sink, mut source) = socket.split();
310
311 let mut ping = tokio::time::interval(keepalive);
316 ping.set_missed_tick_behavior(tokio::time::MissedTickBehavior::Delay);
317 ping.tick().await;
318
319 loop {
320 tokio::select! {
321 frame = source.next() => {
322 let payload = match frame {
323 Some(Ok(Frame::Text(text))) => Bytes::from(text),
324 Some(Ok(Frame::Binary(data))) => data,
325 Some(Ok(Frame::Ping(_) | Frame::Pong(_) | Frame::Frame(_))) => continue,
327 Some(Ok(Frame::Close(_))) | None => break,
328 Some(Err(error)) => {
329 tracing::debug!(%error, %peer, "websocket read failed");
330 break;
331 }
332 };
333 match parse_one(payload, &limits) {
334 Ok(message) => {
335 if events
336 .send(Event::Message {
337 message: Box::new(message),
338 source: peer,
339 transport,
340 id,
341 #[cfg(feature = "quic")]
342 quic_reply: None,
343 })
344 .await
345 .is_err()
346 {
347 return;
348 }
349 }
350 Err(detail) => {
351 tracing::debug!(%peer, %detail, "closing on a malformed websocket message");
358 let _ = events.send(Event::FramingFailed { key: key.clone() }).await;
359 break;
360 }
361 }
362 },
363 Some(bytes) = outgoing.recv() => {
364 if sink.send(frame_for(bytes)).await.is_err() {
365 break;
366 }
367 }
368 _ = ping.tick() => {
369 if sink.send(Frame::Ping(Bytes::new())).await.is_err() {
370 break;
371 }
372 }
373 }
374 }
375
376 let _ = sink.close().await;
381}
382
383#[derive(Debug, thiserror::Error)]
390enum WsFramingError {
391 #[error(transparent)]
392 Sip(#[from] ParseError),
393 #[error(
394 "a WebSocket message carries exactly one SIP message (RFC 7118 §5); \
395 this one held {complete} complete and {trailing} octets of another"
396 )]
397 Shape { complete: usize, trailing: usize },
398}
399
400fn parse_one(frame: Bytes, limits: &Limits) -> Result<Message, WsFramingError> {
401 let mut parser = StreamParser::new(*limits);
406 match parser.push(&frame) {
407 Ok(mut messages) => {
408 let trailing = parser.pending();
409 if trailing == 0
410 && messages.len() == 1
411 && let Some(message) = messages.pop()
412 {
413 return Ok(message);
414 }
415 Err(WsFramingError::Shape {
416 complete: messages.len(),
417 trailing,
418 })
419 }
420 Err(ParseError::Framing(FramingError::ContentLengthRequired)) => {
425 parse_datagram(frame, limits).map_err(WsFramingError::from)
426 }
427 Err(error) => Err(error.into()),
428 }
429}
430
431fn frame_for(bytes: Bytes) -> Frame {
437 match tokio_tungstenite::tungstenite::Utf8Bytes::try_from(bytes.clone()) {
438 Ok(text) => Frame::Text(text),
439 Err(_) => Frame::Binary(bytes),
440 }
441}
442
443#[cfg(test)]
444#[allow(
445 clippy::unwrap_used,
446 clippy::expect_used,
447 clippy::panic,
448 clippy::indexing_slicing
449)]
450mod tests {
451 use super::*;
452
453 const OPTIONS: &str = "OPTIONS sip:a@b.com SIP/2.0\r\n\
454 Via: SIP/2.0/WS df7jal23ls0d.invalid;branch=z9hG4bKx\r\n\
455 To: <sip:a@b.com>\r\n\
456 From: <sip:c@d.net>;tag=1\r\n\
457 Call-ID: x@y\r\n\
458 CSeq: 1 OPTIONS\r\n\
459 Content-Length: 0\r\n\r\n";
460
461 fn parse(text: &str) -> Result<Message, WsFramingError> {
462 parse_one(Bytes::copy_from_slice(text.as_bytes()), &Limits::stream())
463 }
464
465 #[test]
466 fn one_message_in_one_frame_is_parsed() {
467 parse(OPTIONS).expect("one message in one frame");
468 }
469
470 #[test]
473 fn a_message_split_across_frames_is_malformed() {
474 let half = &OPTIONS[..OPTIONS.len() / 2];
475 let error = parse(half).expect_err("half a message is not a message");
476 assert!(error.to_string().contains("exactly one"), "{error}");
477 }
478
479 #[test]
482 fn two_messages_in_one_frame_are_malformed() {
483 let error = parse(&format!("{OPTIONS}{OPTIONS}")).expect_err("two is not one");
484 assert!(error.to_string().contains("exactly one"), "{error}");
485 }
486
487 #[test]
490 fn octets_after_the_message_are_malformed() {
491 parse(&format!("{OPTIONS}garbage"))
492 .expect_err("a frame holds one message and nothing else");
493 }
494
495 #[test]
498 fn a_message_without_content_length_is_accepted() {
499 let without = OPTIONS.replace("Content-Length: 0\r\n", "");
500 parse(&without).expect("the frame says where it ends");
501 }
502
503 #[test]
504 fn a_frame_holding_nothing_like_sip_is_refused() {
505 parse("hello").expect_err("not a SIP message");
506 }
507
508 const RFC3261_FRAMING_PATHS: [TransportKind; 5] = [
512 TransportKind::Udp,
513 TransportKind::Tcp,
514 TransportKind::Tls,
515 TransportKind::Ws,
516 TransportKind::Wss,
517 ];
518
519 fn body_limit_refusal(path: TransportKind, frame: &[u8], limits: &Limits) -> ParseError {
520 match path {
521 TransportKind::Udp => parse_datagram(Bytes::copy_from_slice(frame), limits)
522 .expect_err("the datagram body limit must refuse"),
523 TransportKind::Tcp | TransportKind::Tls => {
524 let mut parser = StreamParser::new(*limits);
525 parser
526 .push(frame)
527 .expect_err("the stream body limit must refuse")
528 }
529 TransportKind::Ws | TransportKind::Wss => {
530 match parse_one(Bytes::copy_from_slice(frame), limits)
531 .expect_err("the WebSocket body limit must refuse")
532 {
533 WsFramingError::Sip(error) => error,
534 error @ WsFramingError::Shape { .. } => {
535 panic!("the SIP limit must run before frame-shape handling: {error}")
536 }
537 }
538 }
539 TransportKind::Quic => {
540 panic!(
541 "QUIC is bounded by its one-stream/one-message reader, not this RFC 3261 table"
542 )
543 }
544 }
545 }
546
547 async fn handshaken_pair(
548 secure: bool,
549 client_limits: Limits,
550 server_limits: Limits,
551 ) -> (
552 Socket<tokio::io::DuplexStream>,
553 Socket<tokio::io::DuplexStream>,
554 ) {
555 let (client_io, server_io) = tokio::io::duplex(4096);
556 let peer = "127.0.0.1:5060".parse().expect("a peer address");
557 let (client, server) = tokio::join!(
558 connect_with_limits(client_io, "example.com", "/", secure, &client_limits),
559 accept_with_limits(server_io, peer, &server_limits),
560 );
561 (
562 client.expect("the client handshake completes"),
563 server.expect("the server handshake completes"),
564 )
565 }
566
567 fn limits_with_message_bound(max_message_bytes: usize) -> Limits {
568 Limits {
569 max_message_bytes,
570 max_body_bytes: max_message_bytes,
571 ..Limits::stream()
572 }
573 }
574
575 fn assert_message_too_long(
576 error: &tokio_tungstenite::tungstenite::Error,
577 expected_size: usize,
578 expected_limit: usize,
579 ) {
580 use tokio_tungstenite::tungstenite::error::CapacityError;
581 assert!(
582 matches!(
583 error,
584 tokio_tungstenite::tungstenite::Error::Capacity(
585 CapacityError::MessageTooLong { size, max_size }
586 ) if *size == expected_size && *max_size == expected_limit
587 ),
588 "the handshake did not install the configured decoder bound: {error}"
589 );
590 }
591
592 #[tokio::test]
596 async fn client_handshake_holds_the_frame_bound_for_ws_and_wss() {
597 const HELD: usize = 32;
598 const SENT: usize = 64;
599 for secure in [false, true] {
600 let (mut client, mut server) = handshaken_pair(
601 secure,
602 limits_with_message_bound(HELD),
603 limits_with_message_bound(256),
604 )
605 .await;
606 assert_eq!(client.get_config().max_frame_size, Some(HELD));
607 assert_eq!(client.get_config().max_message_size, Some(HELD));
608
609 server
610 .send(Frame::binary(vec![0; SENT]))
611 .await
612 .expect("the permissive peer sends the probe");
613 let error = client
614 .next()
615 .await
616 .expect("the probe has a decoder outcome")
617 .expect_err("an oversized frame is refused before SIP parsing");
618 assert_message_too_long(&error, SENT, HELD);
619 }
620 }
621
622 #[tokio::test]
626 async fn server_handshake_holds_the_frame_bound_for_ws_and_wss() {
627 const HELD: usize = 32;
628 const SENT: usize = 64;
629 for secure in [false, true] {
630 let (mut client, mut server) = handshaken_pair(
631 secure,
632 limits_with_message_bound(256),
633 limits_with_message_bound(HELD),
634 )
635 .await;
636 assert_eq!(server.get_config().max_frame_size, Some(HELD));
637 assert_eq!(server.get_config().max_message_size, Some(HELD));
638
639 client
640 .send(Frame::binary(vec![0; SENT]))
641 .await
642 .expect("the permissive peer sends the probe");
643 let error = server
644 .next()
645 .await
646 .expect("the probe has a decoder outcome")
647 .expect_err("an oversized frame is refused before SIP parsing");
648 assert_message_too_long(&error, SENT, HELD);
649 }
650 }
651
652 #[test]
656 fn pre_allocation_body_and_frame_bounds_hold_on_every_framing_path() {
657 let limits = Limits {
658 max_message_bytes: 256,
659 max_body_bytes: 4,
660 ..Limits::stream()
661 };
662 let frame = b"MESSAGE sip:a@b SIP/2.0\r\nContent-Length: 5\r\n\r\n";
663
664 for path in RFC3261_FRAMING_PATHS {
665 assert_eq!(
666 body_limit_refusal(path, frame, &limits),
667 ParseError::Limit {
668 limit: sipx_sip::error::LimitKind::BodyBytes,
669 value: 5,
670 },
671 "{path:?} did not return the typed body-size refusal"
672 );
673
674 if matches!(path, TransportKind::Ws | TransportKind::Wss) {
675 let config = websocket_config(&limits);
676 assert_eq!(
677 config.max_frame_size,
678 Some(limits.max_message_bytes),
679 "{path:?} would allocate an oversized frame before SIP parsing"
680 );
681 assert_eq!(
682 config.max_message_size,
683 Some(limits.max_message_bytes),
684 "{path:?} would assemble an oversized fragmented message"
685 );
686 }
687 }
688 }
689
690 #[test]
695 fn body_length_disagreement_is_typed_or_bounded_on_every_framing_path() {
696 let limits = Limits {
697 max_message_bytes: 256,
698 max_body_bytes: 16,
699 ..Limits::stream()
700 };
701 let prefix = b"MESSAGE sip:a@b SIP/2.0\r\nContent-Length: 4\r\n\r\n";
702 let mut short = prefix.to_vec();
703 short.extend_from_slice(b"abc");
704 let mut long = prefix.to_vec();
705 long.extend_from_slice(b"abcde");
706
707 for path in RFC3261_FRAMING_PATHS {
708 match path {
709 TransportKind::Udp => {
710 assert!(
711 matches!(
712 parse_datagram(Bytes::copy_from_slice(&short), &limits),
713 Err(ParseError::Framing(FramingError::BodyTruncated))
714 ),
715 "UDP did not type the short-body refusal"
716 );
717 let message = parse_datagram(Bytes::copy_from_slice(&long), &limits)
718 .expect("UDP ignores octets beyond the declared body");
719 assert_eq!(message.body().as_ref(), b"abcd");
720 }
721 TransportKind::Tcp | TransportKind::Tls => {
722 let mut short_parser = StreamParser::new(limits);
723 assert!(
724 short_parser.push(&short).expect("bounded wait").is_empty(),
725 "{path:?} read a short body as complete"
726 );
727 assert_eq!(
728 short_parser.pending(),
729 3,
730 "{path:?} did not bound pending input"
731 );
732
733 let mut long_parser = StreamParser::new(limits);
734 let messages = long_parser.push(&long).expect("one complete message");
735 assert_eq!(
736 messages.len(),
737 1,
738 "{path:?} did not frame exactly one message"
739 );
740 assert_eq!(messages[0].body().as_ref(), b"abcd");
741 assert_eq!(
742 long_parser.pending(),
743 1,
744 "{path:?} read the next message's byte into this body"
745 );
746 }
747 TransportKind::Ws | TransportKind::Wss => {
748 assert!(
749 matches!(
750 parse_one(Bytes::copy_from_slice(&short), &limits),
751 Err(WsFramingError::Shape {
752 complete: 0,
753 trailing: 3
754 })
755 ),
756 "{path:?} did not type the short-frame refusal"
757 );
758 assert!(
759 matches!(
760 parse_one(Bytes::copy_from_slice(&long), &limits),
761 Err(WsFramingError::Shape {
762 complete: 1,
763 trailing: 1
764 })
765 ),
766 "{path:?} accepted bytes beyond the declared body"
767 );
768 }
769 TransportKind::Quic => {
770 panic!(
771 "QUIC is bounded by its one-stream/one-message reader, not this RFC 3261 table"
772 )
773 }
774 }
775 }
776 }
777
778 #[test]
779 fn a_sip_message_travels_as_text() {
780 assert!(matches!(
781 frame_for(Bytes::copy_from_slice(OPTIONS.as_bytes())),
782 Frame::Text(_)
783 ));
784 }
785
786 #[test]
789 fn a_binary_body_travels_as_binary() {
790 assert!(matches!(
791 frame_for(Bytes::from_static(b"MESSAGE sip:a SIP/2.0\r\n\r\n\xff\xfe")),
792 Frame::Binary(_)
793 ));
794 }
795
796 #[test]
797 fn the_subprotocol_is_found_however_it_is_offered() {
798 let mut headers = HeaderMap::new();
799 headers.append(PROTOCOL_HEADER, HeaderValue::from_static("sip"));
800 assert!(offers_sip(&headers));
801
802 let mut listed = HeaderMap::new();
803 listed.append(PROTOCOL_HEADER, HeaderValue::from_static("chat, SIP, echo"));
804 assert!(
805 offers_sip(&listed),
806 "comma-separated, and case is not part of it"
807 );
808
809 let mut repeated = HeaderMap::new();
810 repeated.append(PROTOCOL_HEADER, HeaderValue::from_static("chat"));
811 repeated.append(PROTOCOL_HEADER, HeaderValue::from_static("sip"));
812 assert!(offers_sip(&repeated), "repeated headers are one list");
813
814 let mut other = HeaderMap::new();
815 other.append(PROTOCOL_HEADER, HeaderValue::from_static("chat, sipx"));
816 assert!(!offers_sip(&other), "a longer token is a different token");
817
818 assert!(
819 !offers_sip(&HeaderMap::new()),
820 "offering none is not offering sip"
821 );
822 }
823
824 #[test]
828 fn the_upgrade_asks_for_the_resource_it_was_given() {
829 let request = upgrade_request("127.0.0.1:8088", "/ws", false).expect("a request");
830 assert_eq!(request.uri().to_string(), "ws://127.0.0.1:8088/ws");
831 assert_eq!(request.uri().path(), "/ws");
832 assert_eq!(
835 request.headers().get("Host").expect("a Host"),
836 "127.0.0.1:8088"
837 );
838 }
839
840 #[test]
841 fn the_root_is_still_what_a_caller_naming_nothing_asks_for() {
842 let request = upgrade_request("127.0.0.1:5060", "/", false).expect("a request");
843 assert_eq!(request.uri().to_string(), "ws://127.0.0.1:5060/");
844 }
845
846 #[test]
848 fn a_secure_upgrade_keeps_the_resource() {
849 let request = upgrade_request("sipx.test:443", "/ws", true).expect("a request");
850 assert_eq!(request.uri().to_string(), "wss://sipx.test:443/ws");
851 }
852
853 #[test]
856 fn a_query_string_survives_the_handshake() {
857 let request = upgrade_request("127.0.0.1:8088", "/ws?token=abc", false).expect("a request");
858 assert_eq!(request.uri().path(), "/ws");
859 assert_eq!(request.uri().query(), Some("token=abc"));
860 }
861
862 #[test]
865 fn an_invented_sent_by_is_unresolvable_and_unique() {
866 let one = invented_sent_by();
867 assert!(one.ends_with(".invalid"), "{one}");
868 assert_ne!(one, invented_sent_by());
869 }
870}