Skip to main content

btp/
lib.rs

1//! src/lib.rs
2
3//! # Welcome
4//! Btp is stands for 'Blog Transfer Protocol' which developed while developing this project.
5//! The protocol has a basic structure, a 'header' which consists of 10 bytes, and the rest of the message, which can be reach 4GiB.
6//!
7//! This protocol handles a basic transmission task between server and client.
8//! To add this project to repository
9//! ```bash
10//! cargo add btp
11//! ```
12//! or with TLS,
13//! ```bash
14//! cargo add btp --features tls
15//! ```
16//!
17//! After you added the library to project, It will be accessible under the crate name btp.
18//!
19//! # Examples
20//! Let me teach the structure with examples here;
21//!
22//!
23//! ## NO TLS
24//!
25//! Examples below will not use any safe transmission with TLS encryption,
26//! but bare and crystal clear transmission.
27//!
28//! ### Basic client example without TSL;
29//!
30//! ```no_run
31//! use btp::message::BtpMessage;
32//! use btp::socket::{BtpConfig, BtpSocket};
33//!
34//! #[tokio::main]
35//! async fn main() {
36//!     let address = "127.0.0.1:8080".parse().unwrap();
37//!     // First we created a basic client config, includes our server address,
38//!     // and the VERSION of the btp protocol used.
39//!     let client_conf = BtpConfig::from_addr(address);
40//!     // Connected to given address using conf, and awaited since it's an async connection.
41//!     let mut client_socket = BtpSocket::connect(client_conf).await.unwrap();
42//!
43//!     // Created a message as [&str] and converted it into request.
44//!     let body: &str = "Hello, BTP!";
45//!     let msg = BtpMessage::from_str(body);
46//!
47//!     // End sended to client (I guess)
48//!     client_socket.write_message(msg).await.unwrap();
49//! }
50//! ```
51//!
52//! ### Basic server example without TSL;
53//!
54//! ```no_run
55//! use btp::message::BtpMessage;
56//! use btp::socket::{BtpConfig, BtpSocket};
57//!
58//! #[tokio::main]
59//! async fn main() {
60//!     let address = "127.0.0.1:8080".parse().unwrap();
61//!     // First we created a basic client config, includes our server address,
62//!     // and the VERSION of the btp protocol used.
63//!     let client_conf = BtpConfig::from_addr(address);
64//!     // Connected to given address using conf, and awaited since it's an async connection.
65//!     let mut client_socket = BtpSocket::connect(client_conf).await.unwrap();
66//!
67//!     // Created a message as [&str] and converted it into request.
68//!     let body: &str = "Hello, BTP!";
69//!     let msg = BtpMessage::from_str(body);
70//!
71//!     // End sended to client (I guess)
72//!     client_socket.write_message(msg).await.unwrap();
73//! }
74//! ```
75
76pub mod client;
77pub mod message;
78pub mod server;
79pub mod socket;
80
81/// Actual current version of the protocol. Used in [`socket::BtpConfig`]
82pub const VERSION: [u8; 3] = [0, 1, 1];
83
84#[cfg(test)]
85mod tests {
86    use super::*;
87    use crate::message::{BtpFile, BtpMessage, StatusCode};
88    use crate::socket::{BtpConfig, BtpSocket};
89    use std::io::Write;
90    use std::net::SocketAddr;
91    use tempfile::tempdir;
92    use tokio::net::TcpListener;
93
94    fn make_config(addr: SocketAddr) -> BtpConfig {
95        BtpConfig { ver: VERSION, addr }
96    }
97
98    fn make_test_file(content: &str, name: &str) -> BtpFile {
99        let mut temp_file = tempfile::NamedTempFile::new().unwrap();
100        write!(temp_file, "{}", content).unwrap();
101        let file = temp_file.reopen().unwrap();
102        BtpFile::from([StatusCode::File as u8], VERSION, file, name.to_string()).unwrap()
103    }
104
105    mod tests {
106        use super::*;
107        use crate::server::BtpListener;
108        type Error = Box<dyn std::error::Error + Send + Sync>;
109        #[tokio::test]
110        async fn test_message_exchange_no_tls() -> Result<(), Error> {
111            let listener = TcpListener::bind("127.0.0.1:0").await?;
112            let server_addr = listener.local_addr()?;
113            let btp_conf = make_config(server_addr);
114            let server_task = tokio::spawn(async move {
115                let btp_listener = BtpListener::from(listener, btp_conf).await;
116                let mut server_socket = btp_listener.accept().await?;
117                let (_file_opt, msg_opt) = server_socket.read().await?;
118                let msg = msg_opt.ok_or("No message received")?;
119                assert_eq!(msg.body, "Hello, BTP!");
120                let response = BtpMessage::from_str("ACK");
121                server_socket.write_message(response).await?;
122
123                Ok::<_, Error>(())
124            });
125            let client_conf = make_config(server_addr);
126            let mut client_socket = BtpSocket::connect(client_conf).await?;
127
128            let request = BtpMessage::from_str("Hello, BTP!");
129            client_socket.write_message(request).await?;
130
131            let (_file_opt, msg_opt) = client_socket.read().await?;
132            let response = msg_opt.ok_or("No response received")?;
133            assert_eq!(response.body, "ACK");
134
135            server_task.await??;
136
137            Ok(())
138        }
139
140        #[tokio::test]
141        async fn test_file_transfer_no_tls() -> Result<(), Error> {
142            let tmp_dir = tempdir()?;
143            let file_path = tmp_dir.path().join("test.txt");
144            let original_content = "Hello, this is a test file.";
145            tokio::fs::write(&file_path, original_content).await?;
146
147            let listener = TcpListener::bind("127.0.0.1:0").await?;
148            let server_addr = listener.local_addr()?;
149            let btp_conf = make_config(server_addr);
150
151            let server_task = tokio::spawn(async move {
152                let btp_listener = BtpListener::from(listener, btp_conf).await;
153                let mut server_socket = btp_listener.accept().await?;
154
155                let (file_opt, _msg_opt) = server_socket.read().await?;
156                let file = file_opt.ok_or("No file received")?;
157                assert_eq!(file.file_content, original_content);
158
159                let ack = BtpMessage::from_str("FILE_RECEIVED");
160                server_socket.write_message(ack).await?;
161
162                Ok::<_, Error>(())
163            });
164
165            let client_conf = make_config(server_addr);
166            let mut client_socket = BtpSocket::connect(client_conf).await?;
167
168            let file = make_test_file(original_content, "test.txt");
169            client_socket.write_file(file).await?;
170
171            let (_file_opt, msg_opt) = client_socket.read().await?;
172            let ack = msg_opt.ok_or("No acknowledgment received")?;
173            assert_eq!(ack.body, "FILE_RECEIVED");
174
175            server_task.await??;
176
177            Ok(())
178        }
179
180        #[tokio::test]
181        async fn test_full_session_no_tls() -> Result<(), Error> {
182            let listener = TcpListener::bind("127.0.0.1:0").await?;
183            let server_addr = listener.local_addr()?;
184            let btp_conf = make_config(server_addr);
185
186            let server_task = tokio::spawn(async move {
187                let btp_listener = BtpListener::from(listener, btp_conf).await;
188                let server_socket = btp_listener.accept().await?;
189
190                server_socket.attach_handler(
191                    |msg: BtpMessage| {
192                        println!("Server received: {}", msg.body);
193                    },
194                    |_file: BtpFile| {
195                        println!("Server received a file");
196                    },
197                )?;
198
199                tokio::time::sleep(tokio::time::Duration::from_millis(100)).await;
200                Ok::<_, Error>(())
201            });
202
203            let client_conf = make_config(server_addr);
204            let mut client_socket = BtpSocket::connect(client_conf).await?;
205
206            let msg = BtpMessage::from_str("Hello, echo!");
207            client_socket.write_message(msg).await?;
208
209            tokio::time::sleep(tokio::time::Duration::from_millis(50)).await;
210
211            server_task.await??;
212
213            Ok(())
214        }
215    }
216
217    #[cfg(feature = "tls")]
218    mod tls_tests {
219
220        type Error = Box<dyn std::error::Error + Send + Sync>;
221        use crate::server::BtpListenerTls;
222
223        use super::*;
224        use rustls::ServerConfig;
225        use rustls::pki_types::{PrivateKeyDer, PrivatePkcs8KeyDer};
226        use std::sync::Arc;
227        use tokio_rustls::TlsConnector;
228
229        fn make_test_tls_config() -> (Arc<ServerConfig>, Vec<u8>) {
230            let certified = rcgen::generate_simple_self_signed(vec!["127.0.0.1".into()]).unwrap();
231
232            let cert_der = certified.cert.der().to_vec();
233
234            let server_cert = rustls::pki_types::CertificateDer::from(certified.cert.der().clone());
235
236            let key_der = PrivateKeyDer::Pkcs8(PrivatePkcs8KeyDer::from(
237                certified.signing_key.serialize_der(),
238            ));
239
240            let config = ServerConfig::builder()
241                .with_no_client_auth()
242                .with_single_cert(vec![server_cert], key_der)
243                .unwrap();
244
245            (Arc::new(config), cert_der)
246        }
247
248        #[tokio::test]
249        async fn test_message_exchange_with_tls() -> Result<(), Error> {
250            let listener = TcpListener::bind("127.0.0.1:0").await?;
251            let server_addr = listener.local_addr()?;
252            let btp_conf = make_config(server_addr);
253            let (tls_conf, cert_der) = make_test_tls_config();
254
255            let mut root_store = rustls::RootCertStore::empty();
256            root_store.add(cert_der.into()).unwrap();
257
258            let client_config = rustls::ClientConfig::builder()
259                .with_root_certificates(root_store)
260                .with_no_client_auth();
261
262            let server_task = tokio::spawn(async move {
263                let btp_listener = BtpListenerTls::from(listener, btp_conf, tls_conf).await;
264                let mut server_socket = btp_listener.accept().await?;
265
266                let (_file_opt, msg_opt) = server_socket.read().await?;
267                let msg = msg_opt.ok_or("No message received")?;
268                assert_eq!(msg.body, "Hello, TLS BTP!");
269
270                let response = BtpMessage::from_str("TLS ACK");
271                server_socket.write_message(response).await?;
272
273                Ok::<_, Error>(())
274            });
275
276            let connector = TlsConnector::from(Arc::new(client_config));
277            let client_conf = make_config(server_addr);
278            let mut client_socket =
279                BtpSocket::connect_tls(server_addr, connector, client_conf).await?;
280
281            let request = BtpMessage::from_str("Hello, TLS BTP!");
282            client_socket.write_message(request).await?;
283
284            let (_file_opt, msg_opt) = client_socket.read().await?;
285            let response = msg_opt.ok_or("No response received")?;
286            assert_eq!(response.body, "TLS ACK");
287
288            server_task.await??;
289
290            Ok(())
291        }
292
293        #[tokio::test]
294        async fn test_file_transfer_with_tls() -> Result<(), Error> {
295            let tmp_dir = tempdir()?;
296            let file_path = tmp_dir.path().join("tls_test.txt");
297            let original_content = "TLS file transfer test content.";
298            tokio::fs::write(&file_path, original_content).await?;
299
300            let listener = TcpListener::bind("127.0.0.1:0").await?;
301            let server_addr = listener.local_addr()?;
302            let btp_conf = make_config(server_addr);
303            let (tls_conf, cert_der) = make_test_tls_config();
304
305            let mut root_store = rustls::RootCertStore::empty();
306            root_store.add(cert_der.into()).unwrap();
307
308            let client_config = rustls::ClientConfig::builder()
309                .with_root_certificates(root_store)
310                .with_no_client_auth();
311
312            let server_task = tokio::spawn(async move {
313                let btp_listener = BtpListenerTls::from(listener, btp_conf, tls_conf).await;
314                let mut server_socket = btp_listener.accept().await?;
315
316                let (file_opt, _msg_opt) = server_socket.read().await?;
317                let file = file_opt.ok_or("No file received")?;
318                assert_eq!(file.file_content, original_content);
319
320                let ack = BtpMessage::from_str("TLS FILE RECEIVED");
321                server_socket.write_message(ack).await?;
322
323                Ok::<_, Error>(())
324            });
325
326            let connector = TlsConnector::from(Arc::new(client_config));
327            let client_conf = make_config(server_addr);
328            let mut client_socket =
329                BtpSocket::connect_tls(server_addr, connector, client_conf).await?;
330
331            let file = make_test_file(original_content, "tls_test.txt");
332            client_socket.write_file(file).await?;
333
334            let (_file_opt, msg_opt) = client_socket.read().await?;
335            let ack = msg_opt.ok_or("No acknowledgment received")?;
336            assert_eq!(ack.body, "TLS FILE RECEIVED");
337
338            server_task.await??;
339
340            Ok(())
341        }
342    }
343}