use util;
use regex::{Regex};
use std::path::{BytesContainer};
use std::borrow::{ToOwned};
use std::io::net::tcp::{TcpStream};
use std::io::{IoResult, InvalidInput};
use std::io::net::udp::{UdpSocket};
use std::io::net::ip::{SocketAddr, Ipv4Addr};
static DISCOVERY_ADDR: SocketAddr = SocketAddr{ ip: Ipv4Addr(239, 255, 255, 250), port: 1900 };
static GENERIC_SEARCH_REQUEST: &'static str = "M-SEARCH * HTTP/1.1\r
Host: 239.255.255.250:1900\r
Man: \"ssdp:discover\"\r
MX: {1}\r
ST: {2}\r\n\r\n";
static GENERIC_HTTP_REQUEST: &'static str = "GET {1} HTTP/1.1\r
Host: {2}\r
Connection: close\r\n\r\n";
static GENERIC_SOAP_REQUEST: &'static str = "POST {1} HTTP/1.1\r
Host: {2}\r
Connection: close\r
Content-Length: {3}\r
Content-Type: text/xml; charset=\"utf-8\"\r
SOAPAction: \"{4}#{5}\"\r
\r
<?xml version=\"1.0\"?>
<s:Envelope xmlns:s=\"http://schemas.xmlsoap.org/soap/envelope/\"
s:encodingStyle=\"http://schemas.xmlsoap.org/soap/encoding/\">
<s:Body>
<u:{5} xmlns:u=\"{4}\">{6}</u:{5}>
</s:Body>
</s:Envelope>";
static BASE_CONTENT_LENGTH: uint = 216;
static ROOT_REGEX: Regex = regex!(r"upnp:(rootdevice)");
static DEVICE_REGEX: Regex = regex!(r":device:(.+?):(\d+)");
static SERVICE_REGEX: Regex = regex!(r":service:(.+?):(\d+)");
static IDENTIFIER_REGEX: Regex = regex!(r"uuid:([\w\d-]+)");
static ST_REGEX: Regex = regex!(r"(?i)ST: *([^\r\n]+)");
static USN_REGEX: Regex = regex!(r"(?i)USN: *([^\r\n]+)");
static LOCATION_REGEX: Regex = regex!(r"(?i)Location: *([^\r\n]+)");
pub type StrPos = (uint, uint);
pub struct ServiceDesc {
location: SocketAddr,
search_target: String,
control_path: String,
service_desc: String,
actions: Vec<StrPos>,
var_table: Option<StrPos>
}
impl ServiceDesc {
fn parse(location: &str, st: &str) -> IoResult<ServiceDesc> {
let service_find = String::from_str("<service>\\s*?<serviceType>{1}</serviceType>(?:.|\n)+?</service>").replace("{1}", st);
let service_regex = try!(Regex::new(service_find.as_slice()).or_else( |_|
Err(util::get_error(InvalidInput, "Failed To Create Service Regex"))
));
let scpd_find: Regex = regex!(r"<SCPDURL>(.+?)</SCPDURL>");
let control_find: Regex = regex!(r"<controlURL>(.+?)</controlURL>");
let action_regex: Regex = regex!(r" *<action>(?:.|\n)+?</action>");
let state_vars_regex: Regex = regex!(r"<serviceStateTable>(?:.|\n)+?</serviceStateTable>");
let path = try!(util::get_path(location));
let sock_addr = try!(util::get_sockaddr(location));
let request = String::from_str(GENERIC_HTTP_REQUEST).replace("{1}", path)
.replace("{2}", sock_addr.to_string().as_slice());
let mut tcp_sock = try!(TcpStream::connect(sock_addr));
try!(tcp_sock.write_str(request.as_slice()));
let response = try!(tcp_sock.read_to_string());
let (tag_begin, tag_end) = try!(service_regex.find(response.as_slice()).ok_or(
util::get_error(InvalidInput, "Could Not Find Service Tag")
));
let service_tag = response.as_slice().slice(tag_begin, tag_end);
let scpd_path = try!(try!(scpd_find.captures(service_tag).ok_or(
util::get_error(InvalidInput, "Could Not Capture SCPD Path")
)).at(1).ok_or(
util::get_error(InvalidInput, "Could Not Capture SCPD Path")
));
let control_path = try!(try!(control_find.captures(service_tag).ok_or(
util::get_error(InvalidInput, "Could Not Capture Control Path")
)).at(1).ok_or(
util::get_error(InvalidInput, "Could Not Capture Control Path")
));
tcp_sock = try!(TcpStream::connect(sock_addr));
let request = request.replace(path.as_slice(), scpd_path);
try!(tcp_sock.write_str(request.as_slice()));
let response = try!(tcp_sock.read_to_string()).replace("\t", " ");
let actions = action_regex.find_iter(response.as_slice()).collect::<Vec<StrPos>>();
let var_table = state_vars_regex.find(response.as_slice());
Ok(ServiceDesc{ location: sock_addr, search_target: st.to_string(),
control_path: control_path.to_string(), service_desc: response,
actions: actions, var_table: var_table })
}
pub fn actions<'a>(&'a self) -> Vec<&'a str> {
let mut actions_list: Vec<&'a str> = Vec::new();
let service_desc = self.service_desc.as_slice();
for &(start, end) in self.actions.iter() {
actions_list.push(service_desc.slice(start, end));
}
actions_list
}
pub fn state_variables<'a>(&'a self) -> Option<&'a str> {
match self.var_table {
Some((start, end)) => Some(self.service_desc.as_slice().slice(start, end)),
None => None
}
}
pub fn send_action(&self, action: &str, params: &[(&str, &str)]) -> IoResult<String> {
let response_find = String::from_str("<\\w*:?{1}Response(?:.|\n)+?</\\w*:?{1}Response>").replace("{1}", action);
let response_regex = try!(Regex::new(response_find.as_slice()).or_else( |_|
Err(util::get_error(InvalidInput, "Failed To Create Response Regex"))
));
let mut arguments = String::from_str("");
for &(tag, value) in params.iter() {
arguments.push_str("<");
arguments.push_str(tag);
arguments.push_str(">");
arguments.push_str(value);
arguments.push_str("</");
arguments.push_str(tag);
arguments.push_str(">");
}
let content_len = BASE_CONTENT_LENGTH + arguments.len() +
(action.len() * 2) + self.search_target.len();
let request = GENERIC_SOAP_REQUEST.replace("{1}", self.control_path.as_slice())
.replace("{2}", self.location.to_string().as_slice())
.replace("{3}", content_len.to_string().as_slice())
.replace("{4}", self.search_target.as_slice())
.replace("{5}", action)
.replace("{6}", arguments.as_slice());
let mut tcp_sock = try!(TcpStream::connect(self.location));
try!(tcp_sock.write_str(request.as_slice()));
let response = try!(tcp_sock.read_to_string());
if !response.as_slice().contains("200 OK") {
return Err(util::get_error(InvalidInput, "Service Returned An Error"));
}
let (a, b) = try!(response_regex.find(response.as_slice()).ok_or(
util::get_error(InvalidInput, "Unexpected Procedure Return Value")
));
Ok(response.as_slice().slice(a, b).to_string())
}
}
pub enum UPnPIntf {
#[doc(hidden)]
Root(String, StrPos, StrPos, StrPos, StrPos, StrPos),
#[doc(hidden)]
Device(String, StrPos, StrPos, StrPos, StrPos, StrPos),
#[doc(hidden)]
Service(String, StrPos, StrPos, StrPos, StrPos, StrPos),
#[doc(hidden)]
Identifier(String, StrPos, StrPos, StrPos, StrPos, StrPos)
}
impl UPnPIntf {
pub fn find_all(from_addr: SocketAddr) -> IoResult<Vec<UPnPIntf>> {
let request = String::from_str(GENERIC_SEARCH_REQUEST).replace("{1}", "5").replace("{2}", "ssdp:all");
let replies = try!(send_search(from_addr, 5500, request.as_slice()));
parse_interfaces(replies)
}
pub fn find_services(from_addr: SocketAddr, name: &str, version: &str) -> IoResult<Vec<UPnPIntf>> {
let service_st = String::from_str("urn:schemas-upnp-org:service:{1}:{2}").replace("{1}", name).replace("{2}", version);
let request = String::from_str(GENERIC_SEARCH_REQUEST).replace("{1}", "2").replace("{2}", service_st.as_slice());
let replies = try!(send_search(from_addr, 2200, request.as_slice()));
parse_interfaces(replies)
}
pub fn find_devices(from_addr: SocketAddr, name: &str, version: &str) -> IoResult<Vec<UPnPIntf>> {
let device_st = String::from_str("urn:schemas-upnp-org:device:{1}:{2}").replace("{1}", name).replace("{2}", version);
let request = GENERIC_SEARCH_REQUEST.replace("{1}", "2").replace("{2}", device_st.as_slice());
let replies = try!(send_search(from_addr, 2200, request.as_slice()));
parse_interfaces(replies)
}
pub fn find_uuid(from_addr: SocketAddr, uuid: &str) -> IoResult<Option<UPnPIntf>> {
let uuid_st = String::from_str("uuid:{1}").replace("{1}", uuid);
let request = String::from_str(GENERIC_SEARCH_REQUEST).replace("{1}", "5").replace("{2}", uuid_st.as_slice());
let replies = try!(send_search(from_addr, 5500, request.as_slice()));
match parse_interfaces(replies) {
Ok(mut n) => Ok(Some(n.remove(0))),
Err(n) => Err(n)
}
}
pub fn is_root(&self) -> bool {
match self {
&UPnPIntf::Root(..) => true,
_ => false
}
}
pub fn is_device(&self) -> bool {
match self {
&UPnPIntf::Device(..) => true,
_ => false
}
}
pub fn is_service(&self) -> bool {
match self {
&UPnPIntf::Service(..) => true,
_ => false
}
}
pub fn is_identifier(&self) -> bool {
match self {
&UPnPIntf::Identifier(..) => true,
_ => false
}
}
pub fn name<'a>(&'a self) -> &'a str {
let (payload, (start, end)) = match self {
&UPnPIntf::Root(ref n, seg, _, _, _, _) => (n, seg),
&UPnPIntf::Device(ref n, seg, _, _, _, _) => (n, seg),
&UPnPIntf::Service(ref n, seg, _, _, _, _) => (n, seg),
&UPnPIntf::Identifier(ref n, seg, _, _, _, _) => (n, seg)
};
payload.as_slice().slice(start, end)
}
pub fn version<'a>(&'a self) -> &'a str {
let (payload, (start, end)) = match self {
&UPnPIntf::Root(ref n, _, seg, _, _, _) => (n, seg),
&UPnPIntf::Device(ref n, _, seg, _, _, _) => (n, seg),
&UPnPIntf::Service(ref n, _, seg, _, _, _) => (n, seg),
&UPnPIntf::Identifier(ref n, _, seg, _, _, _) => (n, seg)
};
payload.as_slice().slice(start, end)
}
pub fn location<'a>(&'a self) -> &'a str {
let (payload, (start, end)) = match self {
&UPnPIntf::Root(ref n, _, _, seg, _, _) => (n, seg),
&UPnPIntf::Device(ref n, _, _, seg, _, _) => (n, seg),
&UPnPIntf::Service(ref n, _, _, seg, _, _) => (n, seg),
&UPnPIntf::Identifier(ref n, _, _, seg, _, _) => (n, seg)
};
payload.as_slice().slice(start, end)
}
pub fn st<'a>(&'a self) -> &'a str {
let (payload, (start, end)) = match self {
&UPnPIntf::Root(ref n, _, _, _, seg, _) => (n, seg),
&UPnPIntf::Device(ref n, _, _, _, seg, _) => (n, seg),
&UPnPIntf::Service(ref n, _, _, _, seg, _) => (n, seg),
&UPnPIntf::Identifier(ref n, _, _, _, seg, _) => (n, seg)
};
payload.as_slice().slice(start, end)
}
pub fn usn<'a>(&'a self) -> &'a str {
let (payload, (start, end)) = match self {
&UPnPIntf::Root(ref n, _, _, _, _, seg) => (n, seg),
&UPnPIntf::Device(ref n, _, _, _, _, seg) => (n, seg),
&UPnPIntf::Service(ref n, _, _, _, _, seg) => (n, seg),
&UPnPIntf::Identifier(ref n, _, _, _, _, seg) => (n, seg)
};
payload.as_slice().slice(start, end)
}
pub fn service_desc(&self) -> IoResult<ServiceDesc> {
match self {
&UPnPIntf::Service(..) => ServiceDesc::parse(self.location(), self.st()),
_ => Err(util::get_error(InvalidInput, "UPnPIntf Is Not A Service"))
}
}
}
fn parse_interfaces(payloads: Vec<String>) -> IoResult<Vec<UPnPIntf>> {
let mut interfaces: Vec<UPnPIntf> = Vec::new();
for i in payloads.into_iter() {
let usn = try!(try!(USN_REGEX.captures(i.as_slice()).ok_or(
util::get_error(InvalidInput, "USN Match Failed")
)).pos(1).ok_or(util::get_error(InvalidInput, "USN Field Missing")));
let st = try!(try!(ST_REGEX.captures(i.as_slice()).ok_or(
util::get_error(InvalidInput, "ST Match Failed")
)).pos(1).ok_or(util::get_error(InvalidInput, "ST Field Missing")));
let location = try!(try!(LOCATION_REGEX.captures(i.as_slice()).ok_or(
util::get_error(InvalidInput, "Location Match Failed")
)).pos(1).ok_or(util::get_error(InvalidInput, "Location Field Missing")));
if SERVICE_REGEX.is_match(i.as_slice()) {
let (name, version): (StrPos, StrPos);
{ let captures = try!(SERVICE_REGEX.captures(i.as_slice()).ok_or(
util::get_error(InvalidInput, "Service Match Failed")
));
name = try!(captures.pos(1).ok_or(
util::get_error(InvalidInput, "Service Name Match Failed")
));
version = try!(captures.pos(2).ok_or(
util::get_error(InvalidInput, "Service Version Match Failed")
));
}
interfaces.push(UPnPIntf::Service(i, name, version, location, st, usn));
} else if DEVICE_REGEX.is_match(i.as_slice()) {
let (name, version): (StrPos, StrPos);
{ let captures = try!(DEVICE_REGEX.captures(i.as_slice()).ok_or(
util::get_error(InvalidInput, "Device Match Failed")
));
name = try!(captures.pos(1).ok_or(
util::get_error(InvalidInput, "Device Name Match Failed")
));
version = try!(captures.pos(2).ok_or(
util::get_error(InvalidInput, "Device Version Match Failed")
));
}
interfaces.push(UPnPIntf::Device(i, name, version, location, st, usn));
} else if ROOT_REGEX.is_match(i.as_slice()) { let name: StrPos;
{ let captures = try!(ROOT_REGEX.captures(i.as_slice()).ok_or(
util::get_error(InvalidInput, "Root Match Failed")
));
name = try!(captures.pos(1).ok_or(
util::get_error(InvalidInput, "Root Name Match Failed")
));
}
interfaces.push(UPnPIntf::Root(i, name, (0, 0), location, st, usn));
} else if IDENTIFIER_REGEX.is_match(i.as_slice()) {
let name: StrPos;
{ let captures = try!(IDENTIFIER_REGEX.captures(i.as_slice()).ok_or(
util::get_error(InvalidInput, "Identifier Match Failed")
));
name = try!(captures.pos(1).ok_or(
util::get_error(InvalidInput, "Identifier Name Match Failed")
));
}
interfaces.push(UPnPIntf::Identifier(i, name, (0, 0), location, st, usn));
} }
Ok(interfaces)
}
fn send_search(from_addr: SocketAddr, timeout: uint, request: &str) -> IoResult<Vec<String>> {
let mut udp_sock = try!(UdpSocket::bind(from_addr));
udp_sock.set_read_timeout(Some(timeout as u64));
try!(udp_sock.send_to(request.as_bytes(), DISCOVERY_ADDR));
let mut replies: Vec<String> = Vec::new();
loop {
let mut reply_buf = [0u8; 1000];
match udp_sock.recv_from(&mut reply_buf) {
Ok(_) => {
let end = try!(reply_buf.iter().position({ |b|
*b == 0u8
}).ok_or(util::get_error(InvalidInput, "Search Reply Corrupt")));
let payload: String = try!(reply_buf.slice_to(end).container_as_str().ok_or(
util::get_error(InvalidInput, "Search Reply Not A Valid String")
)).to_owned();
replies.push(payload);
},
Err(_) => { break; }
};
}
Ok(replies)
}