This is a UPnP client library for Rust.

Merge pull request #9 from seijikun/master

Attempt to fix #2: Hard parser crashes

authored by tsiry-sandratraina.com and committed by GitHub d16ad47a 291abe0c

-1
Cargo.toml
··· 33 33 owo-colors = "3.5.0" 34 34 serde = "1.0.152" 35 35 serde_json = "1.0.91" 36 - socket2 = "0.4.7" 37 36 surf = { version = "2.3.2", features = ["h1-client-rustls"], default-features = false} 38 37 tokio = { version = "1.24.2", features = ["tokio-macros", "macros", "rt", "rt-multi-thread"] } 39 38 url = "2.3.1"
+1 -1
examples/discover.rs
··· 4 4 5 5 #[tokio::main] 6 6 async fn main() -> Result<(), Box<dyn std::error::Error>> { 7 - let devices = discover_pnp_locations(); 7 + let devices = discover_pnp_locations().await?; 8 8 tokio::pin!(devices); 9 9 10 10 while let Some(device) = devices.next().await {
+2 -2
examples/media_renderer_client.rs
··· 10 10 11 11 #[tokio::main] 12 12 async fn main() -> Result<(), Box<dyn std::error::Error>> { 13 - let devices = discover_pnp_locations(); 13 + let devices = discover_pnp_locations().await?; 14 14 tokio::pin!(devices); 15 15 16 16 let mut kodi_device: Option<Device> = None; ··· 23 23 } 24 24 25 25 let kodi_device = kodi_device.unwrap(); 26 - let device_client = DeviceClient::new(&kodi_device.location).connect().await?; 26 + let device_client = DeviceClient::new(&kodi_device.location)?.connect().await?; 27 27 let mut media_renderer = MediaRendererClient::new(device_client); 28 28 29 29 let options = LoadOptions {
+2 -2
examples/media_server_client.rs
··· 8 8 9 9 #[tokio::main] 10 10 async fn main() -> Result<(), Box<dyn std::error::Error>> { 11 - let devices = discover_pnp_locations(); 11 + let devices = discover_pnp_locations().await?; 12 12 tokio::pin!(devices); 13 13 14 14 let mut kodi_device: Option<Device> = None; ··· 21 21 } 22 22 23 23 let kodi_device = kodi_device.unwrap(); 24 - let device_client = DeviceClient::new(&kodi_device.location).connect().await?; 24 + let device_client = DeviceClient::new(&kodi_device.location)?.connect().await?; 25 25 let media_server_client = MediaServerClient::new(device_client); 26 26 let results = media_server_client 27 27 .browse("0", "BrowseDirectChildren")
+83 -98
src/device_client.rs
··· 3 3 env, 4 4 net::TcpListener, 5 5 sync::{ 6 - mpsc::{self, Receiver, Sender}, 7 - Arc, Mutex, 6 + Arc, 8 7 }, 9 8 time::Duration, 10 9 }; ··· 17 16 types::{AVTransportEvent, Device, Event, Service}, 18 17 BROADCAST_EVENT, 19 18 }; 20 - use anyhow::Error; 19 + use anyhow::{anyhow, Result}; 21 20 use hyper::{ 22 21 server::conn::AddrStream, 23 22 service::{make_service_fn, service_fn}, 24 23 }; 25 24 use hyper::{Body, Request, Response, Server}; 26 25 use surf::{Client, Config, Url}; 26 + use tokio::sync::Mutex; 27 27 use xml_builder::{XMLBuilder, XMLElement, XMLVersion}; 28 28 29 29 #[derive(Clone)] ··· 35 35 } 36 36 37 37 impl DeviceClient { 38 - pub fn new(url: &str) -> Self { 39 - Self { 40 - base_url: Url::parse(url).unwrap(), 38 + pub fn new(url: &str) -> Result<Self> { 39 + Ok(Self { 40 + base_url: Url::parse(url)?, 41 41 http_client: Config::new() 42 42 .set_timeout(Some(Duration::from_secs(5))) 43 - .try_into() 44 - .unwrap(), 43 + .try_into()?, 45 44 device: None, 46 45 stop: Arc::new(Mutex::new(false)), 47 - } 46 + }) 48 47 } 49 48 50 - pub async fn connect(&mut self) -> Result<Self, Error> { 49 + pub async fn connect(&mut self) -> Result<Self> { 51 50 self.device = Some(parse_location(self.base_url.as_str()).await?); 52 51 Ok(Self { 53 52 base_url: self.base_url.clone(), ··· 66 65 service_id: &str, 67 66 action_name: &str, 68 67 params: HashMap<String, String>, 69 - ) -> Result<String, Error> { 68 + ) -> Result<String> { 70 69 if self.device.is_none() { 71 - return Err(Error::msg("Device not connected")); 70 + return Err(anyhow!("Device not connected")); 72 71 } 73 72 let service_id = resolve_service(service_id); 74 73 let service = self.get_service_description(&service_id).await?; ··· 80 79 self.call_action_internal(&service, action_name, params) 81 80 .await 82 81 } 83 - None => Err(Error::msg("Action not found")), 82 + None => Err(anyhow!("Action not found")), 84 83 } 85 84 } 86 85 ··· 89 88 service: &Service, 90 89 action_name: &str, 91 90 params: HashMap<String, String>, 92 - ) -> Result<String, Error> { 93 - let control_url = Url::parse(&service.control_url).unwrap(); 91 + ) -> Result<String> { 92 + let control_url = Url::parse(&service.control_url)?; 94 93 95 94 let mut xml = XMLBuilder::new() 96 95 .version(XMLVersion::XML1_1) ··· 111 110 112 111 for (name, value) in params { 113 112 let mut param = XMLElement::new(name.as_str()); 114 - param.add_text(value).unwrap(); 115 - action.add_child(param).unwrap(); 113 + param.add_text(value)?; 114 + action.add_child(param)?; 116 115 } 117 116 118 - body.add_child(action).unwrap(); 119 - envelope.add_child(body).unwrap(); 117 + body.add_child(action)?; 118 + envelope.add_child(body)?; 120 119 121 120 xml.set_root_element(envelope); 122 121 123 122 let mut writer: Vec<u8> = Vec::new(); 124 - xml.generate(&mut writer).unwrap(); 125 - let xml = String::from_utf8(writer).unwrap(); 123 + xml.generate(&mut writer)?; 124 + let xml = String::from_utf8(writer)?; 126 125 127 126 let soap_action = format!("\"{}#{}\"", service.service_type, action_name); 128 127 ··· 136 135 .body_string(xml.clone()) 137 136 .send() 138 137 .await 139 - .map_err(|e| Error::msg(e.to_string()))?; 140 - Ok(res 138 + .map_err(|e| anyhow!(e.to_string()))?; 139 + res 141 140 .body_string() 142 141 .await 143 - .map_err(|e| Error::msg(e.to_string()))?) 142 + .map_err(|e| anyhow!(e.to_string())) 144 143 } 145 144 146 - async fn get_service_description(&self, service_id: &str) -> Result<Service, Error> { 145 + async fn get_service_description(&self, service_id: &str) -> Result<Service> { 147 146 if let Some(device) = &self.device { 148 147 let service = device 149 148 .services 150 149 .iter() 151 150 .find(|s| s.service_id == service_id) 152 - .unwrap(); 151 + .ok_or_else(|| anyhow!("Service with requested service_id {} does not exist", service_id))?; 153 152 return Ok(service.clone()); 154 153 } 155 - Err(Error::msg("Device not connected")) 154 + Err(anyhow!("Device not connected")) 156 155 } 157 156 158 - pub async fn subscribe(&mut self, service_id: &str) -> Result<(), Error> { 157 + pub async fn subscribe(&mut self, service_id: &str) -> Result<()> { 159 158 if self.device.is_none() { 160 - return Err(Error::msg("Device not connected")); 159 + return Err(anyhow!("Device not connected")); 161 160 } 162 161 let service_id = resolve_service(service_id); 163 162 let service = self.get_service_description(&service_id).await?; ··· 179 178 .header("NT", "upnp:event") 180 179 .header("TIMEOUT", "Second-1800") 181 180 .header("USER-AGENT", user_agent) 182 - .body(hyper::Body::empty()) 183 - .unwrap(); 181 + .body(hyper::Body::empty())?; 184 182 client.request(req).await?; 185 183 Ok(()) 186 184 } 187 185 188 - pub async fn unsubscribe(&mut self, service_id: &str, sid: &str) -> Result<(), Error> { 186 + pub async fn unsubscribe(&mut self, service_id: &str, sid: &str) -> Result<()> { 189 187 if self.device.is_none() { 190 - return Err(Error::msg("Device not connected")); 188 + return Err(anyhow!("Device not connected")); 191 189 } 192 190 let service_id = resolve_service(service_id); 193 - let service = self.get_service_description(&service_id).await.unwrap(); 191 + let service = self.get_service_description(&service_id).await?; 194 192 let client = hyper::Client::new(); 195 193 let req = hyper::Request::builder() 196 194 .method("UNSUBSCRIBE") 197 195 .uri(service.event_sub_url.clone()) 198 196 .header("SID", sid) 199 - .body(hyper::Body::empty()) 200 - .unwrap(); 197 + .body(hyper::Body::empty())?; 201 198 202 199 client.request(req).await?; 203 200 ··· 205 202 Ok(()) 206 203 } 207 204 208 - async fn ensure_eventing_server(&mut self) -> Result<(String, u16), Error> { 205 + async fn ensure_eventing_server(&mut self) -> Result<(String, u16)> { 209 206 let addr: &str = "0.0.0.0:0"; 210 - let listener = TcpListener::bind(&addr).unwrap(); 207 + let listener = TcpListener::bind(addr)?; 211 208 212 209 let service = make_service_fn(|_: &AddrStream| async { 213 210 Ok::<_, hyper::Error>(service_fn(|req: Request<Body>| async move { ··· 231 228 let current_track_metadata = 232 229 parse_current_track_metadata(last_change.as_str()).unwrap(); 233 230 234 - match transport_state { 235 - Some(state) => { 236 - let tx = BROADCAST_EVENT.lock().unwrap(); 237 - let tx = tx.as_ref().clone(); 238 - let ev = AVTransportEvent::TransportState { 239 - sid: sid.clone(), 240 - transport_state: state, 241 - }; 242 - tx.unwrap().send(Event::AVTransport(ev)).unwrap(); 243 - } 244 - None => {} 231 + if let Some(state) = transport_state { 232 + let tx = BROADCAST_EVENT.lock().unwrap(); 233 + let tx = tx.as_ref(); 234 + let ev = AVTransportEvent::TransportState { 235 + sid: sid.clone(), 236 + transport_state: state, 237 + }; 238 + tx.unwrap().send(Event::AVTransport(ev)).unwrap(); 245 239 } 246 240 247 - match play_mode { 248 - Some(mode) => { 249 - let tx = BROADCAST_EVENT.lock().unwrap(); 250 - let tx = tx.as_ref().clone(); 251 - let ev = AVTransportEvent::CurrentPlayMode { 252 - sid: sid.clone(), 253 - play_mode: mode, 254 - }; 255 - tx.unwrap().send(Event::AVTransport(ev)).unwrap(); 256 - } 257 - None => {} 241 + if let Some(mode) = play_mode { 242 + let tx = BROADCAST_EVENT.lock().unwrap(); 243 + let tx = tx.as_ref(); 244 + let ev = AVTransportEvent::CurrentPlayMode { 245 + sid: sid.clone(), 246 + play_mode: mode, 247 + }; 248 + tx.unwrap().send(Event::AVTransport(ev)).unwrap(); 258 249 } 259 250 260 - match av_transport_uri_metadata { 261 - Some(metadata) => { 262 - let tx = BROADCAST_EVENT.lock().unwrap(); 263 - let tx = tx.as_ref().clone(); 264 - let m = deserialize_metadata(metadata.as_str()).unwrap(); 265 - let ev = AVTransportEvent::AVTransportURIMetaData { 266 - sid: sid.clone(), 267 - url: m.url, 268 - title: m.title, 269 - artist: m.artist, 270 - album: m.album, 271 - album_art_uri: m.album_art_uri, 272 - genre: m.genre, 273 - }; 274 - tx.unwrap().send(Event::AVTransport(ev)).unwrap(); 275 - } 276 - None => {} 251 + if let Some(metadata) = av_transport_uri_metadata { 252 + let tx = BROADCAST_EVENT.lock().unwrap(); 253 + let tx = tx.as_ref(); 254 + let m = deserialize_metadata(metadata.as_str()).unwrap(); 255 + let ev = AVTransportEvent::AVTransportURIMetaData { 256 + sid: sid.clone(), 257 + url: m.url, 258 + title: m.title, 259 + artist: m.artist, 260 + album: m.album, 261 + album_art_uri: m.album_art_uri, 262 + genre: m.genre, 263 + }; 264 + tx.unwrap().send(Event::AVTransport(ev)).unwrap(); 277 265 } 278 266 279 - match current_track_metadata { 280 - Some(metadata) => { 281 - let m = deserialize_metadata(metadata.as_str()).unwrap(); 282 - let tx = BROADCAST_EVENT.lock().unwrap(); 283 - let tx = tx.as_ref().clone(); 284 - let ev = AVTransportEvent::CurrentTrackMetadata { 285 - sid: sid.clone(), 286 - url: m.url, 287 - title: m.title, 288 - artist: m.artist, 289 - album: m.album, 290 - album_art_uri: m.album_art_uri, 291 - genre: m.genre, 292 - }; 293 - tx.unwrap().send(Event::AVTransport(ev)).unwrap(); 294 - } 295 - None => {} 267 + if let Some(metadata) = current_track_metadata { 268 + let m = deserialize_metadata(metadata.as_str()).unwrap(); 269 + let tx = BROADCAST_EVENT.lock().unwrap(); 270 + let tx = tx.as_ref(); 271 + let ev = AVTransportEvent::CurrentTrackMetadata { 272 + sid: sid.clone(), 273 + url: m.url, 274 + title: m.title, 275 + artist: m.artist, 276 + album: m.album, 277 + album_art_uri: m.album_art_uri, 278 + genre: m.genre, 279 + }; 280 + tx.unwrap().send(Event::AVTransport(ev)).unwrap(); 296 281 } 297 282 298 283 Ok::<_, hyper::Error>(Response::new(Body::empty())) ··· 311 296 }); 312 297 313 298 tokio::spawn(async move { 314 - while !*stop.lock().unwrap() { 299 + while !*stop.lock().await { 315 300 tokio::time::sleep(Duration::from_millis(100)).await; 316 301 } 317 302 }); ··· 319 304 Ok((address, port)) 320 305 } 321 306 322 - async fn release_eventing_server(&mut self) -> Result<(), Error> { 323 - let mut stop = self.stop.lock().unwrap(); 307 + async fn release_eventing_server(&mut self) -> Result<()> { 308 + let mut stop = self.stop.lock().await; 324 309 *stop = true; 325 310 Ok(()) 326 311 } 327 312 } 328 313 329 314 fn resolve_service(service_id: &str) -> String { 330 - match service_id.contains(":") { 315 + match service_id.contains(':') { 331 316 true => service_id.to_string(), 332 317 false => format!("urn:upnp-org:serviceId:{}", service_id), 333 318 }
+33 -35
src/discovery.rs
··· 1 - use anyhow::Error; 1 + use anyhow::{anyhow, Result}; 2 2 use async_stream::stream; 3 3 use futures_util::Stream; 4 - use socket2::{Domain, Protocol, Socket, Type}; 5 4 use std::collections::HashMap; 6 - use std::mem::MaybeUninit; 7 - use std::net::{IpAddr, Ipv4Addr, SocketAddr}; 5 + use std::net::{Ipv4Addr, SocketAddr}; 8 6 use std::str; 9 - use std::thread::sleep; 10 - use std::time::Duration; 7 + use tokio::net::UdpSocket; 11 8 12 9 use crate::parser::parse_location; 13 10 use crate::types::Device; ··· 19 16 ST: ssdp:all\r\n\ 20 17 \r\n"; 21 18 22 - pub fn discover_pnp_locations() -> impl Stream<Item = Device> { 23 - // Create a UDP socket 24 - let socket = Socket::new(Domain::IPV4, Type::DGRAM, Some(Protocol::UDP)).unwrap(); 19 + pub async fn discover_pnp_locations() -> Result<impl Stream<Item = Device>> { 20 + let any: SocketAddr = ([0, 0, 0, 0], 0).into(); 21 + let socket = UdpSocket::bind(any).await?; 22 + socket.join_multicast_v4(Ipv4Addr::new(239, 255, 255, 250), Ipv4Addr::new(0, 0, 0, 0))?; 25 23 26 24 // Set the socket address to the multicast IP and port for UPnP device discovery 27 - let socket_addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(239, 255, 255, 250)), 1900).into(); 28 - 29 - // Join the UPnP multicast group 30 - socket 31 - .join_multicast_v4( 32 - &Ipv4Addr::new(239, 255, 255, 250), 33 - &Ipv4Addr::new(0, 0, 0, 0), 34 - ) 35 - .unwrap(); 25 + let socket_addr: SocketAddr = ([239, 255, 255, 250], 1900).into(); 36 26 37 27 // Send the discovery request 38 28 socket 39 29 .send_to(DISCOVERY_REQUEST.as_bytes(), &socket_addr) 40 - .unwrap(); 30 + .await?; 41 31 42 - stream! { 32 + Ok(stream! { 43 33 loop { 44 - // Receive the discovery response 45 - let mut buf = [MaybeUninit::uninit(); 2048]; 46 - let (size, _) = socket.recv_from(&mut buf).unwrap(); 47 - // Convert the response to a string 48 - let response = 49 - str::from_utf8(unsafe { std::slice::from_raw_parts(buf.as_ptr() as *const u8, size) }) 50 - .unwrap(); 51 - let headers = parse_raw_http_response(response).unwrap(); 52 - let location = *headers.get("location").unwrap(); 53 - yield parse_location(location).await.unwrap(); 54 - sleep(Duration::from_millis(500)); 55 - } 56 - } 34 + async fn get_next(socket: &UdpSocket) -> Result<String> { 35 + // Receive the discovery response 36 + let mut buf = [0; 2048]; 37 + let (size, _) = socket.recv_from(&mut buf).await?; 38 + // Convert the response to a string 39 + let response = 40 + str::from_utf8(unsafe { std::slice::from_raw_parts(buf.as_ptr() as *const u8, size) })?; 41 + let headers = parse_raw_http_response(response)?; 42 + let location = headers.get("location") 43 + .ok_or_else(|| anyhow!("Response header missing location"))? 44 + .to_string(); 45 + Ok(location) 46 + } 47 + 48 + if let Ok(location) = get_next(&socket).await { 49 + if let Ok(device) = parse_location(&location).await { 50 + yield device; 51 + } 52 + } 53 + } 54 + }) 57 55 } 58 56 59 - fn parse_raw_http_response(response_str: &str) -> Result<HashMap<String, &str>, Error> { 57 + fn parse_raw_http_response(response_str: &str) -> Result<HashMap<String, &str>> { 60 58 let mut headers = HashMap::new(); 61 59 62 60 match response_str.split("\r\n\r\n").next() { ··· 70 68 } 71 69 Ok(headers) 72 70 } 73 - None => Err(Error::msg("Invalid HTTP response")), 71 + None => Err(anyhow!("Invalid HTTP response")), 74 72 } 75 73 }
+1 -1
src/media_renderer.rs
··· 1 - use std::{collections::HashMap, sync::mpsc, time::Duration}; 1 + use std::{collections::HashMap, sync::mpsc}; 2 2 3 3 use anyhow::{Error, Ok}; 4 4 use async_stream::stream;
+1 -1
src/media_server.rs
··· 37 37 38 38 let ip = self.device_client.ip(); 39 39 40 - Ok(parse_browse_response(&response, &ip)?) 40 + parse_browse_response(&response, &ip) 41 41 } 42 42 43 43 pub async fn get_sort_capabilities(&self) -> Result<(), Error> {
+154 -165
src/parser.rs
··· 1 1 use std::time::Duration; 2 2 3 3 use crate::types::{Action, Argument, Container, Device, Item, Metadata, Service, TransportInfo}; 4 - use anyhow::Error; 4 + use anyhow::{anyhow, Result}; 5 5 use elementtree::Element; 6 6 use surf::{http::Method, Client, Config, Url}; 7 7 use xml::reader::XmlEvent; 8 8 use xml::EventReader; 9 9 10 - pub async fn parse_location(location: &str) -> Result<Device, Error> { 10 + pub async fn parse_location(location: &str) -> Result<Device> { 11 11 let client: Client = Config::new() 12 12 .set_timeout(Some(Duration::from_secs(5))) 13 - .try_into() 14 - .unwrap(); 15 - let req = surf::Request::new(Method::Get, location.parse().unwrap()); 16 - let xml_root = client.recv_string(req).await.unwrap(); 17 - 18 - let mut device: Device = Device::default(); 13 + .try_into()?; 14 + let req = surf::Request::new(Method::Get, location.parse()?); 15 + let xml_root = client 16 + .recv_string(req) 17 + .await 18 + .map_err(|e| anyhow!("Failed to retrieve xml from device endpoint: {}", e))?; 19 19 20 - device.location = location.to_string(); 20 + let mut device = Device { 21 + location: location.to_string(), 22 + ..Default::default() 23 + }; 21 24 22 25 device.device_type = parse_attribute( 23 26 &xml_root, ··· 67 70 )?; 68 71 69 72 let base_url = location.split('/').take(3).collect::<Vec<&str>>().join("/"); 70 - device.services = parse_services(&base_url, &xml_root).await; 73 + device.services = parse_services(&base_url, &xml_root).await?; 71 74 72 75 Ok(device) 73 76 } 74 77 75 - fn parse_attribute(xml_root: &str, xml_name: &str) -> Result<String, Error> { 78 + fn parse_attribute(xml_root: &str, xml_name: &str) -> Result<String> { 76 79 let root = Element::from_reader(xml_root.as_bytes())?; 77 80 let mut xml_name = xml_name.split('/'); 78 - match root.find(xml_name.next().unwrap()) { 81 + match root.find( 82 + xml_name 83 + .next() 84 + .ok_or_else(|| anyhow!("xml_name ended unexpectedly"))?, 85 + ) { 79 86 Some(element) => { 80 - let element = element.find(xml_name.next().unwrap()); 87 + let element = element.find( 88 + xml_name 89 + .next() 90 + .ok_or_else(|| anyhow!("xml_name ended unexpectedly"))?, 91 + ); 81 92 match element { 82 93 Some(element) => { 83 94 return Ok(element.text().to_string()); 84 95 } 85 96 None => { 86 - return Ok("".to_string()); 97 + Ok("".to_string()) 87 98 } 88 99 } 89 100 } ··· 91 102 } 92 103 } 93 104 94 - pub async fn parse_services(base_url: &str, xml_root: &str) -> Vec<Service> { 95 - let root = Element::from_reader(xml_root.as_bytes()).unwrap(); 105 + pub async fn parse_services(base_url: &str, xml_root: &str) -> Result<Vec<Service>> { 106 + let root = Element::from_reader(xml_root.as_bytes())?; 96 107 let device = root 97 108 .find("{urn:schemas-upnp-org:device-1-0}device") 98 - .unwrap(); 109 + .ok_or_else(|| anyhow!("Invalid response from device"))?; 99 110 100 111 let mut services_with_actions: Vec<Service> = vec![]; 101 112 if let Some(service_list) = device.find("{urn:schemas-upnp-org:device-1-0}serviceList") { 102 - let services = service_list.children(); 113 + let xml_services = service_list.children(); 103 114 104 - let services: Vec<Service> = services 105 - .into_iter() 106 - .map(|item| Service { 107 - service_type: item 115 + let mut services = Vec::new(); 116 + for xml_service in xml_services { 117 + let mut service = Service { 118 + service_type: xml_service 108 119 .find("{urn:schemas-upnp-org:device-1-0}serviceType") 109 - .unwrap() 120 + .ok_or_else(|| anyhow!("Service missing serviceType"))? 110 121 .text() 111 122 .to_string(), 112 - service_id: item 123 + service_id: xml_service 113 124 .find("{urn:schemas-upnp-org:device-1-0}serviceId") 114 - .unwrap() 125 + .ok_or_else(|| anyhow!("Service missing serviceId"))? 115 126 .text() 116 127 .to_string(), 117 - control_url: item 128 + control_url: xml_service 118 129 .find("{urn:schemas-upnp-org:device-1-0}controlURL") 119 - .unwrap() 130 + .ok_or_else(|| anyhow!("Service missing controlURL"))? 120 131 .text() 121 132 .to_string(), 122 - event_sub_url: item 133 + event_sub_url: xml_service 123 134 .find("{urn:schemas-upnp-org:device-1-0}eventSubURL") 124 - .unwrap() 135 + .ok_or_else(|| anyhow!("Service missing eventSubURL"))? 125 136 .text() 126 137 .to_string(), 127 - scpd_url: item 138 + scpd_url: xml_service 128 139 .find("{urn:schemas-upnp-org:device-1-0}SCPDURL") 129 - .unwrap() 140 + .ok_or_else(|| anyhow!("Service missing SCPDURL"))? 130 141 .text() 131 142 .to_string(), 132 143 actions: vec![], 133 - }) 134 - .map(|mut service| { 135 - service.control_url = build_absolute_url(base_url, &service.control_url); 136 - service.event_sub_url = build_absolute_url(base_url, &service.event_sub_url); 137 - service.scpd_url = build_absolute_url(base_url, &service.scpd_url); 138 - service 139 - }) 140 - .collect(); 144 + }; 145 + 146 + service.control_url = build_absolute_url(base_url, &service.control_url)?; 147 + service.event_sub_url = build_absolute_url(base_url, &service.event_sub_url)?; 148 + service.scpd_url = build_absolute_url(base_url, &service.scpd_url)?; 149 + 150 + services.push(service); 151 + } 141 152 142 153 for service in &services { 143 154 let mut service = service.clone(); 144 - service.actions = parse_service_description(&service.scpd_url).await; 155 + service.actions = parse_service_description(&service.scpd_url).await?; 145 156 services_with_actions.push(service); 146 157 } 147 158 } 148 159 149 - services_with_actions 160 + Ok(services_with_actions) 150 161 } 151 162 152 - fn build_absolute_url(base_url: &str, relative_url: &str) -> String { 153 - let base_url = Url::parse(base_url).unwrap(); 154 - base_url.join(relative_url).unwrap().to_string() 163 + fn build_absolute_url(base_url: &str, relative_url: &str) -> Result<String> { 164 + let base_url = Url::parse(base_url)?; 165 + Ok(base_url.join(relative_url)?.to_string()) 155 166 } 156 167 157 - pub async fn parse_service_description(scpd_url: &str) -> Vec<Action> { 168 + pub async fn parse_service_description(scpd_url: &str) -> Result<Vec<Action>> { 158 169 let client: Client = Config::new() 159 170 .set_timeout(Some(Duration::from_secs(5))) 160 - .try_into() 161 - .unwrap(); 162 - let req = surf::Request::new(Method::Get, scpd_url.parse().unwrap()); 163 - if let Ok(xml_root) = client.recv_string(req).await { 164 - if let Ok(root) = Element::from_reader(xml_root.as_bytes()) { 165 - let action_list = root.find("{urn:schemas-upnp-org:service-1-0}actionList"); 171 + .try_into()?; 172 + let req = surf::Request::new(Method::Get, scpd_url.parse()?); 166 173 167 - if action_list.is_none() { 168 - return vec![]; 169 - } 174 + let xml_root = client 175 + .recv_string(req) 176 + .await 177 + .map_err(|e| anyhow!("Failed to retrieve xml response from device: {}", e))?; 178 + let root = Element::from_reader(xml_root.as_bytes())?; 170 179 171 - let action_list = action_list.unwrap().children(); 172 - let actions: Vec<Action> = action_list 173 - .into_iter() 174 - .map(|item| { 175 - let name = item 180 + let action_list = match root.find("{urn:schemas-upnp-org:service-1-0}actionList") { 181 + Some(action_list) => action_list, 182 + None => return Ok(vec![]), 183 + }; 184 + 185 + let mut actions = Vec::new(); 186 + for xml_action in action_list.children() { 187 + let mut action = Action { 188 + name: xml_action 189 + .find("{urn:schemas-upnp-org:service-1-0}name") 190 + .ok_or_else(|| anyhow!("Service::Action missing name"))? 191 + .text() 192 + .to_string(), 193 + arguments: vec![], 194 + }; 195 + 196 + if let Some(arguments) = xml_action.find("{urn:schemas-upnp-org:service-1-0}argumentList") { 197 + for xml_argument in arguments.children() { 198 + let argument = Argument { 199 + name: xml_argument 176 200 .find("{urn:schemas-upnp-org:service-1-0}name") 177 - .unwrap() 178 - .text(); 179 - let arguments = item.find("{urn:schemas-upnp-org:service-1-0}argumentList"); 180 - let arguments = arguments.unwrap().children(); 181 - let arguments = arguments.into_iter().map(|item| { 182 - let name = item 183 - .find("{urn:schemas-upnp-org:service-1-0}name") 184 - .unwrap() 185 - .text(); 186 - let direction = item 187 - .find("{urn:schemas-upnp-org:service-1-0}direction") 188 - .unwrap() 189 - .text(); 190 - let related_state_variable = item 191 - .find("{urn:schemas-upnp-org:service-1-0}relatedStateVariable") 192 - .unwrap() 193 - .text(); 194 - Argument { 195 - name: name.to_string(), 196 - direction: direction.to_string(), 197 - related_state_variable: related_state_variable.to_string(), 198 - } 199 - }); 200 - Action { 201 - name: name.to_string(), 202 - arguments: arguments.collect(), 203 - } 204 - }) 205 - .collect(); 206 - return actions; 201 + .ok_or_else(|| anyhow!("Service::Action::Argument missing name"))? 202 + .text() 203 + .to_string(), 204 + direction: xml_argument 205 + .find("{urn:schemas-upnp-org:service-1-0}direction") 206 + .ok_or_else(|| anyhow!("Service::Action::Argument missing direction"))? 207 + .text() 208 + .to_string(), 209 + related_state_variable: xml_argument 210 + .find("{urn:schemas-upnp-org:service-1-0}relatedStateVariable") 211 + .ok_or_else(|| { 212 + anyhow!("Service::Action::Argument missing relatedStateVariable") 213 + })? 214 + .text() 215 + .to_string(), 216 + }; 217 + action.arguments.push(argument); 218 + } 207 219 } 220 + actions.push(action); 208 221 } 209 - vec![] 222 + Ok(actions) 210 223 } 211 224 212 - pub fn parse_volume(xml_root: &str) -> Result<u8, Error> { 225 + pub fn parse_volume(xml_root: &str) -> Result<u8> { 213 226 let parser = EventReader::from_str(xml_root); 214 227 let mut in_current_volume = false; 215 228 let mut current_volume: Option<u8> = None; ··· 227 240 } 228 241 Ok(XmlEvent::Characters(volume)) => { 229 242 if in_current_volume { 230 - current_volume = Some(volume.parse().unwrap()); 243 + current_volume = Some(volume.parse()?); 231 244 } 232 245 } 233 246 _ => {} 234 247 } 235 248 } 236 - Ok(current_volume.unwrap()) 249 + current_volume.ok_or_else(|| anyhow!("Invalid response from device")) 237 250 } 238 251 239 - pub fn parse_duration(xml_root: &str) -> Result<u32, Error> { 252 + pub fn parse_duration(xml_root: &str) -> Result<u32> { 240 253 let parser = EventReader::from_str(xml_root); 241 254 let mut in_duration = false; 242 255 let mut duration: Option<String> = None; ··· 254 267 } 255 268 Ok(XmlEvent::Characters(duration_str)) => { 256 269 if in_duration { 257 - let duration_str = duration_str.replace(":", ""); 270 + let duration_str = duration_str.replace(':', ""); 258 271 duration = Some(duration_str); 259 272 } 260 273 } ··· 262 275 } 263 276 } 264 277 265 - let duration = duration.unwrap(); 266 - let hours = duration[0..2].parse::<u32>().unwrap(); 267 - let minutes = duration[2..4].parse::<u32>().unwrap(); 268 - let seconds = duration[4..6].parse::<u32>().unwrap(); 278 + let duration = duration.ok_or_else(|| anyhow!("Invalid response from device"))?; 279 + let hours = duration[0..2].parse::<u32>()?; 280 + let minutes = duration[2..4].parse::<u32>()?; 281 + let seconds = duration[4..6].parse::<u32>()?; 269 282 Ok(hours * 3600 + minutes * 60 + seconds) 270 283 } 271 284 272 - pub fn parse_position(xml_root: &str) -> Result<u32, Error> { 285 + pub fn parse_position(xml_root: &str) -> Result<u32> { 273 286 let parser = EventReader::from_str(xml_root); 274 287 let mut in_position = false; 275 288 let mut position: Option<String> = None; ··· 287 300 } 288 301 Ok(XmlEvent::Characters(position_str)) => { 289 302 if in_position { 290 - let position_str = position_str.replace(":", ""); 303 + let position_str = position_str.replace(':', ""); 291 304 position = Some(position_str); 292 305 } 293 306 } ··· 295 308 } 296 309 } 297 310 298 - let position = position.unwrap(); 299 - let hours = position[0..2].parse::<u32>().unwrap(); 300 - let minutes = position[2..4].parse::<u32>().unwrap(); 301 - let seconds = position[4..6].parse::<u32>().unwrap(); 311 + let position = position.ok_or_else(|| anyhow!("Invalid response from device"))?; 312 + let hours = position[0..2].parse::<u32>()?; 313 + let minutes = position[2..4].parse::<u32>()?; 314 + let seconds = position[4..6].parse::<u32>()?; 302 315 Ok(hours * 3600 + minutes * 60 + seconds) 303 316 } 304 317 305 - pub fn parse_supported_protocols(xml_root: &str) -> Result<Vec<String>, Error> { 318 + pub fn parse_supported_protocols(xml_root: &str) -> Result<Vec<String>> { 306 319 let parser = EventReader::from_str(xml_root); 307 320 let mut in_protocol = false; 308 321 let mut protocols: String = "".to_string(); ··· 326 339 _ => {} 327 340 } 328 341 } 329 - Ok(protocols.split(",").map(|s| s.to_string()).collect()) 342 + Ok(protocols.split(',').map(|s| s.to_string()).collect()) 330 343 } 331 344 332 - pub fn parse_last_change(xml_root: &str) -> Result<Option<String>, Error> { 345 + pub fn parse_last_change(xml_root: &str) -> Result<Option<String>> { 333 346 let parser = EventReader::from_str(xml_root); 334 347 let mut result = None; 335 348 let mut in_last_change = false; ··· 356 369 Ok(result) 357 370 } 358 371 359 - pub fn parse_current_play_mode(xml_root: &str) -> Result<Option<String>, Error> { 372 + pub fn parse_current_play_mode(xml_root: &str) -> Result<Option<String>> { 360 373 let parser = EventReader::from_str(xml_root); 361 374 let mut current_play_mode: Option<String> = None; 362 - for e in parser { 363 - match e { 364 - Ok(XmlEvent::StartElement { 365 - name, attributes, .. 366 - }) => { 367 - if name.local_name == "CurrentPlayMode" { 368 - for attr in attributes { 369 - if attr.name.local_name == "val" { 370 - current_play_mode = Some(attr.value); 371 - } 375 + for e in parser.into_iter().flatten() { 376 + if let XmlEvent::StartElement { name, attributes, .. } = e { 377 + if name.local_name == "CurrentPlayMode" { 378 + for attr in attributes { 379 + if attr.name.local_name == "val" { 380 + current_play_mode = Some(attr.value); 372 381 } 373 382 } 374 383 } 375 - _ => {} 376 384 } 377 385 } 378 386 Ok(current_play_mode) 379 387 } 380 388 381 - pub fn parse_transport_state(xml_root: &str) -> Result<Option<String>, Error> { 389 + pub fn parse_transport_state(xml_root: &str) -> Result<Option<String>> { 382 390 let parser = EventReader::from_str(xml_root); 383 391 let mut transport_state: Option<String> = None; 384 - for e in parser { 385 - match e { 386 - Ok(XmlEvent::StartElement { 387 - name, attributes, .. 388 - }) => { 389 - if name.local_name == "TransportState" { 390 - for attr in attributes { 391 - if attr.name.local_name == "val" { 392 - transport_state = Some(attr.value); 393 - } 392 + for e in parser.into_iter().flatten() { 393 + if let XmlEvent::StartElement { name, attributes, .. } = e { 394 + if name.local_name == "TransportState" { 395 + for attr in attributes { 396 + if attr.name.local_name == "val" { 397 + transport_state = Some(attr.value); 394 398 } 395 399 } 396 400 } 397 - _ => {} 398 401 } 399 402 } 400 403 Ok(transport_state) 401 404 } 402 405 403 - pub fn parse_av_transport_uri_metadata(xml_root: &str) -> Result<Option<String>, Error> { 406 + pub fn parse_av_transport_uri_metadata(xml_root: &str) -> Result<Option<String>> { 404 407 let parser = EventReader::from_str(xml_root); 405 408 let mut av_transport_uri_metadata: Option<String> = None; 406 - for e in parser { 407 - match e { 408 - Ok(XmlEvent::StartElement { 409 - name, attributes, .. 410 - }) => { 411 - if name.local_name == "AVTransportURIMetaData" { 412 - for attr in attributes { 413 - if attr.name.local_name == "val" { 414 - av_transport_uri_metadata = Some(attr.value); 415 - } 409 + for e in parser.into_iter().flatten() { 410 + if let XmlEvent::StartElement { name, attributes, .. } = e { 411 + if name.local_name == "AVTransportURIMetaData" { 412 + for attr in attributes { 413 + if attr.name.local_name == "val" { 414 + av_transport_uri_metadata = Some(attr.value); 416 415 } 417 416 } 418 417 } 419 - _ => {} 420 418 } 421 419 } 422 420 Ok(av_transport_uri_metadata) 423 421 } 424 422 425 - pub fn parse_current_track_metadata(xml_root: &str) -> Result<Option<String>, Error> { 423 + pub fn parse_current_track_metadata(xml_root: &str) -> Result<Option<String>> { 426 424 let parser = EventReader::from_str(xml_root); 427 425 let mut current_track_metadata: Option<String> = None; 428 - for e in parser { 429 - match e { 430 - Ok(XmlEvent::StartElement { 431 - name, attributes, .. 432 - }) => { 433 - if name.local_name == "CurrentTrackMetaData" { 434 - for attr in attributes { 435 - if attr.name.local_name == "val" { 436 - current_track_metadata = Some(attr.value); 437 - } 426 + for e in parser.into_iter().flatten() { 427 + if let XmlEvent::StartElement { name, attributes, .. } = e { 428 + if name.local_name == "CurrentTrackMetaData" { 429 + for attr in attributes { 430 + if attr.name.local_name == "val" { 431 + current_track_metadata = Some(attr.value); 438 432 } 439 433 } 440 434 } 441 - _ => {} 442 435 } 443 436 } 444 437 Ok(current_track_metadata) 445 438 } 446 439 447 - pub fn deserialize_metadata(xml: &str) -> Result<Metadata, Error> { 440 + pub fn deserialize_metadata(xml: &str) -> Result<Metadata> { 448 441 let parser = EventReader::from_str(xml); 449 442 let mut in_title = false; 450 443 let mut in_artist = false; ··· 522 515 }) 523 516 } 524 517 525 - pub fn parse_browse_response(xml: &str, ip: &str) -> Result<(Vec<Container>, Vec<Item>), Error> { 518 + pub fn parse_browse_response(xml: &str, ip: &str) -> Result<(Vec<Container>, Vec<Item>)> { 526 519 let parser = EventReader::from_str(xml); 527 520 let mut in_result = false; 528 521 let mut result: (Vec<Container>, Vec<Item>) = (Vec::new(), Vec::new()); ··· 550 543 Ok(result) 551 544 } 552 545 553 - pub fn deserialize_content_directory( 554 - xml: &str, 555 - ip: &str, 556 - ) -> Result<(Vec<Container>, Vec<Item>), Error> { 546 + pub fn deserialize_content_directory(xml: &str, ip: &str) -> Result<(Vec<Container>, Vec<Item>)> { 557 547 let parser = EventReader::from_str(xml); 558 548 let mut in_container = false; 559 549 let mut in_item = false; ··· 625 615 items.last_mut().unwrap().protocol_info = attr.value.clone(); 626 616 } 627 617 if attr.name.local_name == "size" { 628 - items.last_mut().unwrap().size = 629 - Some(attr.value.parse::<u64>().unwrap()); 618 + items.last_mut().unwrap().size = Some(attr.value.parse::<u64>()?); 630 619 } 631 620 if attr.name.local_name == "duration" { 632 621 items.last_mut().unwrap().duration = Some(attr.value.clone()); ··· 712 701 Ok((containers, items)) 713 702 } 714 703 715 - pub fn parse_transport_info(xml: &str) -> Result<TransportInfo, Error> { 704 + pub fn parse_transport_info(xml: &str) -> Result<TransportInfo> { 716 705 let parser = EventReader::from_str(xml); 717 706 let mut in_transport_state = false; 718 707 let mut in_transport_status = false;