use std::net::Ipv4Addr;
use crate::event::Event;
use crate::state::{Device, HelloInfo, State};
use crate::types::{
BayMirrorStatus, ConnectStatus, DeviceV2ipDetails, DeviceV2ipSink, FirmwareVersion,
HiddenStatus, MuteStatus, PowerStatus, StreamKind, TopologyEntry, V2ipDeviceSettings,
V2ipDscpConfig, V2ipScalingSettings, V2ipStreamSource, V2ipStreamSources, V2ipTilingConfig,
VolumeMuteStatus, SCALING_FLAGS_DEFINED, SCALING_FLAG_MATCH_SOURCE,
SCALING_FLAG_OPTIONS2_VALID, SCALING_FLAG_SKIP_420, VOLUME_UNCHANGED,
};
use crate::wire::{
op, parse_bay_config, BayStatus, BayUid, DeviceFeature, DeviceUid, FirmwareType, Frame,
MxrSignalType, RcAction, RcKey, V2ipDeviceSetting, V2ipFpgaFeature, BAY_CONFIG_SIZE,
FW_VERSION_LEN,
};
use super::Rx;
const RC_NARROW_SIZE: usize = 3;
const RC_WIDE_SIZE: usize = 4;
const SET_VOLUME_LEGACY_SIZE: usize = 20;
fn rc_bay_and_value(f: &Frame) -> Option<(u16, u16)> {
match f.payload().len() {
RC_NARROW_SIZE => Some((u16::from(f.u8(0)?), f.u16(1)?)),
n if n >= RC_WIDE_SIZE => Some((f.u16(0)?, f.u16(2)?)),
_ => None,
}
}
pub(super) fn hello(state: &mut State, rx: &Rx<'_>, ev: &mut Vec<Event>) {
let f = &rx.frame;
let hello = HelloInfo {
supported_protocol: f.u16(0).unwrap_or(0),
name: f.str(2, 16).unwrap_or_default(),
serial: f.str(18, 16).unwrap_or_default(),
version: f.str(34, 16).unwrap_or_default(),
features: DeviceFeature::from_bits(f.u32(50).unwrap_or(0)),
address: rx.address,
};
state.apply_hello(rx.sender(), hello, rx.timestamp, ev);
}
pub(super) fn bay_config(state: &mut State, rx: &Rx<'_>, ev: &mut Vec<Event>) {
let Some(device) = state.device_mut(rx.sender()) else {
return;
};
let payload = rx.frame.payload();
for record in payload.chunks_exact(BAY_CONFIG_SIZE) {
if let Some(cfg) = parse_bay_config(record) {
device.apply_bay_config(&cfg, rx.timestamp, ev);
}
}
if rx.frame.opcode() == op::SYS_BAY_CONFIG {
device.note_bay_config(rx.timestamp, ev);
}
}
pub(super) fn connect_status(state: &mut State, rx: &Rx<'_>, ev: &mut Vec<Event>) {
let Some(port) = rx.frame.u8(0) else {
return;
};
let status = if rx.frame.boolean(1) {
ConnectStatus::Connected
} else {
ConnectStatus::Disconnected
};
if let Some(bay) = state.bay_mut(BayUid::new(rx.sender(), u16::from(port))) {
bay.apply_connect_status(status, ev);
}
}
pub(super) fn power_change(state: &mut State, rx: &Rx<'_>, ev: &mut Vec<Event>) {
let Some(port) = rx.frame.u8(0) else {
return;
};
let power = if rx.frame.boolean(1) {
PowerStatus::On
} else {
PowerStatus::Off
};
if let Some(bay) = state.bay_mut(BayUid::new(rx.sender(), u16::from(port))) {
bay.set_power_status(power, ev);
}
}
pub(super) fn routing_change(state: &mut State, rx: &Rx<'_>, ev: &mut Vec<Event>) {
let f = &rx.frame;
let Some(sink_port) = f.u16(0) else {
return;
};
let sender = rx.sender();
let Some(device) = state.device(sender) else {
return;
};
if device.bay(sink_port).is_none() {
return;
}
let resolve = |port: Option<u16>| {
port.filter(|p| device.bay(*p).is_some())
.map(|p| BayUid::new(sender, p))
};
let video = resolve(f.u16(4));
let audio = resolve(f.u16(7));
if let Some(bay) = state.bay_mut(BayUid::new(sender, sink_port)) {
if let Some(video) = video {
bay.set_video_source(Some(video), ev);
}
if let Some(audio) = audio {
bay.set_audio_source(Some(audio), ev);
}
}
}
pub(super) fn rc_action(state: &mut State, rx: &Rx<'_>, ev: &mut Vec<Event>) {
let Some(device) = state.device(rx.sender()) else {
return;
};
let Some((port, action)) = rc_bay_and_value(&rx.frame) else {
return;
};
if device.bay(port).is_some() {
ev.push(Event::ActionReceived {
bay: BayUid::new(rx.sender(), port),
action: RcAction::from_wire(action),
});
}
}
pub(super) fn rc_key(state: &mut State, rx: &Rx<'_>, ev: &mut Vec<Event>) {
let Some(device) = state.device(rx.sender()) else {
return;
};
let Some((port, key)) = rc_bay_and_value(&rx.frame) else {
return;
};
if device.bay(port).is_some() {
ev.push(Event::KeyPressed {
bay: BayUid::new(rx.sender(), port),
key: RcKey::from_wire(key),
});
}
}
pub(super) fn volume_set(state: &mut State, rx: &Rx<'_>, ev: &mut Vec<Event>) {
let f = &rx.frame;
let (target, port, at) = match f.payload().len() {
n if n > SET_VOLUME_LEGACY_SIZE => (rx.uid_or_zero(0), f.u16(16), 18),
SET_VOLUME_LEGACY_SIZE => {
let serial = f.str(0, 16).unwrap_or_default();
let Some(device) = state.device_by_serial(&serial) else {
return;
};
(device.uid, f.u8(16).map(u16::from), 17)
}
_ => return,
};
let Some(port) = port else {
return;
};
let muted = f.u8(at + 2).filter(|m| *m != VOLUME_UNCHANGED);
let volume = VolumeMuteStatus {
volume_left: f.u8(at).filter(|v| *v <= 100),
volume_right: f.u8(at + 1).filter(|v| *v <= 100),
muted_left: muted.map(|m| MuteStatus::from_wire(m).left()),
muted_right: muted.map(|m| MuteStatus::from_wire(m).right()),
};
if let Some(device) = state.device_mut(target) {
device.apply_bay_volume(port, volume, ev);
}
}
pub(super) fn temperature(state: &mut State, rx: &Rx<'_>, ev: &mut Vec<Event>) {
let f = &rx.frame;
let Some(count) = f.u8(0) else {
return;
};
let temperatures = (1..=usize::from(count)).filter_map(|i| f.u8(i)).collect();
if let Some(device) = state.device_mut(rx.sender()) {
device.set_temperatures(temperatures, ev);
}
}
const V2IP_SOURCE_RECORD: usize = 40;
const V2IP_SOURCE_PAGE_HEADER: usize = 8;
pub(super) fn v2ip_sources(state: &mut State, rx: &Rx<'_>, ev: &mut Vec<Event>) {
let payload = rx.frame.payload();
let (first, total, body) = match payload.len() % V2IP_SOURCE_RECORD {
0 => (0, payload.len() / V2IP_SOURCE_RECORD, payload),
V2IP_SOURCE_PAGE_HEADER => {
let Some(body) = payload.get(V2IP_SOURCE_PAGE_HEADER..) else {
return;
};
(
usize::from(u16_at(payload, 0)),
usize::from(u16_at(payload, 2)),
body,
)
}
_ => return,
};
let sources: Vec<V2ipStreamSources> = body
.chunks_exact(V2IP_SOURCE_RECORD)
.map(|record| V2ipStreamSources {
uid: uid_at(record, 0),
video: stream_source(StreamKind::Video, record, 16),
audio: stream_source(StreamKind::Audio, record, 24),
anc: stream_source(StreamKind::Anc, record, 32),
arc: None,
})
.collect();
if let Some(device) = state.device_mut(rx.sender()) {
device.merge_v2ip_sources(first, total, &sources, ev);
}
}
pub(super) fn v2ip_source_switch(state: &mut State, rx: &Rx<'_>, ev: &mut Vec<Event>) {
let target = rx.uid_or_zero(0);
let payload = rx.frame.payload();
let Some(body) = payload.get(16..24) else {
return;
};
let video = ipv4_at(body, 0);
let audio = ipv4_at(body, 4);
let Some(sink) = state
.device(target)
.and_then(|d| d.first_output_port())
.map(|port| BayUid::new(target, port))
else {
return;
};
apply_stream_route(state, sink, video, audio, ev);
}
pub(super) fn v2ip_manual_source_switch(state: &mut State, rx: &Rx<'_>, ev: &mut Vec<Event>) {
let target = rx.uid_or_zero(0);
let payload = rx.frame.payload();
if payload.len() < 38 {
return;
}
let video = stream_source(StreamKind::Video, payload, 16);
let audio = stream_source(StreamKind::Audio, payload, 24);
let anc = stream_source(StreamKind::Anc, payload, 32);
if let Some(sink) = state
.device(target)
.and_then(|d| d.first_output_port())
.map(|port| BayUid::new(target, port))
{
apply_stream_route(state, sink, video.ip, audio.ip, ev);
}
let sink = DeviceV2ipSink {
addresses: V2ipStreamSources {
uid: DeviceUid::ZERO,
video,
audio,
anc,
arc: None,
},
audio_fmt: payload.get(40..48).and_then(audio_format),
};
if let Some(device) = state.device_mut(target) {
device.set_v2ip_sink(sink, ev);
}
}
fn apply_stream_route(
state: &mut State,
sink: BayUid,
video_ip: Ipv4Addr,
audio_ip: Ipv4Addr,
ev: &mut Vec<Event>,
) {
let video = state.bay_by_stream_ip(video_ip, false);
let audio = state.bay_by_stream_ip(audio_ip, true);
if let Some(bay) = state.bay_mut(sink) {
if let Some(video) = video {
bay.set_video_source(Some(video), ev);
}
if let Some(audio) = audio {
bay.set_audio_source(Some(audio), ev);
}
}
}
fn v2ip_config_subject(state: &State, rx: &Rx<'_>, p: &[u8]) -> Option<DeviceUid> {
let subject = uid_at(p, 0);
if subject == rx.sender() {
return Some(subject);
}
state.device(subject)?;
manages(state, rx.sender(), subject).then_some(subject)
}
pub(super) fn manages(state: &State, sender: DeviceUid, subject: DeviceUid) -> bool {
state.device(sender).is_some_and(Device::is_management)
|| state
.device(subject)
.is_some_and(|d| !d.mesh_master.is_zero() && d.mesh_master == sender)
}
fn scaling_flag_mask(state: &State, rx: &Rx<'_>) -> u8 {
if state
.device(rx.sender())
.is_some_and(Device::config_initialised)
{
return SCALING_FLAGS_DEFINED;
}
SCALING_FLAGS_DEFINED
& !(SCALING_FLAG_OPTIONS2_VALID | SCALING_FLAG_MATCH_SOURCE | SCALING_FLAG_SKIP_420)
}
const V2IP_CONFIG_SIZE_BASE: usize = 64;
const V2IP_CONFIG_SIZE: usize = 88;
const V2IP_SINK_SIZE: usize = 32;
const V2IP_CODEC_AT: usize = V2IP_CONFIG_SIZE + V2IP_SINK_SIZE;
const V2IP_CODEC_SIZE: usize = 8;
const V2IP_SETTINGS_AT: usize = V2IP_CODEC_AT + V2IP_CODEC_SIZE;
const V2IP_SETTINGS_SIZE: usize = 16;
pub(super) fn v2ip_device_configuration(state: &mut State, rx: &Rx<'_>, ev: &mut Vec<Event>) {
let p = rx.frame.payload();
if p.len() < V2IP_CONFIG_SIZE_BASE {
return;
}
let Some(subject) = v2ip_config_subject(state, rx, p) else {
return;
};
let tx_rate = p.get(40).copied().filter(|rate| {
(crate::wire::V2IP_SOURCE_RATE_MIN..=crate::wire::V2IP_SOURCE_RATE_MAX).contains(rate)
});
let details = DeviceV2ipDetails {
video: stream_source(StreamKind::Video, p, 16),
audio: stream_source(StreamKind::Audio, p, 24),
anc: stream_source(StreamKind::Anc, p, 32),
arc: stream_source(StreamKind::Arc, p, 48),
tx_rate,
dscp: V2ipDscpConfig {
video: crate::types::parse_dscp(byte(p, 41)),
audio: crate::types::parse_dscp(byte(p, 42)),
anc: crate::types::parse_dscp(byte(p, 43)),
},
scaling: V2ipScalingSettings {
mode: MxrSignalType::from_wire(u16_at(p, 56)),
refresh: u16_at(p, 58),
flags: byte(p, 60) & scaling_flag_mask(state, rx),
},
};
if let Some(device) = state.device_mut(subject) {
device.set_v2ip_details(details, ev);
}
let target = uid_at(p, 64);
if p.len() >= V2IP_CONFIG_SIZE && !target.is_zero() {
let tiling = V2ipTilingConfig {
target,
pos_x: u16_at(p, 80),
pos_y: u16_at(p, 82),
width: u16_at(p, 84),
height: u16_at(p, 86),
};
if let Some(device) = state.device_mut(subject) {
device.set_tiling(tiling, ev);
}
}
if p.len() >= V2IP_CONFIG_SIZE + V2IP_SINK_SIZE && subject == rx.sender() {
let sink = DeviceV2ipSink {
addresses: V2ipStreamSources {
uid: DeviceUid::ZERO,
video: stream_source(StreamKind::Video, p, 88),
audio: stream_source(StreamKind::Audio, p, 96),
anc: stream_source(StreamKind::Anc, p, 104),
arc: None,
},
audio_fmt: p.get(112..120).and_then(audio_format),
};
if let Some(device) = state.device_mut(subject) {
device.set_v2ip_sink(sink, ev);
}
}
if p.len() >= V2IP_CODEC_AT + V2IP_CODEC_SIZE && subject == rx.sender() {
let features = V2ipFpgaFeature::from_bits(u64_at(p, V2IP_CODEC_AT));
if !features.is_empty() {
if let Some(device) = state.device_mut(subject) {
device.set_v2ip_features(features, ev);
}
}
}
if let Some(s) = p.get(V2IP_SETTINGS_AT..V2IP_SETTINGS_AT + V2IP_SETTINGS_SIZE) {
let frame = V2ipDeviceSettings {
valid: V2ipDeviceSetting::from_bits(u32_at(s, 0)),
flags: V2ipDeviceSetting::from_bits(u32_at(s, 4)),
ir_profiles: u32_at(s, 8),
ir_profile: s[12] as i8,
ir_profile_sink: s[13] as i8,
};
if let Some(device) = state.device_mut(subject) {
let frame = if subject == rx.sender() {
frame
} else {
let reported = device.v2ip_settings.unwrap_or_default().valid;
frame.as_applied_to(reported)
};
device.merge_v2ip_settings(frame, ev);
}
}
}
pub(super) fn bay_hide(state: &mut State, rx: &Rx<'_>, ev: &mut Vec<Event>) {
let target = rx.uid_or_zero(0);
let Some(port) = rx.frame.u16(16) else {
return;
};
let hidden = if rx.frame.boolean(18) {
HiddenStatus::Hidden
} else {
HiddenStatus::Visible
};
if let Some(bay) = state.bay_mut(BayUid::new(target, port)) {
bay.apply_hidden(hidden, ev);
}
}
pub(super) fn bay_status(state: &mut State, rx: &Rx<'_>, ev: &mut Vec<Event>) {
let f = &rx.frame;
let Some(port) = f.u16(0) else {
return;
};
let sender = rx.sender();
let Some(device) = state.device(sender) else {
return;
};
if device.bay(port).is_none() {
return;
}
let is_v2ip = device.is_v2ip();
let features = f.u32(24);
let Some(status) = f.u32(20).map(BayStatus::from_bits) else {
return;
};
let description = f.str(2, 14);
if let Some(bay) = state.bay_mut(BayUid::new(sender, port)) {
if let Some(features) = features {
bay.features = crate::wire::BayFeatures::from_bits(features);
}
bay.apply_bay_status(status, ev);
if !status.has(BayStatus::SIGNAL_DETECTED) || !is_v2ip {
bay.apply_signal_status(status.has(BayStatus::SIGNAL_DETECTED), description, ev);
}
}
}
const LINK_RECORD: usize = 38;
pub(super) fn links(state: &mut State, rx: &Rx<'_>, ev: &mut Vec<Event>) {
let sender = rx.sender();
let records: Vec<(u16, String, String, u32)> = rx
.frame
.payload()
.chunks_exact(LINK_RECORD)
.map(|record| {
(
u16::from(byte(record, 0)),
crate::wire::cstr(&record[2..18]),
crate::wire::cstr(&record[18..34]),
u32_at(record, 34),
)
})
.collect();
for (port, linked_serial, linked_bay, features) in records {
let origin = BayUid::new(sender, port);
if state.bay(origin).is_none() {
continue;
}
state.update_link(origin, linked_serial, linked_bay, features, ev);
}
if let Some(device) = state.device_mut(sender) {
device.note_link_config(rx.timestamp, ev);
}
}
pub(super) fn mirror_status(state: &mut State, rx: &Rx<'_>, ev: &mut Vec<Event>) {
let sender = rx.sender();
let target = rx.uid_or_zero(0);
if sender != target {
return;
}
let Some(port) = state.device(sender).and_then(|d| d.first_output_port()) else {
return;
};
let master = rx.uid_or_zero(16);
let mirror = BayMirrorStatus {
target: (!master.is_zero() && master != target).then(|| BayUid::new(master, 0)),
};
if let Some(bay) = state.bay_mut(BayUid::new(sender, port)) {
bay.set_mirroring(mirror, ev);
}
}
const MESH_REPORT_MEMBERSHIP: u8 = 0xFF;
const MESH_OPERATION_SIZE: usize = 40;
const MESH_OPERATION_PROTOCOL: u16 = 0x1A;
pub(super) fn mesh_operation(state: &mut State, rx: &Rx<'_>, ev: &mut Vec<Event>) {
if rx.frame.payload().len() < MESH_OPERATION_SIZE
|| rx.frame.protocol() < MESH_OPERATION_PROTOCOL
{
return;
}
if rx.frame.u8(0) != Some(MESH_REPORT_MEMBERSHIP) {
return;
}
let master = rx.uid_or_zero(4);
if let Some(device) = state.device_mut(rx.sender()) {
device.set_mesh_master(master, ev);
}
}
pub(super) fn v2ip_bay_mapping(state: &mut State, rx: &Rx<'_>, _ev: &mut [Event]) {
let f = &rx.frame;
let (Some(header), Some(first)) = (f.u16(0), f.u16(2)) else {
return;
};
let count = header >> 1;
let mode = if header & 1 == 1 { "Input" } else { "Output" };
let sender = rx.sender();
let Some(device) = state.device_mut(sender) else {
return;
};
for i in 0..count {
let Some(uid) = f.uid(8 + 16 * usize::from(i)) else {
break;
};
let Some(number) = first.checked_add(i).and_then(|n| u8::try_from(n).ok()) else {
break;
};
device.v2ip_bay_mappings.insert((mode, number), uid);
let port = device.bay_by_mode_num(mode, number).map(|bay| bay.port);
if let Some(bay) = port.and_then(|port| device.bays.get_mut(&port)) {
bay.v2ip_uid = uid;
}
}
}
const TOPOLOGY_ENTRY: usize = 20;
pub(super) fn topology(state: &mut State, rx: &Rx<'_>, ev: &mut Vec<Event>) {
let topology: Vec<TopologyEntry> = rx
.frame
.payload()
.chunks_exact(TOPOLOGY_ENTRY)
.map(|entry| TopologyEntry {
uid: uid_at(entry, 0),
mask: u32_at(entry, 16),
})
.collect();
if let Some(device) = state.device_mut(rx.sender()) {
device.set_topology(topology, ev);
}
}
pub(super) fn firmware_version(state: &mut State, rx: &Rx<'_>, ev: &mut Vec<Event>) {
let f = &rx.frame;
let Some(firmware_type) = f.u8(0) else {
return;
};
let available = f.payload().len().saturating_sub(12);
let name_len = available.min(FW_VERSION_LEN);
if name_len == 0 {
return;
}
let (Some(version), Some(timestamp)) = (f.str(12, name_len), f.u32(8)) else {
return;
};
let version = FirmwareVersion {
firmware_type: FirmwareType::from_wire(firmware_type),
timestamp,
version,
hash: f.u32(4).unwrap_or(0),
};
if let Some(device) = state.device_mut(rx.sender()) {
device.set_firmware_version(version, ev);
}
}
pub(super) fn system_status(state: &mut State, rx: &Rx<'_>, ev: &mut Vec<Event>) {
let Some(status) = rx.frame.u16(16) else {
return;
};
let message = rx.frame.str_to_end(18).unwrap_or_default();
if let Some(device) = state.device_mut(rx.sender()) {
device.set_system_status(status, message, ev);
}
}
pub(super) fn byte(p: &[u8], idx: usize) -> u8 {
p.get(idx).copied().unwrap_or(0)
}
pub(super) fn u16_at(p: &[u8], idx: usize) -> u16 {
p.get(idx..idx + 2)
.and_then(|b| <[u8; 2]>::try_from(b).ok())
.map_or(0, u16::from_le_bytes)
}
pub(super) fn u32_at(p: &[u8], idx: usize) -> u32 {
p.get(idx..idx + 4)
.and_then(|b| <[u8; 4]>::try_from(b).ok())
.map_or(0, u32::from_le_bytes)
}
pub(super) fn u64_at(p: &[u8], idx: usize) -> u64 {
p.get(idx..idx + 8)
.and_then(|b| <[u8; 8]>::try_from(b).ok())
.map_or(0, u64::from_le_bytes)
}
pub(super) fn uid_at(p: &[u8], idx: usize) -> DeviceUid {
p.get(idx..idx + 16)
.and_then(|b| <[u8; 16]>::try_from(b).ok())
.map_or(DeviceUid::ZERO, DeviceUid::from_array)
}
pub(super) fn ipv4_at(p: &[u8], idx: usize) -> Ipv4Addr {
Ipv4Addr::new(
byte(p, idx),
byte(p, idx + 1),
byte(p, idx + 2),
byte(p, idx + 3),
)
}
pub(super) fn stream_source(kind: StreamKind, p: &[u8], idx: usize) -> V2ipStreamSource {
V2ipStreamSource {
kind,
ip: ipv4_at(p, idx),
port: u16_at(p, idx + 4),
}
}
fn audio_format(p: &[u8]) -> Option<crate::types::V2ipAudioFormat> {
let channels = byte(p, 4);
(channels != 0).then(|| crate::types::V2ipAudioFormat {
sample_rate: u32_at(p, 0),
channels,
})
}