nox_core/models/
handshake.rs1use bitflags::bitflags;
4use serde::{Deserialize, Serialize};
5
6pub const PROTOCOL_VERSION: u32 = 5;
12
13pub const MIN_SUPPORTED_VERSION: u32 = 5;
16
17bitflags! {
18 #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
19 pub struct Capabilities: u32 {
20 const RELAY = 0b0000_0001;
21 const EXIT_NODE = 0b0000_0010;
22 const STORAGE = 0b0000_0100;
23 const ALL = Self::RELAY.bits() | Self::EXIT_NODE.bits() | Self::STORAGE.bits();
24 }
25}
26
27impl Default for Capabilities {
28 fn default() -> Self {
29 Self::RELAY
30 }
31}
32
33#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
34pub struct Handshake {
35 pub version: u32,
36 pub capabilities: Capabilities,
37 pub routing_key: String,
39 #[serde(default)]
41 pub eth_address: Option<String>,
42 #[serde(default = "default_payload_version")]
43 pub payload_version_min: u8,
44 #[serde(default = "default_payload_version")]
45 pub payload_version_max: u8,
46 #[serde(default)]
49 pub identity_sig: Option<String>,
50}
51
52fn default_payload_version() -> u8 {
53 1
54}
55
56impl Handshake {
57 #[must_use]
58 pub fn new(routing_key: String, capabilities: Capabilities, eth_address: String) -> Self {
59 use crate::models::payloads::PAYLOAD_VERSION;
60 Self {
61 version: PROTOCOL_VERSION,
62 capabilities,
63 routing_key,
64 eth_address: Some(eth_address),
65 payload_version_min: PAYLOAD_VERSION,
66 payload_version_max: PAYLOAD_VERSION,
67 identity_sig: None,
68 }
69 }
70
71 #[must_use]
72 pub fn with_identity_sig(mut self, sig_hex: String) -> Self {
73 self.identity_sig = Some(sig_hex);
74 self
75 }
76
77 #[must_use]
78 pub fn is_compatible(&self) -> bool {
79 self.version >= MIN_SUPPORTED_VERSION && self.version <= PROTOCOL_VERSION
80 }
81
82 #[must_use]
83 pub fn has_capability(&self, cap: Capabilities) -> bool {
84 self.capabilities.contains(cap)
85 }
86}
87
88#[derive(Debug, Clone)]
89pub struct PeerInfo {
90 pub connected_at: std::time::Instant,
91 pub capabilities: Capabilities,
92 pub routing_key: String,
93 pub eth_address: Option<String>,
94 pub registry_verified: bool,
95}
96
97impl PeerInfo {
98 #[must_use]
99 pub fn from_handshake(handshake: &Handshake, registry_verified: bool) -> Self {
100 Self {
101 connected_at: std::time::Instant::now(),
102 capabilities: handshake.capabilities,
103 routing_key: handshake.routing_key.clone(),
104 eth_address: handshake.eth_address.clone(),
105 registry_verified,
106 }
107 }
108}
109
110#[cfg(test)]
111mod tests {
112 use super::*;
113
114 #[test]
115 fn test_handshake_creation() {
116 let hs = Handshake::new(
117 "routing_key_hex".to_string(),
118 Capabilities::RELAY | Capabilities::EXIT_NODE,
119 "0x1234567890abcdef1234567890abcdef12345678".to_string(),
120 );
121
122 assert_eq!(hs.version, PROTOCOL_VERSION);
123 assert!(hs.has_capability(Capabilities::RELAY));
124 assert!(hs.has_capability(Capabilities::EXIT_NODE));
125 assert!(!hs.has_capability(Capabilities::STORAGE));
126 assert_eq!(
127 hs.eth_address.as_deref(),
128 Some("0x1234567890abcdef1234567890abcdef12345678")
129 );
130 }
131
132 #[test]
133 fn test_version_compatibility() {
134 let hs = Handshake::new("key".to_string(), Capabilities::RELAY, "0xaddr".to_string());
135 assert!(hs.is_compatible());
136
137 let mut old_hs = hs.clone();
139 old_hs.version = 4;
140 assert!(!old_hs.is_compatible());
141
142 let mut current_hs = hs.clone();
144 current_hs.version = PROTOCOL_VERSION;
145 assert!(current_hs.is_compatible());
146
147 let mut future_hs = hs.clone();
149 future_hs.version = PROTOCOL_VERSION + 1;
150 assert!(!future_hs.is_compatible());
151 }
152
153 #[test]
154 fn test_capabilities_bitflags() {
155 let caps = Capabilities::RELAY | Capabilities::EXIT_NODE;
156 assert!(caps.contains(Capabilities::RELAY));
157 assert!(caps.contains(Capabilities::EXIT_NODE));
158 assert!(!caps.contains(Capabilities::STORAGE));
159 }
160
161 #[test]
162 fn test_serialization() {
163 let hs = Handshake::new(
164 "def456".to_string(),
165 Capabilities::ALL,
166 "0xabcdef".to_string(),
167 );
168
169 let json = serde_json::to_string(&hs).unwrap();
170 let parsed: Handshake = serde_json::from_str(&json).unwrap();
171
172 assert_eq!(hs, parsed);
173 }
174
175 #[test]
176 fn test_legacy_deserialization() {
177 let json = r#"{"version":2,"capabilities":"RELAY","routing_key":"abc123"}"#;
181 let parsed: Handshake = serde_json::from_str(json).unwrap();
182 assert_eq!(parsed.version, 2);
183 assert!(parsed.eth_address.is_none());
184 assert!(parsed.identity_sig.is_none());
185 assert!(!parsed.is_compatible()); let json =
188 r#"{"version":3,"capabilities":"RELAY","routing_key":"abc123","eth_address":"0xabc"}"#;
189 let parsed: Handshake = serde_json::from_str(json).unwrap();
190 assert_eq!(parsed.version, 3);
191 assert_eq!(parsed.eth_address.as_deref(), Some("0xabc"));
192 assert!(parsed.identity_sig.is_none());
193 assert!(!parsed.is_compatible()); }
195
196 #[test]
197 fn test_v5_handshake_has_identity_sig() {
198 let hs = Handshake::new("key".to_string(), Capabilities::RELAY, "0xaddr".to_string())
199 .with_identity_sig("deadbeef".to_string());
200 assert_eq!(hs.version, PROTOCOL_VERSION);
201 assert_eq!(hs.identity_sig.as_deref(), Some("deadbeef"));
202 assert!(hs.is_compatible());
203 }
204
205 #[test]
206 fn test_peer_info_from_handshake() {
207 let hs = Handshake::new(
208 "routing_key".to_string(),
209 Capabilities::RELAY,
210 "0xaddr".to_string(),
211 );
212 let info = PeerInfo::from_handshake(&hs, true);
213 assert!(info.registry_verified);
214 assert_eq!(info.eth_address.as_deref(), Some("0xaddr"));
215
216 let info_unverified = PeerInfo::from_handshake(&hs, false);
217 assert!(!info_unverified.registry_verified);
218 }
219}