Skip to main content

nautilus_network/transport/
mod.rs

1// -------------------------------------------------------------------------------------------------
2//  Copyright (C) 2015-2026 Nautech Systems Pty Ltd. All rights reserved.
3//  https://nautechsystems.io
4//
5//  Licensed under the GNU Lesser General Public License Version 3.0 (the "License");
6//  You may not use this file except in compliance with the License.
7//  You may obtain a copy of the License at https://www.gnu.org/licenses/lgpl-3.0.en.html
8//
9//  Unless required by applicable law or agreed to in writing, software
10//  distributed under the License is distributed on an "AS IS" BASIS,
11//  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12//  See the License for the specific language governing permissions and
13//  limitations under the License.
14// -------------------------------------------------------------------------------------------------
15
16//! Transport abstraction layer for WebSocket backends.
17//!
18//! Defines the backend-agnostic surface that higher layers in `nautilus-network`
19//! consume (the reconnecting client, auth tracker, subscription manager, and adapter
20//! crates):
21//!
22//! - [`Message`]: neutral WebSocket message enum.
23//! - [`TransportError`]: neutral error type.
24//! - [`WsTransport`]: `Stream` plus `Sink` trait for backend implementations.
25//!
26//! The `tokio-tungstenite` backend is always compiled (its conversions and adapter
27//! live in [`tungstenite`]). The `sockudo-ws` backend is gated behind the
28//! `transport-sockudo` feature and lives in the `sockudo` submodule; when enabled
29//! it can be selected at runtime via `WebSocketConfig.backend`.
30
31pub mod error;
32pub mod message;
33pub mod stream;
34pub mod tungstenite;
35
36#[cfg(feature = "transport-sockudo")]
37pub mod sockudo;
38
39pub use error::TransportError;
40pub use message::{CloseFrame, Message};
41pub use stream::{BoxedWsTransport, WsTransport};
42
43#[cfg(test)]
44mod tests {
45    use bytes::Bytes;
46    use proptest::prelude::*;
47    use rstest::rstest;
48    #[cfg(feature = "transport-sockudo")]
49    use sockudo_ws::protocol::Message as SockudoMessage;
50    use tokio_tungstenite::tungstenite::Message as TungsteniteMessage;
51
52    use super::*;
53
54    fn message_strategy() -> impl Strategy<Value = Message> {
55        prop_oneof![
56            any::<String>().prop_map(Message::text),
57            prop::collection::vec(any::<u8>(), 0..256).prop_map(Message::binary),
58            prop::collection::vec(any::<u8>(), 0..=125)
59                .prop_map(|bytes| Message::ping(Bytes::from(bytes))),
60            prop::collection::vec(any::<u8>(), 0..=125)
61                .prop_map(|bytes| Message::pong(Bytes::from(bytes))),
62            prop::option::of((any::<u16>(), any::<String>())).prop_map(|frame| {
63                Message::Close(frame.map(|(code, reason)| CloseFrame::new(code, reason)))
64            }),
65        ]
66    }
67
68    proptest! {
69        #[rstest]
70        fn message_conversions_round_trip(message in message_strategy()) {
71            let tungstenite = TungsteniteMessage::try_from(message.clone()).unwrap();
72            let tungstenite_round_trip = Message::from(tungstenite);
73            prop_assert_eq!(&tungstenite_round_trip, &message);
74
75            #[cfg(feature = "transport-sockudo")]
76            {
77                let sockudo = SockudoMessage::from(message.clone());
78                prop_assert_eq!(Message::from(sockudo), message);
79            }
80        }
81
82        #[rstest]
83        fn invalid_text_conversion_is_backend_specific(
84            bytes in prop::collection::vec(any::<u8>(), 1..256)
85                .prop_filter("invalid UTF-8", |bytes| std::str::from_utf8(bytes).is_err())
86        ) {
87            let message = Message::Text(Bytes::from(bytes));
88            let tungstenite = TungsteniteMessage::try_from(message.clone());
89
90            prop_assert!(matches!(tungstenite, Err(TransportError::InvalidUtf8)));
91
92            #[cfg(feature = "transport-sockudo")]
93            {
94                let sockudo = SockudoMessage::from(message.clone());
95                prop_assert_eq!(Message::from(sockudo), message);
96            }
97        }
98    }
99}