M yooper/src/discovery.rs => yooper/src/discovery.rs +12 -10
@@ 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<Service>,
+ /// 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<Vec<Device>, Error> {
- let mut map: HashMap<SocketAddr, Device> = HashMap::new();
+ let mut map: HashMap<String, Device> = 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,
+ });
}
_ => (),
}
M yooper/src/main.rs => yooper/src/main.rs +1 -1
@@ 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)
}
M yooper/src/ssdp/message.rs => yooper/src/ssdp/message.rs +6 -8
@@ 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<String>,
- // 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<i32>,
@@ 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")]
M yooper/src/ssdp/message/types.rs => yooper/src/ssdp/message/types.rs +39 -1
@@ 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<SearchTarget>,
+}
+
+impl FromStr for UniqueServiceName {
+ type Err = Error;
+
+ fn from_str(s: &str) -> Result<Self, Self::Err> {
+ match s.split("::").collect::<Vec<&str>>().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
+ }
+ }
+}