confium_coordinator/coordinator/
net_server.rs1use std::io;
7use std::net::TcpListener;
8use std::net::TcpStream;
9
10pub trait SessionIo: std::io::Read + std::io::Write + Send {
13 fn set_read_timeout(&mut self, _d: Option<std::time::Duration>) -> bool {
22 false
23 }
24
25 fn set_nonblocking(&mut self, _on: bool) -> bool {
28 false
29 }
30}
31
32impl SessionIo for TcpStream {
33 fn set_read_timeout(&mut self, d: Option<std::time::Duration>) -> bool {
34 std::net::TcpStream::set_read_timeout(self, d).is_ok()
35 }
36
37 fn set_nonblocking(&mut self, on: bool) -> bool {
38 std::net::TcpStream::set_nonblocking(self, on).is_ok()
39 }
40}
41
42impl SessionIo for confium_net::io::TransportIo {}
43use std::sync::{Arc, Mutex};
44use std::thread;
45
46use crate::coordinator::coordinator::Coordinator;
47use crate::coordinator::net::{ProtocolMessage, recv_message, send_message};
48use crate::coordinator::session::{Commitment, Share};
49use chrono::Utc;
50
51pub type SharedCoordinator = Arc<Mutex<Coordinator>>;
53
54pub struct CoordinatorServer {
56 addr: String,
57 coordinator: SharedCoordinator,
58 start_time: std::time::Instant,
59}
60
61impl CoordinatorServer {
62 pub fn new(addr: &str) -> Self {
64 Self {
65 addr: addr.to_string(),
66 coordinator: Arc::new(Mutex::new(Coordinator::new())),
67 start_time: std::time::Instant::now(),
68 }
69 }
70
71 pub fn shared_coordinator(&self) -> SharedCoordinator {
73 Arc::clone(&self.coordinator)
74 }
75
76 pub fn start(&self) -> io::Result<String> {
78 let listener = TcpListener::bind(&self.addr)?;
79 let bound_addr = listener.local_addr()?.to_string();
80 let coordinator = Arc::clone(&self.coordinator);
81 let start_time = self.start_time;
82
83 thread::spawn(move || {
84 for stream_result in listener.incoming() {
85 match stream_result {
86 Ok(stream) => {
87 let coord = Arc::clone(&coordinator);
88 thread::spawn(move || {
89 let _ = handle_connection(
90 Box::new(stream) as Box<dyn SessionIo>,
91 coord,
92 start_time,
93 );
94 });
95 }
96 Err(e) => {
97 eprintln!("Coordinator: accept error: {e}");
98 }
99 }
100 }
101 });
102
103 Ok(bound_addr)
104 }
105
106 pub fn start_url(&self, url: &str) -> io::Result<String> {
111 let mut listener =
112 confium_net::listen(url).map_err(|e| io::Error::other(format!("listen {url}: {e}")))?;
113 let coordinator = Arc::clone(&self.coordinator);
114 let start_time = self.start_time;
115
116 thread::spawn(move || {
117 loop {
118 match listener.accept() {
119 Ok(transport) => {
120 let coord = Arc::clone(&coordinator);
121 thread::spawn(move || {
122 let session = confium_net::io::TransportIo::new(transport);
123 let _ = handle_connection(
124 Box::new(session) as Box<dyn SessionIo>,
125 coord,
126 start_time,
127 );
128 });
129 }
130 Err(e) => {
131 eprintln!("Coordinator: accept error: {e}");
132 }
133 }
134 }
135 });
136
137 Ok(url.to_string())
138 }
139}
140
141fn handle_connection(
142 mut stream: Box<dyn SessionIo>,
143 coordinator: SharedCoordinator,
144 start_time: std::time::Instant,
145) -> io::Result<()> {
146 loop {
147 let msg = match recv_message(&mut stream) {
148 Ok(m) => m,
149 Err(ref e) if e.kind() == io::ErrorKind::UnexpectedEof => break,
150 Err(_) => break,
151 };
152
153 let response = process_message(msg, &coordinator, start_time);
154 if let Some(resp) = response {
155 if send_message(&mut stream, &resp).is_err() {
156 break;
157 }
158 }
159 }
160 Ok(())
161}
162
163fn process_message(
164 msg: ProtocolMessage,
165 coordinator: &SharedCoordinator,
166 start_time: std::time::Instant,
167) -> Option<ProtocolMessage> {
168 match msg {
169 ProtocolMessage::Register {
170 signer_id,
171 quorum_id: _,
172 } => Some(ProtocolMessage::Registered {
173 signer_id: signer_id.clone(),
174 }),
175
176 ProtocolMessage::CreateSession {
177 quorum_id,
178 scheme,
179 message,
180 threshold,
181 num_parties,
182 } => {
183 let mut coord = coordinator.lock().unwrap();
184 let request = crate::coordinator::session::SessionRequest {
185 quorum_id,
186 scheme,
187 message,
188 threshold,
189 num_parties,
190 unlock_window_minutes: 240,
191 requested_by: "tcp-client".into(),
192 };
193 match coord.create_session(request) {
194 Ok(session_id) => Some(ProtocolMessage::SessionCreated { session_id }),
195 Err(e) => Some(ProtocolMessage::Error {
196 message: format!("{e:?}"),
197 }),
198 }
199 }
200
201 ProtocolMessage::Commitment {
202 session_id,
203 signer_id,
204 bytes,
205 signature,
206 } => {
207 let mut coord = coordinator.lock().unwrap();
208 let commitment = Commitment {
209 signer_id: signer_id.clone(),
210 bytes,
211 signer_signature: signature,
212 submitted_at: Utc::now(),
213 };
214 match coord.submit_commitment(&session_id, commitment) {
215 Ok(()) => Some(ProtocolMessage::Ack { session_id }),
216 Err(e) => Some(ProtocolMessage::Error {
217 message: format!("{e:?}"),
218 }),
219 }
220 }
221
222 ProtocolMessage::Share {
223 session_id,
224 signer_id,
225 bytes,
226 signature,
227 } => {
228 let mut coord = coordinator.lock().unwrap();
229 let share = Share {
230 signer_id: signer_id.clone(),
231 bytes,
232 signer_signature: signature,
233 submitted_at: Utc::now(),
234 };
235 match coord.submit_share(&session_id, share) {
236 Ok(()) => {
237 let threshold = coord.session_threshold(&session_id).unwrap_or(0);
238 let share_count = coord.session_share_count(&session_id).unwrap_or(0);
239 if share_count >= threshold as usize {
240 match coord.aggregate(&session_id) {
241 Ok(sig) => Some(ProtocolMessage::Signature {
242 session_id: session_id.clone(),
243 bytes: sig.bytes,
244 algorithm: sig.algorithm,
245 contributing_signers: sig.contributing_signers,
246 }),
247 Err(e) => Some(ProtocolMessage::Error {
248 message: format!("{e:?}"),
249 }),
250 }
251 } else {
252 Some(ProtocolMessage::Ack { session_id })
253 }
254 }
255 Err(e) => Some(ProtocolMessage::Error {
256 message: format!("{e:?}"),
257 }),
258 }
259 }
260
261 ProtocolMessage::GetStatus { session_id } => {
262 let coord = coordinator.lock().unwrap();
263 match session_id {
264 Some(sid) => {
265 let state = coord.session_state(&sid);
266 Some(ProtocolMessage::Status {
267 session_id: sid,
268 state: format!("{:?}", state),
269 })
270 }
271 None => Some(ProtocolMessage::Error {
272 message: "session_id required".into(),
273 }),
274 }
275 }
276
277 ProtocolMessage::HealthCheck => {
278 let coord = coordinator.lock().unwrap();
279 let session_count = coord.session_count();
280 Some(ProtocolMessage::HealthStatus {
281 alive: true,
282 ready: true,
283 session_count,
284 uptime_seconds: start_time.elapsed().as_secs(),
285 })
286 }
287
288 _ => Some(ProtocolMessage::Error {
289 message: "unexpected message type".into(),
290 }),
291 }
292}
293
294#[cfg(test)]
295mod tests {
296 use super::*;
297
298 #[test]
299 fn health_check_returns_status() {
300 let server = CoordinatorServer::new("127.0.0.1:0");
301 let coordinator = server.shared_coordinator();
302 let start_time = server.start_time;
303
304 let response = process_message(ProtocolMessage::HealthCheck, &coordinator, start_time);
305 match response {
306 Some(ProtocolMessage::HealthStatus {
307 alive,
308 ready,
309 session_count,
310 uptime_seconds,
311 }) => {
312 assert!(alive);
313 assert!(ready);
314 assert_eq!(session_count, 0);
315 assert!(uptime_seconds < 5);
316 }
317 _ => panic!("expected HealthStatus"),
318 }
319 }
320
321 #[test]
322 fn health_check_after_session_increments_count() {
323 let server = CoordinatorServer::new("127.0.0.1:0");
324 let coordinator = server.shared_coordinator();
325 let start_time = server.start_time;
326
327 let req = crate::coordinator::session::SessionRequest {
328 quorum_id: "q1".into(),
329 scheme: "CMP20".into(),
330 message: vec![0; 32],
331 threshold: 2,
332 num_parties: 3,
333 unlock_window_minutes: 60,
334 requested_by: "test".into(),
335 };
336 coordinator.lock().unwrap().create_session(req).unwrap();
337
338 let response = process_message(ProtocolMessage::HealthCheck, &coordinator, start_time);
339 match response {
340 Some(ProtocolMessage::HealthStatus { session_count, .. }) => {
341 assert_eq!(session_count, 1);
342 }
343 _ => panic!("expected HealthStatus"),
344 }
345 }
346}