use std::net::Ipv4Addr;
use std::net::SocketAddr;
use std::sync::Arc;
use log::debug;
use log::trace;
use tokio::net::UdpSocket;
use crate::http_helper::generate_ssdp_discover_answer;
use crate::socket_helper::join_socket;
use crate::MulticastAddr;
use crate::SSDP_PORT;
#[derive(Clone, PartialEq, Eq, Debug)]
pub struct ServiceDescription {
pub usn_uri: String,
pub service_type_uri: String,
pub expiration: u32,
pub location: String,
}
pub struct Service {
service_description: ServiceDescription,
}
impl Service {
pub fn new(service_description: ServiceDescription) -> Self {
Service {
service_description,
}
}
pub async fn listen(&self, address: MulticastAddr) -> Result<(), Box<dyn std::error::Error>> {
let local_addr = SocketAddr::from((Ipv4Addr::UNSPECIFIED, SSDP_PORT));
let socket = Arc::new(UdpSocket::bind(local_addr).await?);
join_socket(&address, socket.clone())?;
let mut buf = vec![0; 1024];
debug!("Start listening for SSDP discovery messages...");
loop {
let (len, addr) = socket.recv_from(&mut buf).await?;
let copy = buf.clone();
trace!(
"Received {} bytes from {}: {:#?}",
len,
addr,
String::from_utf8(copy).unwrap().replace('\0', "")
);
let mut headers = [httparse::EMPTY_HEADER; 64];
let mut req = httparse::Request::new(&mut headers);
if req.parse(&buf[..len]).is_err() {
trace!("Could not parse request to HTTP");
continue;
}
if req.method.is_none() || req.method.is_some_and(|method| method != "M-SEARCH") {
trace!("Request is not M-SEARCH");
continue;
}
let man = req.headers.get(2);
if man.is_none()
|| man.is_some_and(|man| String::from_utf8_lossy(man.value) != "\"ssdp:discover\"")
{
trace!("Request uses wrong MAN header");
continue;
}
let st = req.headers.get(3);
if st.is_none()
|| st.is_some_and(|st| -> bool {
let st = String::from_utf8_lossy(st.value);
st != "ssdp:all" && st != self.service_description.service_type_uri
})
{
trace!("ST header that's not interesting for us submitted");
continue;
}
let s = req.headers.get(0);
match s {
None => {
trace!("S was not submitted");
continue;
}
Some(s) => {
let s = String::from_utf8_lossy(s.value);
let resp_msg =
generate_ssdp_discover_answer(&self.service_description, s.to_string());
socket.send_to(resp_msg.as_bytes(), &addr).await?;
trace!("Send SSDP response {:#?} to {}", resp_msg, addr);
}
}
if s.is_none() {
trace!("S was not submitted");
continue;
}
}
}
}