Is this the optimal way to communicate over network in Rust? [closed]
I'm converting a library from C to Rust and I want to see if this is the optimal way to implement network communication. This code is just being used to test the library, but I plan to implement the internal messaging system with the same send_message and receive_message functions. I want to use only safe Rust. Some points: The Socket type is an enum that can be either TcpStream or UdpSocket and the send and receive functions are wrappers around the respective raw operations for each protocol The need to support both protocols is why I'm not using buffered operations like BufReader Messages are null terminated because they are unknown length Segment size is set to maximum TCP segment payload preferring unused UDP bytes to TCP fragmentation, and based on IPv6 for the same reason because max payload is lower than IPv4 Some concerns: The buffer resizing in the send loop seems inefficient but was done for two reasons Passing a reference to a Vec slice sizes the slice based on initialized length not allocated capacity, which I'm told is to prevent undefined behavior in the event that something were to try to read from the slice Converting the receive buffer to a string without resizing to received length results in a string with length of the full buffer size padded with null characters This is notably different from C in that I could pass an allocated but uninitialized array of bytes to the receive function confident that the buffer would only be written to and not read from, and any additional string manipulation would rely on the standard null termination Clearing the buffer after receiving and concatenating the message string seems unnecessary but safer pub enum Socket { Tcp (TcpStream), Udp (UdpSocket), } impl Socket { pub fn send(&mut self, data: &[u8]) -> Result { match self { Self::Tcp(ref mut stream) => stream.write(data), Self::Udp(socket) => socket.send(data), } } pub fn receive(&mut self, buffer: &mut [u8]) -> Result { match self { Self::Tcp(ref mut stream) => stream.read(buffer), Self::Udp(socket) => socket.recv(buffer), } } } const SERVER_ADDRESS: &str = "127.0.0.1:8080"; const VALIDATION_FROM_SERVER: &str = "abcde\0"; const VALIDATION_FROM_CLIENT: &str = "12345\0"; const SEGMENT_SIZE: usize = 1440; // Max IPv6 payload (TCP @ 1440 < UDP @ 1452) #[test] fn unauthenticated_tcp() { let server = thread::spawn(unauthenticated_tcp_server); let client = thread::spawn(unauthenticated_tcp_client); server.join().unwrap(); client.join().unwrap(); } fn send_message(socket: &mut Socket, message: &str) -> Result { let mut sent: usize = 0; loop { let chunk = &message[sent..(sent + std::cmp::min(message.len() - sent, SEGMENT_SIZE))]; match socket.send(chunk.as_bytes()) { Ok(length) => { sent += length; if sent == message.len() { break Ok(sent) } }, Err(_) => todo!(), }; } } fn receive_message(socket: &mut Socket) -> Result { let mut buffer: Vec = Vec::with_capacity(SEGMENT_SIZE); let mut message: String = String::with_capacity(SEGMENT_SIZE); loop { buffer.resize(SEGMENT_SIZE, 0); match socket.receive(&mut buffer) { Ok(length) => { buffer.resize(length, 0); let buffer_string = match std::str::from_utf8(&buffer) { Ok(string) => string, Err(_) => todo!(), }; message.push_str(buffer_string); match buffer.get(length - 1) { Some(&0) => return Ok(message), Some(_) => buffer.clear(), None => todo!(), }; }, Err(_) => todo!(), } } } fn unauthenticated_tcp_server() -> () { let listener = match TcpListener::bind(SERVER_ADDRESS) { Ok(listener) => listener, Err(error) => panic!("Could not bind listener: {error:?}") }; let mut session = match listener.accept() { Ok((socket, _)) => server::negotiate_session(Socket::Tcp(socket)), Err(error) => panic!("Could not get client: {error:?}") }; let sent = send_message(&mut session.stream.socket, VALIDATION_FROM_SERVER); assert!(sent.unwrap() == VALIDATION_FROM_SERVER.len()); let received = match receive_message(&mut session.stream.socket) { Ok(message) => message, Err(_) => panic!(), }; assert!(received == VALIDATION_FROM_CLIENT); } fn unauthenticated_tcp_client () -> () { let socket = loop { match TcpStream::connect(SERVER_ADDRESS) { Ok(socket) => break socket, Err(_) => thread::sleep(Duration::from_millis(10)), } }; let mut session = client::negotiate_session(Socket::Tcp(socket)); let received = match receive_message(&mut session.stream.socket) { Ok(message) => message, Err(_) => panic!(), }; assert!(received == VALIDATION_FROM_SERVER); let sent = send_message(&mut session.stream.socket, VALIDATION_FROM_CLIENT); assert!(sent.unwrap() == VALIDATION_FROM_CLIENT.len()); }
![Is this the optimal way to communicate over network in Rust? [closed]](https://cdn.sstatic.net/Sites/softwareengineering/Img/apple-touch-icon@2.png?v=1ef7363febba)
I'm converting a library from C
to Rust
and I want to see if this is the optimal way to implement network communication. This code is just being used to test the library, but I plan to implement the internal messaging system with the same send_message
and receive_message
functions.
I want to use only safe
Rust.
Some points:
- The
Socket
type is anenum
that can be eitherTcpStream
orUdpSocket
and thesend
andreceive
functions are wrappers around the respective raw operations for each protocol - The need to support both protocols is why I'm not using buffered operations like
BufReader
- Messages are null terminated because they are unknown length
- Segment size is set to maximum TCP segment payload preferring unused UDP bytes to TCP fragmentation, and based on
IPv6
for the same reason because max payload is lower thanIPv4
Some concerns:
- The buffer resizing in the send loop seems inefficient but was done for two reasons
- Passing a reference to a
Vec
slice sizes the slice based on initialized length not allocated capacity, which I'm told is to prevent undefined behavior in the event that something were to try to read from the slice - Converting the receive buffer to a string without resizing to received length results in a string with length of the full buffer size padded with
null
characters - This is notably different from
C
in that I could pass an allocated but uninitialized array of bytes to the receive function confident that the buffer would only be written to and not read from, and any additional string manipulation would rely on the standardnull
termination
- Passing a reference to a
- Clearing the buffer after receiving and concatenating the message string seems unnecessary but safer
pub enum Socket {
Tcp (TcpStream),
Udp (UdpSocket),
}
impl Socket {
pub fn send(&mut self, data: &[u8]) -> Result {
match self {
Self::Tcp(ref mut stream) => stream.write(data),
Self::Udp(socket) => socket.send(data),
}
}
pub fn receive(&mut self, buffer: &mut [u8]) -> Result {
match self {
Self::Tcp(ref mut stream) => stream.read(buffer),
Self::Udp(socket) => socket.recv(buffer),
}
}
}
const SERVER_ADDRESS: &str = "127.0.0.1:8080";
const VALIDATION_FROM_SERVER: &str = "abcde\0";
const VALIDATION_FROM_CLIENT: &str = "12345\0";
const SEGMENT_SIZE: usize = 1440; // Max IPv6 payload (TCP @ 1440 < UDP @ 1452)
#[test]
fn unauthenticated_tcp() {
let server = thread::spawn(unauthenticated_tcp_server);
let client = thread::spawn(unauthenticated_tcp_client);
server.join().unwrap();
client.join().unwrap();
}
fn send_message(socket: &mut Socket, message: &str) -> Result {
let mut sent: usize = 0;
loop {
let chunk = &message[sent..(sent + std::cmp::min(message.len() - sent, SEGMENT_SIZE))];
match socket.send(chunk.as_bytes()) {
Ok(length) => {
sent += length;
if sent == message.len() {
break Ok(sent)
}
},
Err(_) => todo!(),
};
}
}
fn receive_message(socket: &mut Socket) -> Result {
let mut buffer: Vec = Vec::with_capacity(SEGMENT_SIZE);
let mut message: String = String::with_capacity(SEGMENT_SIZE);
loop {
buffer.resize(SEGMENT_SIZE, 0);
match socket.receive(&mut buffer) {
Ok(length) => {
buffer.resize(length, 0);
let buffer_string = match std::str::from_utf8(&buffer) {
Ok(string) => string,
Err(_) => todo!(),
};
message.push_str(buffer_string);
match buffer.get(length - 1) {
Some(&0) => return Ok(message),
Some(_) => buffer.clear(),
None => todo!(),
};
},
Err(_) => todo!(),
}
}
}
fn unauthenticated_tcp_server() -> () {
let listener = match TcpListener::bind(SERVER_ADDRESS) {
Ok(listener) => listener,
Err(error) => panic!("Could not bind listener: {error:?}")
};
let mut session = match listener.accept() {
Ok((socket, _)) => server::negotiate_session(Socket::Tcp(socket)),
Err(error) => panic!("Could not get client: {error:?}")
};
let sent = send_message(&mut session.stream.socket, VALIDATION_FROM_SERVER);
assert!(sent.unwrap() == VALIDATION_FROM_SERVER.len());
let received = match receive_message(&mut session.stream.socket) {
Ok(message) => message,
Err(_) => panic!(),
};
assert!(received == VALIDATION_FROM_CLIENT);
}
fn unauthenticated_tcp_client () -> () {
let socket = loop {
match TcpStream::connect(SERVER_ADDRESS) {
Ok(socket) => break socket,
Err(_) => thread::sleep(Duration::from_millis(10)),
}
};
let mut session = client::negotiate_session(Socket::Tcp(socket));
let received = match receive_message(&mut session.stream.socket) {
Ok(message) => message,
Err(_) => panic!(),
};
assert!(received == VALIDATION_FROM_SERVER);
let sent = send_message(&mut session.stream.socket, VALIDATION_FROM_CLIENT);
assert!(sent.unwrap() == VALIDATION_FROM_CLIENT.len());
}