dave 0.2.0

Rust DAVE primitives with Discord Opus audio and video frame transform support.
Documentation
use std::{array::TryFromSliceError, num::NonZeroU16};

use openmls::{
    framing::errors::ProtocolMessageError,
    group::{
        CommitToPendingProposalsError, ExportSecretError, MergeCommitError,
        MergePendingCommitError, NewGroupError, ProcessMessageError, RemoveProposalError,
        WelcomeError,
    },
    prelude::{CryptoError, InvalidExtensionError, KeyPackageNewError, tls_codec},
};
use openmls_rust_crypto::MemoryStorageError;
use thiserror::Error as ThisError;

use crate::{DAVE_PROTOCOL_VERSION, frame::MediaType};

#[derive(Debug, ThisError)]
#[error("unsupported DAVE protocol version {0}; max supported is {DAVE_PROTOCOL_VERSION}")]
pub struct UnsupportedProtocolVersion(pub NonZeroU16);

#[derive(Debug, ThisError)]
pub enum InitError {
    #[error("{0}")]
    UnsupportedProtocolVersion(#[from] UnsupportedProtocolVersion),
    #[error("failed to generate signature key pair: {0}")]
    KeyPairGeneration(#[from] CryptoError),
}

#[derive(Debug, ThisError)]
pub enum SetExternalSenderError {
    #[error("cannot set external sender while session is active")]
    AlreadyInGroup,
    #[error("DAVE MLS storage lock is poisoned")]
    StoragePoisoned,
    #[error("failed to delete current MLS group: {0}")]
    DeleteGroup(#[from] MemoryStorageError),
    #[error("failed to deserialize external sender: {0}")]
    DeserializeExternalSender(#[from] tls_codec::Error),
    #[error("failed to create pending MLS group: {0}")]
    PendingGroup(#[from] PendingGroupError),
}

#[derive(Debug, ThisError)]
pub enum CreateKeyPackageError {
    #[error("failed to deserialize DAVE key-package lifetime: {0}")]
    DeserializeLifetime(tls_codec::Error),
    #[error("failed to build DAVE key package: {0}")]
    Build(#[from] KeyPackageNewError),
    #[error("failed to serialize DAVE key package: {0}")]
    Serialize(tls_codec::Error),
}

#[derive(Debug, ThisError)]
pub enum PendingGroupError {
    #[error("cannot create pending group without external sender")]
    NoExternalSender,
    #[error("failed to add external sender extension: {0}")]
    AddExternalSender(#[from] InvalidExtensionError),
    #[error("failed to create pending MLS group: {0}")]
    CreateGroup(#[from] NewGroupError<MemoryStorageError>),
}

#[derive(Debug, ThisError)]
pub enum ProcessProposalsError {
    #[error("cannot process DAVE proposals without an MLS group")]
    NoGroup,
    #[error("failed to deserialize proposal vector: {0}")]
    DeserializeProposalVector(tls_codec::Error),
    #[error("failed to deserialize MLS message: {0}")]
    DeserializeMessage(tls_codec::Error),
    #[error("message was not a public or private MLS message: {0}")]
    MessageNotPublicOrPrivate(#[from] ProtocolMessageError),
    #[error("failed to process MLS message: {0}")]
    ProcessMessage(#[from] ProcessMessageError<MemoryStorageError>),
    #[error("failed to convert credential content to user ID: {0}")]
    CredentialContent(TryFromSliceError),
    #[error("unexpected proposed user {0}")]
    UnexpectedUser(u64),
    #[error("failed to store pending proposal: {0}")]
    StorePendingProposal(MemoryStorageError),
    #[error("processed MLS message was not a proposal")]
    MessageNotProposal,
    #[error("failed to deserialize proposal reference: {0}")]
    DeserializeProposalRef(tls_codec::Error),
    #[error("failed to remove pending proposal: {0}")]
    RemovePendingProposal(#[from] RemoveProposalError<MemoryStorageError>),
    #[error("failed to clear pending commit: {0}")]
    ClearPendingCommit(MemoryStorageError),
    #[error("failed to commit pending proposals: {0}")]
    CommitPendingProposals(CommitToPendingProposalsError<MemoryStorageError>),
    #[error("failed to serialize DAVE commit: {0}")]
    SerializeCommit(tls_codec::Error),
    #[error("failed to serialize DAVE welcome: {0}")]
    SerializeWelcome(tls_codec::Error),
}

#[derive(Debug, ThisError)]
pub enum ProcessWelcomeError {
    #[error("cannot process DAVE welcome while already active in a group")]
    AlreadyInGroup,
    #[error("cannot process DAVE welcome without an external sender")]
    NoExternalSender,
    #[error("failed to deserialize DAVE welcome: {0}")]
    DeserializeWelcome(#[from] tls_codec::Error),
    #[error("failed to create staged welcome: {0}")]
    StageWelcome(#[from] WelcomeError<MemoryStorageError>),
    #[error("welcome did not contain external senders extension")]
    MissingExternalSenderExtension,
    #[error("welcome contained {0} external senders; expected exactly one")]
    InvalidExternalSenderCount(usize),
    #[error("welcome external sender does not match the gateway external sender")]
    UnexpectedExternalSender,
    #[error("failed to delete pending MLS group: {0}")]
    DeletePendingGroup(MemoryStorageError),
    #[error("failed to update media ratchets: {0}")]
    UpdateRatchets(#[from] UpdateRatchetsError),
}

#[derive(Debug, ThisError)]
pub enum ProcessCommitError {
    #[error("cannot process DAVE commit without an MLS group")]
    NoGroup,
    #[error("cannot process DAVE commit for a pending MLS group")]
    PendingGroup,
    #[error("failed to deserialize DAVE commit: {0}")]
    DeserializeCommit(tls_codec::Error),
    #[error("message was not a public or private MLS message: {0}")]
    MessageNotPublicOrPrivate(#[from] ProtocolMessageError),
    #[error("DAVE commit was for a different MLS group")]
    WrongGroup,
    #[error("failed to merge own pending commit: {0}")]
    MergePendingCommit(#[from] MergePendingCommitError<MemoryStorageError>),
    #[error("failed to merge staged commit: {0}")]
    MergeStagedCommit(#[from] MergeCommitError<MemoryStorageError>),
    #[error("failed to process MLS message: {0}")]
    ProcessMessage(#[from] ProcessMessageError<MemoryStorageError>),
    #[error("processed MLS message was not a staged commit")]
    MessageNotStagedCommit,
    #[error("failed to update media ratchets: {0}")]
    UpdateRatchets(#[from] UpdateRatchetsError),
}

#[derive(Debug, ThisError)]
pub enum UpdateRatchetsError {
    #[error("cannot derive DAVE media ratchets without an established MLS group")]
    NoEstablishedGroup,
    #[error("failed to export MLS secret: {0}")]
    ExportSecret(#[from] ExportSecretError),
    #[error("failed to convert member credential content to user ID: {0}")]
    MemberCredentialContent(TryFromSliceError),
    #[error("failed to derive ratchet secret")]
    DeriveSecret,
}

#[derive(Clone, Debug, ThisError, PartialEq, Eq)]
pub enum EncryptError {
    #[error("DAVE sender ratchet is not active")]
    SenderNotReady,
    #[error("unsupported codec {codec:?} for media type {media_type:?}")]
    UnsupportedCodec {
        media_type: MediaType,
        codec: crate::frame::Codec,
    },
    #[error("encrypted output buffer is too small: need {needed} bytes, got {available}")]
    OutputTooSmall { needed: usize, available: usize },
    #[error("failed to initialize AES-GCM cipher")]
    InvalidKey,
    #[error("AES-GCM encryption failed")]
    Aead,
    #[error("failed to encode DAVE frame supplemental data")]
    FrameEncoding,
    #[error("DAVE frame supplemental data is too large")]
    SupplementalDataTooLarge,
    #[error("encrypted DAVE frame failed codec validation after retrying nonces")]
    TooManyAttempts,
    #[error("DAVE ratchet key is unavailable")]
    MissingRatchetKey,
}

#[derive(Clone, Debug, ThisError, PartialEq, Eq)]
pub enum DecryptError {
    #[error("user {user_id} has no DAVE decryptor")]
    NoDecryptorForUser { user_id: u64 },
    #[error("{0}")]
    Frame(#[from] FrameDecryptError),
}

#[derive(Clone, Debug, ThisError, PartialEq, Eq)]
pub enum FrameDecryptError {
    #[error("frame was not encrypted and passthrough is disabled")]
    PassthroughDisabled,
    #[error("encrypted DAVE frame is malformed")]
    MalformedFrame,
    #[error("frame nonce has already been processed")]
    ReplayedNonce,
    #[error("DAVE frame references an unavailable key generation {generation}")]
    MissingCryptor { generation: u32 },
    #[error("AES-GCM authentication failed for DAVE frame generation {generation}")]
    Aead { generation: u32 },
    #[error(
        "no DAVE cryptor could decrypt {media_type:?} frame; encrypted size {encrypted_size}, plaintext buffer {plaintext_capacity}, managers {manager_count}"
    )]
    NoValidCryptor {
        media_type: MediaType,
        encrypted_size: usize,
        plaintext_capacity: usize,
        manager_count: usize,
    },
    #[error("decrypted output buffer is too small: need {needed} bytes, got {available}")]
    OutputTooSmall { needed: usize, available: usize },
    #[error("failed to initialize AES-GCM cipher")]
    InvalidKey,
}

#[derive(Debug, ThisError)]
pub enum Error {
    #[error("{0}")]
    Init(#[from] InitError),
    #[error("{0}")]
    SetExternalSender(#[from] SetExternalSenderError),
    #[error("{0}")]
    CreateKeyPackage(#[from] CreateKeyPackageError),
    #[error("{0}")]
    ProcessProposals(#[from] ProcessProposalsError),
    #[error("{0}")]
    ProcessWelcome(#[from] ProcessWelcomeError),
    #[error("{0}")]
    ProcessCommit(#[from] ProcessCommitError),
    #[error("{0}")]
    Encrypt(#[from] EncryptError),
    #[error("{0}")]
    Decrypt(#[from] DecryptError),
}