summaryrefslogtreecommitdiff
path: root/src/upnp.rs
blob: 8eeac00edc4da082aa4093ea5c0e71c01752b7ca (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
extern crate hyper;
extern crate regex;

use self::regex::Regex;
use self::hyper::Client;
use self::hyper::rt::{self, Future, Stream};
use std::collections::HashMap;
use std::net::UdpSocket;
use std::str;
use std::io::{self, Write};



const MULTICAST_ADDRESS: &'static str = "239.255.255.250:1900";
const SEARCH_REQUEST: &'static str = "M-SEARCH * HTTP/1.1\r\n
Host: 239.255.255.250:1900\r\n
Man: \"ssdp:discover\"\r\n
ST: ssdp:all\r\n
MX: 3\r\n
User-Agent: slingr/0.1.0\r\n\r\n";

// enum Capabilities {
//     RENDERING_CONTROL,
//     AV_TRANSPORT,
//     CONNECTION_MANAGER
// }

#[derive(Debug)]
pub struct Device {
    usn: String,
    xml: String,
}

const USN_REGEX: &'static str = r"uuid:([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})";
const XML_REGEX: &'static str = r"Location: (http(s?)://[\w\.:/]*)";

fn parse_udp_message(msg: &str) -> Device {
    let usn_re = Regex::new(USN_REGEX).unwrap();
    let xml_re = Regex::new(XML_REGEX).unwrap();
    let usn = usn_re.captures(msg).unwrap().get(1).unwrap().as_str().to_string();
    let xml = xml_re.captures(msg).unwrap().get(1).unwrap().as_str().to_string();
    Device {
        usn,
        xml
    }
}

fn fetch_url(url: hyper::Uri) -> impl Future<Item=(), Error=()> {
    let client = Client::new();

    client
        .get(url)
        .and_then(|res| {
            println!("Response: {}", res.status());
            println!("Headers: {:#?}", res.headers());
            res.into_body().for_each(|chunk| {
                io::stdout().write_all(&chunk)
                    .map_err(|e| panic!("example expects stdout is open, error={}", e))
            })
        })
        .map(|_| {
            println!("\n\nDone.");
        })
        .map_err(|err| {
            eprintln!("Error {}", err);
        })
}

pub fn discover() {
    println!("Discovering...");
    let socket = UdpSocket::bind("[::]:0").unwrap();
    socket.send_to(SEARCH_REQUEST.as_bytes(), MULTICAST_ADDRESS).expect("couldn't send data");

    let mut devices = HashMap::new();
    let d = Device {usn: "a".to_string(), xml: "c".to_string()};
    devices.insert("foo", d);

    let mut buf = [0; 1024];
    loop {
        let (amt, _src) = socket.recv_from(&mut buf).expect("Didn't receive data...");
        let filled_buf = &mut buf[..amt];
        let s = str::from_utf8(filled_buf).unwrap();
        let d = parse_udp_message(s);
        println!("Got: {:?}\n\n", d);
        rt::run(fetch_url(d.xml.parse::<hyper::Uri>().unwrap()));
        break;
    }
}

#[cfg(test)]
mod tests {
    use upnp::*;

    #[test]
    fn test_parsing() {
        let msg = indoc!(r#"
            HTTP/1.1 200 OK
            Content-Type: text/xml; charset="utf-8"
            SERVER: Linux/3.10.0 eHomeMediaCenter/1.0
            Content-Length: 0
            Cache-Control: max-age=1810
            EXT: 
            Date: Fri, 02 Jan 1970 22:25:35 GMT
            ST: urn:schemas-upnp-org:service:ConnectionManager:1
            USN: uuid:9acb38e1-09cc-dba0-ffff-ffffe156cab7::urn:schemas-upnp-org:service:ConnectionManager:1
            Location: http://192.168.33.44:38400/deviceDescription/MediaRenderer
            Foo: foo"#);
        let d = parse_udp_message(msg);
        assert_eq!(d.usn, "9acb38e1-09cc-dba0-ffff-ffffe156cab7");
        assert_eq!(d.xml, "http://192.168.33.44:38400/deviceDescription/MediaRenderer");
    }
}