mpc_manager/state/
session.rs

1//! Session state
2//!
3//! This module contains all the logic related to session management.
4
5use super::ClientId;
6use serde::{Deserialize, Serialize};
7use serde_json::Value;
8use std::collections::{HashMap, HashSet};
9use strum::EnumString;
10use thiserror::Error;
11use uuid::Uuid;
12
13/// Value associated to a session.
14pub type SessionValue = Option<Value>;
15/// Unique ID of a session.
16pub type SessionId = Uuid;
17/// Party number of a session.
18pub type SessionPartyNumber = u16;
19
20/// Error type for session operations.
21#[derive(Debug, Error)]
22pub enum SessionError {
23    #[error("party number `{0}` is already occupied by another party")]
24    PartyNumberAlreadyOccupied(SessionPartyNumber),
25    #[error("client `{0}` is already signed up")]
26    ClientAlreadySignedUp(ClientId),
27}
28
29/// Session kinds available in this implementation.
30#[derive(Debug, Clone, Copy, Deserialize, Serialize, EnumString)]
31pub enum SessionKind {
32    /// Key generation session.
33    #[serde(rename = "keygen")]
34    #[strum(serialize = "keygen")]
35    Keygen,
36    /// Signing session.
37    #[serde(rename = "sign")]
38    #[strum(serialize = "sign")]
39    Sign,
40}
41
42/// Session is subgroup of clients intended to be used for a specific purpose.
43#[derive(Debug, Deserialize, Serialize)]
44pub struct Session {
45    /// Unique ID of the session.
46    pub id: SessionId,
47    /// Session kind
48    pub kind: SessionKind,
49    /// Public value associated to this session.
50    ///
51    /// This value can be set at the moment of creation.
52    /// It can be a message or transaction intended
53    /// to be signed by the session.
54    pub value: SessionValue,
55    /// Map party number to client id, starting at 1.
56    #[serde(skip)]
57    pub party_signups: HashMap<SessionPartyNumber, ClientId>,
58    /// Occupied party numbers, starting at 1.
59    #[serde(skip)]
60    pub occupied_party_numbers: Vec<SessionPartyNumber>,
61    ///
62    /// Party numbers of finished clients
63    #[serde(skip)]
64    pub finished: HashSet<u16>,
65}
66
67impl Session {
68    /// Creates a new session with the given parameters.
69    pub fn new(id: Uuid, kind: SessionKind, value: SessionValue) -> Self {
70        Self {
71            id,
72            kind,
73            value,
74            party_signups: HashMap::new(),
75            occupied_party_numbers: Vec::new(),
76            finished: HashSet::new(),
77        }
78    }
79
80    /// Registers a client in the session and returns its party number.
81    #[cfg(feature = "server")]
82    pub fn signup(&mut self, client_id: ClientId) -> anyhow::Result<SessionPartyNumber> {
83        if self.is_client_in_session(&client_id) {
84            return Err(SessionError::ClientAlreadySignedUp(client_id).into());
85        }
86        let party_number = self.get_next_party_number();
87        self.add_party(client_id, party_number);
88        Ok(party_number)
89    }
90
91    /// Signs in a client in the session with a given party number.
92    #[cfg(feature = "server")]
93    pub fn login(
94        &mut self,
95        client_id: ClientId,
96        party_number: SessionPartyNumber,
97    ) -> anyhow::Result<()> {
98        if self.is_client_in_session(&client_id) {
99            return Ok(()); //TODO: think of a better way to handle this (should we return an error?)
100        }
101        if self.occupied_party_numbers.contains(&party_number) {
102            return Err(SessionError::PartyNumberAlreadyOccupied(party_number).into());
103        }
104        self.add_party(client_id, party_number);
105        Ok(())
106    }
107
108    /// Adds new party assuming `party_number` doesn't exist already.
109    #[cfg(feature = "server")]
110    fn add_party(&mut self, client_id: ClientId, party_number: SessionPartyNumber) {
111        self.occupied_party_numbers.push(party_number);
112        self.occupied_party_numbers.sort();
113        self.party_signups.insert(party_number, client_id);
114    }
115
116    /// Gets the party number of a client.
117    #[cfg(feature = "server")]
118    pub fn get_party_number(&self, client_id: &ClientId) -> Option<SessionPartyNumber> {
119        self.party_signups
120            .iter()
121            .find(|(_, id)| id == &client_id)
122            .map(|(party, _)| *party)
123    }
124
125    /// Returns boolean indicating if the client is already in this session.
126    #[cfg(feature = "server")]
127    pub fn is_client_in_session(&self, client_id: &ClientId) -> bool {
128        self.party_signups.values().any(|id| id == client_id)
129    }
130
131    /// Returns the client id of a given party number.
132    #[cfg(feature = "server")]
133    pub fn get_client_id(&self, party_number: SessionPartyNumber) -> Option<ClientId> {
134        self.party_signups
135            .iter()
136            .find(|(&pn, _)| pn == party_number)
137            .map(|(_, id)| *id)
138    }
139
140    /// Returns all the client ids associated with the session.
141    #[cfg(feature = "server")]
142    pub fn get_all_client_ids(&self) -> Vec<ClientId> {
143        self.party_signups.values().copied().collect()
144    }
145
146    /// Returns the number of clients associated with this session.
147    #[cfg(feature = "server")]
148    pub fn get_number_of_clients(&self) -> usize {
149        self.party_signups.len()
150    }
151
152    /// Gets the next missing party number, assuming `occupied_party_numbers`
153    /// is a sorted array.
154    ///
155    /// # Examples
156    ///
157    /// - if `[1,2,3,4]` it will return 5
158    /// - if `[1,4,5,6]` it will return 2
159    #[cfg(feature = "server")]
160    fn get_next_party_number(&self) -> SessionPartyNumber {
161        for (i, party) in self.occupied_party_numbers.iter().enumerate() {
162            if (i + 1) != *party as usize {
163                return (i + 1) as SessionPartyNumber;
164            }
165        }
166
167        match self.occupied_party_numbers.last() {
168            Some(party) => party + 1,
169            None => 1,
170        }
171    }
172}
173
174impl Clone for Session {
175    /// Clones session parameters, disregarding sensitive information.
176    ///
177    /// Should be used only for logging purposes.
178    fn clone(&self) -> Self {
179        Self {
180            id: self.id,
181            kind: self.kind,
182            value: self.value.clone(),
183            party_signups: HashMap::new(),
184            occupied_party_numbers: Vec::new(),
185            finished: HashSet::new(),
186        }
187    }
188}