use super::connection::RaopShared;
use super::types::*;
use crate::crypto::pairing::Pairing;
use crate::crypto::rsa::RsaKey;
use crate::error::{ServerError, ShairplayError};
use crate::net::mdns::{AirPlayServiceInfo, MdnsService};
use crate::net::server::{BindConfig, HttpServer};
use std::sync::Arc;
const AIRPORT_KEY: &str = include_str!("../../airport.key");
fn airport_rsakey() -> Arc<RsaKey> {
use std::sync::OnceLock;
static KEY: OnceLock<Arc<RsaKey>> = OnceLock::new();
KEY.get_or_init(|| Arc::new(RsaKey::from_pem(AIRPORT_KEY).expect("built-in airport.key is invalid")))
.clone()
}
fn random_hwaddr() -> Vec<u8> {
use rand::RngCore;
let mut hwaddr = [0u8; super::MAX_HWADDR_LEN];
rand::thread_rng().fill_bytes(&mut hwaddr);
hwaddr[0] = (hwaddr[0] | 0x02) & !0x01;
hwaddr.to_vec()
}
pub struct RaopServerBuilder {
max_clients: usize,
hwaddr: Option<Vec<u8>>,
password: Option<String>,
name: String,
bind: BindConfig,
#[cfg(feature = "ap2")]
pairing_store: Option<Arc<dyn PairingStore>>,
output_sample_rate: Option<u32>,
output_max_channels: Option<u8>,
#[cfg(feature = "ap2")]
pin: Option<String>,
#[cfg(feature = "video")]
video_handler: Option<Arc<dyn crate::raop::video::VideoHandler>>,
#[cfg(feature = "hls")]
hls_handler: Option<Arc<dyn crate::raop::hls::HlsHandler>>,
}
impl Default for RaopServerBuilder {
fn default() -> Self {
Self::new()
}
}
impl RaopServerBuilder {
pub fn new() -> Self {
Self {
max_clients: 10,
hwaddr: None,
password: None,
name: "Shairplay".to_string(),
bind: BindConfig::default(),
#[cfg(feature = "ap2")]
pairing_store: None,
output_sample_rate: None,
output_max_channels: None,
#[cfg(feature = "ap2")]
pin: None,
#[cfg(feature = "video")]
video_handler: None,
#[cfg(feature = "hls")]
hls_handler: None,
}
}
pub fn max_clients(mut self, n: usize) -> Self {
self.max_clients = n;
self
}
pub fn hwaddr(mut self, addr: impl Into<Vec<u8>>) -> Self {
self.hwaddr = Some(addr.into());
self
}
pub fn password(mut self, pw: impl Into<String>) -> Self {
self.password = Some(pw.into());
self
}
pub fn port(mut self, port: u16) -> Self {
self.bind.port = port;
self
}
pub fn bind(mut self, config: BindConfig) -> Self {
self.bind = config;
self
}
pub fn name(mut self, name: impl Into<String>) -> Self {
self.name = name.into();
self
}
#[cfg(feature = "ap2")]
pub fn pairing_store(mut self, store: Arc<dyn PairingStore>) -> Self {
self.pairing_store = Some(store);
self
}
pub fn output_sample_rate(mut self, rate: u32) -> Self {
self.output_sample_rate = Some(rate);
self
}
pub fn output_max_channels(mut self, channels: u8) -> Self {
self.output_max_channels = Some(channels);
self
}
#[cfg(feature = "ap2")]
pub fn pin(mut self, pin: impl Into<String>) -> Self {
self.pin = Some(pin.into());
self
}
#[cfg(feature = "video")]
pub fn video_handler(mut self, handler: Arc<dyn crate::raop::video::VideoHandler>) -> Self {
self.video_handler = Some(handler);
self
}
#[cfg(feature = "hls")]
pub fn hls_handler(mut self, handler: Arc<dyn crate::raop::hls::HlsHandler>) -> Self {
self.hls_handler = Some(handler);
self
}
pub fn build(self, handler: Arc<dyn AudioHandler>) -> Result<RaopServer, ShairplayError> {
if self.max_clients == 0 {
return Err(ServerError::MaxClients(0).into());
}
if let Some(password) = self.password.as_ref()
&& password.len() > super::MAX_PASSWORD_LEN
{
return Err(ServerError::InvalidPassword(password.len()).into());
}
let rsakey = airport_rsakey();
let pairing = Arc::new(Pairing::generate()?);
let hwaddr = match self.hwaddr {
Some(addr) if addr.len() == super::MAX_HWADDR_LEN => addr,
Some(addr) => return Err(ServerError::InvalidHwAddr(addr.len()).into()),
None => random_hwaddr(),
};
let shared = Arc::new(RaopShared {
rsakey,
pairing,
hwaddr: hwaddr.clone(),
password: self.password.unwrap_or_default(),
handler,
#[cfg(feature = "ap2")]
pairing_store: self
.pairing_store
.unwrap_or_else(|| Arc::new(MemoryPairingStore::default())),
output_sample_rate: self.output_sample_rate,
output_max_channels: self.output_max_channels,
#[cfg(feature = "ap2")]
pin: self.pin,
#[cfg(feature = "video")]
video_handler: self.video_handler,
#[cfg(feature = "video")]
video_ekey: Arc::new(std::sync::RwLock::new(None)),
#[cfg(feature = "video")]
video_eiv: Arc::new(std::sync::RwLock::new(None)),
#[cfg(feature = "hls")]
hls_handler: self.hls_handler,
});
let mut httpd = HttpServer::new(shared.clone(), self.max_clients);
httpd.set_bind_config(self.bind.clone());
Ok(RaopServer {
shared,
httpd,
mdns: None,
bind: self.bind,
name: self.name,
hwaddr,
})
}
}
pub struct RaopServer {
shared: Arc<RaopShared>,
httpd: HttpServer,
mdns: Option<MdnsService>,
bind: BindConfig,
name: String,
hwaddr: Vec<u8>,
}
impl RaopServer {
pub fn builder() -> RaopServerBuilder {
RaopServerBuilder::new()
}
pub async fn start(&mut self) -> Result<(), ShairplayError> {
let _actual_port = self.httpd.start(self.bind.port).await?;
if std::env::var("CI").is_err() {
let info = self.service_info();
let mut mdns = MdnsService::new()?;
mdns.register_raop(&info)?;
#[cfg(feature = "ap2")]
mdns.register_airplay(&info)?;
self.mdns = Some(mdns);
}
Ok(())
}
pub fn is_running(&self) -> bool {
self.httpd.is_running()
}
pub async fn stop(&mut self) {
if let Some(mut mdns) = self.mdns.take() {
mdns.unregister_raop();
mdns.unregister_airplay();
}
self.httpd.stop().await;
}
pub fn service_info(&self) -> AirPlayServiceInfo {
#[cfg(feature = "ap2")]
{
let device_id = crate::util::hwaddr_airplay(&self.hwaddr);
let (_, vk) = crate::crypto::pairing_homekit::server_keypair(&device_id);
let pk_hex: String = vk.as_bytes().iter().map(|b| format!("{b:02x}")).collect();
let pi = uuid::Uuid::new_v4().to_string();
AirPlayServiceInfo::new_airplay2(
&self.name,
self.httpd.port(),
&self.hwaddr,
!self.shared.password.is_empty(),
&pk_hex,
&pi,
)
}
#[cfg(not(feature = "ap2"))]
{
AirPlayServiceInfo::new(
&self.name,
self.httpd.port(),
&self.hwaddr,
!self.shared.password.is_empty(),
)
}
}
}