M Cargo.lock => Cargo.lock +97 -0
@@ 11,6 11,11 @@ version = "0.5.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
+name = "cc"
+version = "1.0.54"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
name = "cfg-if"
version = "0.1.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ 159,6 164,15 @@ dependencies = [
]
[[package]]
+name = "mac_address"
+version = "1.0.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "nix 0.16.1 (registry+https://github.com/rust-lang/crates.io-index)",
+ "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
name = "memchr"
version = "2.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ 203,6 217,18 @@ dependencies = [
]
[[package]]
+name = "nix"
+version = "0.16.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
+ "cc 1.0.54 (registry+https://github.com/rust-lang/crates.io-index)",
+ "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
+ "libc 0.2.70 (registry+https://github.com/rust-lang/crates.io-index)",
+ "void 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
name = "num_cpus"
version = "1.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ 217,6 243,16 @@ version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
+name = "os_info"
+version = "2.0.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)",
+ "serde 1.0.110 (registry+https://github.com/rust-lang/crates.io-index)",
+ "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
name = "pin-project"
version = "0.4.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ 271,6 307,24 @@ dependencies = [
]
[[package]]
+name = "serde"
+version = "1.0.110"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "serde_derive 1.0.110 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "serde_derive"
+version = "1.0.110"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "proc-macro2 1.0.17 (registry+https://github.com/rust-lang/crates.io-index)",
+ "quote 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)",
+ "syn 1.0.23 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
name = "slab"
version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ 286,6 340,24 @@ dependencies = [
]
[[package]]
+name = "thiserror"
+version = "1.0.19"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "thiserror-impl 1.0.19 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "thiserror-impl"
+version = "1.0.19"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "proc-macro2 1.0.17 (registry+https://github.com/rust-lang/crates.io-index)",
+ "quote 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)",
+ "syn 1.0.23 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
name = "tokio"
version = "0.2.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ 296,6 368,7 @@ dependencies = [
"mio 0.6.22 (registry+https://github.com/rust-lang/crates.io-index)",
"num_cpus 1.13.0 (registry+https://github.com/rust-lang/crates.io-index)",
"pin-project-lite 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)",
+ "slab 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)",
"tokio-macros 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)",
]
@@ 328,6 401,16 @@ version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
+name = "uuid"
+version = "0.8.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
+name = "void"
+version = "1.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
name = "winapi"
version = "0.2.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ 371,8 454,12 @@ version = "0.1.0"
dependencies = [
"bytes 0.5.4 (registry+https://github.com/rust-lang/crates.io-index)",
"futures 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)",
+ "mac_address 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)",
+ "os_info 2.0.6 (registry+https://github.com/rust-lang/crates.io-index)",
+ "thiserror 1.0.19 (registry+https://github.com/rust-lang/crates.io-index)",
"tokio 0.2.21 (registry+https://github.com/rust-lang/crates.io-index)",
"tokio-util 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
+ "uuid 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)",
"yooper_derive 0.1.0",
]
@@ 388,6 475,7 @@ dependencies = [
[metadata]
"checksum bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693"
"checksum bytes 0.5.4 (registry+https://github.com/rust-lang/crates.io-index)" = "130aac562c0dd69c56b3b1cc8ffd2e17be31d0b6c25b61c96b76231aa23e39e1"
+"checksum cc 1.0.54 (registry+https://github.com/rust-lang/crates.io-index)" = "7bbb73db36c1246e9034e307d0fba23f9a2e251faa47ade70c1bd252220c8311"
"checksum cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)" = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822"
"checksum fuchsia-zircon 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "2e9763c69ebaae630ba35f74888db465e49e259ba1bc0eda7d06f4a067615d82"
"checksum fuchsia-zircon-sys 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "3dcaa9ae7725d12cdb85b3ad99a434db70b468c09ded17e012d86b5c1010f7a7"
@@ 406,12 494,15 @@ dependencies = [
"checksum lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
"checksum libc 0.2.70 (registry+https://github.com/rust-lang/crates.io-index)" = "3baa92041a6fec78c687fa0cc2b3fae8884f743d672cf551bed1d6dac6988d0f"
"checksum log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)" = "14b6052be84e6b71ab17edffc2eeabf5c2c3ae1fdb464aae35ac50c67a44e1f7"
+"checksum mac_address 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "5648adc0602159de73f1e955f7c0941ee94382c5b6f66f7c10d15816472d9d66"
"checksum memchr 2.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "3728d817d99e5ac407411fa471ff9800a778d88a24685968b36824eaf4bee400"
"checksum mio 0.6.22 (registry+https://github.com/rust-lang/crates.io-index)" = "fce347092656428bc8eaf6201042cb551b8d67855af7374542a92a0fbfcac430"
"checksum miow 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "8c1f2f3b1cf331de6896aabf6e9d55dca90356cc9960cca7eaaf408a355ae919"
"checksum net2 0.2.34 (registry+https://github.com/rust-lang/crates.io-index)" = "2ba7c918ac76704fb42afcbbb43891e72731f3dcca3bef2a19786297baf14af7"
+"checksum nix 0.16.1 (registry+https://github.com/rust-lang/crates.io-index)" = "dd0eaf8df8bab402257e0a5c17a254e4cc1f72a93588a1ddfb5d356c801aa7cb"
"checksum num_cpus 1.13.0 (registry+https://github.com/rust-lang/crates.io-index)" = "05499f3756671c15885fee9034446956fff3f243d6077b91e5767df161f766b3"
"checksum once_cell 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "0b631f7e854af39a1739f401cf34a8a013dfe09eac4fa4dba91e9768bd28168d"
+"checksum os_info 2.0.6 (registry+https://github.com/rust-lang/crates.io-index)" = "67cc1fe7b45f7e51f755195fd86b0483dbae30fdcf831a3254438d29118d12c4"
"checksum pin-project 0.4.17 (registry+https://github.com/rust-lang/crates.io-index)" = "edc93aeee735e60ecb40cf740eb319ff23eab1c5748abfdb5c180e4ce49f7791"
"checksum pin-project-internal 0.4.17 (registry+https://github.com/rust-lang/crates.io-index)" = "e58db2081ba5b4c93bd6be09c40fd36cb9193a8336c384f3b40012e531aa7e40"
"checksum pin-project-lite 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "f7505eeebd78492e0f6108f7171c4948dbb120ee8119d9d77d0afa5469bef67f"
@@ 420,12 511,18 @@ dependencies = [
"checksum proc-macro-nested 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "8e946095f9d3ed29ec38de908c22f95d9ac008e424c7bcae54c75a79c527c694"
"checksum proc-macro2 1.0.17 (registry+https://github.com/rust-lang/crates.io-index)" = "1502d12e458c49a4c9cbff560d0fe0060c252bc29799ed94ca2ed4bb665a0101"
"checksum quote 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)" = "54a21852a652ad6f610c9510194f398ff6f8692e334fd1145fed931f7fbe44ea"
+"checksum serde 1.0.110 (registry+https://github.com/rust-lang/crates.io-index)" = "99e7b308464d16b56eba9964e4972a3eee817760ab60d88c3f86e1fecb08204c"
+"checksum serde_derive 1.0.110 (registry+https://github.com/rust-lang/crates.io-index)" = "818fbf6bfa9a42d3bfcaca148547aa00c7b915bec71d1757aa2d44ca68771984"
"checksum slab 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "c111b5bd5695e56cffe5129854aa230b39c93a305372fdbb2668ca2394eea9f8"
"checksum syn 1.0.23 (registry+https://github.com/rust-lang/crates.io-index)" = "95b5f192649e48a5302a13f2feb224df883b98933222369e4b3b0fe2a5447269"
+"checksum thiserror 1.0.19 (registry+https://github.com/rust-lang/crates.io-index)" = "b13f926965ad00595dd129fa12823b04bbf866e9085ab0a5f2b05b850fbfc344"
+"checksum thiserror-impl 1.0.19 (registry+https://github.com/rust-lang/crates.io-index)" = "893582086c2f98cde18f906265a65b5030a074b1046c674ae898be6519a7f479"
"checksum tokio 0.2.21 (registry+https://github.com/rust-lang/crates.io-index)" = "d099fa27b9702bed751524694adbe393e18b36b204da91eb1cbbbbb4a5ee2d58"
"checksum tokio-macros 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)" = "f0c3acc6aa564495a0f2e1d59fab677cd7f81a19994cfc7f3ad0e64301560389"
"checksum tokio-util 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "be8242891f2b6cbef26a2d7e8605133c2c554cd35b3e4948ea892d6d68436499"
"checksum unicode-xid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "826e7639553986605ec5979c7dd957c7895e93eabed50ab2ffa7f6128a75097c"
+"checksum uuid 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)" = "9fde2f6a4bea1d6e007c4ad38c6839fa71cbb63b6dbf5b595aa38dc9b1093c11"
+"checksum void 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d"
"checksum winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "167dc9d6949a9b857f3451275e911c3f44255842c1f7a76f33c55103a909087a"
"checksum winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)" = "8093091eeb260906a183e6ae1abdba2ef5ef2257a21801128899c3fc699229c6"
"checksum winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "2d315eee3b34aca4797b2da6b13ed88266e6d612562a0c46390af8299fc699bc"
M yooper/Cargo.toml => yooper/Cargo.toml +8 -1
@@ 8,12 8,19 @@ edition = "2018"
bytes = "0.5.4"
yooper_derive = { path = "../yooper_derive" }
futures = "0.3"
+mac_address = "1.0"
+thiserror = "1.0"
+os_info = "2.0"
[dependencies.tokio]
version = "0.2.20"
-features = ["udp", "macros", "rt-threaded"]
+features = ["udp", "macros", "rt-threaded", "time"]
[dependencies.tokio-util]
version = "0.3.1"
features = ["udp", "codec"]
+
+[dependencies.uuid]
+version = "0.8"
+features = ["v1"]<
\ No newline at end of file
A yooper/src/discovery.rs => yooper/src/discovery.rs +135 -0
@@ 0,0 1,135 @@
+use std::net::{Ipv4Addr, SocketAddr};
+use std::collections::HashMap;
+
+use tokio::{
+ select,
+ net::UdpSocket,
+ time::{self, Duration},
+ stream::StreamExt,
+};
+use tokio_util::udp::UdpFramed;
+use uuid::{Uuid, self};
+use mac_address::{get_mac_address, MacAddressError};
+use os_info;
+use std::time::{SystemTime, UNIX_EPOCH};
+use futures::sink::SinkExt;
+
+use crate::{
+ ssdp::message::{
+ Codec, Message, MSearch
+ },
+ Error,
+};
+
+const VERSION: &'static str = env!("CARGO_PKG_VERSION");
+
+const SSDP_ADDRESS: Ipv4Addr = Ipv4Addr::new(239, 255, 255, 250);
+const SSDP_PORT: u16 = 1900;
+
+pub struct Discovery {
+ uuid: Uuid,
+ user_agent: String,
+ socket: UdpFramed<Codec>,
+}
+
+pub struct Device {
+ pub server: String,
+ pub address: SocketAddr,
+ pub services: Vec<Service>,
+}
+
+pub struct Service {
+ pub service_name: String,
+ pub target: String,
+}
+
+impl Discovery {
+ pub async fn new() -> Result<Self, Error> {
+ let socket = UdpSocket::bind((Ipv4Addr::UNSPECIFIED, SSDP_PORT)).await?;
+ socket.join_multicast_v4(SSDP_ADDRESS, Ipv4Addr::UNSPECIFIED)?;
+ socket.set_multicast_ttl_v4(4)?;
+
+ Self::from_socket(socket)
+ }
+
+ pub fn from_socket(socket: UdpSocket) -> Result<Self, Error> {
+ Ok(Self {
+ socket: UdpFramed::new(socket, Codec::new()),
+ uuid: get_uuid()?,
+ user_agent: user_agent(),
+ })
+ }
+
+ pub async fn start_search(&mut self, secs: u16) -> Result<(), Error> {
+ // TODO: secs should be between 1 and 5
+ let msg = Message::MSearch(
+ MSearch {
+ max_wait: Some(secs.to_string()),
+ target: "ssdp:all".into(),
+ user_agent: Some(self.user_agent.clone()),
+
+ tcp_port: None,
+ friendly_name: Some("yooper".into()),
+ uuid: Some(self.uuid.to_string()),
+ }
+ );
+
+ self.socket.send((msg, (SSDP_ADDRESS, SSDP_PORT).into())).await?;
+
+ Ok(())
+ }
+
+ pub async fn find(&mut self, secs: u16) -> Result<Vec<Device>, Error> {
+ let mut map: HashMap<SocketAddr, Device> = HashMap::new();
+ self.start_search(secs).await?;
+
+ let mut delay = time::delay_for(Duration::from_secs(secs.into()));
+
+ loop {
+ select!{
+ msg = self.socket.next() => {
+ match msg {
+ Some(Err(e)) => eprintln!("Error receiving: {:?}", e),
+ Some(Ok((Message::SearchResponse(sr), address))) => {
+ let device = map.entry(address).or_insert(Device {
+ address,
+ server: sr.server,
+ services: Vec::new(),
+
+ });
+ device.services.push(Service{
+ target: sr.target,
+ service_name: sr.unique_service_name
+ })
+ }
+ _ => (),
+ }
+ }
+ _ = &mut delay => {
+ break
+ }
+ };
+ }
+
+ Ok(map.into_iter().map(|(_k, v)| v ).collect())
+ }
+}
+
+fn user_agent() -> String {
+ let info = os_info::get();
+
+ format!("{}/{}.1 upnp/2.0 yooper/{}", info.os_type(), info.version(), VERSION)
+}
+
+fn get_uuid() -> Result<Uuid, Error> {
+ let mac = get_mac_address()?.ok_or(MacAddressError::InternalError)?;
+ let ctx = uuid::v1::Context::new(0);
+
+ let start = SystemTime::now();
+ let since_the_epoch = start
+ .duration_since(UNIX_EPOCH)
+ .expect("Time went backwards");
+ let ts = uuid::v1::Timestamp::from_unix(&ctx, since_the_epoch.as_secs(), since_the_epoch.subsec_nanos());
+
+ Ok(uuid::Uuid::new_v1(ts, &mac.bytes())?)
+}
M yooper/src/errors.rs => yooper/src/errors.rs +26 -30
@@ 1,43 1,39 @@
+use thiserror::Error;
+
use std::convert::Infallible;
use std::num::ParseIntError;
+use mac_address::MacAddressError;
+use uuid;
-#[derive(Debug)]
+#[derive(Error, Debug)]
pub enum Error {
+ #[error("Parse failure: {0}")]
ParseFailure(String),
- ParseIntError(ParseIntError),
+
+ #[error("couldn't parse integer")]
+ ParseIntError(#[from] ParseIntError),
+
+ #[error("missing required header {0}")]
MissingHeader(&'static str),
+
+ #[error("Required constant header had incorrect value {0}")]
IncorrectHeader(&'static str),
- IO(std::io::Error),
- Fmt(std::fmt::Error),
- UnknownPacket, // TODO EKF more descriptive
-}
-impl From<std::io::Error> for Error {
- fn from(e: std::io::Error) -> Self {
- Error::IO(e)
- }
-}
+ #[error("IO Error {0}")]
+ IO(#[from] std::io::Error),
-impl From<std::fmt::Error> for Error {
- fn from(source: std::fmt::Error) -> Self {
- Error::Fmt(source)
- }
-}
+ #[error("Format Error")]
+ Fmt(#[from]std::fmt::Error),
-impl From<&str> for Error {
- fn from(source: &str) -> Self {
- Error::ParseFailure(source.into())
- }
-}
+ #[error("Received a packet we don't understand")]
+ UnknownPacket, // TODO EKF more descriptive
-impl From<ParseIntError> for Error {
- fn from(e: ParseIntError) -> Self {
- Error::ParseIntError(e)
- }
-}
+ #[error("Couldn't discover this computer's MAC address")]
+ MACAddressError(#[from] MacAddressError),
+
+ #[error("if you see this, something's wrong")]
+ Infallible(#[from] Infallible),
-impl From<Infallible> for Error {
- fn from(_: Infallible) -> Self {
- unreachable!()
- }
+ #[error("Failed to generate a UUID")]
+ UUID(#[from] uuid::Error),
}
M yooper/src/lib.rs => yooper/src/lib.rs +1 -0
@@ 1,4 1,5 @@
mod errors;
pub mod ssdp;
+pub mod discovery;
pub use errors::Error;
M yooper/src/main.rs => yooper/src/main.rs +8 -23
@@ 1,16 1,10 @@
use std::error::Error;
use std::net::Ipv4Addr;
-use futures::stream::StreamExt;
-use tokio::net::UdpSocket;
-use tokio_util::udp::UdpFramed;
+use yooper::discovery::Discovery;
// const VERSION: &'static str = env!("CARGO_PKG_VERSION");
// const OS: &'static str = "linux"; //TODO
-const SSDP_ADDRESS: Ipv4Addr = Ipv4Addr::new(239, 255, 255, 250);
-const SSDP_PORT: u16 = 1900;
-
-const WLAN_IP: Ipv4Addr = Ipv4Addr::new(192, 168, 7, 212);
// M-SEARCH * HTTP/1.1
// HOST: 239.255.255.250:1900
@@ 23,23 17,14 @@ const WLAN_IP: Ipv4Addr = Ipv4Addr::new(192, 168, 7, 212);
#[tokio::main]
async fn main() -> Result<(), Box<dyn Error>> {
- let socket = UdpSocket::bind((Ipv4Addr::UNSPECIFIED, SSDP_PORT)).await?;
- println!("bound on {}:{}", SSDP_ADDRESS, SSDP_PORT);
-
- socket.set_multicast_loop_v4(true)?;
- socket.join_multicast_v4(SSDP_ADDRESS, WLAN_IP).unwrap();
-
- socket.set_multicast_ttl_v4(4)?;
- // socket
- // .send_to(discovery.as_bytes(), (SSDP_ADDRESS, SSDP_PORT))
- // .await?;
+ let mut discovery = Discovery::new().await?;
- let mut framed = UdpFramed::new(socket, yooper::ssdp::message::Codec::new());
+ for result in discovery.find(5).await? {
+ println!("{} at {}", result.server, result.address);
+ for service in result.services {
+ println!("∟ {}", service.target)
+ }
- loop {
- let n = framed.next().await;
- println!("------");
- println!("{:#?}", n);
- println!("------");
}
+ Ok(())
}
M yooper/src/ssdp/message.rs => yooper/src/ssdp/message.rs +1 -1
@@ 89,7 89,7 @@ pub struct SearchResponse {
#[header("securelocation.upnp.org")]
pub secure_location: Option<String>,
- server: String,
+ pub server: String,
// TODO: enum
#[header("st")]