Skip to main content

confium_coordinator/coordinator/
version_negotiation.rs

1//! Protocol version negotiation.
2
3use serde::{Deserialize, Serialize};
4
5/// Current protocol version.
6pub const PROTOCOL_VERSION: u32 = 1;
7
8/// Version negotiation request.
9#[derive(Debug, Clone, Serialize, Deserialize)]
10pub struct VersionHandshake {
11    pub client_version: u32,
12    pub min_supported: u32,
13}
14
15/// Version negotiation response.
16#[derive(Debug, Clone, Serialize, Deserialize)]
17pub struct VersionResponse {
18    pub server_version: u32,
19    pub accepted: bool,
20    pub negotiated_version: u32,
21    pub reason: Option<String>,
22}
23
24/// Check if a client version is compatible with this server.
25pub fn negotiate(client_handshake: &VersionHandshake) -> VersionResponse {
26    if client_handshake.client_version > PROTOCOL_VERSION {
27        if client_handshake.min_supported <= PROTOCOL_VERSION {
28            return VersionResponse {
29                server_version: PROTOCOL_VERSION,
30                accepted: true,
31                negotiated_version: PROTOCOL_VERSION,
32                reason: Some(format!(
33                    "downgraded from {} to {}",
34                    client_handshake.client_version, PROTOCOL_VERSION
35                )),
36            };
37        }
38        return VersionResponse {
39            server_version: PROTOCOL_VERSION,
40            accepted: false,
41            negotiated_version: 0,
42            reason: Some("client requires newer protocol".into()),
43        };
44    }
45    if client_handshake.client_version < 1 {
46        return VersionResponse {
47            server_version: PROTOCOL_VERSION,
48            accepted: false,
49            negotiated_version: 0,
50            reason: Some("invalid client version".into()),
51        };
52    }
53    VersionResponse {
54        server_version: PROTOCOL_VERSION,
55        accepted: true,
56        negotiated_version: client_handshake.client_version.min(PROTOCOL_VERSION),
57        reason: None,
58    }
59}
60
61#[cfg(test)]
62mod tests {
63    use super::*;
64
65    #[test]
66    fn matching_version_accepted() {
67        let hs = VersionHandshake {
68            client_version: 1,
69            min_supported: 1,
70        };
71        let resp = negotiate(&hs);
72        assert!(resp.accepted);
73        assert_eq!(resp.negotiated_version, 1);
74    }
75
76    #[test]
77    fn newer_client_downgrades() {
78        let hs = VersionHandshake {
79            client_version: 5,
80            min_supported: 1,
81        };
82        let resp = negotiate(&hs);
83        assert!(resp.accepted);
84        assert_eq!(resp.negotiated_version, PROTOCOL_VERSION);
85    }
86
87    #[test]
88    fn client_requires_newer_rejected() {
89        let hs = VersionHandshake {
90            client_version: 5,
91            min_supported: 5,
92        };
93        let resp = negotiate(&hs);
94        assert!(!resp.accepted);
95    }
96
97    #[test]
98    fn invalid_version_rejected() {
99        let hs = VersionHandshake {
100            client_version: 0,
101            min_supported: 0,
102        };
103        let resp = negotiate(&hs);
104        assert!(!resp.accepted);
105    }
106
107    #[test]
108    fn handshake_serializes() {
109        let hs = VersionHandshake {
110            client_version: 1,
111            min_supported: 1,
112        };
113        let json = serde_json::to_string(&hs).unwrap();
114        assert!(json.contains("client_version"));
115    }
116}