use shairplay::codec::alac::AlacDecoder;
use shairplay::crypto::aes::AesCtr;
use shairplay::crypto::fairplay::FairPlay;
use shairplay::crypto::pairing::Pairing;
use shairplay::net::mdns::AirPlayServiceInfo;
use shairplay::proto::digest;
use shairplay::proto::http::{HttpRequest, HttpResponse};
use shairplay::proto::plist;
use shairplay::proto::sdp::Sdp;
use shairplay::raop::buffer::RaopBuffer;
#[test]
fn sdp_parse_airplay_session() {
let data = "v=0\r\no=iTunes 123 0 IN IP4 192.168.1.1\r\ns=iTunes\r\nc=IN IP4 192.168.1.1\r\nt=0 0\r\nm=audio 0 RTP/AVP 96\r\na=rtpmap:96 AppleLossless\r\na=fmtp:96 352 0 16 40 10 14 2 255 0 0 44100\r\na=rsaaeskey:AQID\r\na=aesiv:BAUG\r\na=min-latency:11025\r\n";
let sdp = Sdp::parse(data);
assert_eq!(sdp.version(), Some("0"));
assert_eq!(sdp.connection(), Some("IN IP4 192.168.1.1"));
assert_eq!(sdp.rtpmap(), Some("96 AppleLossless"));
assert_eq!(sdp.fmtp(), Some("96 352 0 16 40 10 14 2 255 0 0 44100"));
assert_eq!(sdp.rsaaeskey(), Some("AQID"));
assert_eq!(sdp.aesiv(), Some("BAUG"));
assert_eq!(sdp.min_latency(), Some("11025"));
assert_eq!(sdp.fpaeskey(), None);
}
#[test]
fn sdp_missing_fields() {
let sdp = Sdp::parse("v=0\r\n");
assert_eq!(sdp.version(), Some("0"));
assert_eq!(sdp.connection(), None);
assert_eq!(sdp.rtpmap(), None);
}
#[test]
fn http_parse_rtsp_request() {
let mut req = HttpRequest::new();
req.add_data(b"OPTIONS * RTSP/1.0\r\nCSeq: 1\r\nApple-Challenge: dGVzdA==\r\n\r\n")
.unwrap();
assert!(req.is_complete());
assert_eq!(req.method(), Some("OPTIONS"));
assert_eq!(req.url(), Some("*"));
assert_eq!(req.header("CSeq"), Some("1"));
assert_eq!(req.header("Apple-Challenge"), Some("dGVzdA=="));
assert_eq!(req.header("Missing"), None);
}
#[test]
fn http_parse_http_request() {
let mut req = HttpRequest::new();
req.add_data(b"OPTIONS * HTTP/1.0\r\nCSeq: 1\r\n\r\n").unwrap();
assert!(req.is_complete());
assert_eq!(req.method(), Some("OPTIONS"));
}
#[test]
fn http_incremental_parse() {
let mut req = HttpRequest::new();
req.add_data(b"GET /test RTSP/1.0\r\n").unwrap();
assert!(!req.is_complete());
req.add_data(b"CSeq: 5\r\n\r\n").unwrap();
assert!(req.is_complete());
assert_eq!(req.method(), Some("GET"));
assert_eq!(req.header("CSeq"), Some("5"));
}
#[test]
fn http_request_with_body() {
let mut req = HttpRequest::new();
req.add_data(b"POST /fp-setup RTSP/1.0\r\nContent-Length: 4\r\n\r\nABCD")
.unwrap();
assert!(req.is_complete());
assert_eq!(req.data(), Some(b"ABCD".as_ref()));
}
#[test]
fn http_response_serialize() {
let mut resp = HttpResponse::new("RTSP/1.0", 200, "OK");
resp.add_header("CSeq", "1");
resp.finish(Some(b"hello"));
let data = resp.get_data();
let s = std::str::from_utf8(data).unwrap();
assert!(s.starts_with("RTSP/1.0 200 OK\r\n"));
assert!(s.contains("CSeq: 1\r\n"));
assert!(s.contains("Content-Length: 5\r\n"));
assert!(s.ends_with("hello"));
}
#[test]
fn http_response_disconnect() {
let mut resp = HttpResponse::new("RTSP/1.0", 200, "OK");
assert!(!resp.get_disconnect());
resp.set_disconnect(true);
assert!(resp.get_disconnect());
}
#[test]
fn digest_valid_auth() {
let auth = "Digest username=\"user\", realm=\"airplay\", nonce=\"abc123\", uri=\"/\", response=\"c5b03993ddeeb6f209c5aa08d2aa30d8\"";
assert!(digest::is_valid(
"airplay",
"pass",
"abc123",
"OPTIONS",
"/",
Some(auth)
));
}
#[test]
fn digest_wrong_password() {
let auth = "Digest username=\"user\", realm=\"airplay\", nonce=\"abc123\", uri=\"/\", response=\"c5b03993ddeeb6f209c5aa08d2aa30d8\"";
assert!(!digest::is_valid(
"airplay",
"wrong",
"abc123",
"OPTIONS",
"/",
Some(auth)
));
}
#[test]
fn digest_missing_auth() {
assert!(!digest::is_valid("airplay", "pass", "abc123", "OPTIONS", "/", None));
}
#[test]
fn digest_nonce_length() {
let nonce = digest::generate_nonce(32);
assert_eq!(nonce.len(), 32);
assert!(nonce.chars().all(|c| c.is_ascii_hexdigit()));
}
#[test]
fn plist_roundtrip() {
use std::collections::HashMap;
let mut dict = HashMap::new();
dict.insert("name".to_string(), plist::PlistValue::String("test".to_string()));
dict.insert("value".to_string(), plist::PlistValue::Integer(42));
let original = plist::PlistValue::Dict(dict);
let bytes = plist::to_bplist(&original).unwrap();
assert!(bytes.starts_with(b"bplist"));
let parsed = plist::from_bplist(&bytes).unwrap();
assert_eq!(parsed.dict_get("name").unwrap().as_string(), Some("test"));
assert_eq!(parsed.dict_get("value").unwrap().as_integer(), Some(42));
}
#[test]
fn plist_reject_garbage() {
assert!(plist::from_bplist(b"not a plist").is_none());
assert!(plist::from_bplist(b"").is_none());
}
#[test]
fn aes_ctr_48_bytes() {
let key = [
0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10,
];
let nonce = [
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
];
let mut data: Vec<u8> = (0..48).collect();
let mut aes = AesCtr::new(&key, &nonce);
aes.encrypt(&mut data);
assert_eq!(
hex::encode(&data),
"9a7b041aaec7986b1722564c5fd886fc043d43dabb39e7ce36902ddfe7dc93659233f84a755459b2712ee0fd90d32645"
);
}
#[test]
fn aes_ctr_streaming_matches_oneshot() {
let key = [
0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10,
];
let nonce = [
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
];
let plain: Vec<u8> = (0..48).collect();
let mut d1 = plain.clone();
AesCtr::new(&key, &nonce).encrypt(&mut d1);
let mut d2 = plain.clone();
let mut aes = AesCtr::new(&key, &nonce);
aes.encrypt(&mut d2[0..16]);
aes.encrypt(&mut d2[16..32]);
aes.encrypt(&mut d2[32..48]);
assert_eq!(d1, d2);
}
#[test]
fn aes_ctr_7_bytes() {
let key = [
0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10,
];
let nonce = [
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
];
let mut data: Vec<u8> = (0..7).collect();
AesCtr::new(&key, &nonce).encrypt(&mut data);
assert_eq!(hex::encode(&data), "9a7b041aaec798");
}
#[test]
fn aes_ctr_symmetric() {
let key = [0xaa; 16];
let nonce = [0xbb; 16];
let original = b"AirPlay test data for symmetry check!".to_vec();
let mut encrypted = original.clone();
AesCtr::new(&key, &nonce).encrypt(&mut encrypted);
assert_ne!(encrypted, original);
AesCtr::new(&key, &nonce).encrypt(&mut encrypted);
assert_eq!(encrypted, original);
}
#[test]
fn pairing_handshake_roundtrip() {
let server = Pairing::generate().unwrap();
let client = Pairing::generate().unwrap();
let mut server_session = server.create_session();
{
let fake_ecdh = [0x42u8; 32];
let client_ed = client.public_key();
server_session.handshake(&fake_ecdh, &client_ed).unwrap();
server_session.get_public_key().unwrap()
};
let sig = server_session.get_signature().unwrap();
assert_eq!(sig.len(), 64);
}
#[test]
fn pairing_derive_key_deterministic() {
let p = Pairing::from_seed(&[0xAA; 32]);
let mut s = p.create_session();
s.handshake(&[0x11; 32], &[0x22; 32]).unwrap();
let k1 = s.derive_key(b"test-salt", 16).unwrap();
let k2 = s.derive_key(b"test-salt", 16).unwrap();
assert_eq!(k1, k2);
assert_eq!(k1.len(), 16);
}
#[test]
fn fairplay_setup_modes() {
let mut fp = FairPlay::new();
for mode in 0..4u8 {
let mut req = [0u8; 16];
req[4] = 0x03; req[14] = mode;
let res = fp.setup(&req).unwrap();
assert_eq!(res.len(), 142);
assert_eq!(&res[..4], b"FPLY");
}
}
#[test]
fn fairplay_handshake_response() {
let mut fp = FairPlay::new();
let mut req = [0u8; 164];
req[4] = 0x03;
for (i, b) in req.iter_mut().enumerate().skip(144) {
*b = i as u8;
}
let res = fp.handshake(&req).unwrap();
assert_eq!(&res[..4], b"FPLY");
assert_eq!(&res[12..32], &req[144..164]); }
#[test]
fn fairplay_reject_bad_version() {
let mut fp = FairPlay::new();
let mut req = [0u8; 16];
req[4] = 0x02; assert!(fp.setup(&req).is_err());
}
#[test]
fn alac_init_and_set_info() {
let mut alac = AlacDecoder::new(16, 2);
let mut info = [0u8; 48];
info[24..28].copy_from_slice(&352u32.to_be_bytes());
info[29] = 16; info[30] = 40; info[31] = 10; info[32] = 14; info[33] = 2; info[34..36].copy_from_slice(&255u16.to_be_bytes());
info[44..48].copy_from_slice(&44100u32.to_be_bytes());
alac.set_info(&info);
}
#[test]
fn rtp_buffer_queue_dequeue() {
let key = [0u8; 16];
let iv = [0u8; 16];
let mut buf = RaopBuffer::new("96 352", "96 352 0 16 40 10 14 2 255 0 0 44100", &key, &iv);
let mut pkt = vec![0x80, 0x60, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00];
pkt.extend_from_slice(&[0u8; 256]);
let _ = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
buf.queue(&pkt, true);
}));
}
#[test]
fn rtp_buffer_flush() {
let key = [0u8; 16];
let iv = [0u8; 16];
let mut buf = RaopBuffer::new("96 352", "96 352 0 16 40 10 14 2 255 0 0 44100", &key, &iv);
buf.flush(100);
assert!(buf.dequeue(true).is_none());
}
#[test]
fn rtp_buffer_reject_short_packet() {
let key = [0u8; 16];
let iv = [0u8; 16];
let mut buf = RaopBuffer::new("96 352", "96 352 0 16 40 10 14 2 255 0 0 44100", &key, &iv);
assert_eq!(buf.queue(&[0u8; 4], true), -1); }
#[test]
fn service_info_txt_records() {
let info = AirPlayServiceInfo::new("TestSpeaker", 5000, &[0x48, 0x5d, 0x60, 0x7c, 0xee, 0x22], false);
assert_eq!(info.raop_name, "485D607CEE22@TestSpeaker");
assert_eq!(
info.raop_txt.iter().find(|(k, _)| k == "ch").map(|(_, v)| v.as_str()),
Some("2")
);
assert_eq!(
info.raop_txt.iter().find(|(k, _)| k == "sr").map(|(_, v)| v.as_str()),
Some("44100")
);
assert_eq!(
info.raop_txt.iter().find(|(k, _)| k == "pw").map(|(_, v)| v.as_str()),
Some("false")
);
assert_eq!(
info.airplay_txt
.iter()
.find(|(k, _)| k == "model")
.map(|(_, v)| v.as_str()),
Some("AppleTV2,1")
);
}
#[test]
fn service_info_with_password() {
let info = AirPlayServiceInfo::new("Test", 5000, &[0; 6], true);
assert_eq!(
info.raop_txt.iter().find(|(k, _)| k == "pw").map(|(_, v)| v.as_str()),
Some("true")
);
}
mod hex {
pub fn encode(data: &[u8]) -> String {
data.iter().map(|b| format!("{b:02x}")).collect()
}
}
#[cfg(all(test, feature = "ap2"))]
mod ap2_tests {
#[test]
fn c_vector_audio_packet_decrypt() {
use chacha20poly1305::{ChaCha20Poly1305, KeyInit, Nonce, aead::Aead, aead::Payload};
let packet = hex_decode(
"809a000193eda3fd160000004ea11b7fc9f1c33dbf860ff8ae0b52a18df7c4cbe6066082bdc97419157558ec76f55c1e2bc54b119bf70102030405060708",
);
let shk = [0x42u8; 32];
let cipher = ChaCha20Poly1305::new((&shk).into());
let pkt_len = packet.len();
let mut nonce = [0u8; 12];
nonce[4..12].copy_from_slice(&packet[pkt_len - 8..]);
let aad = &packet[4..12];
let ciphertext = &packet[12..pkt_len - 8];
let plaintext = cipher
.decrypt(Nonce::from_slice(&nonce), Payload { msg: ciphertext, aad })
.expect("decryption should succeed");
assert_eq!(std::str::from_utf8(&plaintext).unwrap(), "Hello AAC frame data here!");
}
#[test]
fn audio_packet_wrong_key_fails() {
use chacha20poly1305::{ChaCha20Poly1305, KeyInit, Nonce, aead::Aead, aead::Payload};
let packet = hex_decode(
"809a000193eda3fd160000004ea11b7fc9f1c33dbf860ff8ae0b52a18df7c4cbe6066082bdc97419157558ec76f55c1e2bc54b119bf70102030405060708",
);
let wrong_key = [0x00u8; 32];
let cipher = ChaCha20Poly1305::new((&wrong_key).into());
let pkt_len = packet.len();
let mut nonce = [0u8; 12];
nonce[4..12].copy_from_slice(&packet[pkt_len - 8..]);
let aad = &packet[4..12];
let ciphertext = &packet[12..pkt_len - 8];
assert!(
cipher
.decrypt(Nonce::from_slice(&nonce), Payload { msg: ciphertext, aad })
.is_err()
);
}
#[test]
fn length_prefix_parsing() {
let total_len: u16 = 102; let bytes = total_len.to_be_bytes();
assert_eq!(bytes, [0x00, 0x66]);
let parsed = u16::from_be_bytes([bytes[0], bytes[1]]);
assert_eq!(parsed, 102);
assert_eq!(parsed - 2, 100); }
#[test]
fn ap2_features_hilo_split() {
use shairplay::net::features::receiver_features;
let lo = receiver_features() & 0xFFFFFFFF;
let hi = (receiver_features() >> 32) & 0xFFFFFFFF;
let formatted = format!("0x{:X},0x{:X}", lo, hi);
assert!(formatted.contains(","));
let recombined = (hi << 32) | lo;
assert_eq!(recombined, receiver_features());
}
#[test]
fn network_time_frac_half_second() {
let frac: u64 = 0x8000_0000_0000_0000;
let frac_ns = ((frac >> 32) * 1_000_000_000) >> 32;
assert_eq!(frac_ns, 500_000_000); }
#[test]
fn network_time_frac_quarter_second() {
let frac: u64 = 0x4000_0000_0000_0000;
let frac_ns = ((frac >> 32) * 1_000_000_000) >> 32;
assert_eq!(frac_ns, 250_000_000); }
#[test]
fn network_time_frac_three_quarters() {
let frac: u64 = 0xC000_0000_0000_0000;
let frac_ns = ((frac >> 32) * 1_000_000_000) >> 32;
assert_eq!(frac_ns, 750_000_000); }
#[test]
fn server_keypair_deterministic() {
let (_, vk1) = shairplay::crypto::pairing_homekit::server_keypair("TestDevice");
let (_, vk2) = shairplay::crypto::pairing_homekit::server_keypair("TestDevice");
assert_eq!(vk1.as_bytes(), vk2.as_bytes());
let (_, vk3) = shairplay::crypto::pairing_homekit::server_keypair("OtherDevice");
assert_ne!(vk1.as_bytes(), vk3.as_bytes());
}
#[test]
fn adts_wrap_produces_valid_sync() {
let raw_aac = vec![0xDE, 0xAD, 0xBE, 0xEF];
let adts = shairplay::codec::aac::wrap_adts(&raw_aac, 44100, 2);
assert_eq!(adts[0], 0xFF); assert_eq!(adts[1] & 0xF0, 0xF0); assert_eq!(&adts[7..], &raw_aac[..]); assert_eq!(adts.len(), 7 + 4); }
#[test]
fn c_vector_server_keypair() {
let (_, vk) = shairplay::crypto::pairing_homekit::server_keypair("AABBCCDD1122");
let pk_hex: String = vk.as_bytes().iter().map(|b| format!("{b:02x}")).collect();
assert_eq!(
pk_hex,
"f336effeaedc188558f1046a97fe5db67cf9c7c1736d9201fb9821985eedf7c1"
);
}
#[test]
fn c_vector_anchor_time_half_second() {
let secs: u64 = 1712345678;
let frac: u64 = 0x8000_0000_0000_0000;
let frac_ns = ((frac >> 32) * 1_000_000_000) >> 32;
let total = secs * 1_000_000_000 + frac_ns;
assert_eq!(total, 1712345678500000000);
}
#[test]
fn c_vector_anchor_time_quarter_second() {
let secs: u64 = 1712345678;
let frac: u64 = 0x4000_0000_0000_0000;
let frac_ns = ((frac >> 32) * 1_000_000_000) >> 32;
let total = secs * 1_000_000_000 + frac_ns;
assert_eq!(total, 1712345678250000000);
}
#[test]
fn test_airplay2_persistent_identity() {
use shairplay::raop::{AudioFormat, AudioHandler, AudioSession, RaopServer};
use std::sync::Arc;
struct DummyHandler;
impl AudioHandler for DummyHandler {
fn audio_init(&self, _format: AudioFormat) -> Box<dyn AudioSession> {
struct DummySession;
impl AudioSession for DummySession {
fn audio_process(&mut self, _samples: &[f32]) {}
}
Box::new(DummySession)
}
}
let hw = [0x01, 0x02, 0x03, 0x04, 0x05, 0x06];
let handler = Arc::new(DummyHandler);
let server = RaopServer::builder()
.name("Test Speaker")
.hwaddr(hw)
.build(handler)
.unwrap();
let info1 = server.service_info();
let info2 = server.service_info();
let pi1 = info1
.airplay_txt
.iter()
.find(|(k, _)| k == "pi")
.map(|(_, v)| v.as_str())
.unwrap();
let pi2 = info2
.airplay_txt
.iter()
.find(|(k, _)| k == "pi")
.map(|(_, v)| v.as_str())
.unwrap();
assert_eq!(pi1, pi2, "Pairing ID must be stable across queries");
assert_eq!(pi1.len(), 36, "Pairing ID should be a valid formatted UUID string");
assert!(pi1.chars().all(|c| c == '-' || c.is_ascii_hexdigit()));
}
fn hex_decode(s: &str) -> Vec<u8> {
(0..s.len())
.step_by(2)
.map(|i| u8::from_str_radix(&s[i..i + 2], 16).unwrap())
.collect()
}
}
#[cfg(all(test, feature = "ap2"))]
mod mixdown_tests {
use shairplay::codec::resample::mixdown;
#[test]
fn stereo_passthrough() {
let input = vec![0.5_f32, -0.5, 0.3, -0.3];
let out = mixdown(&input, 2, 2);
assert_eq!(out, input);
}
#[test]
fn surround_51_to_stereo() {
let input = vec![1.0, 0.0, 0.5, 0.0, 0.0, 0.0_f32];
let out = mixdown(&input, 6, 2);
assert!((out[0] - 1.0).abs() < 0.01); assert!((out[1] - 0.3535).abs() < 0.01);
}
#[test]
fn surround_71_to_stereo() {
let input = vec![0.5, 0.5, 0.0, 0.0, 0.3, 0.3, 0.2, 0.2_f32];
let out = mixdown(&input, 8, 2);
let k: f32 = 0.707;
let expected_l = 0.5 + k * 0.3 + k * 0.2;
let expected_r = 0.5 + k * 0.3 + k * 0.2;
assert!((out[0] - expected_l).abs() < 0.01);
assert!((out[1] - expected_r).abs() < 0.01);
}
#[test]
fn mixdown_clamps_output() {
let input = vec![1.0_f32; 6];
let out = mixdown(&input, 6, 2);
assert!(out[0] <= 1.0);
assert!(out[1] <= 1.0);
}
}
#[cfg(all(test, feature = "ap2"))]
mod ssrc_tests {
use shairplay::codec::aac::AudioSsrc;
#[test]
fn all_ssrc_values_map_correctly() {
let cases = vec![
(0x0000FACE, 44100, 2, false),
(0x15000000, 48000, 2, false),
(0x16000000, 44100, 2, true),
(0x17000000, 48000, 2, true),
(0x27000000, 48000, 6, true),
(0x28000000, 48000, 8, true),
];
for (val, sr, ch, is_aac) in cases {
let ssrc = AudioSsrc::from_u32(val);
assert_ne!(ssrc, AudioSsrc::None, "SSRC 0x{val:08X} should be recognized");
assert_eq!(ssrc.sample_rate(), sr, "SSRC 0x{val:08X} sample rate");
assert_eq!(ssrc.channels(), ch, "SSRC 0x{val:08X} channels");
assert_eq!(ssrc.is_aac(), is_aac, "SSRC 0x{val:08X} is_aac");
}
}
#[test]
fn unknown_ssrc_returns_none() {
assert_eq!(AudioSsrc::from_u32(0x12345678), AudioSsrc::None);
assert_eq!(AudioSsrc::from_u32(0), AudioSsrc::None);
}
#[test]
fn adts_channel_config() {
assert_eq!(AudioSsrc::Aac44100F24Stereo.adts_channel_config(), 2);
assert_eq!(AudioSsrc::Aac48000F24Surround51.adts_channel_config(), 6);
assert_eq!(AudioSsrc::Aac48000F24Surround71.adts_channel_config(), 7);
}
}
#[cfg(all(test, feature = "ap2"))]
mod adts_multi_tests {
use shairplay::codec::aac::adts_header;
#[test]
fn adts_44100_stereo() {
let h = adts_header(100, 44100, 2);
assert_eq!(h[0], 0xFF);
assert_eq!((h[2] >> 2) & 0x0F, 4); }
#[test]
fn adts_48000_stereo() {
let h = adts_header(100, 48000, 2);
assert_eq!((h[2] >> 2) & 0x0F, 3); }
#[test]
fn adts_48000_surround51() {
let h = adts_header(200, 48000, 6);
let chan = ((h[2] & 1) << 2) | ((h[3] >> 6) & 3);
assert_eq!(chan, 6);
}
#[test]
fn adts_48000_surround71() {
let h = adts_header(200, 48000, 7); let chan = ((h[2] & 1) << 2) | ((h[3] >> 6) & 3);
assert_eq!(chan, 7);
}
}
#[cfg(all(test, feature = "ap2"))]
mod frac_edge_tests {
#[test]
fn frac_zero() {
let frac: u64 = 0;
let ns = ((frac >> 32) * 1_000_000_000) >> 32;
assert_eq!(ns, 0);
}
#[test]
fn frac_max() {
let frac: u64 = 0xFFFF_FFFF_FFFF_FFFF;
let ns = ((frac >> 32) * 1_000_000_000) >> 32;
assert!((999_999_000..=1_000_000_000).contains(&ns), "got {ns}");
}
#[test]
fn frac_one_ms() {
let frac: u64 = 0x0041_8937_0000_0000;
let ns = ((frac >> 32) * 1_000_000_000) >> 32;
assert!((ns as i64 - 1_000_000).abs() < 100_000, "got {ns}"); }
}
#[cfg(all(test, feature = "ap2"))]
mod playout_tests {
use shairplay::raop::buffered_audio::PlayoutCommand;
#[test]
fn playout_command_variants() {
let _ = PlayoutCommand::SetRate {
anchor_rtp: 0,
anchor_time_ns: 0,
rate: 1,
};
let _ = PlayoutCommand::Flush {
from_seq: 0,
until_seq: 100,
};
let _ = PlayoutCommand::Stop;
}
}