use std::time::{Duration, Instant};
use crate::event::Event;
use crate::wire::{op, BayFeatures, BayStatus, BayUid, DeviceFeature, V2IP_PORT_VIDEO};
use crate::testing::{bay_config_rec, link_rec, poisoned, stream_rec, uid_n};
use super::Harness;
#[test]
fn discovery_builds_a_device_from_its_configuration() {
let mut h = Harness::new(1);
let sender = h.sender;
let source = uid_n(9);
h.hello(
0x27,
"FF88",
"AB1234",
DeviceFeature::V2IP_SOURCE | DeviceFeature::V2IP_SINK,
);
assert_eq!(h.device().serial(), "AB1234");
assert!(h.device().is_v2ip());
assert_eq!(
h.device().hello.address,
Some(std::net::Ipv4Addr::new(10, 8, 8, 9))
);
let mut cfg = bay_config_rec(
0,
0,
0,
"Input 1",
"Apple TV",
BayStatus::NONE,
BayFeatures::V2IP_SOURCE_LOCAL,
);
cfg.extend(bay_config_rec(
1,
1,
0,
"Output 1",
"Living Room",
BayStatus::NONE,
BayFeatures::V2IP_SINK_LOCAL,
));
h.feed(op::SYS_BAY_CONFIG, &cfg);
assert_eq!(h.device().bays.len(), 2);
let input = h.bay(0);
assert!(input.is_input() && input.is_v2ip_source());
assert_eq!(input.user_name(), "Apple TV");
let mut sources = stream_rec(
source,
"239.1.1.1",
"239.1.1.2",
"239.1.1.3",
V2IP_PORT_VIDEO,
);
sources.extend(stream_rec(
uid_n(10),
"239.2.2.1",
"239.2.2.2",
"239.2.2.3",
V2IP_PORT_VIDEO,
));
h.feed(op::SYS_BAY_V2IP_SOURCES, &sources);
let advertised = h
.device()
.v2ip_sources
.clone()
.expect("no stream addresses");
assert_eq!(advertised.len(), 2);
assert_eq!(advertised[1].uid, uid_n(10));
assert_eq!(
advertised[1].video.ip,
std::net::Ipv4Addr::new(239, 2, 2, 1)
);
assert_eq!(advertised[1].audio.port, V2IP_PORT_VIDEO);
let device = h.device();
let streams = device
.v2ip_source_for(device.bay(0).expect("input bay"))
.expect("no stream addresses");
assert_eq!(streams.video.ip, std::net::Ipv4Addr::new(239, 1, 1, 1));
assert_eq!(streams.audio.ip, std::net::Ipv4Addr::new(239, 1, 1, 2));
assert_eq!(
h.state.link_key(BayUid::new(sender, 0)),
Some(BayUid::new(source, 0))
);
assert!(!h.complete());
h.feed(op::SYS_LINKS, &link_rec(0, "AMP00001", "Zone 1", 0));
assert!(h.complete());
assert!(h.saw(|e| matches!(e, Event::DeviceConfigComplete { .. })));
}
#[test]
fn a_reboot_waits_for_the_bays_again() {
let mut h = Harness::new(4);
let plain = DeviceFeature::from_bits(0);
h.hello(0x2F, "UNIT", "RB0001", plain);
let bays = bay_config_rec(
0,
0,
0,
"Input 1",
"",
BayStatus::NONE,
BayFeatures::HDMI_IN,
);
h.feed(op::SYS_BAY_CONFIG, &bays);
assert!(h.complete());
h.hello(0x2F, "UNIT", "RB0001", DeviceFeature::STATUS_REBOOT);
h.hello(0x2F, "UNIT", "RB0001", plain);
assert!(
!h.complete(),
"described by the bays it sent before rebooting"
);
h.feed(op::SYS_BAY_CONFIG, &bays);
assert!(h.complete());
let later = Instant::now() + Duration::from_secs(120);
let sender = h.sender;
let device = h.state.device_mut(sender).expect("registered");
device.check_online(later, &mut Vec::new());
assert!(!device.online);
h.hello(0x2F, "UNIT", "RB0001", plain);
assert!(
!h.complete(),
"described by the bays it sent before going offline"
);
h.feed(op::SYS_BAY_CONFIG, &bays);
assert!(h.complete());
}
#[test]
fn a_link_moved_to_another_bay_of_the_same_unit_is_reported() {
let mut h = Harness::new(3);
h.hello(0x2F, "FF88", "HD0004", DeviceFeature::VIDEO_ROUTING);
h.feed(
op::SYS_BAY_CONFIG,
&bay_config_rec(
0,
1,
0,
"Output 1",
"TV",
BayStatus::NONE,
BayFeatures::HDMI_OUT,
),
);
h.feed(op::SYS_LINKS, &link_rec(0, "AMP00001", "Zone 1", 0));
h.events.clear();
h.feed(op::SYS_LINKS, &link_rec(0, "AMP00001", "Zone 2", 0));
assert!(h.saw(|e| matches!(e, Event::BayUnlinked { .. })));
assert!(h.saw(|e| matches!(e, Event::BayLinked { .. })));
h.events.clear();
h.feed(op::SYS_LINKS, &link_rec(0, "AMP00001", "Zone 2", 0));
assert!(h.events.is_empty(), "a repeat is not a change");
}
#[test]
fn mirror_and_mesh_reports() {
let mut h = Harness::new(2);
let sender = h.sender;
let master = uid_n(7);
h.hello(
0x27,
"ONEIP",
"RX0001",
DeviceFeature::V2IP_SINK | DeviceFeature::MESH,
);
h.feed(
op::SYS_BAY_CONFIG,
&bay_config_rec(
0,
1,
0,
"Output 1",
"TV",
BayStatus::NONE,
BayFeatures::V2IP_SINK_LOCAL,
),
);
let mut mp = poisoned(32);
mp[0..16].copy_from_slice(sender.as_bytes());
mp[16..32].copy_from_slice(master.as_bytes());
h.feed(op::BAY_MIRROR_STATUS, &mp);
let mirror = h.bay(0).mirror;
assert!(mirror.is_mirroring());
assert_eq!(mirror.target.map(|b| b.device), Some(master));
assert!(h.saw(|e| matches!(e, Event::MirrorStatusChanged { .. })));
mp[16..32].fill(0xFF);
h.feed(op::BAY_MIRROR_STATUS, &mp[..31]);
assert!(h.bay(0).mirror.is_mirroring(), "a short report was taken");
h.feed(op::BAY_MIRROR_STATUS, &mp);
assert!(!h.bay(0).mirror.is_mirroring(), "all ones read as a device");
let controller = uid_n(31);
h.feed_as(
controller,
op::SYS_HELLO,
&crate::testing::hello_payload(0x2F, "Ctrl", "CTRL0009", "4.8.0", DeviceFeature::MANAGER),
);
mp[16..32].copy_from_slice(master.as_bytes());
h.feed_as(controller, op::BAY_MIRROR_STATUS, &mp);
assert!(
h.bay(0).mirror.is_mirroring(),
"management's mirror was lost"
);
let mut mesh = poisoned(40);
mesh[0] = 0xFF;
mesh[4..20].copy_from_slice(master.as_bytes());
h.feed(op::MESH_OPERATION, &mesh);
assert_eq!(h.device().mesh_master, master);
assert!(h.saw(|e| matches!(e, Event::MeshMasterChanged { .. })));
}
#[test]
fn a_mesh_report_below_its_own_gate_is_not_a_master() {
let mut h = Harness::new(3);
let master = uid_n(77);
h.hello(
0x28,
"ONEIP",
"MG0001",
DeviceFeature::V2IP_SINK | DeviceFeature::MESH,
);
let mut mesh = poisoned(40);
mesh[0] = 0xFF;
mesh[4..20].copy_from_slice(master.as_bytes());
h.feed(op::MESH_OPERATION, &mesh[..36]);
assert_eq!(
h.device().mesh_master,
crate::wire::DeviceUid::ZERO,
"a frame short of the struct named a master"
);
h.feed_proto(op::MESH_OPERATION, 0x19, &mesh);
assert_eq!(
h.device().mesh_master,
crate::wire::DeviceUid::ZERO,
"a frame stamped below the accept gate named a master"
);
h.feed_proto(op::MESH_OPERATION, 0x1A, &mesh);
assert_eq!(h.device().mesh_master, master);
}
#[test]
fn a_frame_from_an_unknown_sender_is_not_acted_on() {
use crate::testing::hello_payload;
let mut h = Harness::new(12);
let sink = h.sender;
let stranger = uid_n(99);
h.hello(
0x28,
"ONEIP",
"SG0001",
DeviceFeature::V2IP_SINK | DeviceFeature::MESH,
);
let mut switch = poisoned(40);
switch[0..16].copy_from_slice(sink.as_bytes());
switch[16..20].copy_from_slice(&[239, 1, 1, 1]);
switch[24..28].copy_from_slice(&[239, 1, 1, 2]);
h.feed_as(stranger, op::V2IP_MANUAL_SRC_SWITCH, &switch);
assert!(
h.device().v2ip_sink.is_none(),
"a stranger's route request was applied to the sink"
);
h.feed_as(
stranger,
op::SYS_HELLO,
&hello_payload(0x28, "Ctrl", "SG0002", "4.8.0", DeviceFeature::MANAGER),
);
h.feed_as(stranger, op::V2IP_MANUAL_SRC_SWITCH, &switch);
assert!(
h.device().v2ip_sink.is_some(),
"the hello exemption did not register the sender"
);
}
fn source_device(n: u8) -> Harness {
let mut h = Harness::new(n);
h.hello(
0x27,
"FF88",
"PG0004",
DeviceFeature::V2IP_SOURCE | DeviceFeature::V2IP_SINK,
);
h
}
fn source_page(first: u16, total: u16, records: &[Vec<u8>]) -> Vec<u8> {
let mut p = Vec::new();
p.extend_from_slice(&first.to_le_bytes());
p.extend_from_slice(&total.to_le_bytes());
p.extend_from_slice(&[0; 4]);
for r in records {
p.extend_from_slice(r);
}
p
}
fn source_rec(n: u8) -> Vec<u8> {
stream_rec(
uid_n(n),
&format!("239.9.{n}.1"),
&format!("239.9.{n}.2"),
&format!("239.9.{n}.3"),
V2IP_PORT_VIDEO,
)
}
#[test]
fn a_source_list_of_an_unplaceable_length_is_refused() {
let mut h = source_device(31);
let mut odd = source_rec(41);
odd.extend_from_slice(&[0; 4]);
h.feed(op::SYS_BAY_V2IP_SOURCES, &odd);
assert!(
h.device().v2ip_sources.is_none(),
"a record read at the wrong offset was reported as a source"
);
}
#[test]
fn an_empty_source_list_completes_a_device() {
let mut h = source_device(34);
h.feed(
op::SYS_BAY_CONFIG,
&bay_config_rec(
1,
1,
0,
"Output 1",
"TV",
BayStatus::NONE,
BayFeatures::V2IP_SINK_LOCAL,
),
);
h.feed(op::SYS_LINKS, &link_rec(0, "AMP00001", "Zone 1", 0));
assert!(!h.complete(), "complete before its source list");
h.feed(op::SYS_BAY_V2IP_SOURCES, &[]);
assert_eq!(h.device().v2ip_sources, Some(Vec::new()));
assert!(
h.complete(),
"an empty source list was not taken as the list"
);
}
#[test]
fn source_pages_are_placed_by_their_stated_position() {
let mut h = source_device(32);
h.feed(
op::SYS_BAY_V2IP_SOURCES,
&source_page(2, 4, &[source_rec(43), source_rec(44)]),
);
assert!(
h.device().v2ip_sources.is_none(),
"a list was reported with its first records still missing"
);
h.feed(
op::SYS_BAY_V2IP_SOURCES,
&source_page(0, 4, &[source_rec(41), source_rec(42)]),
);
let sources = h.device().v2ip_sources.clone().expect("no sources");
assert_eq!(
sources.iter().map(|s| s.uid).collect::<Vec<_>>(),
vec![uid_n(41), uid_n(42), uid_n(43), uid_n(44)],
"pages were ordered by arrival rather than by position"
);
}
#[test]
fn a_page_does_not_replace_the_list_it_is_part_of() {
let mut h = source_device(33);
h.feed(
op::SYS_BAY_V2IP_SOURCES,
&[source_rec(41), source_rec(42), source_rec(43)].concat(),
);
assert_eq!(h.device().v2ip_sources.as_ref().map(Vec::len), Some(3));
h.feed(
op::SYS_BAY_V2IP_SOURCES,
&source_page(1, 3, &[source_rec(52)]),
);
let sources = h.device().v2ip_sources.clone().expect("the list was lost");
assert_eq!(
sources.iter().map(|s| s.uid).collect::<Vec<_>>(),
vec![uid_n(41), uid_n(52), uid_n(43)],
"a page was read as the whole list"
);
}
#[test]
fn a_device_that_has_not_sent_its_bays_is_not_fully_described() {
let mut h = Harness::new(34);
h.hello(0x28, "FF88", "PG0005", DeviceFeature::VIDEO_ROUTING);
assert!(
!h.complete(),
"a device was fully described on its hello alone"
);
assert!(
!h.saw(|e| matches!(e, Event::DeviceConfigComplete { .. })),
"completion was announced before anything was known"
);
h.feed(
op::SYS_BAY_CONFIG_SECONDARY,
&bay_config_rec(
2,
1,
0,
"Output 2",
"TV",
BayStatus::NONE,
BayFeatures::HDMI_OUT,
),
);
h.feed(op::SYS_LINKS, &link_rec(2, "AMP00001", "Zone 2", 0));
assert!(
!h.complete(),
"the secondary list was taken for the configuration"
);
h.feed(
op::SYS_BAY_CONFIG,
&bay_config_rec(
1,
1,
0,
"Output 1",
"TV",
BayStatus::NONE,
BayFeatures::HDMI_OUT,
),
);
assert!(h.complete());
assert!(h.saw(|e| matches!(e, Event::DeviceConfigComplete { .. })));
}
#[test]
fn management_is_the_controller_a_unit_follows() {
use crate::testing::hello_payload;
let mut h = Harness::new(5);
h.hello(0x2F, "ONEIP", "MG0001", DeviceFeature::V2IP_SINK);
h.feed(
op::SYS_BAY_CONFIG,
&bay_config_rec(
0,
1,
0,
"Output 1",
"TV",
BayStatus::NONE,
BayFeatures::HDMI_OUT,
),
);
let (followed, other) = (uid_n(40), uid_n(41));
for (uid, features) in [
(followed, DeviceFeature::from_bits(0)),
(other, DeviceFeature::MESH_MASTER),
] {
h.feed_as(
uid,
op::SYS_HELLO,
&hello_payload(0x2F, "ONEIP", "MG0002", "4.8.0", features),
);
}
let unit = h.sender;
let hide = |hidden: u8| {
let mut p = unit.as_bytes().to_vec();
p.extend_from_slice(&[0, 0, hidden, 0, 0, 0, 0, 0]);
p
};
h.feed_as(other, op::BAY_HIDE, &hide(1));
assert_eq!(h.bay(0).hidden, Some(true));
let mut mesh = poisoned(40);
mesh[0] = 0xFF;
mesh[4..20].copy_from_slice(followed.as_bytes());
h.feed(op::MESH_OPERATION, &mesh);
h.feed_as(other, op::BAY_HIDE, &hide(0));
assert_eq!(h.bay(0).hidden, Some(true), "another mesh's controller");
h.feed_as(followed, op::BAY_HIDE, &hide(0));
assert_eq!(h.bay(0).hidden, Some(false), "the controller it follows");
}
#[test]
fn a_released_unit_takes_commands_from_anyone() {
use crate::testing::hello_payload;
let mut h = Harness::new(6);
h.hello(0x27, "ONEIP", "RL0001", DeviceFeature::V2IP_SINK);
h.feed(
op::SYS_BAY_CONFIG,
&bay_config_rec(
0,
1,
0,
"Output 1",
"TV",
BayStatus::NONE,
BayFeatures::HDMI_OUT,
),
);
let peer = uid_n(42);
h.feed_as(
peer,
op::SYS_HELLO,
&hello_payload(
0x27,
"ONEIP",
"RL0002",
"4.4.117",
DeviceFeature::V2IP_SOURCE,
),
);
let mut hide = h.sender.as_bytes().to_vec();
hide.extend_from_slice(&[0, 0, 1, 0, 0, 0, 0, 0]);
h.feed_as(peer, op::BAY_HIDE, &hide);
assert_eq!(h.bay(0).hidden, Some(true));
let unit = h.sender;
let addressed = |len: usize| {
let mut p = vec![0u8; len];
p[0..16].copy_from_slice(unit.as_bytes());
p
};
let mut tiling = addressed(24);
tiling[20..22].copy_from_slice(&1920u16.to_le_bytes());
h.feed_as(peer, op::V2IP_TILING, &tiling);
assert!(h.device().tiling.is_some(), "a window");
let mut switch = addressed(48);
switch[16..20].copy_from_slice(&[239, 1, 1, 1]);
h.feed_as(peer, op::V2IP_MANUAL_SRC_SWITCH, &switch);
assert!(h.device().v2ip_sink.is_some(), "a manual switch");
let mut mirror = addressed(32);
mirror[16..32].copy_from_slice(uid_n(43).as_bytes());
h.feed_as(peer, op::BAY_MIRROR_STATUS, &mirror);
assert!(h.bay(0).mirror.is_mirroring(), "a mirror");
let mut filter = addressed(32);
filter[16..32].copy_from_slice(uid_n(44).as_bytes());
h.feed_as(peer, op::BAY_FILTER_STATUS, &filter);
assert_eq!(h.bay(0).filtered, [uid_n(44)], "a filter");
let source = uid_n(45);
h.feed_as(
source,
op::SYS_HELLO,
&hello_payload(
0x27,
"ONEIP",
"RL0003",
"4.4.117",
DeviceFeature::V2IP_SOURCE,
),
);
h.feed_as(
source,
op::SYS_BAY_CONFIG,
&bay_config_rec(
0,
0,
0,
"Input 1",
"Sky",
BayStatus::NONE,
BayFeatures::HDMI_IN,
),
);
h.feed_as(
source,
op::SYS_BAY_V2IP_SOURCES,
&stream_rec(
source,
"239.4.4.1",
"239.4.4.2",
"239.4.4.3",
V2IP_PORT_VIDEO,
),
);
let mut route = addressed(24);
route[16..20].copy_from_slice(&[239, 4, 4, 1]);
h.feed_as(peer, op::V2IP_SOURCE_SWITCH, &route);
assert_eq!(
h.bay(0).video_source.map(|b| b.device),
Some(source),
"a source switch"
);
let mut view = addressed(25);
view[16] = 1; h.feed_as(peer, op::V2IP_MULTIVIEWER, &view);
assert!(
h.saw(|e| matches!(e, Event::MultiviewerCommand { .. })),
"a multiviewer command"
);
let mut mute = vec![1u8, 0, 0, 0];
mute.extend_from_slice(unit.as_bytes());
mute.extend_from_slice(&[2, 0, 0, 0, 1, 0, 0, 0]);
h.feed_as(peer, op::V2IP_AUDIO, &mute);
assert!(
h.saw(|e| matches!(e, Event::AudioEndpointMute { .. })),
"an audio command"
);
}