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};