~liz/yooper

550163c18df197bfc14a427da32cc39faef6a217 — Ellie Frost 2 years ago a1543f7
Search for devices on the network
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")]