From 04488b7ad84d170d61efe3dd79f0c9c2569918af Mon Sep 17 00:00:00 2001 From: Ellie Frost Date: Sat, 30 May 2020 15:07:15 -0400 Subject: [PATCH] Add UniqueServiceName type --- yooper/src/discovery.rs | 22 ++++++++++-------- yooper/src/main.rs | 2 +- yooper/src/ssdp/message.rs | 14 +++++------ yooper/src/ssdp/message/types.rs | 40 +++++++++++++++++++++++++++++++- 4 files changed, 58 insertions(+), 20 deletions(-) diff --git a/yooper/src/discovery.rs b/yooper/src/discovery.rs index 02000db..ee5ca9f 100644 --- a/yooper/src/discovery.rs +++ b/yooper/src/discovery.rs @@ -14,7 +14,7 @@ use tokio_util::udp::UdpFramed; use uuid::{self, Uuid}; use crate::{ - ssdp::message::{Codec, MSearch, Message, SearchTarget}, + ssdp::message::{Codec, MSearch, Message, SearchTarget, UniqueServiceName}, Error, }; @@ -38,12 +38,14 @@ pub struct Device { pub address: SocketAddr, /// A list of discovered services pub services: Vec, + /// the location to retrieve more service information + pub location: String, } /// A Service represents a running service on a device pub struct Service { /// Unique Service Name identifies a unique instance of a device or service. - pub service_name: String, + pub service_name: UniqueServiceName, /// the search target you would use to describe this service pub target: SearchTarget, } @@ -93,7 +95,7 @@ impl Discovery { /// Will block for n secs then return a list of discovered devices /// secs should be between 1 and 5 to comply with pub async fn find(&mut self, secs: u8) -> Result, Error> { - let mut map: HashMap = HashMap::new(); + let mut map: HashMap = HashMap::new(); self.start_search(secs).await?; let mut delay = time::delay_for(Duration::from_secs(secs.into())); @@ -104,16 +106,16 @@ impl Discovery { match msg { Some(Err(e)) => eprintln!("Error receiving: {:?}", e), Some(Ok((Message::SearchResponse(sr), address))) => { - let device = map.entry(address).or_insert(Device { - address, + let uuid = sr.unique_service_name.uuid.clone(); + map.entry(uuid).or_insert(Device{ server: sr.server, + address, services: Vec::new(), - - }); - device.services.push(Service{ + location: sr.secure_location.unwrap_or(sr.location), + }).services.push(Service{ target: sr.target, - service_name: sr.unique_service_name - }) + service_name: sr.unique_service_name, + }); } _ => (), } diff --git a/yooper/src/main.rs b/yooper/src/main.rs index 14b58e5..fb3d54d 100644 --- a/yooper/src/main.rs +++ b/yooper/src/main.rs @@ -17,7 +17,7 @@ async fn main() -> Result<(), Error> { let mut discovery = Discovery::new().await?; for result in discovery.find(5).await? { - println!("{} at {}", result.server, result.address); + println!("{} at {}", result.server, result.location); for service in result.services { println!("∟ {:?}", service.target) } diff --git a/yooper/src/ssdp/message.rs b/yooper/src/ssdp/message.rs index 6c59cfc..6a0c497 100644 --- a/yooper/src/ssdp/message.rs +++ b/yooper/src/ssdp/message.rs @@ -6,7 +6,7 @@ pub(self) mod types; use crate::ssdp::packet::{FromHeaders, FromPacket, ToHeaders, ToPacket}; pub use codec::Codec; -pub use types::SearchTarget; +pub use types::{SearchTarget, UniqueServiceName}; #[derive(ToHeaders, FromHeaders, Debug, PartialEq, Default)] pub struct MSearch { @@ -25,7 +25,7 @@ pub struct MSearch { // TODO: enum /// Field value contains Search Target. #[header("st")] - pub target: types::SearchTarget, + pub target: SearchTarget, /// Field value shall begin with the following “product tokens” (defined /// by HTTP/1.1). The first product token identifes the operating system in the form OS name/OS version, the /// second token represents the UPnP version and shall be UPnP/2.0, and the third token identifes the product @@ -62,9 +62,9 @@ pub struct Available { #[header("securelocation.upnp.org")] pub secure_location: Option, - // TODO: Enum + #[header("nt")] - pub notification_type: String, + pub notification_type: SearchTarget, /// Field value shall begin with the following “product tokens” /// (defined by HTTP/1.1). The first product token identifes the operating /// system in the form OSname/OSversion, the second token represents @@ -73,9 +73,8 @@ pub struct Available { pub server: String, /// Identifies a unique instance of a device or service. - // TODO: Enum #[header("usn")] - pub unique_service_name: String, + pub unique_service_name: UniqueServiceName, /// presents the boot instance of the device expressed according to a monotonically increasing value. #[header("bootid.upnp.org")] pub boot_id: Option, @@ -114,9 +113,8 @@ pub struct SearchResponse { pub target: types::SearchTarget, /// A unique service name for this particular service - // TODO: Enum #[header("usn")] - pub unique_service_name: String, + pub unique_service_name: UniqueServiceName, /// presents the boot instance of the device expressed according to a monotonically increasing value. #[header("bootid.upnp.org")] diff --git a/yooper/src/ssdp/message/types.rs b/yooper/src/ssdp/message/types.rs index a103fc6..10d1a55 100644 --- a/yooper/src/ssdp/message/types.rs +++ b/yooper/src/ssdp/message/types.rs @@ -40,7 +40,7 @@ impl FromStr for ManDiscover { } /// What kind of control point to search for -#[derive(PartialEq, Debug)] +#[derive(PartialEq, Debug, Hash, Clone)] pub enum SearchTarget { /// Search for all devices and services All, @@ -146,3 +146,41 @@ impl Default for SearchTarget { Self::All } } + +#[derive(Default, PartialEq, Debug, Hash, Clone)] +pub struct UniqueServiceName { + pub uuid: String, + pub search_target: Option, +} + +impl FromStr for UniqueServiceName { + type Err = Error; + + fn from_str(s: &str) -> Result { + match s.split("::").collect::>().as_slice() { + [urn, st] if urn.starts_with("uuid:") => { + let uuid = urn[5..].to_string(); + st.parse().map(|st| UniqueServiceName { + uuid, + search_target: Some(st), + }) + } + [urn] if urn.starts_with("uuid:") => Ok(UniqueServiceName { + uuid: urn[5..].to_string(), + search_target: None, + }), + _ => Err(Error::MalformedHeader("usn", s.to_owned())), + } + } +} + +impl ToString for UniqueServiceName { + fn to_string(&self) -> String { + let out = format!("uuid:{}", self.uuid); + if let Some(st) = &self.search_target { + format!("{}::{}", out, st.to_string()) + } else { + out + } + } +} -- 2.45.2