#![forbid(unsafe_code)]
#![warn(clippy::all, rust_2018_idioms)]
use std::time::Duration;
use thiserror::Error;
pub mod api;
pub mod crypto;
pub mod group;
pub mod key_schedule;
pub mod member;
pub mod protocol;
pub mod quic_integration;
pub mod treekem;
pub mod treekem_group;
pub use api::{
add_member, group_new, group_new_with_config, recv, remove_member, send, Ciphertext,
CommitOptions, GroupId as SimpleGroupId, Identity,
};
pub use crypto::{
AeadCipher, CipherSuite, CipherSuiteId, Hash, HpkeContext, KeyPair, KeySchedule, MlsAead,
MlsHash, MlsKem, MlsSignature,
};
pub use group::{GroupConfig, GroupId, GroupState, MlsGroup};
pub use member::{
Credential, CredentialType, GroupMember, KeyPackage, MemberId, MemberIdentity, MemberState,
TrustStore,
};
pub use protocol::{AuditLogEntry, *};
#[derive(Debug, Error)]
pub enum MlsError {
#[error("Cryptographic operation failed: {0}")]
CryptoError(String),
#[error("Invalid group state: {0}")]
InvalidGroupState(String),
#[error("Member not found: {0:?}")]
MemberNotFound(MemberId),
#[error("Unauthorized operation: {0}")]
Unauthorized(String),
#[error("Protocol error: {0}")]
ProtocolError(String),
#[error("Serialization error: {0}")]
SerializationError(String),
#[error("Key derivation failed: {0}")]
KeyDerivationError(String),
#[error("Message decryption failed")]
DecryptionFailed,
#[error("Invalid epoch: expected {expected}, got {actual}")]
InvalidEpoch { expected: u64, actual: u64 },
#[error("`TreeKEM` operation failed: {0}")]
TreeKemError(String),
#[error("Invalid message: {0}")]
InvalidMessage(String),
#[error("Deserialization error: {0}")]
DeserializationError(String),
#[error("IO error: {0}")]
Io(#[from] std::io::Error),
}
pub type Result<T> = std::result::Result<T, MlsError>;
pub const MLS_VERSION: u16 = 1;
pub const MAX_GROUP_SIZE: usize = 65536;
pub const DEFAULT_KEY_ROTATION_INTERVAL: Duration = Duration::from_secs(24 * 3600);
pub type MessageSequence = u64;
pub type EpochNumber = u64;
#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
pub struct WireFormat {
pub version: u16,
pub extensions: Vec<u16>,
}
impl Default for WireFormat {
fn default() -> Self {
Self {
version: MLS_VERSION,
extensions: Vec::new(),
}
}
}
#[derive(Debug, Clone)]
pub struct MlsConfig {
pub max_group_size: usize,
pub key_rotation_interval: Duration,
pub enable_pcs: bool,
pub enable_fs: bool,
pub cipher_suite: CipherSuite,
pub max_message_age: Duration,
}
impl Default for MlsConfig {
fn default() -> Self {
Self {
max_group_size: MAX_GROUP_SIZE,
key_rotation_interval: DEFAULT_KEY_ROTATION_INTERVAL,
enable_pcs: true,
enable_fs: true,
cipher_suite: CipherSuite::default(),
max_message_age: Duration::from_secs(300), }
}
}
#[derive(Debug, Clone, Default)]
pub struct MlsStats {
pub groups_active: usize,
pub messages_sent: u64,
pub messages_received: u64,
pub key_rotations: u64,
pub member_additions: u64,
pub member_removals: u64,
pub epoch_transitions: u64,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_mls_config_defaults() {
let config = MlsConfig::default();
assert_eq!(config.max_group_size, MAX_GROUP_SIZE);
assert_eq!(config.key_rotation_interval, DEFAULT_KEY_ROTATION_INTERVAL);
assert!(config.enable_pcs);
assert!(config.enable_fs);
}
#[test]
fn test_wire_format_default() {
let format = WireFormat::default();
assert_eq!(format.version, MLS_VERSION);
assert!(format.extensions.is_empty());
}
#[test]
fn test_mls_stats_default() {
let stats = MlsStats::default();
assert_eq!(stats.groups_active, 0);
assert_eq!(stats.messages_sent, 0);
assert_eq!(stats.messages_received, 0);
}
}