1use std::{
4 net::{IpAddr, Ipv4Addr, UdpSocket},
5 time::{Duration, Instant},
6};
7
8use reqwest::StatusCode;
9
10use crate::{
11 errors::{SonosError, SpeakerError, UDPError},
12 speaker::BasicSpeakerInfo,
13 xml::{get_error_code, parse_description_xml},
14};
15
16const DISCOVERY_REQUEST_BODY: &str = "M-SEARCH * HTTP/1.1
17HOST: 239.255.255.250:1900
18MAN: ssdp:discover
19MX: 1
20ST: urn:schemas-upnp-org:device:ZonePlayer:1";
21
22const DESCRIPTION_ENDPOINT: &str = "/xml/device_description.xml";
23
24pub async fn get_speaker_info(ip_addr: Ipv4Addr) -> Result<BasicSpeakerInfo, SpeakerError> {
27 let url = format!("http://{}:1400{}", ip_addr, DESCRIPTION_ENDPOINT);
28
29 let response = reqwest::get(&url).await?;
30
31 let status = response.status();
32 let xml_response = response.text().await?;
33
34 if let StatusCode::OK = status {
35 let speaker_info = parse_description_xml(xml_response, ip_addr)?;
36
37 Ok(speaker_info)
38 } else {
39 let error_code = get_error_code(xml_response)?;
40
41 Err(SpeakerError::from(SonosError::from_err_code(
42 &error_code,
43 &format!("HTTP status code: {}", status),
44 )))
45 }
46}
47
48pub async fn discover_devices(
52 search_timeout: Duration,
53 read_timeout: Duration,
54) -> Result<Vec<BasicSpeakerInfo>, UDPError> {
55 let socket: UdpSocket = UdpSocket::bind("0.0.0.0:0")?;
56
57 socket.set_broadcast(true)?;
58
59 socket.set_read_timeout(Some(read_timeout))?;
60
61 socket.send_to(DISCOVERY_REQUEST_BODY.as_bytes(), "239.255.255.250:1900")?;
62
63 socket.send_to(DISCOVERY_REQUEST_BODY.as_bytes(), "255.255.255.255:1900")?;
64
65 let start_time = Instant::now();
66
67 let mut buf = [0; 1024];
69
70 let mut discovered_speakers = Vec::new();
71
72 loop {
73 if start_time.elapsed() > search_timeout {
74 break;
75 }
76
77 if let Ok((_, addr)) = socket.recv_from(&mut buf) {
78 let ip_addr = addr.ip();
79
80 if let IpAddr::V4(ip_addr) = ip_addr {
81 if let Ok(info) = get_speaker_info(ip_addr).await {
82 if !discovered_speakers.contains(&info) {
83 discovered_speakers.push(info);
84 }
85 }
86 }
87 }
88 }
89
90 Ok(discovered_speakers)
91}