use std::net::Ipv4Addr;
use crate::types::{
AmpZoneSettings, V2ipAudioFormat, VideoWallOp, VideoWallWindow, VolumeMuteStatus,
};
use super::bayconfig::{parse_bay_config, BAY_CONFIG_SIZE};
use super::enums::{
BayFeatures, BayStatus, DeviceFeature, EdidProfile, MultiviewerViewMode, RcAction, RcKey,
};
use super::frame::build_frame;
use super::opcode::{audio_sub, op, protocol_for};
use super::payload::*;
use super::uid::DeviceUid;
const TEST_UID: DeviceUid =
DeviceUid::from_array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]);
const SINK_UID: DeviceUid = DeviceUid::from_array([
0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f,
]);
const SOURCE_UID: DeviceUid = DeviceUid::from_array([
0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f,
]);
fn hex_of(b: &[u8]) -> String {
use core::fmt::Write as _;
b.iter()
.fold(String::with_capacity(b.len() * 2), |mut out, x| {
let _ = write!(out, "{x:02x}");
out
})
}
const MV_OP_VIEW_MODE: u8 = 1;
const MV_OP_AUDIO_VOLUME: u8 = 4;
#[test]
fn uid_round_trips_through_its_dotted_hex_form() {
assert_eq!(TEST_UID.to_string(), "03020100.07060504.0b0a0908.0f0e0d0c");
assert_eq!(
TEST_UID.to_string().parse::<DeviceUid>().unwrap(),
TEST_UID,
"dotted-hex form did not survive a round trip"
);
}
#[test]
fn hello_frame() {
let payload = build_hello(
0x28,
"TestApp",
"P9SN00000000",
"2.1.3",
DeviceFeature::MANAGER.bits(),
);
let got = build_frame(
TEST_UID,
op::SYS_HELLO,
protocol_for(op::SYS_HELLO),
&payload,
);
assert_eq!(
hex_of(&got),
"50380100000102030405060708090a0b0c0d0e0f000036002800546573744170700000000000000000005039534e303030303030303000000000322e312e33000000000000000000000000000800"
);
}
#[test]
fn discover_frame() {
let got = build_frame(
TEST_UID,
op::SYS_DISCOVER,
protocol_for(op::SYS_DISCOVER),
&[],
);
assert_eq!(
hex_of(&got),
"50380100000102030405060708090a0b0c0d0e0f01000000"
);
}
#[test]
fn set_bay_name_frame() {
let payload = build_set_bay_name(TEST_UID, 5, "Living Room");
let got = build_frame(
TEST_UID,
op::CHANGE_BAY_NAME,
protocol_for(op::CHANGE_BAY_NAME),
&payload,
);
assert_eq!(
hex_of(&got),
"50380600000102030405060708090a0b0c0d0e0f22002800000102030405060708090a0b0c0d0e0f05004c6976696e6720526f6f6d0000000000000000000000"
);
}
#[test]
fn set_volume_frame() {
let vol = VolumeMuteStatus {
volume_left: Some(40),
volume_right: Some(40),
muted_left: Some(false),
muted_right: Some(false),
};
assert_eq!(hex_of(&vol.wire()), "282800");
let payload = build_set_volume(TEST_UID, 5, vol);
let got = build_frame(
TEST_UID,
op::AUDIO_SET_VOLUME,
protocol_for(op::AUDIO_SET_VOLUME),
&payload,
);
assert_eq!(
hex_of(&got),
"50381100000102030405060708090a0b0c0d0e0f14001800000102030405060708090a0b0c0d0e0f0500282800000000"
);
}
#[test]
fn manual_source_switch_frame() {
let format = V2ipAudioFormat {
sample_rate: 48000,
channels: 2,
};
let payload = build_v2ip_manual_source_switch(
TEST_UID,
V2ipStreams {
video: StreamAddr {
ip: Ipv4Addr::new(239, 1, 2, 3),
port: 50020,
},
audio: StreamAddr {
ip: Ipv4Addr::new(239, 1, 2, 4),
port: 50022,
},
anc: StreamAddr::default(),
},
Some(format),
);
let got = build_frame(
TEST_UID,
op::V2IP_MANUAL_SRC_SWITCH,
protocol_for(op::V2IP_MANUAL_SRC_SWITCH),
&payload,
);
assert_eq!(
hex_of(&got),
"50380700000102030405060708090a0b0c0d0e0f24003000000102030405060708090a0b0c0d0e0fef01020364c30000ef01020466c30000000000000000000080bb000002000000"
);
}
#[test]
fn edid_profile_frame() {
let payload = build_edid_profile(TEST_UID, EdidProfile::HDR_SURROUND71_4K);
let got = build_frame(
TEST_UID,
op::BAY_EDID_PROFILE,
protocol_for(op::BAY_EDID_PROFILE),
&payload,
);
assert_eq!(
hex_of(&got),
"50380800000102030405060708090a0b0c0d0e0f34001800000102030405060708090a0b0c0d0e0f0800000000000000"
);
}
#[test]
fn reboot_frame() {
let payload = build_target_only(TEST_UID);
let got = build_frame(
TEST_UID,
op::SYS_REBOOT,
protocol_for(op::SYS_REBOOT),
&payload,
);
assert_eq!(
hex_of(&got),
"50380100000102030405060708090a0b0c0d0e0f28001000000102030405060708090a0b0c0d0e0f"
);
}
#[test]
fn audio_select_input_frame() {
let payload = build_audio_select_input(SINK_UID, 0x0203, SOURCE_UID, 0x0405);
let got = build_frame(
TEST_UID,
op::V2IP_AUDIO,
protocol_for(op::V2IP_AUDIO),
&payload,
);
assert_eq!(
hex_of(&got),
"50381a00000102030405060708090a0b0c0d0e0f4300380003000000202122232425262728292a2b2c2d2e2f202122232425262728292a2b2c2d2e2f101112131415161718191a1b1c1d1e1f03020504"
);
}
#[test]
fn rc_tx_key_frame() {
let payload = build_rc_key(TEST_UID, 0x0203, RcKey::from_wire(0x0405));
let got = build_frame(
TEST_UID,
op::RC_TX_KEY,
protocol_for(op::RC_TX_KEY),
&payload,
);
assert_eq!(
hex_of(&got),
"50380c00000102030405060708090a0b0c0d0e0f0c001400000102030405060708090a0b0c0d0e0f03020504"
);
}
#[test]
fn bay_hide_frame() {
let payload = build_bay_hide(TEST_UID, 0x0203, true);
let got = build_frame(TEST_UID, op::BAY_HIDE, protocol_for(op::BAY_HIDE), &payload);
assert_eq!(
hex_of(&got),
"50380600000102030405060708090a0b0c0d0e0f27001800000102030405060708090a0b0c0d0e0f0302010000000000"
);
}
#[test]
fn edid_request_frame() {
let payload = build_edid_request(TEST_UID, true);
let got = build_frame(TEST_UID, op::DEV_EDID, protocol_for(op::DEV_EDID), &payload);
assert_eq!(
hex_of(&got),
"50380100000102030405060708090a0b0c0d0e0f07001100000102030405060708090a0b0c0d0e0f01"
);
}
#[test]
fn v2ip_source_switch_frame() {
let payload = build_v2ip_source_switch(
TEST_UID,
Ipv4Addr::new(10, 8, 8, 9),
Ipv4Addr::new(172, 20, 30, 40),
);
let got = build_frame(
TEST_UID,
op::V2IP_SOURCE_SWITCH,
protocol_for(op::V2IP_SOURCE_SWITCH),
&payload,
);
assert_eq!(
hex_of(&got),
"50380600000102030405060708090a0b0c0d0e0f1f001800000102030405060708090a0b0c0d0e0f0a080809ac141e28"
);
}
#[test]
fn video_wall_frame() {
let window = VideoWallWindow {
pos_x: 1920,
pos_y: 1080,
width: 960,
height: 540,
raster_w: 3840,
raster_h: 2160,
};
let payload = build_video_wall(TEST_UID, window, VideoWallOp::STORE);
let got = build_frame(
TEST_UID,
op::V2IP_VIDEO_WALL,
protocol_for(op::V2IP_VIDEO_WALL),
&payload,
);
assert_eq!(
hex_of(&got),
"50382800000102030405060708090a0b0c0d0e0f49002000000102030405060708090a0b0c0d0e0f80073804c0031c02000f700801000000"
);
}
#[test]
fn rc_action_frame() {
let payload = build_rc_action(TEST_UID, 5, RcAction::POWER_ON);
let got = build_frame(
TEST_UID,
op::RC_TX_ACTION,
protocol_for(op::RC_TX_ACTION),
&payload,
);
assert_eq!(
hex_of(&got),
"50380c00000102030405060708090a0b0c0d0e0f0e001400000102030405060708090a0b0c0d0e0f05000100"
);
}
#[test]
fn multiviewer_frames() {
let view = build_frame(
TEST_UID,
op::V2IP_MULTIVIEWER,
protocol_for(op::V2IP_MULTIVIEWER),
&mv_cmd_payload(
TEST_UID,
MV_OP_VIEW_MODE,
&[MultiviewerViewMode::PIP.to_wire()],
),
);
assert_eq!(
hex_of(&view),
"50381600000102030405060708090a0b0c0d0e0f42001900000102030405060708090a0b0c0d0e0f010000000000000002"
);
let volume = build_frame(
TEST_UID,
op::V2IP_MULTIVIEWER,
protocol_for(op::V2IP_MULTIVIEWER),
&mv_cmd_payload(TEST_UID, MV_OP_AUDIO_VOLUME, &[42, 1]),
);
assert_eq!(
hex_of(&volume),
"50381600000102030405060708090a0b0c0d0e0f42001a00000102030405060708090a0b0c0d0e0f04000000000000002a01"
);
}
#[test]
fn audio_command_frames() {
let mut mute = audio_cmd_header(audio_sub::MUTE, TEST_UID);
mute.extend_from_slice(&audio_param(3, 1));
let got = build_frame(
TEST_UID,
op::V2IP_AUDIO,
protocol_for(op::V2IP_AUDIO),
&mute,
);
assert_eq!(
hex_of(&got),
"50381a00000102030405060708090a0b0c0d0e0f43001c0001000000000102030405060708090a0b0c0d0e0f0300000001000000"
);
let mut volume = audio_cmd_header(audio_sub::VOLUME, TEST_UID);
volume.extend_from_slice(&audio_param(5, 80));
let got = build_frame(
TEST_UID,
op::V2IP_AUDIO,
protocol_for(op::V2IP_AUDIO),
&volume,
);
assert_eq!(
hex_of(&got),
"50381a00000102030405060708090a0b0c0d0e0f43001c0004000000000102030405060708090a0b0c0d0e0f0500000050000000"
);
}
#[test]
fn amp_zone_settings_frame() {
let settings = AmpZoneSettings {
gain_left: 10,
gain_right: 11,
volume_min: 0,
volume_max: 100,
delay_left: 5,
delay_right: 6,
bass: 3,
treble: 4,
bridged: 1,
power_mode: 2,
power_level: 7,
power_timeout: 300,
eq_left: [1, 2, 3, 4, 5],
eq_right: [6, 7, 8, 9, 10],
};
let payload = build_amp_zone_settings(TEST_UID, 5, &settings);
let got = build_frame(
TEST_UID,
op::AMP_ZONE_SETTINGS,
protocol_for(op::AMP_ZONE_SETTINGS),
&payload,
);
assert_eq!(
hex_of(&got),
"50381c00000102030405060708090a0b0c0d0e0f3d003800000102030405060708090a0b0c0d0e0f05000a0b00640000050000000600000003040102070000002c0100000102030405060708090a0000"
);
}
#[test]
fn stats_request_frame() {
let payload = build_stats_request(TEST_UID, true);
let got = build_frame(
TEST_UID,
op::V2IP_STATS,
protocol_for(op::V2IP_STATS),
&payload,
);
assert_eq!(
hex_of(&got),
"50381300000102030405060708090a0b0c0d0e0f3f001100000102030405060708090a0b0c0d0e0f01"
);
}
#[test]
fn bay_config_record_fields() {
let mut p = vec![0u8; BAY_CONFIG_SIZE];
p[0] = 7; p[1] = 1; p[2] = 2; p[3] = 3; p[4] = 4; p[5..13].copy_from_slice(b"Output 3");
p[21..28].copy_from_slice(b"Kitchen");
p[37..44].copy_from_slice(b"1080p60");
p[53] = 1 << 3; p[57] = 1 << 0;
let cfg = parse_bay_config(&p).expect("a full-width record must parse");
assert_eq!((cfg.port, cfg.modenum, cfg.bay), (7, 1, 2));
assert_eq!(cfg.bay_name, "Output 3");
assert_eq!(cfg.user_name, "Kitchen");
assert_eq!(cfg.signal_type, "1080p60");
assert!(cfg.status.has(BayStatus::SIGNAL_DETECTED));
assert!(cfg.features.has(BayFeatures::HDMI_OUT));
}
#[test]
fn a_record_shorter_than_the_declared_width_is_dropped() {
assert!(parse_bay_config(&[0u8; BAY_CONFIG_SIZE - 1]).is_none());
}