Rusty rusty rusty rust...

This commit is contained in:
Adam Ierymenko 2021-07-16 20:41:03 -04:00
parent 52585e9262
commit 5515be2e25
No known key found for this signature in database
GPG key ID: C8877CF2D7A5D7F3
14 changed files with 714 additions and 4 deletions

View file

@ -3,13 +3,14 @@ use std::convert::TryInto;
use std::io::Write;
pub const SHA512_HASH_SIZE: usize = 64;
pub const SHA384_HASH_SIZE: usize = 64;
pub struct SHA512(gcrypt::digest::MessageDigest);
impl SHA512 {
#[inline(always)]
pub fn hash(b: &[u8]) -> [u8; SHA512_HASH_SIZE] {
let mut h = unsafe { MaybeUninit::<[u8; 64]>::uninit().assume_init() };
let mut h = unsafe { MaybeUninit::<[u8; SHA512_HASH_SIZE]>::uninit().assume_init() };
gcrypt::digest::hash(gcrypt::digest::Algorithm::Sha512, b, &mut h);
h
}
@ -21,7 +22,7 @@ impl SHA512 {
let _ = m.set_key(key);
let _ = m.update(msg);
let mut h = [0_u8; SHA512_HASH_SIZE];
m.get_mac(&mut h);
let _ = m.get_mac(&mut h);
h
}
@ -67,3 +68,67 @@ impl Write for SHA512 {
self.0.flush()
}
}
pub struct SHA384(gcrypt::digest::MessageDigest);
impl SHA384 {
#[inline(always)]
pub fn hash(b: &[u8]) -> [u8; SHA384_HASH_SIZE] {
let mut h = unsafe { MaybeUninit::<[u8; SHA384_HASH_SIZE]>::uninit().assume_init() };
gcrypt::digest::hash(gcrypt::digest::Algorithm::Sha384, b, &mut h);
h
}
/// Compute HMAC-SHA384(key, msg)
#[inline(always)]
pub fn hmac(key: &[u8], msg: &[u8]) -> [u8; SHA384_HASH_SIZE] {
let mut m = gcrypt::mac::Mac::new(gcrypt::mac::Algorithm::HmacSha384).unwrap();
let _ = m.set_key(key);
let _ = m.update(msg);
let mut h = [0_u8; SHA384_HASH_SIZE];
let _ = m.get_mac(&mut h);
h
}
#[inline(always)]
pub fn new() -> Self {
Self(gcrypt::digest::MessageDigest::new(gcrypt::digest::Algorithm::Sha384).unwrap())
}
#[inline(always)]
pub fn reset(&mut self) {
self.0.reset();
}
#[inline(always)]
pub fn update(&mut self, b: &[u8]) {
self.0.update(b);
}
#[inline(always)]
pub fn finish(&mut self) -> [u8; SHA384_HASH_SIZE] {
self.0.finish();
self.0.get_only_digest().unwrap().try_into().unwrap()
}
/// Return a reference to an internally stored result.
/// This saves a copy, but the returned result is only valid so long as no other methods are called.
#[inline(always)]
pub fn finish_get_ref(&mut self) -> &[u8] {
self.0.finish();
self.0.get_only_digest().unwrap()
}
}
impl Write for SHA384 {
#[inline(always)]
fn write(&mut self, b: &[u8]) -> std::io::Result<usize> {
self.0.update(b);
Ok(b.len())
}
#[inline(always)]
fn flush(&mut self) -> std::io::Result<()> {
self.0.flush()
}
}

View file

@ -1,3 +1,5 @@
pub mod c25519;
pub mod hash;
pub mod p521;
pub mod salsa;
pub mod poly1305;

View file

@ -1,6 +1,5 @@
use std::str::FromStr;
use std::convert::TryInto;
use std::io::Write;
use gcrypt::sexp::SExpression;

View file

@ -0,0 +1,30 @@
pub struct Poly1305(gcrypt::mac::Mac);
pub const POLY1305_ONE_TIME_KEY_SIZE: usize = 32;
pub const POLY1305_MAC_SIZE: usize = 16;
impl Poly1305 {
#[inline(always)]
pub fn new(key: &[u8]) -> Option<Poly1305> {
if key.len() == 32 {
gcrypt::mac::Mac::new(gcrypt::mac::Algorithm::Poly1305).map_or(None, |mut poly| {
let _ = poly.set_key(key);
Some(Poly1305(poly))
})
} else {
None
}
}
#[inline(always)]
pub fn update(&mut self, data: &[u8]) {
let _ = self.0.update(data);
}
#[inline(always)]
pub fn finish(&mut self) -> [u8; POLY1305_MAC_SIZE] {
let mut mac = [0_u8; POLY1305_MAC_SIZE];
let _ = self.0.get_mac(&mut mac);
mac
}
}

View file

@ -0,0 +1,30 @@
pub struct Salsa(gcrypt::cipher::Cipher);
impl Salsa {
/// Initialize Salsa cipher.
/// Key must be 32 bytes and iv must be 8 bytes. If r12 is true the 12-round
/// variant of Salsa will be used, otherwise 20 rounds are used.
#[inline(always)]
pub fn new(key: &[u8], iv: &[u8], r12: bool) -> Option<Salsa> {
if key.len() == 32 && iv.len() == 8 {
gcrypt::cipher::Cipher::new(if r12 { gcrypt::cipher::Algorithm::Salsa20r12 } else { gcrypt::cipher::Algorithm::Salsa20 }, gcrypt::cipher::Mode::Stream).map_or(None, |mut salsa| {
let _ = salsa.set_key(key);
let _ = salsa.set_iv(iv);
Some(Salsa(salsa))
})
} else {
None
}
}
/// Encrypt or decrypt, which for Salsa is the same operation.
#[inline(always)]
pub fn crypt(&mut self, plaintext: &[u8], ciphertext: &mut [u8]) {
let _ = self.0.encrypt(plaintext, ciphertext);
}
#[inline(always)]
pub fn crypt_in_place(&mut self, plaintext_to_ciphertext: &mut [u8]) {
let _ = self.0.encrypt_inplace(plaintext_to_ciphertext);
}
}

View file

@ -0,0 +1,18 @@
use std::error::Error;
use std::fmt::{Display, Debug};
pub struct InvalidFormatError(pub(crate) &'static str);
impl Display for InvalidFormatError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "UnmarshalError: {}", self.0)
}
}
impl Debug for InvalidFormatError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "UnmarshalError: {}", self.0)
}
}
impl Error for InvalidFormatError {}

View file

@ -1,3 +1,4 @@
pub mod crypto;
pub mod vl1;
pub mod util;
pub mod error;

View file

@ -1,6 +1,6 @@
pub(crate) const HEX_CHARS: [u8; 16] = [ b'0', b'1', b'2', b'3', b'4', b'5', b'6', b'7', b'8', b'9', b'a', b'b', b'c', b'd', b'e', b'f'];
/// Encode a binary string to a series of hex bytes.
/// Encode a byte slice to a hexadecimal string.
pub fn to_string(b: &[u8]) -> String {
let mut s = String::new();
s.reserve(b.len() * 2);
@ -12,6 +12,45 @@ pub fn to_string(b: &[u8]) -> String {
s
}
/// Decode a hex string, ignoring non-hexadecimal characters.
pub fn from_string(s: &str) -> Vec<u8> {
let mut b: Vec<u8> = Vec::new();
b.reserve((s.len() / 2) + 1);
let mut byte = 0;
let mut have_8: bool = false;
for cc in s.as_bytes() {
let c = *cc;
if c >= 48 && c <= 57 {
byte <<= 4;
byte |= c - 48;
if have_8 {
b.push(byte);
byte = 0;
}
have_8 = !have_8;
} else if c >= 65 && c <= 70 {
byte <<= 4;
byte |= c - 55;
if have_8 {
b.push(byte);
byte = 0;
}
have_8 = !have_8;
} else if c >= 97 && c <= 102 {
byte <<= 4;
byte |= c - 87;
if have_8 {
b.push(byte);
byte = 0;
}
have_8 = !have_8;
}
}
b
}
/// Encode bytes from 'b' into hex characters in 'dest'.
/// This will panic if the destination slice is smaller than twice the length of the source.
pub fn to_hex_bytes(b: &[u8], dest: &mut [u8]) {

View file

@ -0,0 +1,104 @@
use std::str::FromStr;
use std::hash::{Hash, Hasher};
use crate::vl1::protocol::ADDRESS_RESERVED_PREFIX;
use crate::error::InvalidFormatError;
use crate::util::hex::HEX_CHARS;
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
#[repr(transparent)]
pub struct Address(u64);
impl Address {
#[inline(always)]
pub fn from_bytes(b: &[u8]) -> Result<Address, InvalidFormatError> {
if b.len() >= 5 {
Ok(Address((b[0] as u64) << 32 | (b[1] as u64) << 24 | (b[2] as u64) << 16 | (b[3] as u64) << 8 as u64 | b[4] as u64))
} else {
Err(InvalidFormatError("invalid ZeroTier address"))
}
}
#[inline(always)]
pub fn is_reserved(&self) -> bool {
(self.0 >> 32) as usize == ADDRESS_RESERVED_PREFIX as usize
}
#[inline(always)]
pub fn is_valid(&self) -> bool {
self.0 != 0 && !self.is_reserved()
}
#[inline(always)]
pub fn is_nil(&self) -> bool {
self.0 == 0
}
#[inline(always)]
pub fn to_bytes(&self) -> [u8; 5] {
[(self.0 >> 32) as u8, (self.0 >> 24) as u8, (self.0 >> 16) as u8, (self.0 >> 8) as u8, self.0 as u8]
}
#[inline(always)]
pub fn to_u64(&self) -> u64 {
self.0
}
}
impl ToString for Address {
#[inline(always)]
fn to_string(&self) -> String {
let mut v = self.0 << 24;
let mut s = String::new();
s.reserve(10);
for _ in 0..10 {
s.push(HEX_CHARS[(v >> 60) as usize] as char);
v <<= 4;
}
s
}
}
impl FromStr for Address {
type Err = InvalidFormatError;
#[inline(always)]
fn from_str(s: &str) -> Result<Self, Self::Err> {
Address::from_bytes(crate::util::hex::from_string(s).as_slice())
}
}
impl Default for Address {
#[inline(always)]
fn default() -> Address {
Address(0)
}
}
impl Hash for Address {
#[inline(always)]
fn hash<H: Hasher>(&self, state: &mut H) {
self.0.hash(state)
}
}
impl From<&[u8; 5]> for Address {
#[inline(always)]
fn from(b: &[u8; 5]) -> Address {
Address((b[0] as u64) << 32 | (b[1] as u64) << 24 | (b[2] as u64) << 16 | (b[3] as u64) << 8 as u64 | b[4] as u64)
}
}
impl From<[u8; 5]> for Address {
#[inline(always)]
fn from(b: [u8; 5]) -> Address {
Self::from(&b)
}
}
impl From<u64> for Address {
#[inline(always)]
fn from(i: u64) -> Address {
Address(i)
}
}

View file

@ -0,0 +1,178 @@
use std::mem::size_of;
use std::marker::PhantomData;
const FAULT_BIT: usize = 1_usize << ((size_of::<usize>() * 8) - 1);
const FAULT_CLEAR_MASK: usize = !FAULT_BIT;
/// Annotates a type as containing only primitive types like integers and arrays.
/// This means it's safe to abuse with raw copy, raw zero, or "type punning."
/// This is ONLY used for packed protocol header or segment objects.
pub unsafe trait RawObject: Sized {}
/// A zero length RawObject for using a Buffer when you don't want a header.
pub struct NoHeader;
unsafe impl RawObject for NoHeader {}
/// A byte array that supports safe appending of data or raw objects.
///
/// This also supports a generic header that must be a RawObject and will always be
/// placed at the beginning of the buffer. When you construct or clear() a buffer
/// space will be maintained for the header. Use NoHeader if you don't want a header.
///
/// If a write overflow occurs during append operations, the operations fail silently
/// without increasing the buffer's size and an internal fault bit is set. The
/// check_overflow() method must be used before the buffer is actually complete to
/// ensure that no write overflows occurred. If this check isn't performed a buffer
/// could be used with incomplete or corrupt data, but no crash or memory errors will
/// occur.
#[derive(Clone)]
pub struct Buffer<H: RawObject, const L: usize>(usize, [u8; L], PhantomData<H>);
unsafe impl<H: RawObject, const L: usize> RawObject for Buffer<H, L> {}
impl<H: RawObject, const L: usize> Default for Buffer<H, L> {
#[inline(always)]
fn default() -> Self {
assert!(size_of::<H>() <= L);
Buffer(size_of::<H>(), [0_u8; L], PhantomData::default())
}
}
impl<H: RawObject, const L: usize> Buffer<H, L> {
#[inline(always)]
pub fn new() -> Self {
Self::default()
}
/// Returns true if there has been a write overflow.
#[inline(always)]
pub fn check_overflow(&self) -> bool {
(self.0 & FAULT_BIT) != 0
}
/// Get a slice containing the entire buffer in raw form including the header.
#[inline(always)]
pub fn as_bytes(&self) -> &[u8] {
&self.1[0..(self.0 & FAULT_CLEAR_MASK)]
}
/// Erase contents and reset size to the size of the header.
#[inline(always)]
pub fn clear(&mut self) {
self.0 = size_of::<H>();
self.1.fill(0);
}
/// Get the length of this buffer (including header, if any).
#[inline(always)]
pub fn len(&self) -> usize {
self.0 & FAULT_CLEAR_MASK
}
/// Get a reference to the header (in place).
#[inline(always)]
pub fn header(&self) -> &H {
unsafe { &*self.1.as_ptr().cast::<H>() }
}
/// Get a mutable reference to the header (in place).
#[inline(always)]
pub fn header_mut(&mut self) -> &mut H {
unsafe { &mut *self.1.as_mut_ptr().cast::<H>() }
}
/// Append a packed structure and initializing it in place via the supplied function.
///
/// If an overflow occurs the overflow fault bit is set internally (see check_overflow())
/// and the supplied function will never be called.
#[inline(always)]
pub fn append_and_init_struct<T: RawObject, F: FnOnce(&mut T)>(&mut self, initializer: F) {
let bl = self.0;
let s = bl + size_of::<T>();
if s <= L {
unsafe {
self.0 = s;
initializer(&mut *self.1.as_mut_ptr().cast::<u8>().offset(bl as isize).cast::<T>());
}
} else {
self.0 = bl | FAULT_BIT;
}
}
/// Append and initialize a byte array with a fixed size set at compile time.
///
/// This is more efficient than setting a size at runtime as it may allow the compiler to
/// skip some bounds checking.
///
/// If an overflow occurs the overflow fault bit is set internally (see check_overflow())
/// and the supplied function will never be called.
#[inline(always)]
pub fn append_and_init_bytes_fixed<F: FnOnce(&mut [u8; N]), const N: usize>(&mut self, initializer: F) {
let bl = self.0;
let s = bl + N;
if s <= L {
unsafe {
let ptr = self.1.as_mut_ptr().cast::<u8>().offset(bl as isize);
self.0 = s;
initializer(&mut *ptr.cast::<[u8; N]>());
}
} else {
self.0 = bl | FAULT_BIT;
}
}
/// Append and initialize a slice with a size that is set at runtime.
///
/// If an overflow occurs the overflow fault bit is set internally (see check_overflow())
/// and the supplied function will never be called.
#[inline(always)]
pub fn append_and_init_bytes<F: FnOnce(&mut [u8])>(&mut self, l: usize, initializer: F) {
let bl = self.0;
let s = bl + l;
if s <= L {
self.0 = s;
initializer(&mut self.1[bl..s]);
} else {
self.0 = bl | FAULT_BIT;
}
}
pub fn read_payload(&self) -> Reader<H, L> {
Reader {
buffer: self,
ptr: size_of::<H>(),
}
}
}
pub struct Reader<'a, H: RawObject, const L: usize> {
ptr: usize,
buffer: &'a Buffer<H, L>,
}
impl<'a, H: RawObject, const L: usize> Reader<'a, H, L> {
pub fn read_struct<T: RawObject, R, F: FnOnce(&T, &mut Self) -> bool>(&mut self, visitor: F) -> bool {
let rl = self.ptr;
let s = rl + size_of::<T>();
if s <= L {
unsafe {
self.ptr = s;
visitor(&*self.buffer.1.as_ptr().cast::<u8>().offset(rl as isize).cast::<T>(), self)
}
} else {
false
}
}
}
#[cfg(test)]
mod tests {
use std::mem::size_of;
use crate::vl1::buffer::NoHeader;
#[test]
fn object_sizing() {
assert_eq!(size_of::<NoHeader>(), 0);
}
}

View file

@ -0,0 +1,81 @@
use std::str::FromStr;
use std::hash::{Hash, Hasher};
use crate::error::InvalidFormatError;
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
#[repr(transparent)]
pub struct MAC(u64);
impl MAC {
#[inline(always)]
pub fn from_bytes(b: &[u8]) -> Result<MAC, InvalidFormatError> {
if b.len() >= 6 {
Ok(MAC((b[0] as u64) << 40 | (b[1] as u64) << 32 | (b[2] as u64) << 24 | (b[3] as u64) << 16 as u64 | (b[4] as u64) << 8 | b[5] as u64))
} else {
Err(InvalidFormatError("invalid MAC address"))
}
}
#[inline(always)]
pub fn to_bytes(&self) -> [u8; 6] {
[(self.0 >> 40) as u8, (self.0 >> 32) as u8, (self.0 >> 24) as u8, (self.0 >> 16) as u8, (self.0 >> 8) as u8, self.0 as u8]
}
#[inline(always)]
pub fn to_u64(&self) -> u64 {
self.0
}
}
impl ToString for MAC {
#[inline(always)]
fn to_string(&self) -> String {
let b: [u8; 6] = self.to_bytes();
format!("{}:{}:{}:{}:{}:{}", b[0], b[1], b[2], b[3], b[4], b[5])
}
}
impl FromStr for MAC {
type Err = InvalidFormatError;
#[inline(always)]
fn from_str(s: &str) -> Result<Self, Self::Err> {
MAC::from_bytes(crate::util::hex::from_string(s).as_slice())
}
}
impl Default for MAC {
#[inline(always)]
fn default() -> MAC {
MAC(0)
}
}
impl Hash for MAC {
#[inline(always)]
fn hash<H: Hasher>(&self, state: &mut H) {
self.0.hash(state)
}
}
impl From<&[u8; 6]> for MAC {
#[inline(always)]
fn from(b: &[u8; 6]) -> MAC {
MAC((b[0] as u64) << 40 | (b[1] as u64) << 32 | (b[2] as u64) << 24 | (b[3] as u64) << 16 as u64 | (b[4] as u64) << 8 | b[5] as u64)
}
}
impl From<[u8; 6]> for MAC {
#[inline(always)]
fn from(b: [u8; 6]) -> MAC {
Self::from(&b)
}
}
impl From<u64> for MAC {
#[inline(always)]
fn from(i: u64) -> MAC {
MAC(i)
}
}

View file

@ -0,0 +1,8 @@
pub mod protocol;
pub mod packet;
pub mod buffer;
mod address;
mod mac;
pub use address::Address;
pub use mac::MAC;

View file

@ -0,0 +1,97 @@
use crate::vl1::buffer::{Buffer, RawObject};
use crate::vl1::protocol::{HEADER_FLAGS_FIELD_MASK_CIPHER, HEADER_FLAGS_FIELD_MASK_HOPS, HEADER_FLAG_FRAGMENTED};
use std::ops::Not;
type PacketID = u64;
#[derive(Clone)]
#[repr(packed)]
pub struct Header {
pub id: [u8; 8],
pub dest: [u8; 5],
pub src: [u8; 5],
pub flags_cipher_hops: u8,
pub message_auth: [u8; 8],
}
unsafe impl RawObject for Header {}
impl Header {
/// Get this packet's ID as a u64.
/// While this returns u64, the returned integer contains raw packet ID bytes
/// in "native" byte order and should be treated conceptually like [u8; 8].
/// In particular, greater/less than comparisons may differ across architectures.
#[inline(always)]
pub fn to_id(&self) -> PacketID {
#[cfg(any(target_arch = "x86", target_arch = "x86_64", target_arch = "aarch64"))]
unsafe { *self.id.as_ptr().cast::<u64>() }
#[cfg(not(any(target_arch = "x86", target_arch = "x86_64", target_arch = "aarch64")))]
u64::from_ne_bytes(self.id.clone())
}
#[inline(always)]
pub fn cipher(&self) -> u8 {
self.flags_cipher_hops & HEADER_FLAGS_FIELD_MASK_CIPHER
}
#[inline(always)]
pub fn hops(&self) -> u8 {
self.flags_cipher_hops & HEADER_FLAGS_FIELD_MASK_HOPS
}
#[inline(always)]
pub fn increment_hops(&mut self) {
let f = self.flags_cipher_hops;
self.flags_cipher_hops = (f & HEADER_FLAGS_FIELD_MASK_HOPS.not()) | ((f + 1) & HEADER_FLAGS_FIELD_MASK_HOPS);
}
#[inline(always)]
pub fn is_fragmented(&self) -> bool {
(self.flags_cipher_hops & HEADER_FLAG_FRAGMENTED) != 0
}
}
/// Packet is a Buffer with the packet Header and the packet max payload size.
pub type Packet = Buffer<Header, { crate::vl1::protocol::PACKET_SIZE_MAX }>;
#[derive(Clone)]
#[repr(packed)]
pub struct FragmentHeader {
pub id: [u8; 8],
pub dest: [u8; 5],
pub fragment_indicator: u8,
pub total_and_fragment_no: u8,
pub hops: u8,
}
unsafe impl RawObject for FragmentHeader {}
impl FragmentHeader {
#[inline(always)]
pub fn total_fragments(&self) -> u8 {
self.total_and_fragment_no >> 4
}
#[inline(always)]
pub fn fragment_no(&self) -> u8 {
self.total_and_fragment_no & 0x0f
}
#[inline(always)]
pub fn hops(&self) -> u8 {
self.hops & HEADER_FLAGS_FIELD_MASK_HOPS
}
}
#[cfg(test)]
mod tests {
use std::mem::size_of;
use crate::vl1::packet::{Header, FragmentHeader};
use crate::vl1::protocol::{PACKET_HEADER_SIZE, FRAGMENT_SIZE_MIN};
#[test]
fn object_sizing() {
assert_eq!(size_of::<Header>(), PACKET_HEADER_SIZE);
assert_eq!(size_of::<FragmentHeader>(), FRAGMENT_SIZE_MIN);
}
}

View file

@ -0,0 +1,58 @@
/// Length of an address in bytes.
pub const ADDRESS_SIZE: usize = 5;
/// Size of packet header that lies outside the encryption envelope.
pub const PACKET_HEADER_SIZE: usize = 27;
/// Maximum packet payload size including the verb/flags field.
/// This is large enough to carry "jumbo MTU" packets. The size is
/// odd because 10005+27 == 10032 which is divisible by 16. This
/// improves memory layout and alignment when buffers are allocated.
/// This value could technically be increased but it would require a
/// protocol version bump and only new nodes would be able to accept
/// the new size.
pub const PACKET_PAYLOAD_SIZE_MAX: usize = 10005;
/// Minimum packet, which is the header plus a verb.
pub const PACKET_SIZE_MIN: usize = PACKET_HEADER_SIZE + 1;
/// Maximum size of an entire packet.
pub const PACKET_SIZE_MAX: usize = PACKET_HEADER_SIZE + PACKET_PAYLOAD_SIZE_MAX;
/// Mask to select cipher from header flags field.
pub const HEADER_FLAGS_FIELD_MASK_CIPHER: u8 = 0x30;
/// Mask to select packet hops from header flags field.
pub const HEADER_FLAGS_FIELD_MASK_HOPS: u8 = 0x07;
/// Packet is not encrypted but contains a Poly1305 MAC of the plaintext.
/// Poly1305 is initialized with Salsa20/12 in the same manner as SALSA2012_POLY1305.
pub const CIPHER_NOCRYPT_POLY1305: u8 = 0;
/// Packet is encrypted and authenticated with Salsa20/12 and Poly1305.
/// Construction is the same as that which is used in the NaCl secret box functions.
pub const CIPHER_SALSA2012_POLY1305: u8 = 0x10;
/// Packet is encrypted and authenticated with AES-GMAC-SIV.
pub const CIPHER_AES_GMAC_SIV: u8 = 0x30;
/// Header (outer) flag indicating that this packet has additional fragments.
pub const HEADER_FLAG_FRAGMENTED: u8 = 0x40;
/// Minimum size of a fragment.
pub const FRAGMENT_SIZE_MIN: usize = 16;
/// Verb (inner) flag indicating that the packet's payload (after the verb) is LZ4 compressed.
pub const VERB_FLAG_COMPRESSED: u8 = 0x80;
/// Maximum number of packet hops allowed by the protocol.
pub const PROTOCOL_MAX_HOPS: usize = 7;
/// Index of packet fragment indicator byte to detect fragments.
pub const FRAGMENT_INDICATOR_INDEX: usize = 13;
/// Byte found at FRAGMENT_INDICATOR_INDEX to indicate a fragment.
pub const FRAGMENT_INDICATOR: u8 = 0xff;
/// Prefix indicating reserved addresses (that can't actually be addresses).
pub const ADDRESS_RESERVED_PREFIX: u8 = 0xff;