// Copyright (c) 2024 Mozilla Corporation and contributors. // SPDX-License-Identifier: (Apache-2.0 OR MIT)
mod state;
use mls_rs::error::{AnyError, IntoAnyError}; use mls_rs::group::proposal::{CustomProposal, ProposalType}; use mls_rs::group::{Capabilities, ExportedTree, ReceivedMessage}; use mls_rs::identity::SigningIdentity; use mls_rs::mls_rs_codec::{MlsDecode, MlsEncode}; use mls_rs::{CipherSuiteProvider, CryptoProvider, Extension, ExtensionList};
use serde::de::{self, MapAccess, Visitor}; use serde::ser::SerializeStruct; use serde::{Deserialize, Deserializer, Serialize, Serializer};
pubuse state::{PlatformState, TemporaryState}; use std::fmt;
/// /// Delete a specific group in the PlatformState. /// pubfn state_delete_group(
state: &PlatformState,
gid: MlsGroupIdArg,
myself: IdentityArg,
) -> Result<GroupIdEpoch, PlatformError> {
state.delete_group(gid, myself)?;
// Return the group id and 0xFF..FF epoch to signal the group is closed
Ok(GroupIdEpoch {
group_id: gid.to_vec(),
group_epoch: 0xFFFFFFFFFFFFFFFF,
})
}
/// /// Configurations ///
// Possibly temporary, allows to add an option to the config without changing every // call to client() function #[derive(Clone, Debug, Default)] pubstruct ClientConfig { pub key_package_extensions: Option<ExtensionList>, pub leaf_node_extensions: Option<ExtensionList>, pub leaf_node_capabilities: Option<Capabilities>, pub key_package_lifetime_s: Option<u64>, pub allow_external_commits: bool,
}
// Note: The identity is needed because it is allowed to have multiple // identities in a group. pubfn mls_group_members(
state: &PlatformState,
gid: MlsGroupIdArg,
myself: IdentityArg,
) -> Result<GroupMembers, PlatformError> { let crypto_provider = DefaultCryptoProvider::default();
let group = state.client_default(myself)?.load_group(gid)?; let epoch = group.current_epoch();
let cipher_suite_provider = crypto_provider
.cipher_suite_provider(group.cipher_suite())
.ok_or(PlatformError::UnsupportedCiphersuite)?;
// Return Vec<(Identity, Credential)> let members = group
.roster()
.member_identities_iter()
.map(|identity| {
Ok(ClientIdentifiers {
identity: cipher_suite_provider
.hash(&identity.signature_key)
.map_err(|e| PlatformError::CryptoError(e.into_any_error()))?,
credential: identity.credential.mls_encode_to_vec()?,
})
})
.collect::<Result<Vec<_>, PlatformError>>()?;
let members = GroupMembers {
group_id: gid.to_vec(),
group_epoch: epoch,
group_members: members,
};
Ok(members)
}
/// /// Group management: Create a Group ///
// Note: We internally set the protocol version to avoid issues with compat pubfn mls_group_create(
pstate: &mut PlatformState,
myself: IdentityArg,
credential: MlsCredentialArg,
gid: Option<MlsGroupIdArg>,
group_context_extensions: Option<ExtensionList>,
config: &ClientConfig,
) -> Result<GroupIdEpoch, PlatformError> { // Build the client letmut credential_slice: &[u8] = credential; let decoded_cred = mls_rs::identity::Credential::mls_decode(&mut credential_slice)?;
let client = pstate.client(myself, Some(decoded_cred), ProtocolVersion::MLS_10, config)?;
// Generate a GroupId if none is provided letmut group = match gid {
Some(gid) => client.create_group_with_id(
gid.to_vec(),
group_context_extensions.unwrap_or_default().clone(),
)?,
None => client.create_group(group_context_extensions.unwrap_or_default().clone())?,
};
// The state needs to be returned or stored somewhere
group.write_to_storage()?; let gid = group.group_id().to_vec(); let epoch = group.current_epoch();
impl Serialize for MlsCommitOutput { fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> where
S: Serializer,
{ letmut state = serializer.serialize_struct("MlsCommitOutput", 4)?;
// Handle serialization for `commit` let commit_bytes = self
.commit
.mls_encode_to_vec()
.map_err(serde::ser::Error::custom)?;
state.serialize_field("commit", &commit_bytes)?;
// Handle serialization for `welcome`. Collect into a Result to handle potential errors. let welcome_bytes: Result<Vec<_>, _> = self
.welcome
.iter()
.map(|msg| msg.mls_encode_to_vec().map_err(serde::ser::Error::custom))
.collect(); // Unwrap the Result here, after all potential errors have been handled.
state.serialize_field("welcome", &welcome_bytes?)?;
pubfn mls_group_add(
pstate: &mut PlatformState,
gid: MlsGroupIdArg,
myself: IdentityArg,
new_members: Vec<MlsMessage>,
) -> Result<MlsCommitOutput, PlatformError> { // Get the group from the state let client = pstate.client_default(myself)?; letmut group = client.load_group(gid)?;
// let proposals: Result<Vec<_>, _> = new_members // .into_iter() // .map(|member| group.propose_add(member, vec![])) // .collect(); // let proposals = proposals?;
// let proposal = proposals.first().unwrap(); // group.write_to_storage()?;
// Ok(proposal.clone()) // }
/// /// Group management: Removing a user. /// pubfn mls_group_remove(
pstate: &PlatformState,
gid: MlsGroupIdArg,
myself: IdentityArg,
removed: IdentityArg, // TODO: Make this Vec<Identities>?
) -> Result<MlsCommitOutput, PlatformError> { letmut group = pstate.client_default(myself)?.load_group(gid)?;
let crypto_provider = DefaultCryptoProvider::default();
let cipher_suite_provider = crypto_provider
.cipher_suite_provider(group.cipher_suite())
.ok_or(PlatformError::UnsupportedCiphersuite)?;
let removed = group
.roster()
.members_iter()
.find_map(|m| { let h = cipher_suite_provider
.hash(&m.signing_identity.signature_key)
.ok()?;
(h == *removed).then_some(m.index)
})
.ok_or(PlatformError::UndefinedIdentity)?; // Handle separate error message for inability to remove yourself
let commit = group.commit_builder().remove_member(removed)?.build()?;
// Write the group to the storage
group.write_to_storage()?;
pubfn mls_group_propose_remove(
pstate: &PlatformState,
gid: MlsGroupIdArg,
myself: IdentityArg,
removed: IdentityArg, // TODO: Make this Vec<Identities>?
) -> Result<MlsMessage, PlatformError> { letmut group = pstate.client_default(myself)?.load_group(gid)?;
let crypto_provider = DefaultCryptoProvider::default();
let cipher_suite_provider = crypto_provider
.cipher_suite_provider(group.cipher_suite())
.ok_or(PlatformError::UnsupportedCiphersuite)?;
let removed = group
.roster()
.members_iter()
.find_map(|m| { let h = cipher_suite_provider
.hash(&m.signing_identity.signature_key)
.ok()?;
(h == *removed).then_some(m.index)
})
.ok_or(PlatformError::UndefinedIdentity)?;
let proposal = group.propose_remove(removed, vec![])?;
// Remember the proposal
group.write_to_storage()?;
Ok(proposal)
}
/// /// Key updates ///
/// TODO: Possibly add a random nonce as an optional parameter. pubfn mls_group_update(
pstate: &mut PlatformState,
gid: MlsGroupIdArg,
myself: IdentityArg,
signature_key: Option<&[u8]>,
credential: Option<MlsCredentialArg>,
group_context_extensions: Option<ExtensionList>,
config: &ClientConfig,
) -> Result<MlsCommitOutput, PlatformError> { let crypto_provider = DefaultCryptoProvider::default();
let commit_output = MlsCommitOutput {
commit: commit_output.commit_message.clone(),
welcome: vec![],
group_info: commit_output.external_commit_group_info,
ratchet_tree: None, // TODO: Handle this !
identity: None,
}; // TODO we should delete state when we receive an ACK. but it's not super clear how to // determine on receive that this was a "close" commit. Would be easier if we had a custom // proposal
// Write the group to the storage
group.write_to_storage()?;
pubfn mls_receive(
pstate: &PlatformState,
myself: IdentityArg,
message_or_ack: &MessageOrAck,
) -> Result<(Vec<u8>, Received), PlatformError> { // Extract the gid from the Message let gid = match &message_or_ack {
MessageOrAck::Ack(gid) => gid,
MessageOrAck::MlsMessage(message) => match message.group_id() {
Some(gid) => gid,
None => return Err(PlatformError::UnsupportedMessage),
},
};
letmut group = pstate.client_default(myself)?.load_group(gid)?;
let received_message = match &message_or_ack {
MessageOrAck::Ack(_) => group.apply_pending_commit().map(ReceivedMessage::Commit),
MessageOrAck::MlsMessage(message) => group.process_incoming_message(message.clone()),
};
// let result = match received_message? {
ReceivedMessage::ApplicationMessage(app_data_description) => Ok((
gid.to_vec(),
Received::ApplicationMessage(app_data_description.data().to_vec()),
)),
ReceivedMessage::Proposal(_proposal) => { // TODO: We inconditionally return the commit for the received proposal let commit = group.commit(vec![])?;
group.write_to_storage()?;
let commit_output = MlsCommitOutput {
commit: commit.commit_message,
welcome: commit.welcome_messages,
group_info: commit.external_commit_group_info,
ratchet_tree: commit
.ratchet_tree
.map(|tree| tree.to_bytes())
.transpose()?,
identity: None,
};
Ok((gid.to_vec(), Received::CommitOutput(commit_output)))
}
ReceivedMessage::Commit(commit) => { // Check if the group is active or not after applying the commit if !commit.state_update.is_active() { // Delete the group from the state of the client
pstate.delete_group(gid, myself)?;
// Return the group id and 0xFF..FF epoch to signal the group is closed let group_epoch = GroupIdEpoch {
group_id: group.group_id().to_vec(),
group_epoch: 0xFFFFFFFFFFFFFFFF,
};
Ok((gid.to_vec(), Received::GroupIdEpoch(group_epoch)))
} else { // TODO: Receiving a group_close commit means the sender receiving // is left alone in the group. We should be able delete group automatically. // As of now, the user calling group_close has to delete group manually.
// If this is a normal commit, return the affected group and new epoch let group_epoch = GroupIdEpoch {
group_id: group.group_id().to_vec(),
group_epoch: group.current_epoch(),
};
Ok((gid.to_vec(), Received::GroupIdEpoch(group_epoch)))
}
} // TODO: We could make this more user friendly by allowing to // pass a Welcome message. KeyPackages should be rejected.
_ => Err(PlatformError::UnsupportedMessage),
}?;
// Write the state to storage
group.write_to_storage()?;
Ok(result)
}
pubfn mls_has_pending_commit(
pstate: &PlatformState,
gid: MlsGroupIdArg,
myself: IdentityArg,
) -> Result<bool, PlatformError> { let group = pstate.client_default(myself)?.load_group(gid)?; let result = group.has_pending_commit();
Ok(result)
}
let received_message = group.apply_pending_commit().map(ReceivedMessage::Commit);
// Check if the group is active or not after applying the commit let result = match received_message? {
ReceivedMessage::Commit(commit) => { // Check if the group is active or not after applying the commit if !commit.state_update.is_active() { // Delete the group from the state of the client
pstate.delete_group(gid, myself)?;
// Return the group id and 0xFF..FF epoch to signal the group is closed let group_epoch = GroupIdEpoch {
group_id: group.group_id().to_vec(),
group_epoch: 0xFFFFFFFFFFFFFFFF,
};
Ok(Received::GroupIdEpoch(group_epoch))
} else { // TODO: Receiving a group_close commit means the sender receiving // is left alone in the group. We should be able delete group automatically. // As of now, the user calling group_close has to delete group manually.
// If this is a normal commit, return the affected group and new epoch let group_epoch = GroupIdEpoch {
group_id: group.group_id().to_vec(),
group_epoch: group.current_epoch(),
};
let (mut group, external_commit) = commit_builder.build(group_info.clone())?; let gid = group.group_id().to_vec();
// Store the state
group.write_to_storage()?;
// Encode the output let gid_and_message = MlsExternalCommitOutput {
gid,
external_commit,
};
Ok(gid_and_message)
}
/// /// Utility functions ///
pubfn mls_get_group_id(message_or_ack: &MessageOrAck) -> Result<Vec<u8>, PlatformError> { // Extract the gid from the Message let gid = match &message_or_ack {
MessageOrAck::Ack(gid) => gid,
MessageOrAck::MlsMessage(message) => match message.group_id() {
Some(gid) => gid,
None => return Err(PlatformError::UnsupportedMessage),
},
};
Ok(gid.to_vec())
}
use serde_json::{Error, Value};
// This function takes a JSON string and converts byte arrays into hex strings. fn convert_bytes_fields_to_hex(input_str: &str) -> Result<String, Error> { // Parse the JSON string into a serde_json::Value letmut value: Value = serde_json::from_str(input_str)?;
// Recursive function to process each element fn process_element(element: &mut Value) { match element {
Value::Array(refmut vec) => { if vec
.iter()
.all(|x| matches!(x, Value::Number(n) if n.is_u64()))
{ // Convert all elements to a Vec<u8> if they are numbers let bytes: Vec<u8> = vec
.iter()
.filter_map(|x| x.as_u64().map(|n| n as u8))
.collect(); // Check if the conversion makes sense (the length matches) if bytes.len() == vec.len() {
*element = Value::String(hex::encode(bytes));
} else {
vec.iter_mut().for_each(process_element);
}
} else {
vec.iter_mut().for_each(process_element);
}
}
Value::Object(refmut map) => {
map.values_mut().for_each(process_element);
}
_ => {}
}
} // Process the element and return the new Json string
process_element(&mut value);
serde_json::to_string(&value)
}
// This function accepts bytes, converts them to a string, and then processes the string. pubfn utils_json_bytes_to_string_custom(input_bytes: &[u8]) -> Result<String, PlatformError> { // Convert input bytes to a string let input_str =
std::str::from_utf8(input_bytes).map_err(|_| PlatformError::JsonConversionError)?;
// Call the original function with the decoded string
convert_bytes_fields_to_hex(input_str).map_err(|_| PlatformError::JsonConversionError)
}
Messung V0.5 in Prozent
¤ Dauer der Verarbeitung: 0.30 Sekunden
(vorverarbeitet am 2026-06-17)
¤
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.