1use std::collections::HashSet;
2use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr};
3use std::sync::Arc;
4use std::time::Duration;
5
6use dns_lookup::lookup_host;
7use get_if_addrs::{IfAddr, get_if_addrs};
8use socket2::{Domain, Protocol, Socket, Type};
9use tokio::io::AsyncWriteExt;
10use tokio::net::UdpSocket;
11use tracing::debug;
12
13use crate::auth::{default_authnz_host, default_authnz_user};
14use crate::transport::read_packet;
15use crate::types::{PvGetError, PvGetOptions};
16use spvirit_codec::epics_decode::{PvaPacket, PvaPacketCommand};
17use spvirit_codec::spvirit_encode::{
18 encode_client_connection_validation, encode_search_request, ip_to_bytes,
19 socket_addr_from_pva_bytes,
20};
21
22#[derive(Clone, Copy, Debug)]
23pub struct SearchTarget {
24 pub target: IpAddr,
25 pub bind: IpAddr,
26}
27
28#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
29pub struct DiscoveredServer {
30 pub guid: [u8; 12],
31 pub tcp_addr: SocketAddr,
32}
33
34pub fn parse_addr_list(env: &str) -> Vec<IpAddr> {
35 env.split(|c| c == ',' || c == ' ' || c == '\t')
36 .filter(|s| !s.trim().is_empty())
37 .filter_map(|s| parse_search_target_ip(s.trim()))
38 .collect()
39}
40
41fn parse_search_target_ip(token: &str) -> Option<IpAddr> {
42 if token.is_empty() {
43 return None;
44 }
45
46 if let Ok(ip) = token.parse::<IpAddr>() {
47 return Some(ip);
48 }
49 if let Ok(sock) = token.parse::<SocketAddr>() {
50 return Some(sock.ip());
51 }
52
53 if let Some((host, port_str)) = token.rsplit_once(':') {
56 if !host.is_empty()
57 && !port_str.is_empty()
58 && port_str.chars().all(|c| c.is_ascii_digit())
59 && !host.contains(']')
60 {
61 if let Ok(ip) = host.parse::<IpAddr>() {
62 return Some(ip);
63 }
64 if let Ok(addrs) = lookup_host(host) {
65 let addrs: Vec<IpAddr> = addrs.collect();
67 if let Some(ip) = addrs
68 .iter()
69 .find(|ip| ip.is_ipv4())
70 .copied()
71 .or_else(|| addrs.into_iter().next())
72 {
73 return Some(ip);
74 }
75 }
76 }
77 }
78
79 if let Ok(addrs) = lookup_host(token) {
80 let addrs: Vec<IpAddr> = addrs.collect();
82 if let Some(ip) = addrs
83 .iter()
84 .find(|ip| ip.is_ipv4())
85 .copied()
86 .or_else(|| addrs.into_iter().next())
87 {
88 return Some(ip);
89 }
90 }
91
92 None
93}
94
95fn unspecified_for(ip: IpAddr) -> IpAddr {
97 match ip {
98 IpAddr::V4(_) => IpAddr::V4(Ipv4Addr::UNSPECIFIED),
99 IpAddr::V6(_) => IpAddr::V6(Ipv6Addr::UNSPECIFIED),
100 }
101}
102
103pub fn build_search_targets(
104 search_addr: Option<IpAddr>,
105 bind_addr: Option<IpAddr>,
106) -> Vec<SearchTarget> {
107 if let Some(ip) = search_addr {
109 return vec![SearchTarget {
110 target: ip,
111 bind: bind_addr.unwrap_or_else(|| unspecified_for(ip)),
112 }];
113 }
114
115 let mut targets = Vec::new();
116 let mut seen = HashSet::new();
117
118 if let Ok(env) = std::env::var("EPICS_PVA_ADDR_LIST") {
120 for ip in parse_addr_list(&env) {
121 if seen.insert(ip) {
122 targets.push(SearchTarget {
123 target: ip,
124 bind: bind_addr.unwrap_or_else(|| unspecified_for(ip)),
125 });
126 }
127 }
128 }
129
130 if is_auto_addr_list_enabled() {
133 for t in build_auto_broadcast_targets() {
134 if seen.insert(t.target) {
135 targets.push(SearchTarget {
136 target: t.target,
137 bind: bind_addr.unwrap_or(t.bind),
138 });
139 }
140 }
141 }
142
143 targets
144}
145
146pub fn is_auto_addr_list_enabled() -> bool {
147 match std::env::var("EPICS_PVA_AUTO_ADDR_LIST") {
148 Ok(v) => {
149 let v = v.trim().to_ascii_uppercase();
150 v == "YES" || v == "Y" || v == "1" || v == "TRUE"
151 }
152 Err(_) => true,
153 }
154}
155
156fn ipv4_is_link_local(ip: Ipv4Addr) -> bool {
157 let octets = ip.octets();
158 octets[0] == 169 && octets[1] == 254
159}
160
161fn choose_default_bind_v4() -> Option<Ipv4Addr> {
162 let ifaces = get_if_addrs().ok()?;
163 for iface in ifaces {
164 if let IfAddr::V4(v4) = iface.addr {
165 let ip = v4.ip;
166 if ip.is_loopback() || ipv4_is_link_local(ip) {
167 continue;
168 }
169 return Some(ip);
170 }
171 }
172 None
173}
174
175fn choose_default_bind_v6() -> Option<Ipv6Addr> {
176 let ifaces = get_if_addrs().ok()?;
177 for iface in ifaces {
178 if let IfAddr::V6(v6) = iface.addr {
179 let ip = v6.ip;
180 if ip.is_loopback() {
181 continue;
182 }
183 let segs = ip.segments();
185 if segs[0] & 0xffc0 == 0xfe80 {
186 continue;
187 }
188 return Some(ip);
189 }
190 }
191 None
192}
193
194fn broadcast_for(ip: Ipv4Addr, netmask: Ipv4Addr) -> Ipv4Addr {
195 let ip_u = u32::from(ip);
196 let mask_u = u32::from(netmask);
197 Ipv4Addr::from(ip_u | !mask_u)
198}
199
200fn discovery_target_for(ip: Ipv4Addr, netmask: Ipv4Addr) -> Ipv4Addr {
201 let limited_broadcast = Ipv4Addr::new(255, 255, 255, 255);
202 if netmask == Ipv4Addr::new(255, 255, 255, 255) || netmask.is_unspecified() {
203 return limited_broadcast;
204 }
205 let directed = broadcast_for(ip, netmask);
206 if directed == ip {
207 limited_broadcast
208 } else {
209 directed
210 }
211}
212
213pub fn build_auto_broadcast_targets() -> Vec<SearchTarget> {
214 let mut targets = Vec::new();
215 let mut fallback_targets = Vec::new();
216 let mut fallback_seen = HashSet::new();
217 let mut added_v4_multicast = false;
218 let mut added_v6_multicast = false;
219 let ifaces = match get_if_addrs() {
220 Ok(v) => v,
221 Err(_) => return targets,
222 };
223 for iface in &ifaces {
224 if let IfAddr::V4(v4) = &iface.addr {
225 let ip = v4.ip;
226 if ip.is_loopback() || ipv4_is_link_local(ip) {
227 continue;
228 }
229 let bcast = discovery_target_for(ip, v4.netmask);
230 targets.push(SearchTarget {
231 target: IpAddr::V4(bcast),
232 bind: IpAddr::V4(ip),
233 });
234 targets.push(SearchTarget {
237 target: IpAddr::V4(PVA_MULTICAST_V4),
238 bind: IpAddr::V4(ip),
239 });
240 if fallback_seen.insert(IpAddr::V4(bcast)) {
241 fallback_targets.push(SearchTarget {
242 target: IpAddr::V4(bcast),
243 bind: IpAddr::V4(Ipv4Addr::UNSPECIFIED),
244 });
245 }
246 if !added_v4_multicast {
247 added_v4_multicast = true;
248 fallback_targets.push(SearchTarget {
249 target: IpAddr::V4(PVA_MULTICAST_V4),
250 bind: IpAddr::V4(Ipv4Addr::UNSPECIFIED),
251 });
252 }
253 }
254 }
255 for iface in &ifaces {
257 if let IfAddr::V6(v6) = &iface.addr {
258 let ip = v6.ip;
259 if ip.is_loopback() {
260 continue;
261 }
262 let segs = ip.segments();
263 if segs[0] & 0xffc0 == 0xfe80 {
264 continue; }
266 let multicast_target = IpAddr::V6(PVA_MULTICAST_V6);
267 targets.push(SearchTarget {
268 target: multicast_target,
269 bind: IpAddr::V6(ip),
270 });
271 if !added_v6_multicast {
272 added_v6_multicast = true;
273 fallback_targets.push(SearchTarget {
274 target: multicast_target,
275 bind: IpAddr::V6(Ipv6Addr::UNSPECIFIED),
276 });
277 }
278 }
279 }
280 targets.extend(fallback_targets);
281 targets
282}
283
284const PVA_MULTICAST_V4: Ipv4Addr = Ipv4Addr::new(224, 0, 0, 128);
286
287const PVA_MULTICAST_V6: Ipv6Addr = Ipv6Addr::new(0xff02, 0, 0, 0, 0, 0, 0x42, 1);
289
290fn join_multicast_any(socket: &std::net::UdpSocket, bind: IpAddr) {
292 match bind {
293 IpAddr::V4(iface) => {
294 let _ = socket.join_multicast_v4(&PVA_MULTICAST_V4, &iface);
295 }
296 IpAddr::V6(_) => {
297 let _ = socket.join_multicast_v6(&PVA_MULTICAST_V6, 0);
299 }
300 }
301}
302
303fn decode_search_response_addr(addr: [u8; 16], port: u16, src: SocketAddr) -> SocketAddr {
304 socket_addr_from_pva_bytes(addr, port)
305 .filter(|a| !a.ip().is_unspecified())
306 .unwrap_or_else(|| SocketAddr::new(src.ip(), port))
307}
308
309fn normalize_discovered_servers(items: Vec<DiscoveredServer>) -> Vec<DiscoveredServer> {
310 let mut seen = HashSet::new();
311 let mut out = Vec::new();
312 for item in items {
313 if seen.insert((item.guid, item.tcp_addr)) {
314 out.push(item);
315 }
316 }
317 out.sort_by(|a, b| a.tcp_addr.to_string().cmp(&b.tcp_addr.to_string()));
318 out
319}
320
321fn bind_udp_reuse(addr: SocketAddr) -> std::io::Result<std::net::UdpSocket> {
328 let domain = if addr.is_ipv4() {
329 Domain::IPV4
330 } else {
331 Domain::IPV6
332 };
333 let sock = Socket::new(domain, Type::DGRAM, Some(Protocol::UDP))?;
334 #[cfg(unix)]
335 sock.set_reuse_address(true)?;
336 sock.set_nonblocking(true)?;
337 sock.bind(&addr.into())?;
338 Ok(sock.into())
339}
340
341pub async fn search_pv(
342 pv_name: &str,
343 udp_port: u16,
344 timeout_dur: Duration,
345 targets: &[SearchTarget],
346 debug_enabled: bool,
347) -> Result<SocketAddr, PvGetError> {
348 if targets.is_empty() {
349 return Err(PvGetError::Search("no search targets"));
350 }
351
352 let now = std::time::SystemTime::now()
353 .duration_since(std::time::UNIX_EPOCH)
354 .unwrap_or_default();
355 let seq = (now.as_nanos() as u32).wrapping_add(std::process::id());
356 let cid = seq ^ 0x9E37_79B9;
357
358 let mut last_io_error: Option<std::io::Error> = None;
359 let deadline = tokio::time::Instant::now() + timeout_dur;
360
361 let mut bind_groups: Vec<(IpAddr, Vec<IpAddr>)> = Vec::new();
363 for t in targets {
364 if let Some(group) = bind_groups.iter_mut().find(|(b, _)| *b == t.bind) {
365 group.1.push(t.target);
366 } else {
367 bind_groups.push((t.bind, vec![t.target]));
368 }
369 }
370
371 let mut socket_info: Vec<(Arc<UdpSocket>, Vec<u8>, Vec<SocketAddr>)> = Vec::new();
374
375 for (bind_ip, group_targets) in &bind_groups {
376 let bind_addr = SocketAddr::new(*bind_ip, 0);
382 let (std_sock, actual_bind_addr) = match bind_udp_reuse(bind_addr) {
383 Ok(sock) => {
384 let actual = sock.local_addr().unwrap_or(bind_addr);
385 (sock, actual)
386 }
387 Err(err) => {
388 if debug_enabled {
389 debug!(
390 "pva search skipping bind={} step=bind kind={:?} err={}",
391 bind_addr,
392 err.kind(),
393 err
394 );
395 }
396 last_io_error = Some(err);
397 continue;
398 }
399 };
400 if let Err(err) = std_sock.set_broadcast(true) {
401 if debug_enabled {
402 debug!(
403 "pva search skipping bind={} step=set_broadcast kind={:?} err={}",
404 bind_addr,
405 err.kind(),
406 err
407 );
408 }
409 last_io_error = Some(err);
410 continue;
411 }
412
413 join_multicast_any(&std_sock, *bind_ip);
414
415 let reply_addr = ip_to_bytes(*bind_ip);
416 let reply_port = match std_sock.local_addr() {
417 Ok(addr) => addr.port(),
418 Err(err) => {
419 if debug_enabled {
420 debug!(
421 "pva search skipping bind={} step=local_addr kind={:?} err={}",
422 bind_addr,
423 err.kind(),
424 err
425 );
426 }
427 last_io_error = Some(err);
428 continue;
429 }
430 };
431 let requests = [(cid, pv_name)];
432 let msg = encode_search_request(seq, 0x81, reply_port, reply_addr, &requests, 2, false);
433
434 let socket = match UdpSocket::from_std(std_sock) {
435 Ok(socket) => socket,
436 Err(err) => {
437 if debug_enabled {
438 debug!(
439 "pva search skipping bind={} step=from_std kind={:?} err={}",
440 bind_addr,
441 err.kind(),
442 err
443 );
444 }
445 last_io_error = Some(err);
446 continue;
447 }
448 };
449
450 let dests: Vec<SocketAddr> = group_targets
451 .iter()
452 .map(|ip| SocketAddr::new(*ip, udp_port))
453 .collect();
454
455 for dest in &dests {
457 if debug_enabled {
458 debug!(
459 "pva search bind={} target={} server_port={} reply_port={}",
460 actual_bind_addr,
461 dest.ip(),
462 udp_port,
463 reply_port
464 );
465 debug!("pva search seq={} cid={}", seq, cid);
466 debug!("pva search send {} bytes to {}", msg.len(), dest);
467 }
468 if let Err(err) = socket.send_to(&msg, dest).await {
469 if debug_enabled {
470 debug!(
471 "pva search send_to target={} kind={:?} err={}",
472 dest,
473 err.kind(),
474 err
475 );
476 }
477 last_io_error = Some(err);
478 }
479 }
480
481 socket_info.push((Arc::new(socket), msg, dests));
482 }
483
484 if socket_info.is_empty() {
485 if let Some(err) = last_io_error {
486 return Err(PvGetError::Io(err));
487 }
488 return Err(PvGetError::Timeout("search response"));
489 }
490
491 let (tx, mut rx) = tokio::sync::mpsc::channel::<(Vec<u8>, SocketAddr)>(64);
493 for (sock, _, _) in &socket_info {
494 let sock = Arc::clone(sock);
495 let tx = tx.clone();
496 tokio::spawn(async move {
497 loop {
498 let mut buf = vec![0u8; 2048];
499 match sock.recv_from(&mut buf).await {
500 Ok((len, src)) => {
501 buf.truncate(len);
502 if tx.send((buf, src)).await.is_err() {
503 break;
504 }
505 }
506 Err(_) => break,
507 }
508 }
509 });
510 }
511 drop(tx); let retransmit_offsets = [100u64, 500, 1000, 2000];
515 let start = tokio::time::Instant::now();
516 let mut next_retransmit = 0usize;
517
518 loop {
519 let next_retransmit_at = if next_retransmit < retransmit_offsets.len() {
521 start + Duration::from_millis(retransmit_offsets[next_retransmit])
522 } else {
523 deadline
524 };
525 let wake_at = next_retransmit_at.min(deadline);
526
527 tokio::select! {
528 recv = rx.recv() => {
529 let Some((buf, src)) = recv else { break };
530 let mut pkt = PvaPacket::new(&buf);
531 let cmd = pkt
532 .decode_payload()
533 .ok_or(PvGetError::Search("failed to decode search response"))?;
534 if let PvaPacketCommand::SearchResponse(payload) = cmd {
535 if debug_enabled {
536 debug!(
537 "pva search response found={} cids={:?} addr={:?} port={}",
538 payload.found, payload.cids, payload.addr, payload.port
539 );
540 }
541 if payload.seq != seq {
542 continue;
543 }
544 if !payload.protocol.is_empty() && !payload.protocol.eq_ignore_ascii_case("tcp") {
545 continue;
546 }
547 if !payload.found {
548 continue;
549 }
550 if !payload.cids.is_empty() && !payload.cids.contains(&cid) {
551 continue;
552 }
553
554 let addr = decode_search_response_addr(payload.addr, payload.port, src);
555 if debug_enabled {
556 debug!("pva search response from {}", addr);
557 }
558 return Ok(addr);
559 }
560 }
561 _ = tokio::time::sleep_until(wake_at) => {
562 if tokio::time::Instant::now() >= deadline {
563 break;
564 }
565 if next_retransmit < retransmit_offsets.len() {
567 if debug_enabled {
568 debug!("pva search retransmit round {}", next_retransmit + 1);
569 }
570 for (sock, msg, dests) in &socket_info {
571 for dest in dests {
572 let _ = sock.send_to(msg, dest).await;
573 }
574 }
575 next_retransmit += 1;
576 }
577 }
578 }
579 }
580
581 Err(PvGetError::Timeout("search response"))
582}
583
584pub fn default_bind_ip() -> Option<IpAddr> {
585 choose_default_bind_v4()
586 .map(IpAddr::V4)
587 .or_else(|| choose_default_bind_v6().map(IpAddr::V6))
588}
589
590pub fn parse_name_servers(env_val: &str) -> Vec<SocketAddr> {
594 let mut out = Vec::new();
595 for token in env_val.split(|c| c == ',' || c == ' ' || c == '\t') {
596 let token = token.trim();
597 if token.is_empty() {
598 continue;
599 }
600 if let Ok(addr) = token.parse::<SocketAddr>() {
601 out.push(addr);
602 continue;
603 }
604 if let Ok(ip) = token.parse::<IpAddr>() {
605 out.push(SocketAddr::new(ip, 5075));
606 continue;
607 }
608 use std::net::ToSocketAddrs;
609 if let Ok(mut addrs) = token.to_socket_addrs() {
610 if let Some(addr) = addrs.next() {
611 out.push(addr);
612 continue;
613 }
614 }
615 let with_port = format!("{}:5075", token);
616 if let Ok(mut addrs) = with_port.to_socket_addrs() {
617 if let Some(addr) = addrs.next() {
618 out.push(addr);
619 }
620 }
621 }
622 out
623}
624
625fn encode_search_validation(version: u8, is_be: bool) -> Vec<u8> {
627 let user = default_authnz_user();
628 let host = default_authnz_host();
629 encode_client_connection_validation(87_040, 32_767, 0, "ca", &user, &host, version, is_be)
630}
631
632pub async fn search_pv_tcp(
637 pv_name: &str,
638 name_server: SocketAddr,
639 timeout_dur: Duration,
640 debug_enabled: bool,
641) -> Result<SocketAddr, PvGetError> {
642 let deadline = tokio::time::Instant::now() + timeout_dur;
643
644 let mut stream = tokio::time::timeout(timeout_dur, tokio::net::TcpStream::connect(name_server))
645 .await
646 .map_err(|_| PvGetError::Timeout("name server connect"))??;
647
648 let mut version = 2u8;
649 let mut is_be = false;
650
651 for _ in 0..2 {
653 let now = tokio::time::Instant::now();
654 if now >= deadline {
655 return Err(PvGetError::Timeout("name server handshake"));
656 }
657 let remaining = deadline - now;
658 if let Ok(bytes) = read_packet(&mut stream, remaining).await {
659 let mut pkt = PvaPacket::new(&bytes);
660 if let Some(cmd) = pkt.decode_payload() {
661 match cmd {
662 PvaPacketCommand::Control(payload) => {
663 if payload.command == 2 {
664 is_be = pkt.header.flags.is_msb;
665 }
666 }
667 PvaPacketCommand::ConnectionValidation(_) => {
668 version = pkt.header.version;
669 is_be = pkt.header.flags.is_msb;
670 }
671 _ => {}
672 }
673 }
674 }
675 }
676
677 let validation = encode_search_validation(version, is_be);
678 stream.write_all(&validation).await?;
679
680 loop {
682 let now = tokio::time::Instant::now();
683 if now >= deadline {
684 return Err(PvGetError::Timeout("name server validated"));
685 }
686 let remaining = deadline - now;
687 let bytes = read_packet(&mut stream, remaining).await?;
688 let mut pkt = PvaPacket::new(&bytes);
689 if let Some(cmd) = pkt.decode_payload() {
690 if matches!(cmd, PvaPacketCommand::ConnectionValidated(_)) {
691 break;
692 }
693 }
694 }
695
696 let now_ts = std::time::SystemTime::now()
698 .duration_since(std::time::UNIX_EPOCH)
699 .unwrap_or_default();
700 let seq = (now_ts.as_nanos() as u32).wrapping_add(std::process::id());
701 let cid = seq ^ 0x9E37_79B9;
702 let requests = [(cid, pv_name)];
703 let msg = encode_search_request(seq, 0x80, 0, [0u8; 16], &requests, version, is_be);
704 stream.write_all(&msg).await?;
705
706 if debug_enabled {
707 debug!(
708 "pva tcp search sent to name_server={} pv={}",
709 name_server, pv_name
710 );
711 }
712
713 loop {
715 let now = tokio::time::Instant::now();
716 if now >= deadline {
717 return Err(PvGetError::Timeout("name server search response"));
718 }
719 let remaining = deadline - now;
720 let bytes = read_packet(&mut stream, remaining).await?;
721 let mut pkt = PvaPacket::new(&bytes);
722 if let Some(cmd) = pkt.decode_payload() {
723 if let PvaPacketCommand::SearchResponse(payload) = cmd {
724 if !payload.found {
725 continue;
726 }
727 if !payload.cids.is_empty() && !payload.cids.contains(&cid) {
728 continue;
729 }
730 let addr = decode_search_response_addr(payload.addr, payload.port, name_server);
731 if debug_enabled {
732 debug!(
733 "pva tcp search response from name_server={}: {}",
734 name_server, addr
735 );
736 }
737 return Ok(addr);
738 }
739 }
740 }
741}
742
743pub async fn resolve_pv_server(opts: &PvGetOptions) -> Result<SocketAddr, PvGetError> {
750 if let Some(addr) = opts.server_addr {
751 return Ok(addr);
752 }
753
754 let mut name_servers = opts.name_servers.clone();
755 if let Ok(env) = std::env::var("EPICS_PVA_NAME_SERVERS") {
756 name_servers.extend(parse_name_servers(&env));
757 }
758
759 let no_broadcast = opts.no_broadcast;
760
761 if no_broadcast && name_servers.is_empty() {
763 return Err(PvGetError::Search(
764 "no search strategy: specify --name-server or --server when using --no-broadcast",
765 ));
766 }
767
768 let targets = build_search_targets(opts.search_addr, opts.bind_addr);
771
772 let pv = opts.pv_name.clone();
773 let timeout_dur = opts.timeout;
774 let debug_enabled = opts.debug;
775 let udp_port = opts.udp_port;
776
777 let mut set = tokio::task::JoinSet::new();
778
779 for ns in name_servers {
780 let pv = pv.clone();
781 set.spawn(async move {
782 let addr = search_pv_tcp(&pv, ns, timeout_dur, debug_enabled).await?;
783 Ok::<SocketAddr, PvGetError>(addr)
784 });
785 }
786
787 if !no_broadcast {
788 let pv = pv.clone();
789 let targets = targets.clone();
790 set.spawn(async move {
791 let addr = search_pv(&pv, udp_port, timeout_dur, &targets, debug_enabled).await?;
792 Ok(addr)
793 });
794 }
795
796 let mut last_err = None;
797 while let Some(result) = set.join_next().await {
798 match result {
799 Ok(Ok(addr)) => {
800 set.abort_all();
801 return Ok(addr);
802 }
803 Ok(Err(e)) => {
804 if debug_enabled {
805 debug!("pva search strategy failed: {}", e);
806 }
807 last_err = Some(e);
808 }
809 Err(join_err) => {
810 if debug_enabled {
811 debug!("pva search task panicked: {}", join_err);
812 }
813 }
814 }
815 }
816
817 Err(last_err.unwrap_or(PvGetError::Timeout("search response")))
818}
819
820pub async fn discover_servers(
821 udp_port: u16,
822 timeout_dur: Duration,
823 targets: &[SearchTarget],
824 debug_enabled: bool,
825) -> Result<Vec<DiscoveredServer>, PvGetError> {
826 if targets.is_empty() {
827 return Err(PvGetError::Search("no search targets"));
828 }
829
830 let now = std::time::SystemTime::now()
831 .duration_since(std::time::UNIX_EPOCH)
832 .unwrap_or_default();
833 let seq = (now.as_nanos() as u32).wrapping_add(std::process::id());
834
835 let mut found: Vec<DiscoveredServer> = Vec::new();
836 let mut last_io_error: Option<std::io::Error> = None;
837 let deadline = tokio::time::Instant::now() + timeout_dur;
838
839 let mut bind_groups: Vec<(IpAddr, Vec<IpAddr>)> = Vec::new();
841 for t in targets {
842 if let Some(group) = bind_groups.iter_mut().find(|(b, _)| *b == t.bind) {
843 group.1.push(t.target);
844 } else {
845 bind_groups.push((t.bind, vec![t.target]));
846 }
847 }
848
849 let mut socket_info: Vec<(Arc<UdpSocket>, Vec<u8>, Vec<SocketAddr>)> = Vec::new();
852
853 for (bind_ip, group_targets) in &bind_groups {
854 let bind_addr = SocketAddr::new(*bind_ip, 0);
860 let (std_sock, actual_bind_addr) = match bind_udp_reuse(bind_addr) {
861 Ok(sock) => {
862 let actual = sock.local_addr().unwrap_or(bind_addr);
863 (sock, actual)
864 }
865 Err(err) => {
866 if debug_enabled {
867 debug!(
868 "pva discover skipping bind={} step=bind kind={:?} err={}",
869 bind_addr,
870 err.kind(),
871 err
872 );
873 }
874 last_io_error = Some(err);
875 continue;
876 }
877 };
878 if let Err(err) = std_sock.set_broadcast(true) {
879 if debug_enabled {
880 debug!(
881 "pva discover skipping bind={} step=set_broadcast kind={:?} err={}",
882 bind_addr,
883 err.kind(),
884 err
885 );
886 }
887 last_io_error = Some(err);
888 continue;
889 }
890
891 join_multicast_any(&std_sock, *bind_ip);
892
893 let reply_addr = ip_to_bytes(*bind_ip);
894 let reply_port = match std_sock.local_addr() {
895 Ok(addr) => addr.port(),
896 Err(err) => {
897 if debug_enabled {
898 debug!(
899 "pva discover skipping bind={} step=local_addr kind={:?} err={}",
900 bind_addr,
901 err.kind(),
902 err
903 );
904 }
905 last_io_error = Some(err);
906 continue;
907 }
908 };
909 let msg = encode_search_request(seq, 0x81, reply_port, reply_addr, &[], 2, false);
910
911 let socket = match UdpSocket::from_std(std_sock) {
912 Ok(socket) => socket,
913 Err(err) => {
914 if debug_enabled {
915 debug!(
916 "pva discover skipping bind={} step=from_std kind={:?} err={}",
917 bind_addr,
918 err.kind(),
919 err
920 );
921 }
922 last_io_error = Some(err);
923 continue;
924 }
925 };
926
927 let dests: Vec<SocketAddr> = group_targets
928 .iter()
929 .map(|ip| SocketAddr::new(*ip, udp_port))
930 .collect();
931
932 for dest in &dests {
934 if debug_enabled {
935 debug!(
936 "pva discover bind={} target={} server_port={} reply_port={} seq={}",
937 actual_bind_addr,
938 dest.ip(),
939 udp_port,
940 reply_port,
941 seq
942 );
943 }
944 if let Err(err) = socket.send_to(&msg, dest).await {
945 if debug_enabled {
946 debug!(
947 "pva discover send_to target={} kind={:?} err={}",
948 dest,
949 err.kind(),
950 err
951 );
952 }
953 last_io_error = Some(err);
954 }
955 }
956
957 socket_info.push((Arc::new(socket), msg, dests));
958 }
959
960 if socket_info.is_empty() {
961 if let Some(err) = last_io_error {
962 return Err(PvGetError::Io(err));
963 }
964 return Err(PvGetError::Search("no search targets"));
965 }
966
967 let (tx, mut rx) = tokio::sync::mpsc::channel::<(Vec<u8>, SocketAddr)>(64);
969 for (sock, _, _) in &socket_info {
970 let sock = Arc::clone(sock);
971 let tx = tx.clone();
972 tokio::spawn(async move {
973 loop {
974 let mut buf = vec![0u8; 2048];
975 match sock.recv_from(&mut buf).await {
976 Ok((len, src)) => {
977 buf.truncate(len);
978 if tx.send((buf, src)).await.is_err() {
979 break;
980 }
981 }
982 Err(_) => break,
983 }
984 }
985 });
986 }
987 drop(tx); let retransmit_offsets = [100u64, 500, 1000, 2000];
991 let start = tokio::time::Instant::now();
992 let mut next_retransmit = 0usize;
993
994 loop {
995 let next_retransmit_at = if next_retransmit < retransmit_offsets.len() {
997 start + Duration::from_millis(retransmit_offsets[next_retransmit])
998 } else {
999 deadline
1000 };
1001 let wake_at = next_retransmit_at.min(deadline);
1002
1003 tokio::select! {
1004 recv = rx.recv() => {
1005 let Some((buf, src)) = recv else { break };
1006 let mut pkt = PvaPacket::new(&buf);
1007 let Some(cmd) = pkt.decode_payload() else {
1008 continue;
1009 };
1010 if let PvaPacketCommand::SearchResponse(payload) = cmd {
1011 if payload.seq != seq {
1012 continue;
1013 }
1014 if !payload.protocol.is_empty() && !payload.protocol.eq_ignore_ascii_case("tcp") {
1015 continue;
1016 }
1017 let tcp_addr = decode_search_response_addr(payload.addr, payload.port, src);
1018 found.push(DiscoveredServer {
1019 guid: payload.guid,
1020 tcp_addr,
1021 });
1022 }
1023 }
1024 _ = tokio::time::sleep_until(wake_at) => {
1025 if tokio::time::Instant::now() >= deadline {
1026 break;
1027 }
1028 if next_retransmit < retransmit_offsets.len() {
1030 if debug_enabled {
1031 debug!("pva discover retransmit round {}", next_retransmit + 1);
1032 }
1033 for (sock, msg, dests) in &socket_info {
1034 for dest in dests {
1035 let _ = sock.send_to(msg, dest).await;
1036 }
1037 }
1038 next_retransmit += 1;
1039 }
1040 }
1041 }
1042 }
1043
1044 Ok(normalize_discovered_servers(found))
1045}
1046
1047#[cfg(test)]
1048mod tests {
1049 use super::*;
1050 use spvirit_codec::epics_decode::{PvaPacket, PvaPacketCommand};
1051
1052 #[test]
1053 fn encode_decode_search_request_roundtrip() {
1054 let seq = 1234;
1055 let cid = 42;
1056 let port = 5076;
1057 let pv_name = "TEST:PV";
1058 let reply_addr = ip_to_bytes(IpAddr::V4(Ipv4Addr::new(192, 168, 1, 20)));
1059 let requests = [(cid, pv_name)];
1060 let msg = encode_search_request(seq, 0x81, port, reply_addr, &requests, 2, false);
1061 let mut pkt = PvaPacket::new(&msg);
1062 let cmd = pkt.decode_payload().expect("decoded");
1063 match cmd {
1064 PvaPacketCommand::Search(payload) => {
1065 assert_eq!(payload.seq, seq);
1066 assert_eq!(payload.mask, 0x81);
1067 assert_eq!(payload.addr, reply_addr);
1068 assert_eq!(payload.port, port);
1069 assert_eq!(payload.protocols, vec!["tcp".to_string()]);
1070 assert_eq!(payload.pv_requests.len(), 1);
1071 assert_eq!(payload.pv_requests[0].0, cid);
1072 assert_eq!(payload.pv_requests[0].1, pv_name.to_string());
1073 }
1074 other => panic!("unexpected decode: {:?}", other),
1075 }
1076 }
1077
1078 #[test]
1079 fn encode_decode_server_discovery_request_roundtrip() {
1080 let seq = 4321;
1081 let port = 5076;
1082 let reply_addr = ip_to_bytes(IpAddr::V4(Ipv4Addr::new(10, 20, 30, 40)));
1083 let msg = encode_search_request(seq, 0x81, port, reply_addr, &[], 2, false);
1084 let mut pkt = PvaPacket::new(&msg);
1085 let cmd = pkt.decode_payload().expect("decoded");
1086 match cmd {
1087 PvaPacketCommand::Search(payload) => {
1088 assert_eq!(payload.seq, seq);
1089 assert_eq!(payload.pv_requests.len(), 0);
1090 assert_eq!(payload.protocols, vec!["tcp".to_string()]);
1091 }
1092 other => panic!("unexpected decode: {:?}", other),
1093 }
1094 }
1095
1096 #[test]
1097 fn normalize_discovered_servers_deduplicates_by_guid_and_addr() {
1098 let guid = [1u8; 12];
1099 let s1 = DiscoveredServer {
1100 guid,
1101 tcp_addr: "127.0.0.1:5075".parse().unwrap(),
1102 };
1103 let s2 = DiscoveredServer {
1104 guid,
1105 tcp_addr: "127.0.0.1:5075".parse().unwrap(),
1106 };
1107 let s3 = DiscoveredServer {
1108 guid: [2u8; 12],
1109 tcp_addr: "127.0.0.1:5075".parse().unwrap(),
1110 };
1111 let normalized = normalize_discovered_servers(vec![s1, s2, s3]);
1112 assert_eq!(normalized.len(), 2);
1113 }
1114
1115 #[test]
1116 fn parse_addr_list_accepts_ip_and_ip_port() {
1117 let items = parse_addr_list("192.168.1.10 10.0.0.1:5076");
1118 assert!(items.contains(&IpAddr::V4(Ipv4Addr::new(192, 168, 1, 10))));
1119 assert!(items.contains(&IpAddr::V4(Ipv4Addr::new(10, 0, 0, 1))));
1120 }
1121
1122 #[test]
1123 fn discovery_target_falls_back_to_limited_broadcast_for_invalid_netmask() {
1124 let ip = Ipv4Addr::new(130, 246, 90, 92);
1125 assert_eq!(
1126 discovery_target_for(ip, Ipv4Addr::new(255, 255, 255, 255)),
1127 Ipv4Addr::new(255, 255, 255, 255)
1128 );
1129 assert_eq!(
1130 discovery_target_for(ip, Ipv4Addr::new(0, 0, 0, 0)),
1131 Ipv4Addr::new(255, 255, 255, 255)
1132 );
1133 }
1134
1135 #[test]
1136 fn discovery_target_uses_directed_broadcast_for_normal_subnet() {
1137 let ip = Ipv4Addr::new(192, 168, 56, 1);
1138 let netmask = Ipv4Addr::new(255, 255, 255, 0);
1139 assert_eq!(
1140 discovery_target_for(ip, netmask),
1141 Ipv4Addr::new(192, 168, 56, 255)
1142 );
1143 }
1144
1145 #[test]
1146 fn parse_name_servers_ip_with_port() {
1147 let addrs = parse_name_servers("192.168.1.10:5075");
1148 assert_eq!(
1149 addrs,
1150 vec!["192.168.1.10:5075".parse::<SocketAddr>().unwrap()]
1151 );
1152 }
1153
1154 #[test]
1155 fn parse_name_servers_ip_without_port_defaults_to_5075() {
1156 let addrs = parse_name_servers("10.0.0.1");
1157 assert_eq!(
1158 addrs,
1159 vec![SocketAddr::new(
1160 IpAddr::V4(Ipv4Addr::new(10, 0, 0, 1)),
1161 5075
1162 )]
1163 );
1164 }
1165
1166 #[test]
1167 fn parse_name_servers_multiple_comma_separated() {
1168 let addrs = parse_name_servers("10.0.0.1:5075,10.0.0.2:9876");
1169 assert_eq!(addrs.len(), 2);
1170 assert_eq!(addrs[0], "10.0.0.1:5075".parse::<SocketAddr>().unwrap());
1171 assert_eq!(addrs[1], "10.0.0.2:9876".parse::<SocketAddr>().unwrap());
1172 }
1173
1174 #[test]
1175 fn parse_name_servers_multiple_space_separated() {
1176 let addrs = parse_name_servers("10.0.0.1 10.0.0.2:5075");
1177 assert_eq!(addrs.len(), 2);
1178 assert_eq!(
1179 addrs[0],
1180 SocketAddr::new(IpAddr::V4(Ipv4Addr::new(10, 0, 0, 1)), 5075)
1181 );
1182 assert_eq!(addrs[1], "10.0.0.2:5075".parse::<SocketAddr>().unwrap());
1183 }
1184
1185 #[test]
1186 fn parse_name_servers_empty_string() {
1187 let addrs = parse_name_servers("");
1188 assert!(addrs.is_empty());
1189 }
1190
1191 #[test]
1192 fn parse_name_servers_whitespace_only() {
1193 let addrs = parse_name_servers(" \t ");
1194 assert!(addrs.is_empty());
1195 }
1196
1197 #[test]
1198 fn parse_name_servers_mixed_separators() {
1199 let addrs = parse_name_servers("10.0.0.1:5075, 10.0.0.2 , 10.0.0.3:9999");
1200 assert_eq!(addrs.len(), 3);
1201 assert_eq!(addrs[0], "10.0.0.1:5075".parse::<SocketAddr>().unwrap());
1202 assert_eq!(
1203 addrs[1],
1204 SocketAddr::new(IpAddr::V4(Ipv4Addr::new(10, 0, 0, 2)), 5075)
1205 );
1206 assert_eq!(addrs[2], "10.0.0.3:9999".parse::<SocketAddr>().unwrap());
1207 }
1208
1209 #[test]
1210 fn parse_name_servers_ipv6_with_port() {
1211 let addrs = parse_name_servers("[::1]:5075");
1212 assert_eq!(
1213 addrs,
1214 vec![SocketAddr::new(IpAddr::V6(Ipv6Addr::LOCALHOST), 5075)]
1215 );
1216 }
1217
1218 #[test]
1219 fn parse_name_servers_ipv6_without_port() {
1220 let addrs = parse_name_servers("::1");
1221 assert_eq!(
1222 addrs,
1223 vec![SocketAddr::new(IpAddr::V6(Ipv6Addr::LOCALHOST), 5075)]
1224 );
1225 }
1226
1227 #[test]
1228 fn decode_search_response_addr_falls_back_to_udp_source_when_unspecified() {
1229 let src: SocketAddr = "192.168.1.20:5076".parse().unwrap();
1230 let decoded = decode_search_response_addr([0u8; 16], 5075, src);
1231 assert_eq!(decoded, "192.168.1.20:5075".parse().unwrap());
1232 }
1233}