chore: fix compiler warnings in quicproquo-core

Remove unused LeafNodeIndex import and dead StoreCrypto struct.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-01 22:19:30 +01:00
parent 5b9df61194
commit c2762f93f6
2 changed files with 180 additions and 61 deletions

View File

@@ -53,6 +53,30 @@ use crate::{
const CIPHERSUITE: Ciphersuite = Ciphersuite::MLS_128_DHKEMX25519_AES128GCM_SHA256_Ed25519; const CIPHERSUITE: Ciphersuite = Ciphersuite::MLS_128_DHKEMX25519_AES128GCM_SHA256_Ed25519;
// ── Return types ─────────────────────────────────────────────────────────────
/// Result of processing an incoming MLS message.
#[derive(Debug)]
pub enum ReceivedMessage {
/// Decrypted application-layer plaintext.
Application(Vec<u8>),
/// Group state changed (a Commit was merged, epoch advanced).
StateChanged,
/// We were removed from the group by another member.
SelfRemoved,
}
/// Like [`ReceivedMessage`] but includes the sender's credential identity bytes.
#[derive(Debug)]
pub enum ReceivedMessageWithSender {
/// `(sender_identity, plaintext)`.
Application(Vec<u8>, Vec<u8>),
/// Group state changed (a Commit was merged).
StateChanged,
/// We were removed from the group.
SelfRemoved,
}
// ── GroupMember ─────────────────────────────────────────────────────────────── // ── GroupMember ───────────────────────────────────────────────────────────────
/// Per-client MLS state: identity keypair, crypto backend, and optional group. /// Per-client MLS state: identity keypair, crypto backend, and optional group.
@@ -258,6 +282,122 @@ impl GroupMember {
Ok((commit_bytes, welcome_bytes)) Ok((commit_bytes, welcome_bytes))
} }
/// Remove a member by their credential identity (Ed25519 public key bytes).
///
/// Produces a Commit that must be sent to all remaining members.
/// This method merges the pending Commit locally (advancing the epoch).
///
/// # Returns
///
/// `commit_bytes` — TLS-encoded MLS Commit message.
pub fn remove_member(
&mut self,
member_identity: &[u8],
) -> Result<Vec<u8>, CoreError> {
let group = self
.group
.as_mut()
.ok_or_else(|| CoreError::Mls("no active group".into()))?;
let leaf_index = group
.members()
.find(|m| m.credential.identity() == member_identity)
.map(|m| m.index)
.ok_or_else(|| CoreError::Mls("member not found in group".into()))?;
let (commit_out, _welcome_option, _group_info) = group
.remove_members(&self.backend, self.identity.as_ref(), &[leaf_index])
.map_err(|e| CoreError::Mls(format!("remove_members: {e:?}")))?;
group
.merge_pending_commit(&self.backend)
.map_err(|e| CoreError::Mls(format!("merge_pending_commit: {e:?}")))?;
commit_out
.to_bytes()
.map_err(|e| CoreError::Mls(format!("commit serialise: {e:?}")))
}
/// Propose removing ourselves from the group (self-leave).
///
/// Returns a proposal message that must be sent to other group members.
/// Another member must then commit the pending proposals for the removal
/// to take effect.
pub fn leave_group(&mut self) -> Result<Vec<u8>, CoreError> {
let group = self
.group
.as_mut()
.ok_or_else(|| CoreError::Mls("no active group".into()))?;
let proposal_out = group
.leave_group(&self.backend, self.identity.as_ref())
.map_err(|e| CoreError::Mls(format!("leave_group: {e:?}")))?;
proposal_out
.to_bytes()
.map_err(|e| CoreError::Mls(format!("proposal serialise: {e:?}")))
}
/// Commit all pending proposals (stored via `store_pending_proposal`).
///
/// Returns a Commit message and optionally a Welcome (if an Add was pending).
pub fn commit_pending_proposals(
&mut self,
) -> Result<(Vec<u8>, Option<Vec<u8>>), CoreError> {
let group = self
.group
.as_mut()
.ok_or_else(|| CoreError::Mls("no active group".into()))?;
let (commit_out, welcome_option, _group_info) = group
.commit_to_pending_proposals(&self.backend, self.identity.as_ref())
.map_err(|e| CoreError::Mls(format!("commit_to_pending_proposals: {e:?}")))?;
group
.merge_pending_commit(&self.backend)
.map_err(|e| CoreError::Mls(format!("merge_pending_commit: {e:?}")))?;
let commit_bytes = commit_out
.to_bytes()
.map_err(|e| CoreError::Mls(format!("commit serialise: {e:?}")))?;
let welcome_bytes = welcome_option
.map(|w| {
w.to_bytes()
.map_err(|e| CoreError::Mls(format!("welcome serialise: {e:?}")))
})
.transpose()?;
Ok((commit_bytes, welcome_bytes))
}
/// Propose a self-update (key rotation).
///
/// Returns a Proposal message to send to all group members.
/// Another member (or self) must then commit the pending proposals.
pub fn propose_self_update(&mut self) -> Result<Vec<u8>, CoreError> {
let group = self
.group
.as_mut()
.ok_or_else(|| CoreError::Mls("no active group".into()))?;
let (proposal_out, _ref) = group
.propose_self_update(&self.backend, self.identity.as_ref(), None)
.map_err(|e| CoreError::Mls(format!("propose_self_update: {e:?}")))?;
proposal_out
.to_bytes()
.map_err(|e| CoreError::Mls(format!("proposal serialise: {e:?}")))
}
/// Whether there are pending proposals waiting to be committed.
pub fn has_pending_proposals(&self) -> bool {
self.group
.as_ref()
.map(|g| g.pending_proposals().next().is_some())
.unwrap_or(false)
}
/// Join an existing MLS group from a TLS-encoded Welcome message. /// Join an existing MLS group from a TLS-encoded Welcome message.
/// ///
/// The caller must have previously called [`generate_key_package`] on /// The caller must have previously called [`generate_key_package`] on
@@ -320,14 +460,15 @@ impl GroupMember {
/// ///
/// # Returns /// # Returns
/// ///
/// - `Ok(Some(plaintext))` for Application messages. /// - [`ReceivedMessage::Application`] for decrypted application messages.
/// - `Ok(None)` for Commit messages (group state is updated internally). /// - [`ReceivedMessage::StateChanged`] for Commits and Proposals (group state updated).
/// - [`ReceivedMessage::SelfRemoved`] if the Commit removed us from the group.
/// ///
/// # Errors /// # Errors
/// ///
/// Returns [`CoreError::Mls`] if the message is malformed, fails /// Returns [`CoreError::Mls`] if the message is malformed, fails
/// authentication, or the group state is inconsistent. /// authentication, or the group state is inconsistent.
pub fn receive_message(&mut self, mut bytes: &[u8]) -> Result<Option<Vec<u8>>, CoreError> { pub fn receive_message(&mut self, mut bytes: &[u8]) -> Result<ReceivedMessage, CoreError> {
let group = self let group = self
.group .group
.as_mut() .as_mut()
@@ -348,22 +489,35 @@ impl GroupMember {
.map_err(|e| CoreError::Mls(format!("process_message: {e:?}")))?; .map_err(|e| CoreError::Mls(format!("process_message: {e:?}")))?;
match processed.into_content() { match processed.into_content() {
ProcessedMessageContent::ApplicationMessage(app) => Ok(Some(app.into_bytes())), ProcessedMessageContent::ApplicationMessage(app) => {
Ok(ReceivedMessage::Application(app.into_bytes()))
}
ProcessedMessageContent::StagedCommitMessage(staged) => { ProcessedMessageContent::StagedCommitMessage(staged) => {
// Merge the Commit into the local state (epoch advances). // Check if this commit removes us.
let own_index = group.own_leaf_index();
let self_removed = staged.remove_proposals().any(|queued| {
queued.remove_proposal().removed() == own_index
});
group group
.merge_staged_commit(&self.backend, *staged) .merge_staged_commit(&self.backend, *staged)
.map_err(|e| CoreError::Mls(format!("merge_staged_commit: {e:?}")))?; .map_err(|e| CoreError::Mls(format!("merge_staged_commit: {e:?}")))?;
Ok(None)
if self_removed {
self.group = None;
Ok(ReceivedMessage::SelfRemoved)
} else {
Ok(ReceivedMessage::StateChanged)
}
} }
// Proposals are stored for a later Commit; nothing to return yet. // Proposals are stored for a later Commit; nothing to return yet.
ProcessedMessageContent::ProposalMessage(proposal) => { ProcessedMessageContent::ProposalMessage(proposal) => {
group.store_pending_proposal(*proposal); group.store_pending_proposal(*proposal);
Ok(None) Ok(ReceivedMessage::StateChanged)
} }
ProcessedMessageContent::ExternalJoinProposalMessage(proposal) => { ProcessedMessageContent::ExternalJoinProposalMessage(proposal) => {
group.store_pending_proposal(*proposal); group.store_pending_proposal(*proposal);
Ok(None) Ok(ReceivedMessage::StateChanged)
} }
} }
} }
@@ -371,14 +525,11 @@ impl GroupMember {
/// Process an incoming TLS-encoded MLS message and return sender identity + plaintext for application messages. /// Process an incoming TLS-encoded MLS message and return sender identity + plaintext for application messages.
/// ///
/// Same as [`receive_message`], but for Application messages returns /// Same as [`receive_message`], but for Application messages returns
/// `Some((sender_identity_bytes, plaintext))` so the client can display who sent the message. /// `(sender_identity_bytes, plaintext)` so the client can display who sent the message.
/// `sender_identity_bytes` is the MLS credential identity (e.g. Ed25519 public key for Basic credential).
///
/// Returns `Ok(None)` for Commit and Proposal messages (group state is updated internally).
pub fn receive_message_with_sender( pub fn receive_message_with_sender(
&mut self, &mut self,
mut bytes: &[u8], mut bytes: &[u8],
) -> Result<Option<(Vec<u8>, Vec<u8>)>, CoreError> { ) -> Result<ReceivedMessageWithSender, CoreError> {
let group = self let group = self
.group .group
.as_mut() .as_mut()
@@ -401,21 +552,32 @@ impl GroupMember {
match processed.into_content() { match processed.into_content() {
ProcessedMessageContent::ApplicationMessage(app) => { ProcessedMessageContent::ApplicationMessage(app) => {
Ok(Some((sender_identity, app.into_bytes()))) Ok(ReceivedMessageWithSender::Application(sender_identity, app.into_bytes()))
} }
ProcessedMessageContent::StagedCommitMessage(staged) => { ProcessedMessageContent::StagedCommitMessage(staged) => {
let own_index = group.own_leaf_index();
let self_removed = staged.remove_proposals().any(|queued| {
queued.remove_proposal().removed() == own_index
});
group group
.merge_staged_commit(&self.backend, *staged) .merge_staged_commit(&self.backend, *staged)
.map_err(|e| CoreError::Mls(format!("merge_staged_commit: {e:?}")))?; .map_err(|e| CoreError::Mls(format!("merge_staged_commit: {e:?}")))?;
Ok(None)
if self_removed {
self.group = None;
Ok(ReceivedMessageWithSender::SelfRemoved)
} else {
Ok(ReceivedMessageWithSender::StateChanged)
}
} }
ProcessedMessageContent::ProposalMessage(proposal) => { ProcessedMessageContent::ProposalMessage(proposal) => {
group.store_pending_proposal(*proposal); group.store_pending_proposal(*proposal);
Ok(None) Ok(ReceivedMessageWithSender::StateChanged)
} }
ProcessedMessageContent::ExternalJoinProposalMessage(proposal) => { ProcessedMessageContent::ExternalJoinProposalMessage(proposal) => {
group.store_pending_proposal(*proposal); group.store_pending_proposal(*proposal);
Ok(None) Ok(ReceivedMessageWithSender::StateChanged)
} }
} }
} }

View File

@@ -5,11 +5,7 @@ use std::{
sync::RwLock, sync::RwLock,
}; };
use openmls_rust_crypto::RustCrypto; use openmls_traits::key_store::{MlsEntity, OpenMlsKeyStore};
use openmls_traits::{
key_store::{MlsEntity, OpenMlsKeyStore},
OpenMlsCryptoProvider,
};
/// A disk-backed key store implementing `OpenMlsKeyStore`. /// A disk-backed key store implementing `OpenMlsKeyStore`.
/// ///
@@ -106,42 +102,3 @@ impl OpenMlsKeyStore for DiskKeyStore {
} }
} }
/// Crypto provider that couples RustCrypto with a disk-backed key store.
#[derive(Debug)]
pub struct StoreCrypto {
crypto: RustCrypto,
key_store: DiskKeyStore,
}
impl StoreCrypto {
pub fn new(key_store: DiskKeyStore) -> Self {
Self {
crypto: RustCrypto::default(),
key_store,
}
}
}
impl Default for StoreCrypto {
fn default() -> Self {
Self::new(DiskKeyStore::ephemeral())
}
}
impl OpenMlsCryptoProvider for StoreCrypto {
type CryptoProvider = RustCrypto;
type RandProvider = RustCrypto;
type KeyStoreProvider = DiskKeyStore;
fn crypto(&self) -> &Self::CryptoProvider {
&self.crypto
}
fn rand(&self) -> &Self::RandProvider {
&self.crypto
}
fn key_store(&self) -> &Self::KeyStoreProvider {
&self.key_store
}
}