anubis-wormhole 1.0.0

A post-quantum secure file transfer tool based on the Magic Wormhole protocol.
Documentation
use crate::aead_policy::{RecordType, Flags};
#[cfg(feature = "policy-l5")]
use crate::aead_policy::sc;
use crate::frame::Frame;
use crate::providers::aes_gcm_siv::Aes256GcmSivProvider;
use crate::aead_policy::AAD;
use crate::journal::{Journal, JournalEvent, NoopJournal};
use crate::error::{AnubisError, Result};
use std::collections::VecDeque;

pub struct SubchSend {
    pub subchannel: u32,
    pub next_seq: u64,
    pub closed: bool,
    capacity: usize,
    queue: VecDeque<Vec<u8>>,
}

impl SubchSend {
    pub fn new(subchannel: u32) -> Self { Self { subchannel, next_seq: 0, closed: false, capacity: 64, queue: VecDeque::new() } }
    pub fn with_capacity(mut self, cap: usize) -> Self { self.capacity = cap; self }
    pub fn seal(&mut self, key: &[u8;32], session_id: [u8;16], role: super::aead_policy::Role, rtype: RecordType, payload: &[u8], fin: bool) -> Vec<u8> {
        assert!(!self.closed);
        #[allow(unused_mut)]
        let mut flags = if fin { Flags::FIN } else { Flags::empty() };
        // Under L5 policy, set L5_POLICY on the very first CONTROL record (seq==0)
        #[cfg(feature = "policy-l5")]
        {
            if crate::policy::l5_enforcement_enabled() {
                if rtype == RecordType::Control && self.subchannel == sc::CONTROL && self.next_seq == 0 {
                    flags |= Flags::L5_POLICY;
                }
            }
        }
        let aad = AAD { version: 1, role, record_type: rtype, subchannel: self.subchannel, seq: self.next_seq, flags, session_id, capabilities_hash16: None };
        let ct = Frame::seal(&Aes256GcmSivProvider, key, &aad, payload).expect("seal");
        if fin { self.closed = true; }
        self.next_seq += 1;
        ct
    }

    pub fn enqueue_with_journal<J: Journal>(&mut self, key: &[u8;32], session_id: [u8;16], role: super::aead_policy::Role, rtype: RecordType, payload: &[u8], fin: bool, journal: &mut J) -> Result<()> {
        if self.queue.len() >= self.capacity { journal.record(&JournalEvent::SubchQueueOverflow { subchannel: self.subchannel, capacity: self.capacity }); return Err(AnubisError::InvalidInput); }
        let ct = self.seal(key, session_id, role, rtype, payload, fin);
        self.queue.push_back(ct);
        Ok(())
    }
    pub fn enqueue(&mut self, key: &[u8;32], session_id: [u8;16], role: super::aead_policy::Role, rtype: RecordType, payload: &[u8], fin: bool) -> Result<()> {
        let mut j = NoopJournal; self.enqueue_with_journal(key, session_id, role, rtype, payload, fin, &mut j)
    }
    pub fn dequeue(&mut self) -> Option<Vec<u8>> { self.queue.pop_front() }
    pub fn is_full(&self) -> bool { self.queue.len() >= self.capacity }
    pub fn len(&self) -> usize { self.queue.len() }
}

pub struct SubchRecv {
    pub subchannel: u32,
    pub expected_seq: u64,
    pub closed: bool,
}

impl SubchRecv {
    pub fn new(subchannel: u32) -> Self { Self { subchannel, expected_seq: 0, closed: false } }
    pub fn open_with_journal<J: Journal>(&mut self, key: &[u8;32], session_id: [u8;16], role: super::aead_policy::Role, rtype: RecordType, ct: &[u8], flags: Flags, seq: u64, journal: &mut J) -> Result<Vec<u8>> {
        if self.closed { journal.record(&JournalEvent::SubchFinAfterFin { subchannel: self.subchannel }); return Err(AnubisError::InvalidInput); }
        if seq != self.expected_seq {
            if seq < self.expected_seq { journal.record(&JournalEvent::SubchDuplicate { subchannel: self.subchannel, expected: self.expected_seq, got: seq }); } else { journal.record(&JournalEvent::SubchOutOfOrder { subchannel: self.subchannel, expected: self.expected_seq, got: seq }); }
            return Err(AnubisError::InvalidInput);
        }
        // Under L5 policy, enforce L5_POLICY flag on first CONTROL record
        #[cfg(feature = "policy-l5")]
        {
            if crate::policy::l5_enforcement_enabled() {
                if rtype == RecordType::Control && self.subchannel == sc::CONTROL && seq == 0 {
                    if !flags.contains(Flags::L5_POLICY) {
                        return Err(AnubisError::InvalidInput);
                    }
                }
            }
        }
        let aad = AAD { version: 1, role, record_type: rtype, subchannel: self.subchannel, seq, flags, session_id, capabilities_hash16: None };
        let pt = Frame::open(&Aes256GcmSivProvider, key, &aad, ct)?;
        self.expected_seq += 1;
        if flags.contains(Flags::FIN) { self.closed = true; }
        Ok(pt)
    }
    pub fn open(&mut self, key: &[u8;32], session_id: [u8;16], role: super::aead_policy::Role, rtype: RecordType, ct: &[u8], flags: Flags, seq: u64) -> Option<Vec<u8>> {
        let mut j = NoopJournal;
        self.open_with_journal(key, session_id, role, rtype, ct, flags, seq, &mut j).ok()
    }
}