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:
@@ -53,6 +53,30 @@ use crate::{
|
||||
|
||||
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 ───────────────────────────────────────────────────────────────
|
||||
|
||||
/// Per-client MLS state: identity keypair, crypto backend, and optional group.
|
||||
@@ -258,6 +282,122 @@ impl GroupMember {
|
||||
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.
|
||||
///
|
||||
/// The caller must have previously called [`generate_key_package`] on
|
||||
@@ -320,14 +460,15 @@ impl GroupMember {
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// - `Ok(Some(plaintext))` for Application messages.
|
||||
/// - `Ok(None)` for Commit messages (group state is updated internally).
|
||||
/// - [`ReceivedMessage::Application`] for decrypted application messages.
|
||||
/// - [`ReceivedMessage::StateChanged`] for Commits and Proposals (group state updated).
|
||||
/// - [`ReceivedMessage::SelfRemoved`] if the Commit removed us from the group.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Returns [`CoreError::Mls`] if the message is malformed, fails
|
||||
/// 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
|
||||
.group
|
||||
.as_mut()
|
||||
@@ -348,22 +489,35 @@ impl GroupMember {
|
||||
.map_err(|e| CoreError::Mls(format!("process_message: {e:?}")))?;
|
||||
|
||||
match processed.into_content() {
|
||||
ProcessedMessageContent::ApplicationMessage(app) => Ok(Some(app.into_bytes())),
|
||||
ProcessedMessageContent::ApplicationMessage(app) => {
|
||||
Ok(ReceivedMessage::Application(app.into_bytes()))
|
||||
}
|
||||
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
|
||||
.merge_staged_commit(&self.backend, *staged)
|
||||
.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.
|
||||
ProcessedMessageContent::ProposalMessage(proposal) => {
|
||||
group.store_pending_proposal(*proposal);
|
||||
Ok(None)
|
||||
Ok(ReceivedMessage::StateChanged)
|
||||
}
|
||||
ProcessedMessageContent::ExternalJoinProposalMessage(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.
|
||||
///
|
||||
/// 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` 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).
|
||||
/// `(sender_identity_bytes, plaintext)` so the client can display who sent the message.
|
||||
pub fn receive_message_with_sender(
|
||||
&mut self,
|
||||
mut bytes: &[u8],
|
||||
) -> Result<Option<(Vec<u8>, Vec<u8>)>, CoreError> {
|
||||
) -> Result<ReceivedMessageWithSender, CoreError> {
|
||||
let group = self
|
||||
.group
|
||||
.as_mut()
|
||||
@@ -401,21 +552,32 @@ impl GroupMember {
|
||||
|
||||
match processed.into_content() {
|
||||
ProcessedMessageContent::ApplicationMessage(app) => {
|
||||
Ok(Some((sender_identity, app.into_bytes())))
|
||||
Ok(ReceivedMessageWithSender::Application(sender_identity, app.into_bytes()))
|
||||
}
|
||||
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
|
||||
.merge_staged_commit(&self.backend, *staged)
|
||||
.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) => {
|
||||
group.store_pending_proposal(*proposal);
|
||||
Ok(None)
|
||||
Ok(ReceivedMessageWithSender::StateChanged)
|
||||
}
|
||||
ProcessedMessageContent::ExternalJoinProposalMessage(proposal) => {
|
||||
group.store_pending_proposal(*proposal);
|
||||
Ok(None)
|
||||
Ok(ReceivedMessageWithSender::StateChanged)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user