use crate::room_state::ban::BansV1;
use crate::room_state::ChatRoomParametersV1;
use crate::util::{sign_struct, truncated_base32, verify_struct};
use crate::ChatRoomStateV1;
use ed25519_dalek::{Signature, SigningKey, VerifyingKey};
use freenet_scaffold::util::{fast_hash, FastHash};
use freenet_scaffold::ComposableState;
use serde::{Deserialize, Serialize};
use std::collections::{HashMap, HashSet};
use std::fmt;
use std::fmt::{Debug, Display};
use std::hash::{Hash, Hasher};
#[derive(Serialize, Deserialize, Eq, PartialEq, Clone, Debug, Default)]
pub struct MembersV1 {
pub members: Vec<AuthorizedMember>,
}
impl ComposableState for MembersV1 {
type ParentState = ChatRoomStateV1;
type Summary = HashSet<MemberId>;
type Delta = MembersDelta;
type Parameters = ChatRoomParametersV1;
fn verify(
&self,
parent_state: &Self::ParentState,
parameters: &Self::Parameters,
) -> Result<(), String> {
if self.members.is_empty() {
return Ok(());
}
if self.members.len() > parent_state.configuration.configuration.max_members {
return Err(format!(
"Too many members: {} > {}",
self.members.len(),
parent_state.configuration.configuration.max_members
));
}
let owner_id = parameters.owner_id();
let members_by_id = self.members_by_member_id();
for member in &self.members {
if member.member.id() == owner_id {
return Err("Owner should not be included in the members list".to_string());
}
if member.member.member_vk == parameters.owner {
return Err(
"Member cannot have the same verifying key as the room owner".to_string(),
);
}
if member.member.invited_by == member.member.id() {
return Err("Self-invitation detected".to_string());
}
self.get_invite_chain_with_lookup(member, parameters, &members_by_id)?;
}
Ok(())
}
fn summarize(
&self,
_parent_state: &Self::ParentState,
_parameters: &Self::Parameters,
) -> Self::Summary {
self.members.iter().map(|m| m.member.id()).collect()
}
fn delta(
&self,
_parent_state: &Self::ParentState,
_parameters: &Self::Parameters,
old_state_summary: &Self::Summary,
) -> Option<Self::Delta> {
let added = self
.members
.iter()
.filter(|m| !old_state_summary.contains(&m.member.id()))
.cloned()
.collect::<Vec<_>>();
if added.is_empty() {
None
} else {
Some(MembersDelta { added })
}
}
fn apply_delta(
&mut self,
parent_state: &Self::ParentState,
parameters: &Self::Parameters,
delta: &Option<Self::Delta>,
) -> Result<(), String> {
let max_members = parent_state.configuration.configuration.max_members;
if let Some(delta) = delta {
let mut combined_members_by_id = self.members_by_member_id();
for member in &delta.added {
combined_members_by_id
.entry(member.member.id())
.or_insert(member);
}
for member in &delta.added {
self.verify_member_invite_with_lookup(member, parameters, &combined_members_by_id)?;
}
for member in &delta.added {
if self
.members
.iter()
.any(|m| m.member.id() == member.member.id())
{
continue;
}
self.members.push(member.clone());
}
}
self.remove_banned_members(&parent_state.bans, parameters);
self.remove_excess_members(parameters, max_members);
self.members.sort_by_key(|m| m.member.id());
Ok(())
}
}
impl MembersV1 {
fn verify_member_invite_with_lookup(
&self,
member: &AuthorizedMember,
parameters: &ChatRoomParametersV1,
members_by_id: &HashMap<MemberId, &AuthorizedMember>,
) -> Result<(), String> {
if member.member.invited_by == parameters.owner_id() {
member
.verify_signature(¶meters.owner)
.map_err(|e| format!("Invalid signature for member invited by owner: {}", e))?;
} else {
self.get_invite_chain_with_lookup(member, parameters, members_by_id)?;
}
Ok(())
}
}
impl MembersV1 {
pub fn is_inviter_of(
&self,
member_id: MemberId,
target_id: MemberId,
params: &ChatRoomParametersV1,
) -> bool {
if member_id == params.owner_id() {
self.members
.iter()
.find(|m| m.member.id() == target_id)
.map(|m| m.member.invited_by == member_id)
.unwrap_or(false)
} else {
self.members
.iter()
.find(|m| m.member.id() == target_id)
.map(|m| m.member.invited_by == member_id)
.unwrap_or(false)
}
}
pub fn members_by_member_id(&self) -> HashMap<MemberId, &AuthorizedMember> {
self.members.iter().map(|m| (m.member.id(), m)).collect()
}
pub fn has_banned_members(&self, bans_v1: &BansV1, parameters: &ChatRoomParametersV1) -> bool {
self.check_banned_members(bans_v1, parameters).is_some()
}
fn remove_banned_members(&mut self, bans_v1: &BansV1, _parameters: &ChatRoomParametersV1) {
let mut banned_ids = HashSet::new();
for ban in &bans_v1.0 {
banned_ids.insert(ban.ban.banned_user);
banned_ids.extend(self.get_downstream_members(ban.ban.banned_user));
}
self.members
.retain(|m| !banned_ids.contains(&m.member.id()));
}
fn get_downstream_members(&self, member_id: MemberId) -> HashSet<MemberId> {
let mut downstream = HashSet::new();
let mut to_check = vec![member_id];
while let Some(current) = to_check.pop() {
for member in &self.members {
if member.member.invited_by == current {
downstream.insert(member.member.id());
to_check.push(member.member.id());
}
}
}
downstream
}
fn remove_excess_members(&mut self, parameters: &ChatRoomParametersV1, max_members: usize) {
if self.members.len() <= max_members {
return;
}
let members_by_id = self.members_by_member_id();
let owner_id = parameters.owner_id();
let mut chain_lengths: Vec<(MemberId, usize)> = self
.members
.iter()
.map(|m| {
let len = Self::invite_chain_length(m, owner_id, &members_by_id);
(m.member.id(), len)
})
.collect();
chain_lengths.sort_by(|a, b| b.1.cmp(&a.1).then_with(|| b.0.cmp(&a.0)));
let excess = self.members.len() - max_members;
let ids_to_remove: HashSet<MemberId> = chain_lengths
.iter()
.take(excess)
.map(|(id, _)| *id)
.collect();
self.members
.retain(|m| !ids_to_remove.contains(&m.member.id()));
}
fn check_banned_members(
&self,
bans_v1: &BansV1,
parameters: &ChatRoomParametersV1,
) -> Option<HashSet<MemberId>> {
let banned_user_ids: HashSet<MemberId> =
bans_v1.0.iter().map(|b| b.ban.banned_user).collect();
if banned_user_ids.is_empty() {
return None;
}
let members_by_id = self.members_by_member_id();
let owner_id = parameters.owner_id();
let mut result = HashSet::new();
for m in &self.members {
let chain_ids = Self::invite_chain_ids(m, owner_id, &members_by_id);
if chain_ids.iter().any(|id| banned_user_ids.contains(id)) {
result.insert(m.member.id());
}
}
if result.is_empty() {
None
} else {
Some(result)
}
}
pub fn get_invite_chain(
&self,
member: &AuthorizedMember,
parameters: &ChatRoomParametersV1,
) -> Result<Vec<AuthorizedMember>, String> {
let members_by_id = self.members_by_member_id();
self.get_invite_chain_with_lookup(member, parameters, &members_by_id)
}
fn get_invite_chain_with_lookup(
&self,
member: &AuthorizedMember,
parameters: &ChatRoomParametersV1,
members_by_id: &HashMap<MemberId, &AuthorizedMember>,
) -> Result<Vec<AuthorizedMember>, String> {
let mut invite_chain = Vec::new();
let mut current_member = member;
let owner_id = parameters.owner_id();
let mut visited_members = HashSet::new();
loop {
if !visited_members.insert(current_member.member.id()) {
return Err(format!(
"Circular invite chain detected for member {:?}",
current_member.member.id()
));
}
if current_member.member.invited_by == current_member.member.id() {
return Err(format!(
"Self-invitation detected for member {:?}",
current_member.member.id()
));
}
if current_member.member.invited_by == owner_id {
current_member
.verify_signature(¶meters.owner)
.map_err(|e| {
format!(
"Invalid signature for member {:?} invited by owner: {}",
current_member.member.id(),
e
)
})?;
break;
} else {
let inviter = members_by_id
.get(¤t_member.member.invited_by)
.ok_or_else(|| {
format!(
"Inviter {:?} not found for member {:?}",
current_member.member.invited_by,
current_member.member.id()
)
})?;
current_member
.verify_signature(&inviter.member.member_vk)
.map_err(|e| {
format!(
"Invalid signature for member {:?}: {}",
current_member.member.id(),
e
)
})?;
invite_chain.push((*inviter).clone());
current_member = inviter;
}
}
Ok(invite_chain)
}
fn invite_chain_length(
member: &AuthorizedMember,
owner_id: MemberId,
members_by_id: &HashMap<MemberId, &AuthorizedMember>,
) -> usize {
let mut length = 0;
let mut current_id = member.member.invited_by;
let mut visited = HashSet::new();
visited.insert(member.member.id());
while current_id != owner_id {
if !visited.insert(current_id) {
break; }
length += 1;
match members_by_id.get(¤t_id) {
Some(inviter) => current_id = inviter.member.invited_by,
None => break, }
}
length
}
fn invite_chain_ids(
member: &AuthorizedMember,
owner_id: MemberId,
members_by_id: &HashMap<MemberId, &AuthorizedMember>,
) -> Vec<MemberId> {
let mut chain_ids = vec![member.member.id()];
let mut current_id = member.member.invited_by;
let mut visited = HashSet::new();
visited.insert(member.member.id());
while current_id != owner_id {
if !visited.insert(current_id) {
break;
}
chain_ids.push(current_id);
match members_by_id.get(¤t_id) {
Some(inviter) => current_id = inviter.member.invited_by,
None => break,
}
}
chain_ids
}
}
#[derive(Serialize, Deserialize, Eq, PartialEq, Clone, Debug)]
pub struct MembersDelta {
added: Vec<AuthorizedMember>,
}
impl MembersDelta {
pub fn new(added: Vec<AuthorizedMember>) -> Self {
MembersDelta { added }
}
pub fn added(&self) -> &[AuthorizedMember] {
&self.added
}
pub fn into_added(self) -> Vec<AuthorizedMember> {
self.added
}
}
#[derive(Serialize, Deserialize, Eq, PartialEq, Clone, Debug)]
pub struct AuthorizedMember {
pub member: Member,
pub signature: Signature,
}
impl AuthorizedMember {
pub fn new(member: Member, inviter_signing_key: &SigningKey) -> Self {
assert_eq!(
member.invited_by,
VerifyingKey::from(inviter_signing_key).into(),
"The member's invited_by must match the inviter's signing key"
);
Self {
member: member.clone(),
signature: sign_struct(&member, inviter_signing_key),
}
}
pub fn with_signature(member: Member, signature: Signature) -> Self {
Self { member, signature }
}
pub fn verify_signature(&self, inviter_vk: &VerifyingKey) -> Result<(), String> {
verify_struct(&self.member, &self.signature, inviter_vk)
.map_err(|e| format!("Invalid signature: {}", e))
}
}
impl Hash for AuthorizedMember {
fn hash<H: Hasher>(&self, state: &mut H) {
self.member.hash(state);
}
}
#[derive(Serialize, Deserialize, Eq, PartialEq, Hash, Clone)]
pub struct Member {
pub owner_member_id: MemberId,
pub invited_by: MemberId,
pub member_vk: VerifyingKey,
}
impl fmt::Debug for Member {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Member")
.field(
"public_key",
&format_args!("{}", truncated_base32(self.member_vk.as_bytes())),
)
.finish()
}
}
#[derive(Eq, PartialEq, Hash, Serialize, Deserialize, Clone, Ord, PartialOrd, Copy)]
pub struct MemberId(pub FastHash);
impl Display for MemberId {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", truncated_base32(&self.0 .0.to_le_bytes()))
}
}
impl Debug for MemberId {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"MemberId({})",
truncated_base32(&self.0 .0.to_le_bytes())
)
}
}
impl From<&VerifyingKey> for MemberId {
fn from(vk: &VerifyingKey) -> Self {
MemberId(fast_hash(&vk.to_bytes()))
}
}
impl From<VerifyingKey> for MemberId {
fn from(vk: VerifyingKey) -> Self {
MemberId(fast_hash(&vk.to_bytes()))
}
}
impl Member {
pub fn id(&self) -> MemberId {
self.member_vk.into()
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::room_state::ban::{AuthorizedUserBan, UserBan};
use ed25519_dalek::SigningKey;
use rand::rngs::OsRng;
use std::time::SystemTime;
fn create_test_member(owner_id: MemberId, invited_by: MemberId) -> (Member, SigningKey) {
let signing_key = SigningKey::generate(&mut OsRng);
let verifying_key = signing_key.verifying_key();
let member = Member {
owner_member_id: owner_id,
invited_by,
member_vk: verifying_key,
};
(member, signing_key)
}
#[test]
fn test_members_verify() {
let owner_signing_key = SigningKey::generate(&mut OsRng);
let owner_verifying_key = VerifyingKey::from(&owner_signing_key);
let owner_id = owner_verifying_key.into();
let (member1, member1_signing_key) = create_test_member(owner_id, owner_id);
let (member2, _) = create_test_member(owner_id, member1.id());
let authorized_member1 = AuthorizedMember::new(member1.clone(), &owner_signing_key);
let authorized_member2 = AuthorizedMember::new(member2.clone(), &member1_signing_key);
let members = MembersV1 {
members: vec![authorized_member1.clone(), authorized_member2.clone()],
};
println!("Member1 ID: {:?}", member1.id());
println!("Member2 ID: {:?}", member2.id());
println!("Owner ID: {:?}", owner_id);
let mut parent_state = ChatRoomStateV1::default();
parent_state.configuration.configuration.max_members = 3;
let parameters = ChatRoomParametersV1 {
owner: owner_verifying_key,
};
let result = members.verify(&parent_state, ¶meters);
println!("Verification result: {:?}", result);
assert!(result.is_ok(), "Verification failed: {:?}", result);
let owner_member = Member {
owner_member_id: owner_id,
invited_by: owner_id,
member_vk: owner_verifying_key,
};
let authorized_owner = AuthorizedMember::new(owner_member, &owner_signing_key);
let members_with_owner = MembersV1 {
members: vec![authorized_owner, authorized_member1, authorized_member2],
};
let result_with_owner = members_with_owner.verify(&parent_state, ¶meters);
println!("Verification result with owner: {:?}", result_with_owner);
assert!(
result_with_owner.is_err(),
"Verification should fail when owner is included: {:?}",
result_with_owner
);
}
#[test]
fn test_members_summarize() {
let owner_signing_key = SigningKey::generate(&mut OsRng);
let owner_verifying_key = VerifyingKey::from(&owner_signing_key);
let owner_id = owner_verifying_key.into();
let (member1, member1_signing_key) = create_test_member(owner_id, owner_id);
let (member2, _) = create_test_member(owner_id, member1.id());
let authorized_member1 = AuthorizedMember::new(member1.clone(), &owner_signing_key);
let authorized_member2 = AuthorizedMember::new(member2.clone(), &member1_signing_key);
let members = MembersV1 {
members: vec![authorized_member1, authorized_member2],
};
let parent_state = ChatRoomStateV1::default();
let parameters = ChatRoomParametersV1 {
owner: owner_verifying_key,
};
let summary = members.summarize(&parent_state, ¶meters);
assert_eq!(summary.len(), 2);
assert!(summary.contains(&member1.id()));
assert!(summary.contains(&member2.id()));
}
#[test]
fn test_members_delta() {
let owner_signing_key = SigningKey::generate(&mut OsRng);
let owner_verifying_key = VerifyingKey::from(&owner_signing_key);
let owner_id = owner_verifying_key.into();
let (member1, member1_signing_key) = create_test_member(owner_id, owner_id);
let (member2, _) = create_test_member(owner_id, member1.id());
let (member3, _) = create_test_member(owner_id, member1.id());
let authorized_member1 = AuthorizedMember::new(member1.clone(), &owner_signing_key);
let authorized_member2 = AuthorizedMember::new(member2.clone(), &member1_signing_key);
let authorized_member3 = AuthorizedMember::new(member3.clone(), &member1_signing_key);
let old_members = MembersV1 {
members: vec![authorized_member1.clone(), authorized_member2.clone()],
};
let new_members = MembersV1 {
members: vec![authorized_member1.clone(), authorized_member3.clone()],
};
let parent_state = ChatRoomStateV1::default();
let parameters = ChatRoomParametersV1 {
owner: owner_verifying_key,
};
let old_summary = old_members.summarize(&parent_state, ¶meters);
let delta = new_members
.delta(&parent_state, ¶meters, &old_summary)
.unwrap();
assert_eq!(delta.added.len(), 1);
assert_eq!(delta.added[0].member.id(), member3.id());
}
#[test]
fn test_members_apply_delta_simple() {
let owner_signing_key = SigningKey::generate(&mut OsRng);
let owner_verifying_key = VerifyingKey::from(&owner_signing_key);
let owner_id = owner_verifying_key.into();
let (member1, member1_signing_key) = create_test_member(owner_id, owner_id);
let (member2, _) = create_test_member(owner_id, member1.id());
let (member3, _) = create_test_member(owner_id, member1.id());
let authorized_member1 = AuthorizedMember::new(member1.clone(), &owner_signing_key);
let authorized_member2 = AuthorizedMember::new(member2.clone(), &member1_signing_key);
let authorized_member3 = AuthorizedMember::new(member3.clone(), &member1_signing_key);
let original_members = MembersV1 {
members: vec![authorized_member1.clone(), authorized_member2.clone()],
};
let delta = MembersDelta {
added: vec![authorized_member3.clone()],
};
let mut parent_state = ChatRoomStateV1::default();
parent_state.configuration.configuration.max_members = 3;
let parameters = ChatRoomParametersV1 {
owner: owner_verifying_key,
};
let mut modified_members = original_members.clone();
assert!(modified_members
.apply_delta(&parent_state, ¶meters, &Some(delta))
.is_ok());
assert_eq!(modified_members.members.len(), 3);
assert!(modified_members
.members
.iter()
.any(|m| m.member.id() == member1.id()));
assert!(modified_members
.members
.iter()
.any(|m| m.member.id() == member3.id()));
assert!(modified_members
.members
.iter()
.any(|m| m.member.id() == member2.id()));
}
#[test]
fn test_authorized_member_validate() {
let owner_signing_key = SigningKey::generate(&mut OsRng);
let owner_verifying_key = VerifyingKey::from(&owner_signing_key);
let owner_id = owner_verifying_key.into();
let (member1, member1_signing_key) = create_test_member(owner_id, owner_id);
let (member2, _) = create_test_member(owner_id, member1.id());
let authorized_member1 = AuthorizedMember::new(member1.clone(), &owner_signing_key);
let authorized_member2 = AuthorizedMember::new(member2.clone(), &member1_signing_key);
assert!(authorized_member1
.verify_signature(&owner_verifying_key)
.is_ok());
assert!(authorized_member2
.verify_signature(&member1.member_vk)
.is_ok());
let invalid_member2 = AuthorizedMember {
member: member2.clone(),
signature: Signature::from_bytes(&[0; 64]),
};
assert!(invalid_member2
.verify_signature(&member1.member_vk)
.is_err());
}
#[test]
fn test_member_id() {
let owner_id = MemberId(FastHash(0));
let (member, _) = create_test_member(owner_id, owner_id);
let member_id = member.id();
assert_eq!(member_id, member.member_vk.into());
}
#[test]
fn test_verify_self_invited_member() {
let owner_signing_key = SigningKey::generate(&mut OsRng);
let owner_verifying_key = VerifyingKey::from(&owner_signing_key);
let owner_id = owner_verifying_key.into();
let (mut member, member_signing_key) = create_test_member(owner_id, owner_id);
member.invited_by = member.id();
let authorized_member = AuthorizedMember::new(member, &member_signing_key);
let members = MembersV1 {
members: vec![authorized_member],
};
let parent_state = ChatRoomStateV1::default();
let parameters = ChatRoomParametersV1 {
owner: owner_verifying_key,
};
let result = members.verify(&parent_state, ¶meters);
assert!(result.is_err());
assert!(result.unwrap_err().contains("Self-invitation detected"));
}
#[test]
fn test_verify_circular_invite_chain() {
let owner_signing_key = SigningKey::generate(&mut OsRng);
let owner_verifying_key = VerifyingKey::from(&owner_signing_key);
let owner_id = owner_verifying_key.into();
let (mut member1, member1_signing_key) = create_test_member(owner_id, owner_id);
let (member2, member2_signing_key) = create_test_member(owner_id, member1.id());
let (member3, member3_signing_key) = create_test_member(owner_id, member2.id());
member1.invited_by = member3.id();
let authorized_member1 = AuthorizedMember::new(member1, &member3_signing_key);
let authorized_member2 = AuthorizedMember::new(member2, &member1_signing_key);
let authorized_member3 = AuthorizedMember::new(member3, &member2_signing_key);
let members = MembersV1 {
members: vec![authorized_member1, authorized_member2, authorized_member3],
};
let parent_state = ChatRoomStateV1::default();
let parameters = ChatRoomParametersV1 {
owner: owner_verifying_key,
};
let result = members.verify(&parent_state, ¶meters);
assert!(result.is_err());
assert!(result
.unwrap_err()
.contains("Circular invite chain detected"));
}
#[test]
fn test_check_invite_chain() {
let owner_signing_key = SigningKey::generate(&mut OsRng);
let owner_verifying_key = VerifyingKey::from(&owner_signing_key);
let owner_id = owner_verifying_key.into();
let (member1, member1_signing_key) = create_test_member(owner_id, owner_id);
let (member2, member2_signing_key) = create_test_member(owner_id, member1.id());
let (member3, _) = create_test_member(owner_id, member2.id());
let authorized_member1 = AuthorizedMember::new(member1.clone(), &owner_signing_key);
let authorized_member2 = AuthorizedMember::new(member2, &member1_signing_key);
let authorized_member3 = AuthorizedMember::new(member3, &member2_signing_key);
let members = MembersV1 {
members: vec![authorized_member1, authorized_member2.clone()],
};
let parameters = ChatRoomParametersV1 {
owner: owner_verifying_key,
};
let result = members.get_invite_chain(&authorized_member3, ¶meters);
assert!(result.is_ok());
assert_eq!(result.unwrap().len(), 2);
let (mut circular_member1, circular_member1_signing_key) =
create_test_member(owner_id, owner_id);
let (circular_member2, circular_member2_signing_key) =
create_test_member(owner_id, circular_member1.id());
circular_member1.invited_by = circular_member2.id();
let circular_authorized_member1 =
AuthorizedMember::new(circular_member1, &circular_member2_signing_key);
let circular_authorized_member2 =
AuthorizedMember::new(circular_member2, &circular_member1_signing_key);
let circular_members = MembersV1 {
members: vec![
circular_authorized_member1.clone(),
circular_authorized_member2,
],
};
let result = circular_members.get_invite_chain(&circular_authorized_member1, ¶meters);
assert!(result.is_err());
assert!(result
.clone()
.unwrap_err()
.contains("Circular invite chain detected"));
let non_existent_inviter_id = MemberId(FastHash(999));
let (orphan_member, _) = create_test_member(owner_id, non_existent_inviter_id);
let orphan_authorized_member = AuthorizedMember {
member: orphan_member,
signature: Signature::from_bytes(&[0; 64]), };
let result = members.get_invite_chain(&orphan_authorized_member, ¶meters);
assert!(result.is_err());
let err = result.unwrap_err();
assert!(err.contains("Inviter"), "Error message: {}", err);
assert!(err.contains("not found"), "Error message: {}", err);
let (invalid_member, _) = create_test_member(owner_id, member1.id());
let invalid_authorized_member = AuthorizedMember {
member: invalid_member,
signature: Signature::from_bytes(&[0; 64]),
};
let result = members.get_invite_chain(&invalid_authorized_member, ¶meters);
assert!(result.is_err());
assert!(result.unwrap_err().contains("Invalid signature"));
}
#[test]
fn test_has_banned_members() {
let owner_signing_key = SigningKey::generate(&mut OsRng);
let owner_verifying_key = VerifyingKey::from(&owner_signing_key);
let owner_id = owner_verifying_key.into();
let (member1, member1_signing_key) = create_test_member(owner_id, owner_id);
let (member2, member2_signing_key) = create_test_member(owner_id, member1.id());
let (member3, _) = create_test_member(owner_id, member2.id());
let authorized_member1 = AuthorizedMember::new(member1.clone(), &owner_signing_key);
let authorized_member2 = AuthorizedMember::new(member2.clone(), &member1_signing_key);
let authorized_member3 = AuthorizedMember::new(member3.clone(), &member2_signing_key);
let members = MembersV1 {
members: vec![authorized_member1, authorized_member2, authorized_member3],
};
let parameters = ChatRoomParametersV1 {
owner: owner_verifying_key,
};
let empty_bans = BansV1(vec![]);
assert!(!members.has_banned_members(&empty_bans, ¶meters));
let banned_member = UserBan {
owner_member_id: owner_id,
banned_at: SystemTime::now(),
banned_user: member2.id(),
};
let authorized_ban = AuthorizedUserBan::new(banned_member, owner_id, &owner_signing_key);
let bans = BansV1(vec![authorized_ban]);
assert!(members.has_banned_members(&bans, ¶meters));
}
#[test]
fn test_remove_banned_members() {
let owner_signing_key = SigningKey::generate(&mut OsRng);
let owner_verifying_key = VerifyingKey::from(&owner_signing_key);
let owner_id = owner_verifying_key.into();
let (member1, member1_signing_key) = create_test_member(owner_id, owner_id);
let (member2, member2_signing_key) = create_test_member(owner_id, member1.id());
let (member3, _) = create_test_member(owner_id, member2.id());
let (member4, _) = create_test_member(owner_id, member1.id());
let authorized_member1 = AuthorizedMember::new(member1.clone(), &owner_signing_key);
let authorized_member2 = AuthorizedMember::new(member2.clone(), &member1_signing_key);
let authorized_member3 = AuthorizedMember::new(member3.clone(), &member2_signing_key);
let authorized_member4 = AuthorizedMember::new(member4.clone(), &member1_signing_key);
let mut members = MembersV1 {
members: vec![
authorized_member1.clone(),
authorized_member2.clone(),
authorized_member3.clone(),
authorized_member4.clone(),
],
};
let parameters = ChatRoomParametersV1 {
owner: owner_verifying_key,
};
let empty_bans = BansV1(vec![]);
members.remove_banned_members(&empty_bans, ¶meters);
assert_eq!(members.members.len(), 4);
let banned_member = UserBan {
owner_member_id: owner_id,
banned_at: SystemTime::now(),
banned_user: member2.id(),
};
let authorized_ban = AuthorizedUserBan::new(banned_member, owner_id, &owner_signing_key);
let bans = BansV1(vec![authorized_ban]);
members.remove_banned_members(&bans, ¶meters);
assert_eq!(members.members.len(), 2);
assert!(members
.members
.iter()
.any(|m| m.member.id() == member1.id()));
assert!(members
.members
.iter()
.any(|m| m.member.id() == member4.id()));
assert!(!members
.members
.iter()
.any(|m| m.member.id() == member2.id()));
assert!(!members
.members
.iter()
.any(|m| m.member.id() == member3.id()));
members = MembersV1 {
members: vec![
authorized_member1,
authorized_member2,
authorized_member3,
authorized_member4,
],
};
let banned_member = UserBan {
owner_member_id: owner_id,
banned_at: SystemTime::now(),
banned_user: member4.id(),
};
let authorized_ban = AuthorizedUserBan::new(banned_member, owner_id, &owner_signing_key);
let bans = BansV1(vec![authorized_ban]);
members.remove_banned_members(&bans, ¶meters);
assert_eq!(members.members.len(), 3);
assert!(members
.members
.iter()
.any(|m| m.member.id() == member1.id()));
assert!(members
.members
.iter()
.any(|m| m.member.id() == member2.id()));
assert!(members
.members
.iter()
.any(|m| m.member.id() == member3.id()));
assert!(!members
.members
.iter()
.any(|m| m.member.id() == member4.id()));
}
#[test]
fn test_remove_excess_members() {
let owner_signing_key = SigningKey::generate(&mut OsRng);
let owner_verifying_key = VerifyingKey::from(&owner_signing_key);
let owner_id = owner_verifying_key.into();
let (member1, member1_signing_key) = create_test_member(owner_id, owner_id);
let (member2, member2_signing_key) = create_test_member(owner_id, member1.id());
let (member3, _) = create_test_member(owner_id, member2.id());
let authorized_member1 = AuthorizedMember::new(member1.clone(), &owner_signing_key);
let authorized_member2 = AuthorizedMember::new(member2.clone(), &member1_signing_key);
let authorized_member3 = AuthorizedMember::new(member3.clone(), &member2_signing_key);
let mut members = MembersV1 {
members: vec![authorized_member1, authorized_member2, authorized_member3],
};
let parameters = ChatRoomParametersV1 {
owner: owner_verifying_key,
};
members.remove_excess_members(¶meters, 3);
assert_eq!(members.members.len(), 3);
members.remove_excess_members(¶meters, 2);
assert_eq!(members.members.len(), 2);
assert!(members
.members
.iter()
.any(|m| m.member.id() == member1.id()));
assert!(members
.members
.iter()
.any(|m| m.member.id() == member2.id()));
assert!(!members
.members
.iter()
.any(|m| m.member.id() == member3.id()));
}
#[test]
fn test_members_by_member_id() {
let owner_signing_key = SigningKey::generate(&mut OsRng);
let owner_verifying_key = VerifyingKey::from(&owner_signing_key);
let owner_id = owner_verifying_key.into();
let (member1, member1_signing_key) = create_test_member(owner_id, owner_id);
let (member2, _) = create_test_member(owner_id, member1.id());
let authorized_member1 = AuthorizedMember::new(member1.clone(), &owner_signing_key);
let authorized_member2 = AuthorizedMember::new(member2.clone(), &member1_signing_key);
let members = MembersV1 {
members: vec![authorized_member1.clone(), authorized_member2.clone()],
};
let members_map = members.members_by_member_id();
assert_eq!(members_map.len(), 2);
assert_eq!(
members_map.get(&member1.id()).unwrap().member.id(),
member1.id()
);
assert_eq!(
members_map.get(&member2.id()).unwrap().member.id(),
member2.id()
);
}
#[test]
fn test_invite_chain_length() {
let owner_signing_key = SigningKey::generate(&mut OsRng);
let owner_verifying_key = VerifyingKey::from(&owner_signing_key);
let owner_id: MemberId = owner_verifying_key.into();
let (member1, member1_signing_key) = create_test_member(owner_id, owner_id);
let (member2, member2_signing_key) = create_test_member(owner_id, member1.id());
let (member3, _) = create_test_member(owner_id, member2.id());
let auth_m1 = AuthorizedMember::new(member1.clone(), &owner_signing_key);
let auth_m2 = AuthorizedMember::new(member2.clone(), &member1_signing_key);
let auth_m3 = AuthorizedMember::new(member3.clone(), &member2_signing_key);
let members = MembersV1 {
members: vec![auth_m1.clone(), auth_m2.clone(), auth_m3.clone()],
};
let members_by_id = members.members_by_member_id();
assert_eq!(
MembersV1::invite_chain_length(&auth_m1, owner_id, &members_by_id),
0
);
assert_eq!(
MembersV1::invite_chain_length(&auth_m2, owner_id, &members_by_id),
1
);
assert_eq!(
MembersV1::invite_chain_length(&auth_m3, owner_id, &members_by_id),
2
);
}
#[test]
fn test_invite_chain_ids() {
let owner_signing_key = SigningKey::generate(&mut OsRng);
let owner_verifying_key = VerifyingKey::from(&owner_signing_key);
let owner_id: MemberId = owner_verifying_key.into();
let (member1, member1_signing_key) = create_test_member(owner_id, owner_id);
let (member2, member2_signing_key) = create_test_member(owner_id, member1.id());
let (member3, _) = create_test_member(owner_id, member2.id());
let auth_m1 = AuthorizedMember::new(member1.clone(), &owner_signing_key);
let auth_m2 = AuthorizedMember::new(member2.clone(), &member1_signing_key);
let auth_m3 = AuthorizedMember::new(member3.clone(), &member2_signing_key);
let members = MembersV1 {
members: vec![auth_m1.clone(), auth_m2.clone(), auth_m3.clone()],
};
let members_by_id = members.members_by_member_id();
let ids = MembersV1::invite_chain_ids(&auth_m1, owner_id, &members_by_id);
assert_eq!(ids, vec![member1.id()]);
let ids = MembersV1::invite_chain_ids(&auth_m2, owner_id, &members_by_id);
assert_eq!(ids, vec![member2.id(), member1.id()]);
let ids = MembersV1::invite_chain_ids(&auth_m3, owner_id, &members_by_id);
assert_eq!(ids, vec![member3.id(), member2.id(), member1.id()]);
}
#[test]
fn test_members_apply_delta_complex() {
let owner_signing_key = SigningKey::generate(&mut OsRng);
let owner_verifying_key = VerifyingKey::from(&owner_signing_key);
let owner_id = owner_verifying_key.into();
let (member1, member1_signing_key) = create_test_member(owner_id, owner_id);
let (member2, _) = create_test_member(owner_id, member1.id());
let (member3, _) = create_test_member(owner_id, member1.id());
let (member4, _) = create_test_member(owner_id, member1.id());
let authorized_member1 = AuthorizedMember::new(member1.clone(), &owner_signing_key);
let authorized_member2 = AuthorizedMember::new(member2.clone(), &member1_signing_key);
let authorized_member3 = AuthorizedMember::new(member3.clone(), &member1_signing_key);
let authorized_member4 = AuthorizedMember::new(member4.clone(), &member1_signing_key);
let mut members = MembersV1 {
members: vec![authorized_member1.clone(), authorized_member2.clone()],
};
let mut parent_state = ChatRoomStateV1::default();
parent_state.configuration.configuration.max_members = 3;
let parameters = ChatRoomParametersV1 {
owner: owner_verifying_key,
};
let delta = MembersDelta {
added: vec![authorized_member3.clone(), authorized_member4.clone()],
};
let result = members.apply_delta(&parent_state, ¶meters, &Some(delta));
assert!(result.is_ok());
assert_eq!(members.members.len(), 3);
assert!(members
.members
.iter()
.any(|m| m.member.id() == member1.id()));
let kept_count = [member2.id(), member3.id(), member4.id()]
.iter()
.filter(|id| members.members.iter().any(|m| m.member.id() == **id))
.count();
assert_eq!(
kept_count, 2,
"Exactly 2 of the 3 equal-chain-length members should be kept"
);
let delta = MembersDelta {
added: vec![authorized_member2.clone()],
};
let result = members.apply_delta(&parent_state, ¶meters, &Some(delta));
assert!(result.is_ok());
assert_eq!(members.members.len(), 3);
}
#[test]
fn test_remove_excess_members_edge_cases() {
let owner_signing_key = SigningKey::generate(&mut OsRng);
let owner_verifying_key = VerifyingKey::from(&owner_signing_key);
let owner_id = owner_verifying_key.into();
let (member1, member1_signing_key) = create_test_member(owner_id, owner_id);
let (member2, _) = create_test_member(owner_id, member1.id());
let authorized_member1 = AuthorizedMember::new(member1.clone(), &owner_signing_key);
let authorized_member2 = AuthorizedMember::new(member2.clone(), &member1_signing_key);
let mut members = MembersV1 {
members: vec![authorized_member1.clone(), authorized_member2.clone()],
};
let parameters = ChatRoomParametersV1 {
owner: owner_verifying_key,
};
members.remove_excess_members(¶meters, 0);
assert_eq!(members.members.len(), 0);
members.members = vec![authorized_member1.clone(), authorized_member2.clone()];
members.remove_excess_members(¶meters, 3);
assert_eq!(members.members.len(), 2);
}
#[test]
#[should_panic(expected = "The member's invited_by must match the inviter's signing key")]
fn test_authorized_member_new_mismatch() {
let owner_signing_key = SigningKey::generate(&mut OsRng);
let owner_verifying_key = VerifyingKey::from(&owner_signing_key);
let owner_id = owner_verifying_key.into();
let (member, _) = create_test_member(owner_id, owner_id);
let wrong_signing_key = SigningKey::generate(&mut OsRng);
AuthorizedMember::new(member, &wrong_signing_key);
}
#[test]
fn test_members_verify_edge_cases() {
let owner_signing_key = SigningKey::generate(&mut OsRng);
let owner_verifying_key = VerifyingKey::from(&owner_signing_key);
let owner_id = owner_verifying_key.into();
let mut parent_state = ChatRoomStateV1::default();
parent_state.configuration.configuration.max_members = 2;
let parameters = ChatRoomParametersV1 {
owner: owner_verifying_key,
};
let empty_members = MembersV1 { members: vec![] };
assert!(empty_members.verify(&parent_state, ¶meters).is_ok());
let (member1, member1_signing_key) = create_test_member(owner_id, owner_id);
let (member2, _) = create_test_member(owner_id, member1.id());
let authorized_member1 = AuthorizedMember::new(member1.clone(), &owner_signing_key);
let authorized_member2 = AuthorizedMember::new(member2.clone(), &member1_signing_key);
let max_members = MembersV1 {
members: vec![authorized_member1, authorized_member2],
};
assert!(max_members.verify(&parent_state, ¶meters).is_ok());
let non_existent_signing_key = SigningKey::generate(&mut OsRng);
let non_existent_verifying_key = VerifyingKey::from(&non_existent_signing_key);
let non_existent_id = non_existent_verifying_key.into();
let (invalid_member, _) = create_test_member(owner_id, non_existent_id);
let invalid_authorized_member =
AuthorizedMember::new(invalid_member, &non_existent_signing_key);
let invalid_members = MembersV1 {
members: vec![invalid_authorized_member],
};
assert!(invalid_members.verify(&parent_state, ¶meters).is_err());
}
#[test]
fn test_room_owner_key_not_allowed_in_members() {
let owner_signing_key = SigningKey::generate(&mut OsRng);
let owner_verifying_key = VerifyingKey::from(&owner_signing_key);
let owner_id = owner_verifying_key.into();
let owner_member = Member {
owner_member_id: owner_id,
invited_by: owner_id,
member_vk: owner_verifying_key,
};
let authorized_owner_member = AuthorizedMember::new(owner_member, &owner_signing_key);
let members = MembersV1 {
members: vec![authorized_owner_member],
};
let mut parent_state = ChatRoomStateV1::default();
parent_state.configuration.configuration.max_members = 2;
let parameters = ChatRoomParametersV1 {
owner: owner_verifying_key,
};
let result = members.verify(&parent_state, ¶meters);
assert!(
result.is_err(),
"Room owner should not be allowed in the members list"
);
assert!(result
.unwrap_err()
.contains("Owner should not be included in the members list"));
}
}