Revise some V1 crypto stuff, rearrange some things, simplify Buffer.

This commit is contained in:
Adam Ierymenko 2021-07-28 13:30:58 -04:00
parent 7a2361b62c
commit ed142273d0
No known key found for this signature in database
GPG key ID: C8877CF2D7A5D7F3
15 changed files with 229 additions and 306 deletions

View file

@ -0,0 +1,12 @@
use crate::crypto::hash::SHA384;
/// Derive a key using KBKDF prefaced by the bytes 'ZT' for use in ZeroTier.
/// This is a fixed cost key derivation function used to derive sub-keys from a single original
/// shared secret for different uses, such as the K0/K1 in AES-GMAC-SIV.
/// Key must be 384 bits in length.
pub fn zt_kbkdf_hmac_sha384(key: &[u8], label: u8, context: u8, iter: u32) -> [u8; 48] {
debug_assert!(key.len() == 48);
// HMAC'd message is: preface | iteration[4], preface[2], label, 0x00, context, hash size[4]
// See: https://nvlpubs.nist.gov/nistpubs/Legacy/SP/nistspecialpublication800-108.pdf
SHA384::hmac(key, &[(iter >> 24) as u8, (iter >> 16) as u8, (iter >> 8) as u8, iter as u8, b'Z', b'T', label, 0, context, 0, 0, 0x01, 0x80])
}

View file

@ -4,3 +4,6 @@ pub mod p521;
pub mod salsa;
pub mod poly1305;
pub mod balloon;
pub mod kbkdf;
pub use aes_gmac_siv;

View file

@ -1,5 +1,10 @@
pub mod crypto;
pub mod vl1;
pub mod util;
pub mod error;
pub mod vl1;
pub mod vl2;
pub const VERSION_MAJOR: u8 = 1;
pub const VERSION_MINOR: u8 = 9;
pub const VERSION_REVISION: u8 = 1;
pub const VERSION_STR: &'static str = "1.9.1";

View file

@ -1,7 +1,7 @@
use std::str::FromStr;
use std::hash::{Hash, Hasher};
use crate::vl1::protocol::ADDRESS_RESERVED_PREFIX;
use crate::vl1::constants::ADDRESS_RESERVED_PREFIX;
use crate::error::InvalidFormatError;
use crate::util::hex::HEX_CHARS;

View file

@ -1,5 +1,5 @@
use std::mem::{size_of, MaybeUninit, transmute};
use std::marker::PhantomData;
use std::mem::{size_of, MaybeUninit, zeroed};
use std::ptr::write_bytes;
use std::io::Write;
const OVERFLOW_ERR_MSG: &'static str = "overflow";
@ -9,47 +9,23 @@ const OVERFLOW_ERR_MSG: &'static str = "overflow";
/// 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 by setting the buffer's size to the
/// header size. The header must have a size less than or equal to L. Use NoHeader
/// if you don't want a header.
#[derive(Clone, PartialEq, Eq)]
pub struct Buffer<H: RawObject, const L: usize>(usize, [u8; L], PhantomData<H>);
pub struct Buffer<const L: usize>(usize, [u8; L]);
unsafe impl<H: RawObject, const L: usize> RawObject for Buffer<H, L> {}
unsafe impl<const L: usize> RawObject for Buffer<L> {}
impl<H: RawObject, const L: usize> Default for Buffer<H, L> {
impl<const L: usize> Default for Buffer<L> {
#[inline(always)]
fn default() -> Self {
debug_assert!(size_of::<H>() <= L);
Buffer(size_of::<H>(), [0_u8; L], PhantomData::default())
unsafe { zeroed() }
}
}
impl<H: RawObject, const L: usize> Buffer<H, L> {
impl<const L: usize> Buffer<L> {
#[inline(always)]
pub fn new() -> Self {
debug_assert!(size_of::<H>() <= L);
Self::default()
}
/// Change the header "personality" of this buffer.
/// This is a free operation, but the returned reference obviously only lives as long as the source.
#[inline(always)]
pub fn transmute_header<NH: RawObject>(&self) -> &Buffer<NH, L> {
debug_assert!(size_of::<H>() <= L);
debug_assert!(size_of::<NH>() <= L);
unsafe {
&*(self as *const Self).cast::<Buffer<NH, L>>()
}
unsafe { zeroed() }
}
/// Create a buffer that contains a copy of a slice, truncating if the slice is too long.
@ -78,8 +54,7 @@ impl<H: RawObject, const L: usize> Buffer<H, L> {
/// Erase contents and reset size to the size of the header.
#[inline(always)]
pub fn clear(&mut self) {
self.1[0..self.0].fill(0);
self.0 = size_of::<H>();
unsafe { write_bytes((self as *mut Self).cast::<u8>(), 0, size_of::<Self>()) }
}
/// Get the length of this buffer (including header, if any).
@ -88,20 +63,6 @@ impl<H: RawObject, const L: usize> Buffer<H, L> {
self.0
}
/// Get a reference to the header (in place).
#[inline(always)]
pub fn header(&self) -> &H {
debug_assert!(size_of::<H>() <= L);
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 {
debug_assert!(size_of::<H>() <= L);
unsafe { &mut *self.1.as_mut_ptr().cast::<H>() }
}
/// Append a packed structure and call a function to initialize it in place.
/// Anything not initialized will be zero.
#[inline(always)]
@ -234,12 +195,6 @@ impl<H: RawObject, const L: usize> Buffer<H, L> {
}
}
/// Get the index of the start of the payload after the header.
#[inline(always)]
pub fn cursor_after_header(&self) -> usize {
size_of::<H>()
}
/// Get a structure at a given position in the buffer and advance the cursor.
#[inline(always)]
pub fn get_struct<T: RawObject>(&self, cursor: &mut usize) -> std::io::Result<&T> {
@ -336,7 +291,7 @@ impl<H: RawObject, const L: usize> Buffer<H, L> {
}
}
impl<H: RawObject, const L: usize> Write for Buffer<H, L> {
impl<const L: usize> Write for Buffer<L> {
#[inline(always)]
fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
let ptr = self.0;
@ -356,41 +311,16 @@ impl<H: RawObject, const L: usize> Write for Buffer<H, L> {
}
}
impl<H: RawObject, const L: usize> AsRef<[u8]> for Buffer<H, L> {
impl<const L: usize> AsRef<[u8]> for Buffer<L> {
#[inline(always)]
fn as_ref(&self) -> &[u8] {
self.as_bytes()
}
}
impl<H: RawObject, const L: usize> AsMut<[u8]> for Buffer<H, L> {
impl<const L: usize> AsMut<[u8]> for Buffer<L> {
#[inline(always)]
fn as_mut(&mut self) -> &mut [u8] {
self.as_bytes_mut()
}
}
impl<H: RawObject, const L: usize> AsRef<H> for Buffer<H, L> {
#[inline(always)]
fn as_ref(&self) -> &H {
self.header()
}
}
impl<H: RawObject, const L: usize> AsMut<H> for Buffer<H, L> {
#[inline(always)]
fn as_mut(&mut self) -> &mut H {
self.header_mut()
}
}
#[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,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;

View file

@ -1,6 +1,6 @@
use crate::vl1::{Address, MAC};
use crate::vl1::inetaddress::InetAddress;
use crate::vl1::buffer::{RawObject, Buffer};
use crate::vl1::buffer::Buffer;
use std::hash::{Hash, Hasher};
const TYPE_NIL: u8 = 0;
@ -75,7 +75,7 @@ impl Endpoint {
}
}
pub fn marshal<BH: RawObject, const BL: usize>(&self, buf: &mut Buffer<BH, BL>) -> std::io::Result<()> {
pub fn marshal<const BL: usize>(&self, buf: &mut Buffer<BL>) -> std::io::Result<()> {
match self {
Endpoint::Nil => {
buf.append_u8(Type::Nil as u8)
@ -101,6 +101,9 @@ impl Endpoint {
ip.marshal(buf)
}
Endpoint::IpUdp(ip) => {
// IP/UDP endpoints are marshaled as naked InetAddress objects for backward
// compatibility. This is why 16 is added to all the other type IDs. Naked
// InetAddress objects always start with either 4 or 6.
ip.marshal(buf)
}
Endpoint::IpTcp(ip) => {
@ -122,7 +125,7 @@ impl Endpoint {
}
}
pub fn unmarshal<BH: RawObject, const BL: usize>(buf: &Buffer<BH, BL>, cursor: &mut usize) -> std::io::Result<Endpoint> {
pub fn unmarshal<const BL: usize>(buf: &Buffer<BL>, cursor: &mut usize) -> std::io::Result<Endpoint> {
let type_byte = buf.get_u8(cursor)?;
if type_byte < 16 {
let ip = InetAddress::unmarshal(buf, cursor)?;

View file

@ -2,17 +2,17 @@ use std::alloc::{Layout, dealloc, alloc};
use std::ptr::{slice_from_raw_parts_mut, slice_from_raw_parts};
use std::io::Write;
use std::str::FromStr;
use std::convert::TryInto;
use std::cmp::Ordering;
use crate::vl1::Address;
use crate::vl1::buffer::{Buffer, RawObject, NoHeader};
use crate::crypto::c25519::{C25519_PUBLIC_KEY_SIZE, ED25519_PUBLIC_KEY_SIZE, C25519_SECRET_KEY_SIZE, ED25519_SECRET_KEY_SIZE, C25519KeyPair, Ed25519KeyPair};
use crate::vl1::buffer::Buffer;
use crate::crypto::c25519::{C25519_PUBLIC_KEY_SIZE, ED25519_PUBLIC_KEY_SIZE, C25519_SECRET_KEY_SIZE, ED25519_SECRET_KEY_SIZE, C25519KeyPair, Ed25519KeyPair, ED25519_SIGNATURE_SIZE};
use crate::crypto::p521::{P521KeyPair, P521PublicKey, P521_ECDSA_SIGNATURE_SIZE, P521_PUBLIC_KEY_SIZE, P521_SECRET_KEY_SIZE};
use crate::crypto::hash::{SHA384, SHA512, SHA512_HASH_SIZE};
use crate::crypto::balloon;
use crate::crypto::salsa::Salsa;
use crate::error::InvalidFormatError;
use std::convert::TryInto;
use std::cmp::Ordering;
// Memory parameter for V0 address derivation work function.
const V0_IDENTITY_GEN_MEMORY: usize = 2097152;
@ -22,6 +22,9 @@ const V1_BALLOON_SPACE_COST: usize = 16384;
const V1_BALLOON_TIME_COST: usize = 3;
const V1_BALLOON_DELTA: usize = 3;
pub const IDENTITY_TYPE_0_SIGNATURE_SIZE: usize = ED25519_SIGNATURE_SIZE + 32;
pub const IDENTITY_TYPE_1_SIGNATURE_SIZE: usize = P521_ECDSA_SIGNATURE_SIZE + ED25519_SIGNATURE_SIZE;
#[derive(Copy, Clone)]
#[repr(u8)]
pub enum Type {
@ -241,13 +244,33 @@ impl Identity {
/// its secrets or a key being invalid.
pub fn agree(&self, other_identity: &Identity) -> Option<[u8; 48]> {
self.secrets.as_ref().map_or(None, |secrets| {
let c25519_secret = SHA384::hash(&secrets.c25519.agree(&other_identity.c25519));
secrets.v1.as_ref().map_or_else(|| {
Some(SHA384::hash(&secrets.c25519.agree(&other_identity.c25519)))
Some(c25519_secret)
}, |p521_secret| {
other_identity.v1.as_ref().map_or_else(|| {
Some(SHA384::hash(&secrets.c25519.agree(&other_identity.c25519)))
Some(c25519_secret)
}, |other_p521_public| {
p521_secret.0.agree(&other_p521_public.0).map_or(None, |secret| Some(SHA384::hash(&secret)))
p521_secret.0.agree(&other_p521_public.0).map_or(None, |p521_secret| {
//
// For NIST P-521 key agreement, we use a single step key derivation function to derive
// the final shared secret using the C25519 shared secret as a "salt." This should be
// FIPS140-compliant as per section 8.2 of:
//
// https://nvlpubs.nist.gov/nistpubs/SpecialPublications/NIST.SP.800-56Cr2.pdf
//
// This is also stated in the following FAQ, which though it pertains to post-quantum
// algorithms states that non-FIPS derived shared secrets can be used as salts in key
// derivation from a FIPS-compliant algorithm derived shared secret:
//
// https://csrc.nist.gov/Projects/post-quantum-cryptography/faqs
//
// Hashing the C25519 secret with the P521 secret results in a secret that is as strong
// as the stronger of the two algorithms. This should make the FIPS people happy and the
// people who are paranoid about NIST curves happy.
//
Some(SHA384::hmac(&c25519_secret, &p521_secret))
})
})
})
})
@ -258,10 +281,25 @@ impl Identity {
/// type. None is returned if this identity lacks secret keys or another error occurs.
pub fn sign(&self, msg: &[u8]) -> Option<Vec<u8>> {
self.secrets.as_ref().map_or(None, |secrets| {
let c25519_sig = secrets.ed25519.sign_zt(msg);
secrets.v1.as_ref().map_or_else(|| {
Some(secrets.ed25519.sign(msg).to_vec())
Some(c25519_sig.to_vec())
}, |p521_secret| {
p521_secret.1.sign(msg).map_or(None, |sig| Some(sig.to_vec()))
p521_secret.1.sign(msg).map_or(None, |p521_sig| {
//
// For type 1 identity signatures we sign with both algorithms and append the Ed25519
// signature to the NIST P-521 signature. The Ed25519 signature is only checked if the
// P-521 signature validates. Note that we only append the first 64 bytes of sign_zt()
// output. For legacy reasons type 0 signatures include the first 32 bytes of the message
// hash after the signature, but this is not required and isn't included here.
//
// This should once again make both the FIPS people and the people paranoid about NIST
// curves happy.
//
let mut p521_sig = p521_sig.to_vec();
let _ = p521_sig.write_all(&c25519_sig[0..64]);
Some(p521_sig)
})
})
})
}
@ -271,7 +309,11 @@ impl Identity {
self.v1.as_ref().map_or_else(|| {
crate::crypto::c25519::ed25519_verify(&self.ed25519, signature, msg)
}, |p521| {
(*p521).1.verify(msg, signature)
if signature.len() == IDENTITY_TYPE_1_SIGNATURE_SIZE {
(*p521).1.verify(msg, &signature[0..P521_ECDSA_SIGNATURE_SIZE]) && crate::crypto::c25519::ed25519_verify(&self.ed25519, &signature[P521_ECDSA_SIGNATURE_SIZE..], msg)
} else {
false
}
})
}
@ -297,7 +339,7 @@ impl Identity {
}
/// Append this in binary format to a buffer.
pub fn marshal<BH: RawObject, const BL: usize>(&self, buf: &mut Buffer<BH, BL>, include_private: bool) -> std::io::Result<()> {
pub fn marshal<const BL: usize>(&self, buf: &mut Buffer<BL>, include_private: bool) -> std::io::Result<()> {
buf.append_bytes_fixed(&self.address.to_bytes())?;
if self.v1.is_some() {
let p521 = self.v1.as_ref().unwrap();
@ -339,7 +381,7 @@ impl Identity {
/// Deserialize an Identity from a buffer.
/// The supplied cursor is advanced.
pub fn unmarshal<BH: RawObject, const BL: usize>(buf: &Buffer<BH, BL>, cursor: &mut usize) -> std::io::Result<Identity> {
pub fn unmarshal<const BL: usize>(buf: &Buffer<BL>, cursor: &mut usize) -> std::io::Result<Identity> {
let addr = Address::from_bytes(buf.get_bytes_fixed::<5>(cursor)?).unwrap();
let id_type = buf.get_u8(cursor)?;
if id_type == Type::C25519 as u8 {
@ -413,7 +455,7 @@ impl Identity {
/// Get this identity in byte array format.
pub fn marshal_to_bytes(&self, include_private: bool) -> Vec<u8> {
let mut buf: Buffer<NoHeader, 2048> = Buffer::new();
let mut buf: Buffer<2048> = Buffer::new();
self.marshal(&mut buf, include_private).expect("overflow");
buf.as_bytes().to_vec()
}
@ -422,7 +464,7 @@ impl Identity {
/// On success the identity and the number of bytes actually read from the slice are
/// returned.
pub fn unmarshal_from_bytes(bytes: &[u8]) -> std::io::Result<(Identity, usize)> {
let buf = Buffer::<NoHeader, 2048>::from_bytes_lossy(bytes);
let buf = Buffer::<2048>::from_bytes_lossy(bytes);
let mut cursor: usize = 0;
let id = Self::unmarshal(&buf, &mut cursor)?;
Ok((id, cursor))

View file

@ -7,10 +7,10 @@ use std::net::{IpAddr, Ipv6Addr};
use crate::error::InvalidFormatError;
use crate::util::equal_bytes;
use crate::vl1::buffer::Buffer;
#[cfg(windows)]
use winapi::um::winsock2 as winsock2;
use crate::vl1::buffer::{RawObject, Buffer};
#[allow(non_camel_case_types)]
#[cfg(not(windows))]
@ -203,7 +203,7 @@ impl InetAddress {
/// Get raw IP bytes, with length dependent on address family.
#[inline(always)]
pub fn ip(&self) -> &[u8] {
pub fn ip_bytes(&self) -> &[u8] {
unsafe {
match self.sa.sa_family as u8 {
AF_INET => &*(&self.sin.sin_addr.s_addr as *const u32).cast::<[u8; 4]>(),
@ -373,7 +373,7 @@ impl InetAddress {
}
}
pub fn marshal<BH: RawObject, const BL: usize>(&self, buf: &mut Buffer<BH, BL>) -> std::io::Result<()> {
pub fn marshal<const BL: usize>(&self, buf: &mut Buffer<BL>) -> std::io::Result<()> {
unsafe {
match self.sa.sa_family as u8 {
AF_INET => {
@ -399,7 +399,7 @@ impl InetAddress {
}
}
pub fn unmarshal<BH: RawObject, const BL: usize>(buf: &Buffer<BH, BL>, cursor: &mut usize) -> std::io::Result<InetAddress> {
pub fn unmarshal<const BL: usize>(buf: &Buffer<BL>, cursor: &mut usize) -> std::io::Result<InetAddress> {
match buf.get_u8(cursor)? {
4 => {
let b: &[u8; 6] = buf.get_bytes_fixed(cursor)?;

View file

@ -1,7 +1,9 @@
pub(crate) mod constants;
pub(crate) mod protocol;
pub(crate) mod packet;
pub(crate) mod buffer;
pub(crate) mod node;
pub(crate) mod path;
pub(crate) mod peer;
pub mod dictionary;
pub mod identity;

View file

@ -1,59 +0,0 @@
//use crate::vl1::Address;
/*
/// Handler for events generated by the node that pertain to VL1.
pub trait VL1NodeEventHandler: Sync + Send {
/// Called when a core ZeroTier event occurs.
fn event(&self, event: Event, event_data: &[u8]);
/// Called to store an object into the object store.
fn state_put(&self, obj_type: StateObjectType, obj_id: &[u64], obj_data: &[u8]) -> std::io::Result<()>;
/// Called to retrieve an object from the object store.
fn state_get(&self, obj_type: StateObjectType, obj_id: &[u64]) -> std::io::Result<Vec<u8>>;
/// Called to send a packet over the physical network (virtual -> physical).
fn wire_packet_send(&self, local_socket: i64, sock_addr: &InetAddress, data: &[u8], packet_ttl: u32) -> i32;
/// Called to check and see if a physical address should be used for ZeroTier traffic.
fn path_check(&self, address: Address, id: &Identity, local_socket: i64, sock_addr: &InetAddress) -> bool;
/// Called to look up a path to a known node, allowing out of band lookup methods for physical paths to nodes.
fn path_lookup(&self, address: Address, id: &Identity, desired_family: InetAddressFamily) -> Option<InetAddress>;
}
pub struct Node<H: VL1NodeEventHandler> {
handler: H,
}
impl<H: VL1NodeEventHandler> Node<H> {
pub fn new(handler: H) -> Self {
Self {
handler,
}
}
pub fn handler(&self) -> &H {
&self.handler
}
pub fn handler_mut(&mut self) -> &mut H {
&mut self.handler
}
/// Perform periodic background tasks.
/// The first call should happen no more than NODE_BACKGROUND_TASKS_MAX_INTERVAL milliseconds
/// since the node was created, and after this runs it returns the amount of time the caller
/// should wait before calling it again.
#[inline(always)]
pub fn process_background_tasks(&self, clock: i64, ticks: i64) -> i64 {
0
}
/// Get the address of this node.
#[inline(always)]
pub fn address(&self) -> Address {
Address::default()
}
}
*/

View file

@ -1,97 +0,0 @@
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 crate::vl1::buffer::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
}
}
pub type Packet = crate::vl1::buffer::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 crate::vl1::buffer::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
}
}
pub type Fragment = crate::vl1::buffer::Buffer<FragmentHeader, { crate::vl1::protocol::PACKET_SIZE_MAX }>;
#[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

View file

View file

@ -1,58 +1,82 @@
/// Length of an address in bytes.
pub const ADDRESS_SIZE: usize = 5;
use crate::vl1::constants::{HEADER_FLAGS_FIELD_MASK_CIPHER, HEADER_FLAGS_FIELD_MASK_HOPS, HEADER_FLAG_FRAGMENTED};
use std::ops::Not;
use crate::vl1::buffer::RawObject;
/// Size of packet header that lies outside the encryption envelope.
pub const PACKET_HEADER_SIZE: usize = 27;
type PacketID = u64;
/// 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;
#[derive(Clone)]
#[repr(packed)]
pub struct PacketHeader {
pub id: PacketID,
pub dest: [u8; 5],
pub src: [u8; 5],
pub flags_cipher_hops: u8,
pub message_auth: [u8; 8],
}
/// Minimum packet, which is the header plus a verb.
pub const PACKET_SIZE_MIN: usize = PACKET_HEADER_SIZE + 1;
unsafe impl RawObject for PacketHeader {}
/// Maximum size of an entire packet.
pub const PACKET_SIZE_MAX: usize = PACKET_HEADER_SIZE + PACKET_PAYLOAD_SIZE_MAX;
impl PacketHeader {
#[inline(always)]
pub fn cipher(&self) -> u8 {
self.flags_cipher_hops & HEADER_FLAGS_FIELD_MASK_CIPHER
}
/// Mask to select cipher from header flags field.
pub const HEADER_FLAGS_FIELD_MASK_CIPHER: u8 = 0x30;
#[inline(always)]
pub fn hops(&self) -> u8 {
self.flags_cipher_hops & HEADER_FLAGS_FIELD_MASK_HOPS
}
/// Mask to select packet hops from header flags field.
pub const HEADER_FLAGS_FIELD_MASK_HOPS: u8 = 0x07;
#[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);
}
/// 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;
#[inline(always)]
pub fn is_fragmented(&self) -> bool {
(self.flags_cipher_hops & HEADER_FLAG_FRAGMENTED) != 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;
#[derive(Clone)]
#[repr(packed)]
pub struct FragmentHeader {
pub id: PacketID,
pub dest: [u8; 5],
pub fragment_indicator: u8,
pub total_and_fragment_no: u8,
pub hops: u8,
}
/// Packet is encrypted and authenticated with AES-GMAC-SIV.
pub const CIPHER_AES_GMAC_SIV: u8 = 0x30;
unsafe impl crate::vl1::buffer::RawObject for FragmentHeader {}
/// Header (outer) flag indicating that this packet has additional fragments.
pub const HEADER_FLAG_FRAGMENTED: u8 = 0x40;
impl FragmentHeader {
#[inline(always)]
pub fn total_fragments(&self) -> u8 {
self.total_and_fragment_no >> 4
}
/// Minimum size of a fragment.
pub const FRAGMENT_SIZE_MIN: usize = 16;
#[inline(always)]
pub fn fragment_no(&self) -> u8 {
self.total_and_fragment_no & 0x0f
}
/// Verb (inner) flag indicating that the packet's payload (after the verb) is LZ4 compressed.
pub const VERB_FLAG_COMPRESSED: u8 = 0x80;
#[inline(always)]
pub fn hops(&self) -> u8 {
self.hops & HEADER_FLAGS_FIELD_MASK_HOPS
}
}
/// Maximum number of packet hops allowed by the protocol.
pub const PROTOCOL_MAX_HOPS: usize = 7;
#[cfg(test)]
mod tests {
use std::mem::size_of;
use crate::vl1::protocol::{PacketHeader, FragmentHeader};
use crate::vl1::constants::{PACKET_HEADER_SIZE, FRAGMENT_SIZE_MIN};
/// 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;
#[test]
fn object_sizing() {
assert_eq!(size_of::<PacketHeader>(), PACKET_HEADER_SIZE);
assert_eq!(size_of::<FragmentHeader>(), FRAGMENT_SIZE_MIN);
}
}