1use crate::{proto::{Server, ServerTlv, ServerTlvMap, SLIM_PORT}, Capabilities};
5
6use std::{
7 collections::HashMap,
8 io,
9 net::{Ipv4Addr, SocketAddr, UdpSocket, SocketAddrV4},
10 sync::{
11 atomic::{AtomicBool, Ordering},
12 Arc,
13 },
14 thread::{sleep, spawn},
15 time::Duration,
16};
17
18pub fn discover(timeout: Option<Duration>) -> io::Result<Option<Server>> {
29 const UDPMAXSIZE: usize = 1450; let cx = UdpSocket::bind((Ipv4Addr::new(0, 0, 0, 0), 0))?;
32 cx.set_broadcast(true)?;
33 cx.set_read_timeout(timeout)?;
34
35 let cx_send = cx.try_clone()?;
36 let running = Arc::new(AtomicBool::new(true));
37 let is_running = running.clone();
38 spawn(move || {
39 let buf = b"eNAME\0IPAD\0JSON\0VERS"; while is_running.load(Ordering::Relaxed) {
41 cx_send
42 .send_to(buf, (Ipv4Addr::new(255, 255, 255, 255), SLIM_PORT))
43 .ok();
44 sleep(Duration::from_secs(5));
45 }
46 });
47
48 let mut buf = [0u8; UDPMAXSIZE];
49 let response = cx.recv_from(&mut buf);
50 running.store(false, Ordering::Relaxed);
51
52 response.map_or_else(
53 |e| match e.kind() {
54 io::ErrorKind::WouldBlock => Ok(None),
55 _ => Err(e),
56 },
57 |(len, sock_addr)| match sock_addr {
58 SocketAddr::V4(addr) => Ok(Some(Server {
59 socket: SocketAddrV4::new(*addr.ip(), SLIM_PORT),
60 tlv_map: {
63 if len > 0 && buf[0] == b'E' {
64 Some(decode_tlv(&buf[1..]))
65 } else {
66 None
67 }
68 },
69 sync_group_id: None,
70 caps: Capabilities(Vec::new()), })),
72 _ => Ok(None),
73 },
74 )
75}
76
77fn decode_tlv(buf: &[u8]) -> ServerTlvMap {
78 let mut ret = HashMap::new();
79 let mut view = &buf[..];
80
81 while view.len() > 4 && view[0].is_ascii() {
82 let token = String::from_utf8(view[..4].to_vec()).unwrap_or_default();
83 let valen = view[4] as usize;
84 view = &view[5..];
85
86 if view.len() < valen {
87 break;
88 }
89
90 let value = String::from_utf8(view[..valen].to_vec()).unwrap_or_default();
91
92 let value = match token.as_str() {
93 "NAME" => ServerTlv::Name(value),
94 "VERS" => ServerTlv::Version(value),
95 "IPAD" => {
96 if let Ok(addr) = value.parse::<Ipv4Addr>() {
97 ServerTlv::Address(addr)
98 } else {
99 break;
100 }
101 }
102 "JSON" => {
103 if let Ok(port) = value.parse::<u16>() {
104 ServerTlv::Port(port)
105 } else {
106 break;
107 }
108 }
109 _ => {
110 break;
111 }
112 };
113
114 ret.insert(token, value);
115 view = &view[valen..];
116 }
117
118 ret
119}
120
121#[cfg(test)]
122mod tests {
123 use super::*;
124
125 #[test]
126 fn server_discover() {
127 let res = discover(Some(Duration::from_secs(1)));
128 assert!(res.is_ok());
129
130 if let Ok(Some(server)) = res {
131 assert!(!server.socket.ip().is_unspecified());
132 assert!(server.tlv_map.is_some());
133 }
134 }
135}