1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
//! Group state
//!
//! This module contains all the logic related to group management.

use super::{
    parameters::Parameters,
    session::{Session, SessionId},
    ClientId,
};
use serde::{Deserialize, Serialize};
use std::collections::{HashMap, HashSet};
use thiserror::Error;
use uuid::Uuid;

#[cfg(feature = "server")]
use super::session::{SessionKind, SessionValue};

/// Unique ID of a group.
pub type GroupId = Uuid;

/// Error type for group operations.
#[derive(Debug, Error)]
pub enum GroupError {
    /// Error generated when the group is full.
    #[error("group is full")]
    GroupFull,
}

/// Group is a collection of clients. It is the main unit of communication.
#[derive(Debug, Deserialize, Serialize)]
pub struct Group {
    /// Unique ID of the group.
    pub id: GroupId,
    /// Parameters of the group.
    pub params: Parameters,
    /// Sessions belonging to this group.
    #[serde(skip)]
    pub(crate) sessions: HashMap<SessionId, Session>,
    /// Clients that joined this group.
    #[serde(skip)]
    pub(crate) clients: HashSet<ClientId>,
}

impl Group {
    /// Creates a new group with the given parameters.
    pub fn new(id: GroupId, params: Parameters) -> Self {
        Self {
            id,
            params,
            sessions: HashMap::new(),
            clients: HashSet::new(),
        }
    }

    /// Adds a client to the group.
    #[cfg(feature = "server")]
    pub fn add_client(&mut self, client_id: ClientId) -> anyhow::Result<()> {
        let clients = self.clients.len();
        if clients >= self.params.n().into() {
            return Err(GroupError::GroupFull.into());
        }
        self.clients.insert(client_id);
        Ok(())
    }

    /// Removes a client from the group.
    #[cfg(feature = "server")]
    pub fn drop_client(&mut self, client_id: ClientId) {
        self.clients.remove(&client_id);
        // FIXME: delete from sessions too
    }

    /// Adds a new session and adds it to the group.
    #[cfg(feature = "server")]
    pub fn add_session(&mut self, kind: SessionKind, value: SessionValue) -> Session {
        let session_id = Uuid::new_v4();
        let session = Session::new(session_id, kind, value);
        let session_c = session.clone();
        self.sessions.insert(session_id, session);
        session_c
    }

    /// Returns a session by its ID, if it exists.
    #[cfg(feature = "server")]
    pub fn get_session(&self, session_id: &SessionId) -> Option<&Session> {
        self.sessions.get(session_id)
    }

    /// Returns a mutable session by its ID, if it exists.
    #[cfg(feature = "server")]
    pub fn get_session_mut(&mut self, session_id: &SessionId) -> Option<&mut Session> {
        self.sessions.get_mut(session_id)
    }

    /// Returns a boolean indicating if the group is empty.
    #[cfg(feature = "server")]
    pub fn is_empty(&self) -> bool {
        self.clients.len() == 0
    }

    /// Returns a boolean indicating if the group is full.
    #[cfg(feature = "server")]
    pub fn is_full(&self) -> bool {
        self.clients.len() == self.params.n() as usize
    }

    /// Returns the ID of the group.
    #[cfg(feature = "server")]
    pub fn id(&self) -> GroupId {
        self.id
    }

    /// Returns the client ids associated with the group.
    #[cfg(feature = "server")]
    pub fn clients(&self) -> &HashSet<ClientId> {
        &self.clients
    }
}

impl Clone for Group {
    /// Clones group parameters, disregarding sensitive information.
    ///
    /// Should be used only for logging purposes.
    fn clone(&self) -> Self {
        Self {
            id: self.id,
            params: self.params.clone(),
            sessions: HashMap::new(),
            clients: HashSet::new(),
        }
    }
}