chore: rename quicproquo → quicprochat in Rust workspace
Rename all crate directories, package names, binary names, proto package/module paths, ALPN strings, env var prefixes, config filenames, mDNS service names, and plugin ABI symbols from quicproquo/qpq to quicprochat/qpc.
This commit is contained in:
589
crates/quicprochat-sdk/src/groups.rs
Normal file
589
crates/quicprochat-sdk/src/groups.rs
Normal file
@@ -0,0 +1,589 @@
|
||||
//! Group lifecycle — create DMs, groups, invite members, join from Welcome.
|
||||
//!
|
||||
//! All functions are free-standing (not methods on `QpqClient`) so they can be
|
||||
//! tested and composed independently.
|
||||
|
||||
use std::sync::Arc;
|
||||
|
||||
use bytes::Bytes;
|
||||
use prost::Message;
|
||||
use tracing::debug;
|
||||
|
||||
use quicprochat_core::{
|
||||
hybrid_encrypt, GroupMember, HybridKeypair, HybridPublicKey, IdentityKeypair,
|
||||
};
|
||||
use quicprochat_proto::method_ids;
|
||||
use quicprochat_proto::qpc::v1::{
|
||||
CreateChannelRequest, CreateChannelResponse, EnqueueRequest, EnqueueResponse,
|
||||
ListGroupMembersRequest, ListGroupMembersResponse, RemoveMemberRequest, RemoveMemberResponse,
|
||||
RotateKeysRequest, RotateKeysResponse, UpdateGroupMetadataRequest, UpdateGroupMetadataResponse,
|
||||
};
|
||||
use quicprochat_rpc::client::RpcClient;
|
||||
|
||||
use crate::conversation::{
|
||||
now_ms, Conversation, ConversationId, ConversationKind, ConversationStore,
|
||||
};
|
||||
use crate::error::SdkError;
|
||||
|
||||
// ── DM (1:1) ────────────────────────────────────────────────────────────────
|
||||
|
||||
/// Create or join a 1:1 DM channel with a peer.
|
||||
///
|
||||
/// Returns `(conversation_id, was_new)`.
|
||||
/// - `was_new = true` — caller created the MLS group and sent the Welcome.
|
||||
/// - `was_new = false` — peer is the MLS initiator; caller should wait for Welcome.
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub async fn create_dm(
|
||||
rpc: &RpcClient,
|
||||
conv_store: &ConversationStore,
|
||||
member: &mut GroupMember,
|
||||
my_identity: &IdentityKeypair,
|
||||
peer_key: &[u8],
|
||||
peer_key_package: &[u8],
|
||||
hybrid_kp: Option<&HybridKeypair>,
|
||||
peer_hybrid_pk: Option<&HybridPublicKey>,
|
||||
) -> Result<(ConversationId, bool), SdkError> {
|
||||
// 1. Call CREATE_CHANNEL RPC.
|
||||
let req = CreateChannelRequest {
|
||||
peer_key: peer_key.to_vec(),
|
||||
};
|
||||
let resp_bytes = rpc
|
||||
.call(method_ids::CREATE_CHANNEL, Bytes::from(req.encode_to_vec()))
|
||||
.await?;
|
||||
let resp = CreateChannelResponse::decode(resp_bytes)
|
||||
.map_err(|e| SdkError::Crypto(format!("decode CreateChannelResponse: {e}")))?;
|
||||
|
||||
let conv_id = ConversationId::from_slice(&resp.channel_id)
|
||||
.ok_or_else(|| SdkError::Other(anyhow::anyhow!("server returned invalid channel_id")))?;
|
||||
|
||||
let was_new = resp.was_new;
|
||||
|
||||
if was_new {
|
||||
// 2a. We are the MLS initiator.
|
||||
member
|
||||
.create_group(&resp.channel_id)
|
||||
.map_err(|e| SdkError::Crypto(format!("create_group: {e}")))?;
|
||||
|
||||
let (_commit, welcome) = member
|
||||
.add_member(peer_key_package)
|
||||
.map_err(|e| SdkError::Crypto(format!("add_member: {e}")))?;
|
||||
|
||||
// Optionally hybrid-wrap the welcome.
|
||||
let payload = wrap_hybrid(hybrid_kp, peer_hybrid_pk, &welcome, member.is_hybrid())?;
|
||||
|
||||
// Enqueue welcome to peer.
|
||||
enqueue_to_peer(rpc, peer_key, &payload).await?;
|
||||
|
||||
// Save conversation with MLS state.
|
||||
let member_keys = member.member_identities();
|
||||
let mls_blob = member
|
||||
.group_ref()
|
||||
.map(bincode::serialize)
|
||||
.transpose()
|
||||
.map_err(|e| SdkError::Storage(format!("serialize MLS group: {e}")))?;
|
||||
|
||||
let conv = Conversation {
|
||||
id: conv_id.clone(),
|
||||
kind: ConversationKind::Dm {
|
||||
peer_key: peer_key.to_vec(),
|
||||
peer_username: None,
|
||||
},
|
||||
display_name: format!("DM:{}", hex::encode(&peer_key[..4])),
|
||||
mls_group_blob: mls_blob,
|
||||
keystore_blob: None,
|
||||
member_keys,
|
||||
unread_count: 0,
|
||||
last_activity_ms: now_ms(),
|
||||
created_at_ms: now_ms(),
|
||||
is_hybrid: member.is_hybrid(),
|
||||
last_seen_seq: 0,
|
||||
};
|
||||
conv_store
|
||||
.save_conversation(&conv)
|
||||
.map_err(|e| SdkError::Storage(e.to_string()))?;
|
||||
|
||||
debug!(conv = %conv_id.hex(), "DM created (initiator)");
|
||||
} else {
|
||||
// 2b. Peer is the MLS initiator — save a stub.
|
||||
let conv = Conversation {
|
||||
id: conv_id.clone(),
|
||||
kind: ConversationKind::Dm {
|
||||
peer_key: peer_key.to_vec(),
|
||||
peer_username: None,
|
||||
},
|
||||
display_name: format!("DM:{}", hex::encode(&peer_key[..4])),
|
||||
mls_group_blob: None,
|
||||
keystore_blob: None,
|
||||
member_keys: vec![my_identity.public_key_bytes().to_vec(), peer_key.to_vec()],
|
||||
unread_count: 0,
|
||||
last_activity_ms: now_ms(),
|
||||
created_at_ms: now_ms(),
|
||||
is_hybrid: false,
|
||||
last_seen_seq: 0,
|
||||
};
|
||||
conv_store
|
||||
.save_conversation(&conv)
|
||||
.map_err(|e| SdkError::Storage(e.to_string()))?;
|
||||
|
||||
debug!(conv = %conv_id.hex(), "DM stub saved (waiting for Welcome)");
|
||||
}
|
||||
|
||||
Ok((conv_id, was_new))
|
||||
}
|
||||
|
||||
// ── Group creation ──────────────────────────────────────────────────────────
|
||||
|
||||
/// Create a new group conversation (local only, no RPC).
|
||||
pub fn create_group(
|
||||
conv_store: &ConversationStore,
|
||||
member: &mut GroupMember,
|
||||
group_name: &str,
|
||||
) -> Result<ConversationId, SdkError> {
|
||||
let conv_id = ConversationId::from_group_name(group_name);
|
||||
|
||||
member
|
||||
.create_group(conv_id.0.as_slice())
|
||||
.map_err(|e| SdkError::Crypto(format!("create_group: {e}")))?;
|
||||
|
||||
let member_keys = member.member_identities();
|
||||
let mls_blob = member
|
||||
.group_ref()
|
||||
.map(bincode::serialize)
|
||||
.transpose()
|
||||
.map_err(|e| SdkError::Storage(format!("serialize MLS group: {e}")))?;
|
||||
|
||||
let conv = Conversation {
|
||||
id: conv_id.clone(),
|
||||
kind: ConversationKind::Group {
|
||||
name: group_name.to_string(),
|
||||
},
|
||||
display_name: format!("#{group_name}"),
|
||||
mls_group_blob: mls_blob,
|
||||
keystore_blob: None,
|
||||
member_keys,
|
||||
unread_count: 0,
|
||||
last_activity_ms: now_ms(),
|
||||
created_at_ms: now_ms(),
|
||||
is_hybrid: member.is_hybrid(),
|
||||
last_seen_seq: 0,
|
||||
};
|
||||
conv_store
|
||||
.save_conversation(&conv)
|
||||
.map_err(|e| SdkError::Storage(e.to_string()))?;
|
||||
|
||||
debug!(conv = %conv_id.hex(), group = group_name, "group created");
|
||||
Ok(conv_id)
|
||||
}
|
||||
|
||||
// ── Group invite ────────────────────────────────────────────────────────────
|
||||
|
||||
/// Invite a peer to an existing group.
|
||||
///
|
||||
/// Sends the Welcome to the new peer and the Commit to all existing members.
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub async fn invite_to_group(
|
||||
rpc: &RpcClient,
|
||||
conv_store: &ConversationStore,
|
||||
member: &mut GroupMember,
|
||||
my_identity: &IdentityKeypair,
|
||||
conv_id: &ConversationId,
|
||||
peer_key: &[u8],
|
||||
peer_key_package: &[u8],
|
||||
hybrid_kp: Option<&HybridKeypair>,
|
||||
peer_hybrid_pk: Option<&HybridPublicKey>,
|
||||
) -> Result<(), SdkError> {
|
||||
let my_key = my_identity.public_key_bytes();
|
||||
|
||||
let (_commit, welcome) = member
|
||||
.add_member(peer_key_package)
|
||||
.map_err(|e| SdkError::Crypto(format!("add_member: {e}")))?;
|
||||
|
||||
// Send Welcome to new peer.
|
||||
let payload = wrap_hybrid(hybrid_kp, peer_hybrid_pk, &welcome, member.is_hybrid())?;
|
||||
enqueue_to_peer(rpc, peer_key, &payload).await?;
|
||||
|
||||
// Persist updated MLS state.
|
||||
save_mls_state(conv_store, conv_id, member)?;
|
||||
|
||||
debug!(
|
||||
conv = %conv_id.hex(),
|
||||
peer = %hex::encode(&peer_key[..4]),
|
||||
"invited peer to group"
|
||||
);
|
||||
let _ = my_key; // used for filtering in future commit broadcast
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// ── Join from Welcome ───────────────────────────────────────────────────────
|
||||
|
||||
/// Join a group from a Welcome message.
|
||||
///
|
||||
/// Returns the conversation ID derived from the MLS group ID.
|
||||
pub fn join_from_welcome(
|
||||
conv_store: &ConversationStore,
|
||||
member: &mut GroupMember,
|
||||
welcome_bytes: &[u8],
|
||||
hybrid_kp: Option<&HybridKeypair>,
|
||||
) -> Result<ConversationId, SdkError> {
|
||||
// Try hybrid decryption if we have a hybrid keypair.
|
||||
let decrypted;
|
||||
let welcome_data = if let Some(hkp) = hybrid_kp {
|
||||
match quicprochat_core::hybrid_decrypt(hkp, welcome_bytes, b"", b"") {
|
||||
Ok(plain) => {
|
||||
decrypted = plain;
|
||||
&decrypted[..]
|
||||
}
|
||||
Err(_) => welcome_bytes, // not hybrid-encrypted, use as-is
|
||||
}
|
||||
} else {
|
||||
welcome_bytes
|
||||
};
|
||||
|
||||
member
|
||||
.join_group(welcome_data)
|
||||
.map_err(|e| SdkError::Crypto(format!("join_group: {e}")))?;
|
||||
|
||||
let group_id = member
|
||||
.group_id()
|
||||
.ok_or_else(|| SdkError::Crypto("no group after join".into()))?;
|
||||
|
||||
let conv_id = ConversationId::from_slice(&group_id)
|
||||
.ok_or_else(|| SdkError::Crypto("group_id is not 16 bytes".into()))?;
|
||||
|
||||
let member_keys = member.member_identities();
|
||||
let mls_blob = member
|
||||
.group_ref()
|
||||
.map(bincode::serialize)
|
||||
.transpose()
|
||||
.map_err(|e| SdkError::Storage(format!("serialize MLS group: {e}")))?;
|
||||
|
||||
// Upsert conversation — the stub may already exist from create_dm.
|
||||
let existing = conv_store
|
||||
.load_conversation(&conv_id)
|
||||
.map_err(|e| SdkError::Storage(e.to_string()))?;
|
||||
|
||||
let conv = if let Some(mut ex) = existing {
|
||||
ex.mls_group_blob = mls_blob;
|
||||
ex.member_keys = member_keys;
|
||||
ex.is_hybrid = member.is_hybrid();
|
||||
ex.last_activity_ms = now_ms();
|
||||
ex
|
||||
} else {
|
||||
Conversation {
|
||||
id: conv_id.clone(),
|
||||
kind: ConversationKind::Group {
|
||||
name: format!("group-{}", conv_id.hex()),
|
||||
},
|
||||
display_name: format!("group-{}", &conv_id.hex()[..8]),
|
||||
mls_group_blob: mls_blob,
|
||||
keystore_blob: None,
|
||||
member_keys,
|
||||
unread_count: 0,
|
||||
last_activity_ms: now_ms(),
|
||||
created_at_ms: now_ms(),
|
||||
is_hybrid: member.is_hybrid(),
|
||||
last_seen_seq: 0,
|
||||
}
|
||||
};
|
||||
|
||||
conv_store
|
||||
.save_conversation(&conv)
|
||||
.map_err(|e| SdkError::Storage(e.to_string()))?;
|
||||
|
||||
debug!(conv = %conv_id.hex(), "joined group from Welcome");
|
||||
Ok(conv_id)
|
||||
}
|
||||
|
||||
// ── Member removal ─────────────────────────────────────────────────────────
|
||||
|
||||
/// Remove a member from a group.
|
||||
///
|
||||
/// Generates an MLS Commit for the removal, sends it via the server RPC,
|
||||
/// and broadcasts the commit to remaining members.
|
||||
pub async fn remove_member_from_group(
|
||||
rpc: &RpcClient,
|
||||
conv_store: &ConversationStore,
|
||||
member: &mut GroupMember,
|
||||
conv_id: &ConversationId,
|
||||
member_identity_key: &[u8],
|
||||
) -> Result<(), SdkError> {
|
||||
// 1. MLS removal — generates a commit.
|
||||
let commit = member
|
||||
.remove_member(member_identity_key)
|
||||
.map_err(|e| SdkError::Crypto(format!("remove_member: {e}")))?;
|
||||
|
||||
// 2. Call the server-side RemoveMember RPC.
|
||||
let req = RemoveMemberRequest {
|
||||
group_id: conv_id.0.to_vec(),
|
||||
member_identity_key: member_identity_key.to_vec(),
|
||||
};
|
||||
let resp_bytes = rpc
|
||||
.call(method_ids::REMOVE_MEMBER, Bytes::from(req.encode_to_vec()))
|
||||
.await?;
|
||||
let _resp = RemoveMemberResponse::decode(resp_bytes)
|
||||
.map_err(|e| SdkError::Crypto(format!("decode RemoveMemberResponse: {e}")))?;
|
||||
|
||||
// 3. Broadcast the commit to remaining members.
|
||||
let remaining = member.member_identities();
|
||||
for key in &remaining {
|
||||
enqueue_to_peer(rpc, key, &commit).await?;
|
||||
}
|
||||
|
||||
// 4. Persist updated MLS state.
|
||||
save_mls_state(conv_store, conv_id, member)?;
|
||||
|
||||
debug!(
|
||||
conv = %conv_id.hex(),
|
||||
removed = %hex::encode(&member_identity_key[..4.min(member_identity_key.len())]),
|
||||
"removed member from group"
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// ── Leave group ────────────────────────────────────────────────────────────
|
||||
|
||||
/// Leave a group. Generates a removal proposal for self and notifies members.
|
||||
pub async fn leave_group(
|
||||
rpc: &RpcClient,
|
||||
conv_store: &ConversationStore,
|
||||
member: &mut GroupMember,
|
||||
conv_id: &ConversationId,
|
||||
) -> Result<(), SdkError> {
|
||||
let proposal = member
|
||||
.leave_group()
|
||||
.map_err(|e| SdkError::Crypto(format!("leave_group: {e}")))?;
|
||||
|
||||
// Send the leave proposal to all remaining members so they can commit it.
|
||||
let members = member.member_identities();
|
||||
for key in &members {
|
||||
enqueue_to_peer(rpc, key, &proposal).await?;
|
||||
}
|
||||
|
||||
// Persist updated MLS state (now in a "left" state).
|
||||
save_mls_state(conv_store, conv_id, member)?;
|
||||
|
||||
debug!(conv = %conv_id.hex(), "left group");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// ── Key rotation ───────────────────────────────────────────────────────────
|
||||
|
||||
/// Rotate group keys — self-update + commit pending proposals.
|
||||
///
|
||||
/// Broadcasts the commit to all group members via the server.
|
||||
pub async fn rotate_group_keys(
|
||||
rpc: &RpcClient,
|
||||
conv_store: &ConversationStore,
|
||||
member: &mut GroupMember,
|
||||
conv_id: &ConversationId,
|
||||
) -> Result<(), SdkError> {
|
||||
// 1. Propose self-update (new leaf key material).
|
||||
member
|
||||
.propose_self_update()
|
||||
.map_err(|e| SdkError::Crypto(format!("propose_self_update: {e}")))?;
|
||||
|
||||
// 2. Commit all pending proposals (including the self-update).
|
||||
let (commit, _welcome) = member
|
||||
.commit_pending_proposals()
|
||||
.map_err(|e| SdkError::Crypto(format!("commit_pending_proposals: {e}")))?;
|
||||
|
||||
// 3. Call server-side RotateKeys RPC.
|
||||
let req = RotateKeysRequest {
|
||||
group_id: conv_id.0.to_vec(),
|
||||
};
|
||||
let resp_bytes = rpc
|
||||
.call(method_ids::ROTATE_KEYS, Bytes::from(req.encode_to_vec()))
|
||||
.await?;
|
||||
let _resp = RotateKeysResponse::decode(resp_bytes)
|
||||
.map_err(|e| SdkError::Crypto(format!("decode RotateKeysResponse: {e}")))?;
|
||||
|
||||
// 4. Broadcast commit to all members.
|
||||
let members = member.member_identities();
|
||||
for key in &members {
|
||||
enqueue_to_peer(rpc, key, &commit).await?;
|
||||
}
|
||||
|
||||
// 5. Persist updated MLS state.
|
||||
save_mls_state(conv_store, conv_id, member)?;
|
||||
|
||||
debug!(conv = %conv_id.hex(), "rotated group keys");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// ── Group metadata ─────────────────────────────────────────────────────────
|
||||
|
||||
/// Update group metadata (name, description, avatar) on the server.
|
||||
pub async fn set_group_metadata(
|
||||
rpc: &RpcClient,
|
||||
conv_store: &ConversationStore,
|
||||
conv_id: &ConversationId,
|
||||
name: &str,
|
||||
description: &str,
|
||||
avatar_hash: &[u8],
|
||||
) -> Result<(), SdkError> {
|
||||
let req = UpdateGroupMetadataRequest {
|
||||
group_id: conv_id.0.to_vec(),
|
||||
name: name.to_string(),
|
||||
description: description.to_string(),
|
||||
avatar_hash: avatar_hash.to_vec(),
|
||||
};
|
||||
let resp_bytes = rpc
|
||||
.call(
|
||||
method_ids::UPDATE_GROUP_METADATA,
|
||||
Bytes::from(req.encode_to_vec()),
|
||||
)
|
||||
.await?;
|
||||
let resp = UpdateGroupMetadataResponse::decode(resp_bytes)
|
||||
.map_err(|e| SdkError::Crypto(format!("decode UpdateGroupMetadataResponse: {e}")))?;
|
||||
|
||||
if !resp.success {
|
||||
return Err(SdkError::Other(anyhow::anyhow!(
|
||||
"server rejected metadata update"
|
||||
)));
|
||||
}
|
||||
|
||||
// Update local conversation display name if name is provided.
|
||||
if !name.is_empty() {
|
||||
if let Ok(Some(mut conv)) = conv_store.load_conversation(conv_id) {
|
||||
conv.display_name = format!("#{name}");
|
||||
conv.kind = ConversationKind::Group {
|
||||
name: name.to_string(),
|
||||
};
|
||||
let _ = conv_store.save_conversation(&conv);
|
||||
}
|
||||
}
|
||||
|
||||
debug!(conv = %conv_id.hex(), name = name, "updated group metadata");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Fetch group members from the server.
|
||||
pub async fn get_group_members(
|
||||
rpc: &RpcClient,
|
||||
conv_id: &ConversationId,
|
||||
) -> Result<Vec<GroupMemberInfoResult>, SdkError> {
|
||||
let req = ListGroupMembersRequest {
|
||||
group_id: conv_id.0.to_vec(),
|
||||
};
|
||||
let resp_bytes = rpc
|
||||
.call(
|
||||
method_ids::LIST_GROUP_MEMBERS,
|
||||
Bytes::from(req.encode_to_vec()),
|
||||
)
|
||||
.await?;
|
||||
let resp = ListGroupMembersResponse::decode(resp_bytes)
|
||||
.map_err(|e| SdkError::Crypto(format!("decode ListGroupMembersResponse: {e}")))?;
|
||||
|
||||
let members = resp
|
||||
.members
|
||||
.into_iter()
|
||||
.map(|m| GroupMemberInfoResult {
|
||||
identity_key: m.identity_key,
|
||||
username: m.username,
|
||||
joined_at: m.joined_at,
|
||||
})
|
||||
.collect();
|
||||
|
||||
Ok(members)
|
||||
}
|
||||
|
||||
/// SDK-side group member info returned by [`get_group_members`].
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct GroupMemberInfoResult {
|
||||
pub identity_key: Vec<u8>,
|
||||
pub username: String,
|
||||
pub joined_at: u64,
|
||||
}
|
||||
|
||||
// ── MLS state persistence ───────────────────────────────────────────────────
|
||||
|
||||
/// Save MLS group state into a conversation record.
|
||||
pub fn save_mls_state(
|
||||
conv_store: &ConversationStore,
|
||||
conv_id: &ConversationId,
|
||||
member: &GroupMember,
|
||||
) -> Result<(), SdkError> {
|
||||
let mut conv = conv_store
|
||||
.load_conversation(conv_id)
|
||||
.map_err(|e| SdkError::Storage(e.to_string()))?
|
||||
.ok_or_else(|| SdkError::ConversationNotFound(conv_id.hex()))?;
|
||||
|
||||
conv.mls_group_blob = member
|
||||
.group_ref()
|
||||
.map(bincode::serialize)
|
||||
.transpose()
|
||||
.map_err(|e| SdkError::Storage(format!("serialize MLS group: {e}")))?;
|
||||
conv.member_keys = member.member_identities();
|
||||
conv.is_hybrid = member.is_hybrid();
|
||||
|
||||
conv_store
|
||||
.save_conversation(&conv)
|
||||
.map_err(|e| SdkError::Storage(e.to_string()))?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Restore a `GroupMember` from a conversation record.
|
||||
///
|
||||
/// Returns `Err` if the conversation has no MLS group blob.
|
||||
pub fn restore_mls_state(
|
||||
conv: &Conversation,
|
||||
identity: &Arc<IdentityKeypair>,
|
||||
) -> Result<GroupMember, SdkError> {
|
||||
let group_blob = conv
|
||||
.mls_group_blob
|
||||
.as_ref()
|
||||
.ok_or_else(|| SdkError::Crypto("no MLS group blob in conversation".into()))?;
|
||||
|
||||
let mls_group = bincode::deserialize(group_blob)
|
||||
.map_err(|e| SdkError::Crypto(format!("deserialize MLS group: {e}")))?;
|
||||
|
||||
let ks = quicprochat_core::DiskKeyStore::ephemeral();
|
||||
let member = GroupMember::new_with_state(Arc::clone(identity), ks, Some(mls_group), conv.is_hybrid);
|
||||
|
||||
Ok(member)
|
||||
}
|
||||
|
||||
// ── Helpers ─────────────────────────────────────────────────────────────────
|
||||
|
||||
/// Optionally wrap data in hybrid encryption.
|
||||
///
|
||||
/// If `is_hybrid_mls` is true, MLS already provides PQ protection and we skip
|
||||
/// the outer envelope.
|
||||
fn wrap_hybrid(
|
||||
my_kp: Option<&HybridKeypair>,
|
||||
peer_pk: Option<&HybridPublicKey>,
|
||||
data: &[u8],
|
||||
is_hybrid_mls: bool,
|
||||
) -> Result<Vec<u8>, SdkError> {
|
||||
if is_hybrid_mls {
|
||||
return Ok(data.to_vec());
|
||||
}
|
||||
match (my_kp, peer_pk) {
|
||||
(Some(_), Some(pk)) => {
|
||||
hybrid_encrypt(pk, data, b"", b"")
|
||||
.map_err(|e| SdkError::Crypto(format!("hybrid encrypt: {e}")))
|
||||
}
|
||||
_ => Ok(data.to_vec()),
|
||||
}
|
||||
}
|
||||
|
||||
/// Enqueue a payload to a peer via the ENQUEUE RPC.
|
||||
async fn enqueue_to_peer(
|
||||
rpc: &RpcClient,
|
||||
recipient_key: &[u8],
|
||||
payload: &[u8],
|
||||
) -> Result<(), SdkError> {
|
||||
let req = EnqueueRequest {
|
||||
recipient_key: recipient_key.to_vec(),
|
||||
payload: payload.to_vec(),
|
||||
channel_id: Vec::new(),
|
||||
ttl_secs: 0,
|
||||
message_id: Vec::new(),
|
||||
};
|
||||
let resp_bytes = rpc
|
||||
.call(method_ids::ENQUEUE, Bytes::from(req.encode_to_vec()))
|
||||
.await?;
|
||||
let _resp = EnqueueResponse::decode(resp_bytes)
|
||||
.map_err(|e| SdkError::Crypto(format!("decode EnqueueResponse: {e}")))?;
|
||||
Ok(())
|
||||
}
|
||||
Reference in New Issue
Block a user