Skip to content

Commit 442ca13

Browse files
committed
feat: Initial mDNS-SD discovery
1 parent 8a4a827 commit 442ca13

File tree

6 files changed

+204
-0
lines changed

6 files changed

+204
-0
lines changed

Cargo.toml

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
[package]
2+
name = "wot-discovery"
3+
version = "0.1.0"
4+
edition = "2021"
5+
license = "MIT"
6+
description = "Web of Things (WoT) Discovery"
7+
repository = "https://github.com/sifis-home/wot-discovery"
8+
keywords = ["wot", "WebofThings"]
9+
10+
[dependencies]
11+
serde_json = "1.0"
12+
mdns-sd = "0.7.3"
13+
thiserror = "1.0"
14+
reqwest = { version = "0.11", features = ["json"] }
15+
wot-td = "0.3.1"
16+
futures-core = "0.3"
17+
futures-util = "0.3.21"
18+
tracing = "0.1.35"
19+
20+
[dev-dependencies]
21+
tokio = { version = "1.19.2", features = ["macros", "rt-multi-thread"] }
22+
tracing-subscriber = "0.3.11"
23+
wot-serve = "0.3.1"

LICENSE

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
MIT License
2+
3+
Copyright (c) 2023 Luca Barbato
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy
6+
of this software and associated documentation files (the "Software"), to deal
7+
in the Software without restriction, including without limitation the rights
8+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
copies of the Software, and to permit persons to whom the Software is
10+
furnished to do so, subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included in all
13+
copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
SOFTWARE.

README.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,11 @@
22

33
Tiny implementation of WoT [Discovery](https://www.w3.org/TR/wot-discovery/).
44

5+
## Supported Introduction Mechanisms
6+
7+
- [x] mDNS-SD (HTTP/HTTPS)
8+
- [ ] CoRE Resource Directory
9+
510
## Acknowledgements
611

712
This software has been developed in the scope of the H2020 project SIFIS-Home with GA n. 952652.

examples/list.rs

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
use std::future::ready;
2+
3+
use futures_util::StreamExt;
4+
use tracing::{info, warn};
5+
use wot_discovery::Discoverer;
6+
7+
#[tokio::main]
8+
async fn main() -> Result<(), Box<dyn std::error::Error>> {
9+
tracing_subscriber::fmt().init();
10+
11+
let d = Discoverer::new()?;
12+
13+
d.stream()?
14+
.for_each(|thing| {
15+
match thing {
16+
Ok(t) => info!("found {:?} {:?}", t.title, t.id),
17+
Err(e) => warn!("something went wrong {:?}", e),
18+
}
19+
ready(())
20+
})
21+
.await;
22+
23+
Ok(())
24+
}

src/lib.rs

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
//! Web of Things Discovery
2+
//!
3+
//! Discover [Web Of Things](https://www.w3.org/WoT/) that advertise themselves in the network.
4+
//!
5+
//! ## Supported Introduction Mechanisms
6+
//!
7+
//! - [x] [mDNS-SD (HTTP)](https://www.w3.org/TR/wot-discovery/#introduction-dns-sd-sec)
8+
9+
use futures_core::Stream;
10+
use futures_util::StreamExt;
11+
use mdns_sd::{ServiceDaemon, ServiceEvent, ServiceInfo};
12+
use tracing::debug;
13+
14+
use wot_td::thing::Thing;
15+
16+
/// The error type for Discovery operation
17+
#[derive(thiserror::Error, Debug)]
18+
#[non_exhaustive]
19+
pub enum Error {
20+
#[error("mdns cannot be accessed {0}")]
21+
Mdns(#[from] mdns_sd::Error),
22+
#[error("reqwest error {0}")]
23+
Reqwest(#[from] reqwest::Error),
24+
#[error("Missing address")]
25+
NoAddress,
26+
}
27+
28+
/// A specialized [`Result`] type
29+
pub type Result<T> = std::result::Result<T, Error>;
30+
31+
const WELL_KNOWN: &str = "/.well-known/wot";
32+
33+
/// Discover [Web Of Things](https://www.w3.org/WoT/) via a supported Introduction Mechanism.
34+
pub struct Discoverer {
35+
mdns: ServiceDaemon,
36+
service_type: String,
37+
}
38+
39+
async fn get_thing(info: ServiceInfo) -> Result<Thing> {
40+
let host = info.get_addresses().iter().next().ok_or(Error::NoAddress)?;
41+
let port = info.get_port();
42+
let props = info.get_properties();
43+
let path = props.get_property_val_str("td").unwrap_or(WELL_KNOWN);
44+
let proto = match props.get_property_val_str("tls") {
45+
Some(x) if x == "1" => "https",
46+
_ => "http",
47+
};
48+
49+
debug!("Got {proto} {host} {port} {path}");
50+
51+
let r = reqwest::get(format!("{proto}://{host}:{port}{path}")).await?;
52+
53+
let t = r.json().await?;
54+
55+
Ok(t)
56+
}
57+
58+
impl Discoverer {
59+
/// Creates a new Discoverer
60+
pub fn new() -> Result<Self> {
61+
let mdns = ServiceDaemon::new()?;
62+
let service_type = "_wot._tcp.local.".to_owned();
63+
Ok(Self { mdns, service_type })
64+
}
65+
66+
/// Returns an Stream of discovered things
67+
pub fn stream(&self) -> Result<impl Stream<Item = Result<Thing>>> {
68+
let receiver = self.mdns.browse(&self.service_type)?;
69+
70+
let s = receiver.into_stream().filter_map(|v| async move {
71+
if let ServiceEvent::ServiceResolved(info) = v {
72+
let t = get_thing(info).await;
73+
Some(t)
74+
} else {
75+
None
76+
}
77+
});
78+
79+
Ok(s)
80+
}
81+
}

tests/mdns.rs

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
use std::time::Duration;
2+
3+
use futures_util::StreamExt;
4+
5+
use wot_discovery::Discoverer;
6+
use wot_serve::servient::*;
7+
8+
use tokio::task;
9+
10+
async fn run_servient() {
11+
let servient = Servient::builder("TestThing")
12+
.finish_extend()
13+
.http_bind("127.0.0.1:8080".parse().unwrap())
14+
.build_servient()
15+
.unwrap();
16+
17+
eprintln!("Listening to 127.0.0.1:8080");
18+
19+
let _ = tokio::time::timeout(Duration::from_secs(30), async {
20+
servient.serve().await.unwrap()
21+
})
22+
.await;
23+
}
24+
25+
#[tokio::test(flavor = "multi_thread")]
26+
async fn discoverer() -> Result<(), Box<dyn std::error::Error>> {
27+
let local = task::LocalSet::new();
28+
29+
local
30+
.run_until(async {
31+
task::spawn_local(async {
32+
run_servient().await;
33+
});
34+
35+
task::spawn_local(async {
36+
let d = Discoverer::new().unwrap();
37+
38+
let t = std::pin::pin!(d.stream().unwrap())
39+
.next()
40+
.await
41+
.unwrap()
42+
.unwrap();
43+
44+
assert_eq!("TestThing", t.title);
45+
});
46+
})
47+
.await;
48+
49+
Ok(())
50+
}

0 commit comments

Comments
 (0)