Crate binary_util
source ·Expand description
Binary Util
A panic-free way to read and write binary data over the wire.
BinaryUtils provides the following features:
binary_util::io, to read and write to streams manually.binary_util::interfaces, to allow automation of reading data structures.binary_util::BinaryIo, to automatically implementbinary_util::interfaces::Readerandbinary_util::interfaces::Writer.
Getting Started
Binary Utils is available on crates.io, add the following to your Cargo.toml:
[dependencies]
binary_util = "0.3.0"
Optionally, if you wish to remove the macros feature, you can add the following to your Cargo.toml:
[dependencies]
binary_util = { version = "0.3.0", default-features = false }
Binary IO
The io module provides a way to contingiously write and read binary data with the garauntees of being panic-free.
This module provides two structs, ByteReader and ByteWriter, which are both wrappers
around bytes::Buf and bytes::BufMut respectively.
Generally, you will want to use ByteReader and ByteWriter when you are reading and writing binary data manually.
Read Example:
The following example shows how to read a varint from a stream:
use binary_util::io::ByteReader;
const BUFFER: &[u8] = &[255, 255, 255, 255, 7]; // 2147483647
fn main() {
let mut buf = ByteReader::from(&BUFFER[..]);
buf.read_var_u32().unwrap();
}Write Example:
The following is an example of how to write a string to a stream:
use binary_util::io::ByteWriter;
fn main() {
let mut buf = ByteWriter::new();
buf.write_string("Hello world!");
}Real-world example:
A more real-world use-case of this module could be a simple pong server,
where you have two packets, Ping and Pong, that respectively get relayed
over udp.
This is an example using both ByteReader and ByteWriter utilizing std::net::UdpSocket
to send and receive packets.
use binary_util::io::{ByteReader, ByteWriter};
use std::net::UdpSocket;
pub struct PingPacket {
pub time: u64
}
pub struct PongPacket {
pub time: u64,
pub ping_time: u64
}
fn main() -> std::io::Result<()> {
let socket = UdpSocket::bind("127.0.0.1:5000")?;
let mut buf = [0; 1024];
loop {
let (amt, src) = socket.recv_from(&mut buf)?;
let mut buf = ByteReader::from(&buf[..amt]);
match buf.read_u8()? {
0 => {
let ping = PingPacket {
time: buf.read_var_u64()?
};
println!("Received ping from {}", src);
let mut writer = ByteWriter::new();
let pong = PongPacket {
time: std::time::SystemTime::now()
.duration_since(
std::time::UNIX_EPOCH
)
.unwrap()
.as_millis() as u64,
ping_time: ping.time
};
// Write pong packet
writer.write_u8(1);
writer.write_var_u64(pong.time);
writer.write_var_u64(pong.ping_time);
socket.send_to(writer.as_slice(), src)?;
},
1 => {
let pong = PongPacket {
time: buf.read_var_u64()?,
ping_time: buf.read_var_u64()?
};
println!(
"Received pong from {} with ping time of {}ms",
src,
pong.time - pong.ping_time
);
}
_ => {
println!("Received unknown packet from {}", src);
}
}
}
}Interfaces
The interfaces module provides a way to implement reading and writing binary data with
two traits, Reader and Writer.
Generally, you will refer to using BinaryIo when you are implementing or enum; However in the
scenario you are implementing a type that may not be compatible with BinaryIo, you can use
these traits instead.
Example:
The following example implements the Reader and Writer traits for a HelloPacket allowing
it to be used with BinaryIo; this example also allows you to read and write the packet with an
easier convention.
use binary_util::interfaces::{Reader, Writer};
use binary_util::io::{ByteReader, ByteWriter};
pub struct HelloPacket {
pub name: String,
pub age: u8,
pub is_cool: bool,
pub friends: Vec<String>
}
impl Reader<HelloPacket> for HelloPacket {
fn read(buf: &mut ByteReader) -> std::io::Result<Self> {
Ok(Self {
name: buf.read_string()?,
age: buf.read_u8()?,
is_cool: buf.read_bool()?,
friends: Vec::<String>::read(buf)?
})
}
}
impl Writer<HelloPacket> for HelloPacket {
fn write(&self, buf: &mut ByteWriter) -> std::io::Result<()> {
buf.write_string(&self.name);
buf.write_u8(self.age);
buf.write_bool(self.is_cool);
self.friends.write(buf)?;
Ok(())
}
}With the example above, you now are able to read and write the packet with BinaryIo,
as well as the added functionality of being able to read and write the packet with
easier with the read and write methods that are now implemented.
fn main() {
let mut buf = ByteWriter::new();
let packet = HelloPacket {
name: "John".to_string(),
age: 18,
is_cool: true,
friends: vec!["Bob".to_string(), "Joe".to_string()]
};
buf.write_type(&packet).unwrap();
}Codegen
The BinaryIo derive macro provides a way to implement both Reader and Writer for a type.
This macro is extremely useful when you are trying to implement multiple data structures that you want
to seemlessly read and write with the io module.
Example:
The following example implements the BinaryIo trait for a HelloPacket, shortening the previous
example to just a few lines of code.
use binary_util::BinaryIo;
#[derive(BinaryIo)]
pub struct HelloPacket {
pub name: String,
pub age: u8,
pub is_cool: bool,
pub friends: Vec<String>
}
fn main() {
let mut buf = ByteWriter::new();
let packet = HelloPacket {
name: "John".to_string(),
age: 18,
is_cool: true,
friends: vec!["Bob".to_string(), "Joe".to_string()]
};
buf.write_type(&packet).unwrap();
}You can view additional implementations of the derive macro by looking at the examples on the module page.
Re-exports
pub use interfaces::Streamable;pub use io::ByteReader;pub use io::ByteWriter;
Modules
- This is a legacy module that will be removed in the future. This module has been replaced in favor of
std::io::Error. - Provides a panic-free way to read and write binary data. All of the methods within this module follow the protobuf specification at https://protobuf.dev/programming-guides/encoding/.
- The io module contains implementations of these traits for
bytes::Bufandbytes::BufMut.
Derive Macros
- This proc-macro implements both the
ReaderandWritertraits frombinary_util::interfaces. It is important to note that not all attributes can be used on all types, and some attributes are exclusive to certain variants. - DEPRECATED. This is a legacy proc-macro that is used to generate a BufferStream. It provides an easy way to implement the
Streamabletrait.