anubis-wormhole 1.0.0

A post-quantum secure file transfer tool based on the Magic Wormhole protocol.
Documentation
use crate::providers::aes_gcm_siv::Nonce96;
use bitflags::bitflags;

pub const NONCE_LEN: usize = 12; // 96-bit
pub const TAG_LEN: usize = 16;   // AES-GCM-SIV tag

pub mod sc {
    pub const CONTROL: u32 = 0;
    pub const DATA: u32 = 1;
    pub const DILATE: u32 = 2;
}

#[repr(u8)]
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum RecordType { Control = 0, Data = 1, Dilate = 2 }

#[repr(u8)]
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Role { Initiator = 0, Responder = 1 }

bitflags! {
    #[derive(Clone, Copy, Debug, PartialEq, Eq)]
    pub struct Flags: u8 {
        const FIN       = 0x01;
        // Reserved policy bit for first CONTROL record under L5 policy
        const L5_POLICY = 0x80;
    }
}

#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct SessionId(pub [u8; 16]);

#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct SubchannelId(pub u32);

#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct Seq(pub u64);

pub const AAD_LEN_BASE: usize = 1 + 1 + 1 + 4 + 8 + 1 + 16;
pub const AAD_LEN_WITH_CAP: usize = AAD_LEN_BASE + 16;

#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct AAD {
    pub version: u8,
    pub role: Role,
    pub record_type: RecordType,
    pub subchannel: u32,
    pub seq: u64,
    pub flags: Flags,
    pub session_id: [u8; 16],
    pub capabilities_hash16: Option<[u8; 16]>,
}

impl AAD {
    pub fn encode_into(&self, out: &mut [u8]) -> usize {
        let need = if self.capabilities_hash16.is_some() { AAD_LEN_WITH_CAP } else { AAD_LEN_BASE };
        assert!(out.len() >= need);
        let mut i = 0;
        out[i] = self.version; i += 1;
        out[i] = self.role as u8; i += 1;
        out[i] = self.record_type as u8; i += 1;
        out[i..i+4].copy_from_slice(&self.subchannel.to_be_bytes()); i += 4;
        out[i..i+8].copy_from_slice(&self.seq.to_be_bytes()); i += 8;
        out[i] = self.flags.bits(); i += 1;
        out[i..i+16].copy_from_slice(&self.session_id); i += 16;
        if let Some(h) = self.capabilities_hash16 { out[i..i+16].copy_from_slice(&h); i += 16; }
        i
    }
    pub fn to_bytes(&self) -> Vec<u8> { let mut v = vec![0u8; if self.capabilities_hash16.is_some(){AAD_LEN_WITH_CAP}else{AAD_LEN_BASE}]; let _ = self.encode_into(&mut v[..]); v }
}

#[inline]
pub fn pack_nonce(subchannel: u32, seq: u64) -> Nonce96 {
    let mut n = [0u8; 12];
    n[..4].copy_from_slice(&subchannel.to_be_bytes());
    n[4..].copy_from_slice(&seq.to_be_bytes());
    n
}

pub fn capabilities_hash16(cap_bytes: &[u8]) -> [u8; 16] {
    use sha2::{Digest, Sha256};
    let h = Sha256::digest(cap_bytes);
    let mut out = [0u8; 16];
    out.copy_from_slice(&h[..16]);
    out
}

#[cfg(test)]
mod tests {
    use super::*;
    #[test]
    fn nonce_layout() {
        let n = pack_nonce(0x11223344, 0x0102030405060708);
        assert_eq!(&n[..4], &[0x11,0x22,0x33,0x44]);
        assert_eq!(&n[4..], &[0x01,0x02,0x03,0x04,0x05,0x06,0x07,0x08]);
    }
    #[test]
    fn aad_bytes_len() {
        let a = AAD { version: 1, role: Role::Initiator, record_type: RecordType::Control, subchannel: sc::CONTROL, seq: 9, flags: Flags::empty(), session_id: [0u8;16], capabilities_hash16: None };
        assert_eq!(a.to_bytes().len(), AAD_LEN_BASE);
        let mut a2 = a; a2.capabilities_hash16 = Some([1u8;16]);
        assert_eq!(a2.to_bytes().len(), AAD_LEN_WITH_CAP);
    }
}