1use std::net::SocketAddrV4;
4
5use hiero_sdk_proto::services;
6
7use crate::protobuf::ToProtobuf;
8use crate::{
9 AccountId,
10 Error,
11 FromProtobuf,
12 ServiceEndpoint,
13};
14
15fn parse_socket_addr_v4(ip: Vec<u8>, port: i32) -> crate::Result<SocketAddrV4> {
16 let octets: Result<[u8; 4], _> = ip.try_into();
17 let octets = octets.map_err(|v| {
18 Error::from_protobuf(format!("expected 4 byte ip address, got `{}` bytes", v.len()))
19 })?;
20
21 let port = u16::try_from(port).map_err(|_| {
22 Error::from_protobuf(format!(
23 "expected 16 bit non-negative port number, but the port was actually `{port}`",
24 ))
25 })?;
26
27 Ok(SocketAddrV4::new(octets.into(), port))
28}
29
30#[derive(Debug, Clone)]
33pub struct NodeAddress {
34 pub node_id: u64,
36
37 pub rsa_public_key: Vec<u8>,
39
40 pub node_account_id: AccountId,
42
43 pub tls_certificate_hash: Vec<u8>,
51
52 pub service_endpoints: Vec<String>,
54
55 pub description: String,
57}
58
59impl FromProtobuf<services::NodeAddress> for NodeAddress {
60 fn from_protobuf(pb: services::NodeAddress) -> crate::Result<Self>
61 where
62 Self: Sized,
63 {
64 let mut addresses = Vec::with_capacity(pb.service_endpoint.len() + 1);
66
67 #[allow(deprecated)]
69 if !pb.ip_address.is_empty() {
70 let socket_addr = parse_socket_addr_v4(pb.ip_address, pb.portno)?;
71 addresses.push(format!("{}:{}", socket_addr.ip(), socket_addr.port()));
72 }
73
74 for address in pb.service_endpoint {
75 let endpoint = ServiceEndpoint::from_protobuf(address)?;
76 let host = match endpoint.ip_address_v4 {
77 Some(ip) => ip.to_string(),
78 None => endpoint.domain_name,
79 };
80 addresses.push(format!("{}:{}", host, endpoint.port));
81 }
82
83 let node_account_id = AccountId::from_protobuf(pb_getf!(pb, node_account_id)?)?;
84
85 Ok(Self {
86 description: pb.description,
87 rsa_public_key: hex::decode(pb.rsa_pub_key).map_err(Error::from_protobuf)?,
88 node_id: pb.node_id as u64,
89 service_endpoints: addresses,
90 tls_certificate_hash: pb.node_cert_hash,
91 node_account_id,
92 })
93 }
94}
95
96impl ToProtobuf for NodeAddress {
97 type Protobuf = services::NodeAddress;
98
99 fn to_protobuf(&self) -> Self::Protobuf {
100 let service_endpoint = self
101 .service_endpoints
102 .iter()
103 .map(|endpoint_str| {
104 let parts: Vec<&str> = endpoint_str.split(':').collect();
106 if parts.len() != 2 {
107 return services::ServiceEndpoint {
108 ip_address_v4: Vec::new(),
109 port: 50211,
110 domain_name: String::new(),
111 };
112 }
113
114 let host = parts[0];
115 let port = parts[1].parse::<i32>().unwrap_or(50211);
116
117 if let Ok(ip) = host.parse::<std::net::Ipv4Addr>() {
119 services::ServiceEndpoint {
120 ip_address_v4: ip.octets().to_vec(),
121 port,
122 domain_name: String::new(),
123 }
124 } else {
125 services::ServiceEndpoint {
126 ip_address_v4: Vec::new(),
127 port,
128 domain_name: host.to_string(),
129 }
130 }
131 })
132 .collect();
133
134 services::NodeAddress {
135 rsa_pub_key: hex::encode(&self.rsa_public_key),
136 node_id: self.node_id as i64,
137 node_account_id: Some(self.node_account_id.to_protobuf()),
138 node_cert_hash: self.tls_certificate_hash.clone(),
139 service_endpoint,
140 description: self.description.clone(),
141
142 ..Default::default()
144 }
145 }
146}
147
148#[cfg(test)]
149mod tests {
150 use super::*;
151
152 #[test]
153 fn test_node_address_with_ip_endpoints() {
154 let pb = services::NodeAddress {
155 node_id: 3,
156 rsa_pub_key: "746573745f6b6579".to_string(), node_account_id: Some(AccountId::new(0, 0, 3).to_protobuf()),
158 node_cert_hash: vec![1, 2, 3, 4],
159 service_endpoint: vec![
160 services::ServiceEndpoint {
161 ip_address_v4: vec![192, 168, 1, 1],
162 port: 50211,
163 domain_name: String::new(),
164 },
165 services::ServiceEndpoint {
166 ip_address_v4: vec![10, 0, 0, 1],
167 port: 50211,
168 domain_name: String::new(),
169 },
170 ],
171 description: "Test node".to_string(),
172 ..Default::default()
173 };
174
175 let node_address = NodeAddress::from_protobuf(pb).unwrap();
176 assert_eq!(node_address.node_id, 3);
177 assert_eq!(node_address.node_account_id, AccountId::new(0, 0, 3));
178 assert_eq!(node_address.service_endpoints, vec!["192.168.1.1:50211", "10.0.0.1:50211"]);
179 assert_eq!(node_address.description, "Test node");
180 }
181
182 #[test]
183 fn test_node_address_with_domain_endpoints() {
184 let pb = services::NodeAddress {
185 node_id: 4,
186 rsa_pub_key: "746573745f6b6579".to_string(), node_account_id: Some(AccountId::new(0, 0, 4).to_protobuf()),
188 node_cert_hash: vec![1, 2, 3, 4],
189 service_endpoint: vec![
190 services::ServiceEndpoint {
191 ip_address_v4: vec![],
192 port: 50211,
193 domain_name: "example.com".to_string(),
194 },
195 services::ServiceEndpoint {
196 ip_address_v4: vec![],
197 port: 50211,
198 domain_name: "localhost".to_string(),
199 },
200 ],
201 description: "Test node with domains".to_string(),
202 ..Default::default()
203 };
204
205 let node_address = NodeAddress::from_protobuf(pb).unwrap();
206 assert_eq!(node_address.node_id, 4);
207 assert_eq!(node_address.node_account_id, AccountId::new(0, 0, 4));
208 assert_eq!(node_address.service_endpoints, vec!["example.com:50211", "localhost:50211"]);
209 assert_eq!(node_address.description, "Test node with domains");
210 }
211
212 #[test]
213 fn test_node_address_with_mixed_endpoints() {
214 let pb = services::NodeAddress {
215 node_id: 5,
216 rsa_pub_key: "746573745f6b6579".to_string(), node_account_id: Some(AccountId::new(0, 0, 5).to_protobuf()),
218 node_cert_hash: vec![1, 2, 3, 4],
219 service_endpoint: vec![
220 services::ServiceEndpoint {
221 ip_address_v4: vec![192, 168, 1, 1],
222 port: 50211,
223 domain_name: String::new(),
224 },
225 services::ServiceEndpoint {
226 ip_address_v4: vec![],
227 port: 50211,
228 domain_name: "example.com".to_string(),
229 },
230 ],
231 description: "Test node with mixed endpoints".to_string(),
232 ..Default::default()
233 };
234
235 let node_address = NodeAddress::from_protobuf(pb).unwrap();
236 assert_eq!(node_address.node_id, 5);
237 assert_eq!(node_address.node_account_id, AccountId::new(0, 0, 5));
238 assert_eq!(node_address.service_endpoints, vec!["192.168.1.1:50211", "example.com:50211"]);
239 assert_eq!(node_address.description, "Test node with mixed endpoints");
240 }
241
242 #[test]
243 fn test_node_address_to_protobuf() {
244 let node_address = NodeAddress {
245 node_id: 7,
246 rsa_public_key: vec![1, 2, 3, 4],
247 node_account_id: AccountId::new(0, 0, 7),
248 tls_certificate_hash: vec![5, 6, 7, 8],
249 service_endpoints: vec![
250 "192.168.1.1:50211".to_string(),
251 "example.com:50211".to_string(),
252 ],
253 description: "Test node for to_protobuf".to_string(),
254 };
255
256 let pb = node_address.to_protobuf();
257 assert_eq!(pb.node_id, 7);
258 assert_eq!(pb.rsa_pub_key, "01020304");
259 assert_eq!(pb.node_cert_hash, vec![5, 6, 7, 8]);
260 assert_eq!(pb.description, "Test node for to_protobuf");
261 assert_eq!(pb.service_endpoint.len(), 2);
262
263 assert_eq!(pb.service_endpoint[0].ip_address_v4, vec![192, 168, 1, 1]);
265 assert_eq!(pb.service_endpoint[0].port, 50211);
266 assert_eq!(pb.service_endpoint[0].domain_name, "");
267
268 assert_eq!(pb.service_endpoint[1].ip_address_v4, vec![] as Vec<u8>);
270 assert_eq!(pb.service_endpoint[1].port, 50211);
271 assert_eq!(pb.service_endpoint[1].domain_name, "example.com");
272 }
273
274 #[test]
275 fn test_node_address_round_trip() {
276 let original = NodeAddress {
277 node_id: 8,
278 rsa_public_key: vec![1, 2, 3, 4],
279 node_account_id: AccountId::new(0, 0, 8),
280 tls_certificate_hash: vec![5, 6, 7, 8],
281 service_endpoints: vec![
282 "192.168.1.1:50211".to_string(),
283 "example.com:50211".to_string(),
284 "localhost:50211".to_string(),
285 ],
286 description: "Test node round trip".to_string(),
287 };
288
289 let pb = original.to_protobuf();
290 let deserialized = NodeAddress::from_protobuf(pb).unwrap();
291
292 assert_eq!(deserialized.node_id, original.node_id);
293 assert_eq!(deserialized.node_account_id, original.node_account_id);
294 assert_eq!(deserialized.service_endpoints, original.service_endpoints);
295 assert_eq!(deserialized.description, original.description);
296 }
297
298 #[test]
299 fn test_node_address_with_invalid_string_format() {
300 let node_address = NodeAddress {
301 node_id: 9,
302 rsa_public_key: vec![1, 2, 3, 4],
303 node_account_id: AccountId::new(0, 0, 9),
304 tls_certificate_hash: vec![5, 6, 7, 8],
305 service_endpoints: vec![
306 "invalid-format".to_string(), "192.168.1.1:invalid-port".to_string(), ],
309 description: "Test node with invalid strings".to_string(),
310 };
311
312 let pb = node_address.to_protobuf();
313 assert_eq!(pb.service_endpoint.len(), 2);
315 assert_eq!(pb.service_endpoint[0].port, 50211); assert_eq!(pb.service_endpoint[1].port, 50211); }
318
319 #[test]
320 fn test_node_address_with_localhost_and_127_0_0_1() {
321 let pb = services::NodeAddress {
322 node_id: 10,
323 rsa_pub_key: "746573745f6b6579".to_string(), node_account_id: Some(AccountId::new(0, 0, 10).to_protobuf()),
325 node_cert_hash: vec![1, 2, 3, 4],
326 service_endpoint: vec![
327 services::ServiceEndpoint {
328 ip_address_v4: vec![],
329 port: 50211,
330 domain_name: "localhost".to_string(),
331 },
332 services::ServiceEndpoint {
333 ip_address_v4: vec![127, 0, 0, 1],
334 port: 50211,
335 domain_name: String::new(),
336 },
337 ],
338 description: "Test node with localhost".to_string(),
339 ..Default::default()
340 };
341
342 let node_address = NodeAddress::from_protobuf(pb).unwrap();
343 assert_eq!(node_address.service_endpoints, vec!["localhost:50211", "127.0.0.1:50211"]);
344 }
345
346 #[test]
347 fn test_node_address_with_kubernetes_style_domain() {
348 let pb = services::NodeAddress {
349 node_id: 11,
350 rsa_pub_key: "746573745f6b6579".to_string(), node_account_id: Some(AccountId::new(0, 0, 11).to_protobuf()),
352 node_cert_hash: vec![1, 2, 3, 4],
353 service_endpoint: vec![services::ServiceEndpoint {
354 ip_address_v4: vec![],
355 port: 50211,
356 domain_name: "network-node1-svc.solo-e2e.svc.cluster.local".to_string(),
357 }],
358 description: "Test node with k8s domain".to_string(),
359 ..Default::default()
360 };
361
362 let node_address = NodeAddress::from_protobuf(pb).unwrap();
363 assert_eq!(
364 node_address.service_endpoints,
365 vec!["network-node1-svc.solo-e2e.svc.cluster.local:50211"]
366 );
367 }
368
369 #[test]
370 fn test_node_address_with_different_ports() {
371 let pb = services::NodeAddress {
372 node_id: 12,
373 rsa_pub_key: "746573745f6b6579".to_string(), node_account_id: Some(AccountId::new(0, 0, 12).to_protobuf()),
375 node_cert_hash: vec![1, 2, 3, 4],
376 service_endpoint: vec![
377 services::ServiceEndpoint {
378 ip_address_v4: vec![192, 168, 1, 1],
379 port: 50211,
380 domain_name: String::new(),
381 },
382 services::ServiceEndpoint {
383 ip_address_v4: vec![10, 0, 0, 1],
384 port: 50212,
385 domain_name: String::new(),
386 },
387 ],
388 description: "Test node with different ports".to_string(),
389 ..Default::default()
390 };
391
392 let node_address = NodeAddress::from_protobuf(pb).unwrap();
393 assert_eq!(node_address.service_endpoints, vec!["192.168.1.1:50211", "10.0.0.1:50212"]);
394 }
395}