flnet/lib.rs
1//! # FLNet - sending data over WebRTC in libc and wasm
2//!
3//! FLNet is used in fledger to communicate between browsers.
4//! It uses the WebRTC protocol and a signalling-server with websockets.
5//! The outstanding feature of this implementation is that it works
6//! both for WASM and libc with the same interface.
7//! This allows to write the core code once, and then re-use it for both WASM and libc.
8//!
9//! ## WebRTC in short
10//!
11//! WebRTC is an amazing technique that has been invented to let browsers exchange
12//! data without a third party server.
13//! There are many protocols involved in making this happen, and the first use-case
14//! of WebRTC was to share video and audio directly between two browsers.
15//! But WebRTC also allows for a 'data' channel where you can send any data you want.
16//!
17//! For WebRTC to work, the following is needed:
18//!
19//! - a WebRTC implementation on both ends: WebRTC exists for browsers, node, and since
20//! the end of 2021 also as a rust-crate
21//! - a [signalling server](https://crates.io/crates/flsignal)
22//! for the initial setup: as most browsers are behind a router,
23//! it is not possible to connect directly without some help. The signalling server
24//! is needed to exchange the information for setting up the communication
25//! - a STUN server: for WebRTC to work also behind a NAT, somebody needs to tell the
26//! browser which address it has. The STUN server does amazing things like
27//! poking holes through firewalls, but this doesn't always work, this is why you
28//! need an optional
29//! - a TURN server: if WebRTC doesn't manage to connect two WebRTC endpoints with each
30//! other, the last resort is using a TURN server. This is the old-school way of connecting
31//! two endpoints using a server that forwards traffic in both directions
32//!
33//! Here is a
34//! [docker-compose.signalling.yaml](https://github.com/ineiti/fledger/tree/0.7.0/examples/docker-compose/docker-compose.signalling.yaml)
35//! ready to run the `flsignal` and [coturn](https://github.com/coturn/coturn).
36//!
37//! For a more in-depth presentation, including some pitfalls, I liked this article:
38//! [What is WebRTC and how to avoid its 3 deadliest pitfalls](https://www.mindk.com/blog/what-is-webrtc-and-how-to-avoid-its-3-deadliest-pitfalls/)
39//!
40//! ## Example client/server
41//!
42//! Here is a short example of how two nodes can communicate with each other. First of all
43//! you need a locally runing signalling server that you can spin up with:
44//!
45//! ```bash
46//! cd cli; cargo run --bin flsignal -- -vv
47//! ```
48//!
49//! For more debugging, it is sometimes useful to add `-vvv`, or even `-vvvv`, but then
50//! it gets very verbose!
51//!
52//! Here is a small example of how to setup a server that listens to messages from clients.
53//!
54//! ```bash
55//! // The server waits for connections by the clients and prints the messages received.
56//! async fn server() -> Result<(), BrokerError> {
57//! // Create a random node-configuration. It uses serde for easy serialization.
58//! let nc = flnet::config::NodeConfig::new();
59//! // Connect to the signalling server and wait for connection requests.
60//! let mut net = flnet::network_start(nc.clone(), "ws://localhost:8765")
61//! .await
62//! .expect("Starting network");
63//!
64//! // Print our ID so it can be copied to the server
65//! log::info!("Our ID is: {:?}", nc.info.get_id());
66//! loop {
67//! // Wait for messages and print them to stdout.
68//! log::info!("Got message: {:?}", net.recv().await);
69//! }
70//! }
71//! ```
72//!
73//! It is contacted by the client node, which needs the ID of the server to know where
74//! to connect to:
75//!
76//! ```bash
77//! // A client sends a single message to a server and then disconnects.
78//! async fn client(server_id: &str) -> Result<(), BrokerError> {
79//! // Create a random node-configuration. It uses serde for easy serialization.
80//! let nc = flnet::config::NodeConfig::new();
81//! // Connect to the signalling server and wait for connection requests.
82//! let mut net = flnet::network_start(nc.clone(), "ws://localhost:8765")
83//! .await
84//! .expect("Starting network");
85//!
86//! // Need to get the client-id from the message in client()
87//! let server_id = U256::from_str(server_id).expect("get client id");
88//! log::info!("Server id is {server_id:?}");
89//! // This sends the message by setting up a connection using the signalling server.
90//! // The client must already be running and be registered with the signalling server.
91//! // Using `SendNodeMessage` will set up a connection using the signalling server, but
92//! // in the best case, the signalling server will not be used anymore afterwards.
93//! net.send_msg(server_id, "ping".into())?;
94//!
95//! // Wait for the connection to be set up and the message to be sent.
96//! wait_ms(1000).await;
97//!
98//! Ok(())
99//! }
100//! ```
101//!
102//! You can find this code and more examples in the examples directory here:
103//! <https://github.com/ineiti/fledger/tree/0.7.0/examples>
104//! Other examples include a full example with a shared code, a libc and a wasm implementation:
105//! [Ping-Pong](https://github.com/ineiti/fledger/tree/0.7.0/examples/ping-pong).
106//!
107//! ## Features
108//!
109//! FLNet has two features to choose between WASM and libc implementation.
110//! Unfortunately it is not detected automatically yet.
111//! If you write code that is shared between the two, you can add
112//! `flnet` without any features to your `Cargo.toml`.
113//! The appropriate feature will then be added once you compile the
114//! final code.
115//!
116//! The `wasm` feature enables the WASM backend, which only
117//! includes the WebRTC connections and the signalling client:
118//!
119//! ```cargo.toml
120//! flnet = {features = ["wasm"], version = "0.7"}
121//! ```
122//!
123//! With the `libc` feature, the libc backend is enabled,
124//! which has the WebRTC connections, and both the signalling client
125//! and server part:
126//!
127//! ```cargo.toml
128//! flnet = {features = ["libc"], version = "0.7"}
129//! ```
130//!
131//! ## Cross-platform usage
132//!
133//! If you build a software that will run both in WASM and with libc,
134//! one way to do so is to use [trunk](https://trunkrs.dev/), which allows
135//! you to write the wasm part very similar to the libc part.
136//!
137//! In fledger, this is accomplished with the following structure:
138//!
139//! ```text
140//! flbrowser fledger-cli
141//! \ /
142//! shared
143//! |
144//! flnet
145//! ```
146//!
147//! The `shared` code only depends on the `flnet` crate without a given feature.
148//! It is common between the WASM and the libc implementation.
149//! Only the `flbrowser` and `fledger-cli` depend on the `flnet` crate with the
150//! appropriate feature set.
151//!
152//! You can find an example in the [ping-pong](https://github.com/ineiti/fledger/tree/0.7.0/examples/ping-pong/) directory.
153//!
154//! ## libc
155//!
156//! The libc code is made possible by the excellent [webrtc](https://crates.io/crates/webrtc) library.
157//! The `libc` feature enables both the WebSocket-server and the WebRTC for libc-systems.
158//!
159//! ## wasm
160//!
161//! This feature implements the `WebSocket` and `WebRTC` trait for the wasm
162//! platform.
163//! It can be used for the browser or for node.
164
165use thiserror::Error;
166
167pub mod config;
168pub mod network;
169pub mod signal;
170pub mod web_rtc;
171pub mod websocket;
172
173use crate::{config::NodeConfig, network::NetworkMessage};
174
175pub use flmodules::broker;
176pub use flmodules::nodeids::{NodeID, NodeIDs, U256};
177
178#[derive(Error, Debug)]
179/// Errors when setting up a new network connection
180pub enum NetworkSetupError {
181 /// Missing wasm or libc feature
182 #[error("No libc or wasm feature given")]
183 NoFeature,
184 /// Something went wrong with the broker initialization
185 #[error(transparent)]
186 Broker(#[from] flmodules::broker::BrokerError),
187 /// Problem in the websocket client
188 #[error(transparent)]
189 WebSocketClient(#[from] websocket::WSClientError),
190 #[cfg(feature = "libc")]
191 /// Problem in the websocket server
192 #[error(transparent)]
193 WebSocketServer(#[from] websocket::WSSError),
194 /// Generic network error
195 #[error(transparent)]
196 Network(#[from] network::NetworkError),
197}
198
199#[cfg(feature = "testing")]
200pub mod testing;
201
202#[cfg(all(feature = "libc", feature = "wasm"))]
203std::compile_error!("flnet cannot have 'libc' and 'wasm' feature simultaneously");
204
205#[cfg(feature = "libc")]
206mod arch {
207 mod libc;
208 pub use libc::*;
209}
210
211#[cfg(feature = "wasm")]
212mod arch {
213 mod wasm;
214 pub use wasm::*;
215}
216
217#[cfg(any(feature = "libc", feature = "wasm"))]
218pub use arch::*;
219
220/// Starts a new [`broker::Broker<NetworkMessage>`] with a given `node`- and `connection`-configuration.
221/// This returns a raw broker which is mostly suited to connect to other brokers.
222/// If you need an easier access to the WebRTC network, use [`network_start`], which returns
223/// a structure with a more user-friendly API.
224///
225/// # Example
226///
227/// ```bash
228/// async fn start_network() -> Result<(), NetworkSetupError>{
229/// let net = network_broker_start();
230/// }
231/// ```
232pub async fn network_broker_start(
233 node: NodeConfig,
234 connection: config::ConnectionConfig,
235) -> Result<broker::Broker<NetworkMessage>, NetworkSetupError> {
236 #[cfg(any(feature = "libc", feature = "wasm"))]
237 {
238 use crate::{network::NetworkBroker, web_rtc::WebRTCConn};
239
240 let ws = web_socket_client::WebSocketClient::connect(&connection.signal()).await?;
241 let webrtc = WebRTCConn::new(web_rtc_setup::web_rtc_spawner(connection)).await?;
242 Ok(NetworkBroker::start(node.clone(), ws, webrtc).await?)
243 }
244 #[cfg(not(any(feature = "libc", feature = "wasm")))]
245 {
246 log::error!("Couldn't connect node {node:?} to {connection:?}");
247 Err(NetworkSetupError::NoFeature)
248 }
249}
250
251/// Starts a new connection to the signalling server using the `node`-
252/// and `connection`-configuration.
253/// It returns a user-friendly API that can be used to send and
254/// receive messages.
255/// If you want to connect the network with other brokers, then use
256/// the [`network_broker_start`] method.
257pub async fn network_start(
258 node: NodeConfig,
259 connection: config::ConnectionConfig,
260) -> Result<network::Network, NetworkSetupError> {
261 #[cfg(any(feature = "libc", feature = "wasm"))]
262 {
263 let net_broker = network_broker_start(node, connection).await?;
264 Ok(network::Network::start(net_broker).await?)
265 }
266 #[cfg(not(any(feature = "libc", feature = "wasm")))]
267 {
268 log::error!("Couldn't connect node {node:?} to {connection:?}");
269 Err(NetworkSetupError::NoFeature)
270 }
271}