binary_util/lib.rs
1//! # Binary Util
2//! A panic-free way to read and write binary data over the wire.
3//!
4//! BinaryUtils provides the following features:
5//! * [`binary_util::io`], to read and write to streams manually.
6//! * [`binary_util::interfaces`], to allow automation of reading data structures.
7//! * [`binary_util::BinaryIo`], to automatically implement [`binary_util::interfaces::Reader`]
8//! and [`binary_util::interfaces::Writer`] .
9//! * [`binary_util::types`] for reading and writing non-primitive types like `u24` and `varint`.
10//!
11//! [`binary_util::io`]: crate::io
12//! [`binary_util::interfaces`]: crate::interfaces
13//! [`binary_util::BinaryIo`]: crate::BinaryIo
14//! [`binary_util::interfaces::Reader`]: crate::interfaces::Reader
15//! [`binary_util::interfaces::Writer`]: crate::interfaces::Writer
16//! [`binary_util::types`]: crate::types
17//!
18//! <style>
19//! .warning2 {
20//! background: rgba(255,76,76,0.34) !important;
21//! padding: 0.75em;
22//! border-left: 2px solid #fc0f0f;
23//! }
24//!
25//! .warning2 code {
26//! background: rgba(96,37,37,0.64) !important;
27//! }
28//! </style>
29//! <div class="warning2">
30//! <strong>v0.4.0</strong> is the next major release of Binary Utils and will contain breaking changes.
31//! <br>
32//! These changes include:
33//! <ul>
34//! <li>
35//! Removal of the <code>Streamable</code> trait in favor of
36//! <code>binary_util::io::Reader</code> and <code>binary_util::io::Writer</code>.
37//! </li>
38//! <li>
39//! Removal of the <code>Error</code> module in favor of
40//! <code>std::io::Error</code>.
41//! </li>
42//! </ul>
43//! </div>
44//!
45//! # Getting Started
46//! Binary Utils is available on [crates.io](https://crates.io/crates/binary_util), add the following to your `Cargo.toml`:
47//! ```toml
48//! [dependencies]
49//! binary_util = "0.3.4"
50//! ```
51//!
52//! Optionally, if you wish to remove the `macros` feature, you can add the following to your `Cargo.toml`:
53//! ```toml
54//! [dependencies]
55//! binary_util = { version = "0.3.4", default-features = false }
56//! ```
57//!
58//! # Binary IO
59//! The [`io`] module provides a way to contingiously write and read binary data with the garauntees of being panic-free.
60//! This module provides two structs, [`ByteReader`] and [`ByteWriter`], which are both wrappers
61//! around [`bytes::Buf`] and [`bytes::BufMut`] respectively.
62//!
63//! Generally, you will want to use [`ByteReader`] and [`ByteWriter`] when you are reading and writing binary data manually.
64//!
65//! **Read Example:**
66//!
67//! The following example shows how to read a varint from a stream:
68//! ```no_run
69//! use binary_util::io::ByteReader;
70//!
71//! const BUFFER: &[u8] = &[255, 255, 255, 255, 7]; // 2147483647
72//!
73//! fn main() {
74//! let mut buf = ByteReader::from(&BUFFER[..]);
75//! buf.read_var_u32().unwrap();
76//! }
77//! ```
78//!
79//! **Write Example:**
80//!
81//! The following is an example of how to write a string to a stream:
82//! ```no_run
83//! use binary_util::io::ByteWriter;
84//!
85//! fn main() {
86//! let mut buf = ByteWriter::new();
87//! buf.write_string("Hello world!");
88//! }
89//! ```
90//!
91//! **Real-world example:**
92//!
93//! A more real-world use-case of this module could be a simple pong server,
94//! where you have two packets, `Ping` and `Pong`, that respectively get relayed
95//! over udp.
96//! This is an example using both [`ByteReader`] and [`ByteWriter`] utilizing [`std::net::UdpSocket`]
97//! to send and receive packets.
98//! ```ignore
99//! use binary_util::io::{ByteReader, ByteWriter};
100//! use std::net::UdpSocket;
101//!
102//! pub struct PingPacket {
103//! pub time: u64
104//! }
105//!
106//! pub struct PongPacket {
107//! pub time: u64,
108//! pub ping_time: u64
109//! }
110//!
111//! fn main() -> std::io::Result<()> {
112//! let socket = UdpSocket::bind("127.0.0.1:5000")?;
113//! let mut buf = [0; 1024];
114//!
115//! loop {
116//! let (amt, src) = socket.recv_from(&mut buf)?;
117//! let mut buf = ByteReader::from(&buf[..amt]);
118//!
119//! match buf.read_u8()? {
120//! 0 => {
121//! let ping = PingPacket {
122//! time: buf.read_var_u64()?
123//! };
124//!
125//! println!("Received ping from {}", src);
126//!
127//! let mut writer = ByteWriter::new();
128//! let pong = PongPacket {
129//! time: std::time::SystemTime::now()
130//! .duration_since(
131//! std::time::UNIX_EPOCH
132//! )
133//! .unwrap()
134//! .as_millis() as u64,
135//! ping_time: ping.time
136//! };
137//!
138//! // Write pong packet
139//! writer.write_u8(1);
140//! writer.write_var_u64(pong.time);
141//! writer.write_var_u64(pong.ping_time);
142//! socket.send_to(writer.as_slice(), src)?;
143//! },
144//! 1 => {
145//! let pong = PongPacket {
146//! time: buf.read_var_u64()?,
147//! ping_time: buf.read_var_u64()?
148//! };
149//! println!(
150//! "Received pong from {} with ping time of {}ms",
151//! src,
152//! pong.time - pong.ping_time
153//! );
154//! }
155//! _ => {
156//! println!("Received unknown packet from {}", src);
157//! }
158//! }
159//! }
160//! }
161//! ```
162//!
163//! [`io`]: crate::io
164//! [`ByteReader`]: crate::io::ByteReader
165//! [`ByteWriter`]: crate::io::ByteWriter
166//! [`bytes::Buf`]: bytes::Buf
167//! [`bytes::BufMut`]: bytes::BufMut
168//! [`std::net::UdpSocket`]: std::net::UdpSocket
169//!
170//! # Interfaces
171//! The [`interfaces`] module provides a way to implement reading and writing binary data with
172//! two traits, [`Reader`] and [`Writer`].
173//!
174//! Generally, you will refer to using [`BinaryIo`] when you are implementing or enum; However in the
175//! scenario you are implementing a type that may not be compatible with [`BinaryIo`], you can use
176//! these traits instead.
177//!
178//! **Example:**
179//! The following example implements the [`Reader`] and [`Writer`] traits for a `HelloPacket` allowing
180//! it to be used with [`BinaryIo`]; this example also allows you to read and write the packet with an
181//! easier convention.
182//!
183//! ```ignore
184//! use binary_util::interfaces::{Reader, Writer};
185//! use binary_util::io::{ByteReader, ByteWriter};
186//!
187//! pub struct HelloPacket {
188//! pub name: String,
189//! pub age: u8,
190//! pub is_cool: bool,
191//! pub friends: Vec<String>
192//! }
193//!
194//! impl Reader<HelloPacket> for HelloPacket {
195//! fn read(buf: &mut ByteReader) -> std::io::Result<Self> {
196//! Ok(Self {
197//! name: buf.read_string()?,
198//! age: buf.read_u8()?,
199//! is_cool: buf.read_bool()?,
200//! friends: Vec::<String>::read(buf)?
201//! })
202//! }
203//! }
204//!
205//! impl Writer<HelloPacket> for HelloPacket {
206//! fn write(&self, buf: &mut ByteWriter) -> std::io::Result<()> {
207//! buf.write_string(&self.name);
208//! buf.write_u8(self.age);
209//! buf.write_bool(self.is_cool);
210//! self.friends.write(buf)?;
211//! Ok(())
212//! }
213//! }
214//! ```
215//!
216//! With the example above, you now are able to read and write the packet with [`BinaryIo`],
217//! as well as the added functionality of being able to read and write the packet with
218//! easier with the `read` and `write` methods that are now implemented.
219//!
220//! ```ignore
221//! fn main() {
222//! let mut buf = ByteWriter::new();
223//! let packet = HelloPacket {
224//! name: "John".to_string(),
225//! age: 18,
226//! is_cool: true,
227//! friends: vec!["Bob".to_string(), "Joe".to_string()]
228//! };
229//! buf.write_type(&packet).unwrap();
230//! }
231//! ```
232//!
233//! [`interfaces`]: crate::interfaces
234//! [`Reader`]: crate::interfaces::Reader
235//! [`Writer`]: crate::interfaces::Writer
236//! [`BinaryIo`]: crate::BinaryIo
237//!
238//! # Types
239//! The [`types`] module provides a way to implement non-primitive types when using the [`BinaryIo`] derive macro.
240
241//! This module provides the following helper types:
242//! * [`varu32`] - An unsigned 32-bit variable length integer
243//! * [`vari32`] - A signed 32-bit variable length integer
244//! * [`varu64`] - An unsigned 64-bit variable length integer
245//! * [`vari64`] - A signed 64-bit variable length integer
246//! * [`u24`] - A 24-bit unsigned integer
247//! * [`i24`] - A 24-bit signed integer
248//! * [`LE`] - A little endian type
249//! * [`BE`] - A big endian type
250//!
251//! **General Usage:**
252//! ```ignore
253//! use binary_util::BinaryIo;
254//! use binary_util::io::{ByteReader, ByteWriter};
255//! use binary_util::types::{varu64, varu32, u24, i24, LE, BE};
256//!
257//! #[derive(BinaryIo)]
258//! pub struct ProxyStatusPacket {
259//! pub clients: u24,
260//! pub max_clients: u24,
261//! pub net_download: varu32,
262//! pub net_upload: varu64,
263//! }
264//!
265//! fn main() {
266//! let mut buf = ByteWriter::new();
267//! let packet = ProxyStatusPacket {
268//! clients: 10,
269//! max_clients: 100,
270//! net_download: 1000.into(),
271//! net_upload: 1000.into()
272//! };
273//!
274//! buf.write_type(&packet).unwrap();
275//! let mut buf = ByteReader::from(buf.as_slice());
276//! let packet = ProxyStatusPacket::read(&mut buf).unwrap();
277//! println!("Clients: {}", packet.clients);
278//! println!("Max Clients: {}", packet.max_clients);
279//! println!("Net Download: {}", packet.net_download.0);
280//! println!("Net Upload: {}", packet.net_upload.0);
281//! }
282//! ```
283//!
284//! [`types`]: crate::types
285//! [`varu32`]: crate::types::varu32
286//! [`vari32`]: crate::types::vari32
287//! [`varu64`]: crate::types::varu64
288//! [`vari64`]: crate::types::vari64
289//! [`u24`]: crate::types::u24
290//! [`i24`]: crate::types::i24
291//! [`LE`]: crate::types::LE
292//! [`BE`]: crate::types::BE
293//! [`BinaryIo`]: crate::BinaryIo
294//!
295//! # Codegen
296//! The [`BinaryIo`] derive macro provides a way to implement both [`Reader`] and [`Writer`] for a type.
297//! This macro is extremely useful when you are trying to implement multiple data structures that you want
298//! to seemlessly read and write with the [`io`] module.
299//!
300//! **Example:**
301//! The following example implements the [`BinaryIo`] trait for a `HelloPacket`, shortening the previous
302//! example to just a few lines of code.
303//! ```ignore
304//! use binary_util::BinaryIo;
305//!
306//! #[derive(BinaryIo)]
307//! pub struct HelloPacket {
308//! pub name: String,
309//! pub age: u8,
310//! pub is_cool: bool,
311//! pub friends: Vec<String>
312//! }
313//!
314//! fn main() {
315//! let mut buf = ByteWriter::new();
316//! let packet = HelloPacket {
317//! name: "John".to_string(),
318//! age: 18,
319//! is_cool: true,
320//! friends: vec!["Bob".to_string(), "Joe".to_string()]
321//! };
322//! buf.write_type(&packet).unwrap();
323//! }
324//! ```
325//!
326//! You can view additional implementations of the derive macro by looking at the examples on the [module](crate::BinaryIo) page.
327//!
328//! [`BinaryIo`]: crate::BinaryIo
329//! [`io`]: crate::io
330//! [`Reader`]: crate::interfaces::Reader
331//! [`Writer`]: crate::interfaces::Writer
332//!
333/// Provides a panic-free way to read and write binary data.
334/// All of the methods within this module follow the protobuf specification at <https://protobuf.dev/programming-guides/encoding/>.
335///
336/// ## Example
337/// ```no_run
338/// use binary_util::io::ByteReader;
339///
340/// const VARINT: &[u8] = &[255, 255, 255, 255, 7]; // 2147483647
341/// fn main() {
342/// let mut buf = ByteReader::from(&VARINT[..]);
343/// assert_eq!(buf.read_var_u32().unwrap(), 2147483647);
344/// }
345/// ```
346pub mod interfaces;
347/// Provides a derive macro that implements `::binary_util::interfaces::Reader<T>` and `::binary_util::interfaces::Writer<T>`.
348///
349pub use binary_util_derive::*;
350/// The io module contains implementations of these traits for `bytes::Buf` and `bytes::BufMut`.
351///
352/// Example:
353/// ```no_run
354/// use binary_util::io::ByteReader;
355/// use bytes::{Buf, BufMut, BytesMut, Bytes};
356///
357/// fn main() {
358/// const VARINT: &[u8] = &[255, 255, 255, 255, 7]; // 2147483647
359/// let mut buf = ByteReader::from(&VARINT[..]);
360/// assert_eq!(buf.read_var_u32().unwrap(), 2147483647);
361/// }
362/// ```
363pub mod io;
364pub mod pool;
365/// This module contains all of the types that are used within the `binary_util` crate.
366/// For example, Sometimes you may need to use a `u24` or `varu32` type, on structs,
367/// and this module provides those types.
368pub mod types;
369/// This is a legacy module that will be removed in the future.
370/// This module has been replaced in favor of `std::io::Error`.
371///
372/// <p style="background:rgba(255,181,77,0.16);padding:0.75em;border-left: 2px solid orange;">
373/// <strong>Warning:</strong> This module is deprecated and will be removed in <strong>v0.4.0</strong>.
374/// </p>
375#[deprecated = "This module is deprecated in favor of std::io::Error."]
376pub mod error {
377 /// An enum consisting of a Binary Error
378 /// (recoverable)
379 #[derive(Debug, PartialEq)]
380 pub enum BinaryError {
381 /// Offset is out of bounds
382 ///
383 /// **Tuple Values:**
384 /// - `usize` = Given Offset.
385 /// - `usize` = Stream length.
386 /// - `&'static str` = Message to add on to the error.
387 OutOfBounds(usize, usize, &'static str),
388
389 /// Similar to `OutOfBounds` except it means;
390 /// the stream tried to read more than possible.
391 ///
392 /// **Tuple Values:**
393 /// - `usize` = Stream length.
394 EOF(usize),
395
396 /// A known error that was recoverable to safely proceed the stack.
397 RecoverableKnown(String),
398
399 /// An unknown error occurred, but it wasn't critical,
400 /// we can safely proceed on the stack.
401 RecoverableUnknown,
402 }
403
404 impl BinaryError {
405 pub fn get_message(&self) -> String {
406 match self {
407 Self::OutOfBounds(offset, length, append) => {
408 format!("Offset {} out of range for a buffer size with: {}. {}", offset, length, append)
409 },
410 Self::EOF(length) => format!("Buffer reached End Of File at offset: {}", length),
411 Self::RecoverableKnown(msg) => msg.clone(),
412 Self::RecoverableUnknown => "An interruption occurred when performing a binary operation, however this error was recovered safely.".to_string()
413 }
414 }
415 }
416
417 impl From<std::io::Error> for BinaryError {
418 fn from(_error: std::io::Error) -> Self {
419 Self::RecoverableUnknown
420 }
421 }
422
423 impl std::fmt::Display for BinaryError {
424 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
425 write!(f, "{}", self.get_message())
426 }
427 }
428}
429
430#[allow(deprecated)]
431pub use interfaces::Streamable;
432pub use io::{ByteReader, ByteWriter};