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::Reader
andbinary_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::Buf
andbytes::BufMut
.
Derive Macros
- This proc-macro implements both the
Reader
andWriter
traits 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
Streamable
trait.