| Crates.io | octs |
| lib.rs | octs |
| version | 1.0.0 |
| created_at | 2024-04-13 16:00:18.914498+00 |
| updated_at | 2024-10-05 17:34:10.963555+00 |
| description | Finally, a good byte manipulation library |
| homepage | |
| repository | https://github.com/aecsocket/octs |
| max_upload_size | |
| id | 1207678 |
| size | 61,912 |
octsFinally, a good byte manipulation library.
This crate builds on top of the types defined by bytes by replacing its panicking get and
put functions with fallible, non-panicking read and write functions via octs::Read and
octs::Write.
Based on bytes - which provides useful types for byte manipulation, and allows cheaply
cloning byte allocations via reference counting. Great for writing zero-copy networking code.
Panicking functions were a mistake - in networking, you can't trust your inputs. So why should
it ever be possible to panic on malformed input?? All functions in octs which can fail return a
[Result].
Your types are first-class citizens - instead of get_u16, put_f32, etc., just use one
read and write function for all types. This means you can implement [Decode] and be able
to read it from any buffer, and likewise for [Encode] and write.
Dedicated varints - one of the staples of networking primitives is implemented here, without
needing any extensions. Just read or write a [VarInt] as you would any other value.
Zero unsafe - I'm not smart enough to write unsafe code.
#![no_std] - just like bytes, but it still requires alloc.
use octs::{Read, Write, VarInt};
fn write_packet(
mut buf: octs::BytesMut,
// ^^^^^^^^^^^^^^
// | re-exports the core `bytes` types
packet_id: u16,
timestamp: u64,
payload: &[u8],
) -> Result<(), octs::BufTooShort> {
// ^^^^^^^^^^^^^^^^^
// | the main error type
buf.write(packet_id)?;
// ^^^^^
// | one `write` function for all your types
buf.write(timestamp)?;
// +---------------^
// | just use ? for errors
// | no panics
buf.write(VarInt(payload.len()))?;
// ^^^^^^^
// | inbuilt support for varints
// | using the Protocol Buffers spec
buf.write_from(payload)?;
// ^^^^^^^^^^
// | copy from an existing buffer
Ok(())
}
use core::num::NonZeroU8;
use octs::{Bytes, BufError, Decode, Read, BufTooShortOr, VarInt};
#[derive(Debug)]
struct Fragment {
num_frags: NonZeroU8,
payload: Bytes,
}
#[derive(Debug)]
enum FragmentError {
InvalidNumFrags,
PayloadTooLarge,
}
impl Decode for Fragment {
// ^^^^^^
// | implement this trait to be able to `read`
// | this value from a buffer
type Error = FragmentError;
fn decode(mut buf: impl Read) -> Result<Self, BufTooShortOr<Self::Error>> {
let num_frags = buf
.read::<NonZeroU8>()
.map_err(|e| e.map_or(|_| FragmentError::InvalidNumFrags))?;
// +--------------^^^^^^^
// | map the `InvalidValue` error of reading
// | a `NonZeroU8` to your own error value
let VarInt(payload_len) = buf
.read::<VarInt<usize>>()
.map_err(|e| e.map_or(|_| FragmentError::PayloadTooLarge))?;
let payload = buf.read_next(payload_len)?;
// +-------------^^^^^^^^^^
// | read the next `payload_len` bytes directly into `Bytes`
// | if `buf` is also a `Bytes`, this is zero-copy!
Ok(Self {
num_frags,
payload
})
}
}
bytes - core byte manipulation primitives, such as the possibly-non-contiguous [bytes::Buf]
trait, and the cheaply-cloneable [bytes::Bytes] type.octets - general API style, and having varints be a core part of the APIsafer-bytes - making a good version of the bytes APIinteger-encoding - implementations of varint encode/decode