use std::net::Ipv4Addr;
use crate::event::Event;
use crate::state::{Device, HelloInfo, MeshBayChange, State};
use crate::types::{
BayMirrorStatus, ConnectStatus, DeviceV2ipDetails, DeviceV2ipSink, FirmwareVersion,
HiddenStatus, MuteStatus, PowerStatus, StreamKind, TimeZone, TopologyEntry, V2ipDeviceSettings,
V2ipDownmix, V2ipDscpConfig, V2ipPowerSaveSchedule, V2ipPtpMessages, V2ipPtpState,
V2ipScalingSettings, V2ipStreamSource, V2ipStreamSources, V2ipTestSync, V2ipTestTone,
V2ipTestcard, V2ipTilingConfig, V2ipVlan, VolumeMuteStatus, SCALING_FLAGS_DEFINED,
SCALING_FLAG_OPTIONS_VALID, VOLUME_UNCHANGED,
};
use crate::wire::{
cstr, op, parse_bay_config, BayStatus, BayUid, DeviceFeature, DeviceUid, FirmwareType, Frame,
MxrSignalType, RcAction, RcKey, V2ipDeviceSetting, V2ipDownmixLevel, V2ipDownmixMode,
V2ipFpgaFeature, V2ipPowerSaveLevel, V2ipPtpFlag, V2ipPtpLock, V2ipPtpMode, V2ipTestPattern,
V2ipTestcardFlag, V2ipToneMode, V2ipVlanFlag, BAY_CONFIG_SIZE, FW_VERSION_LEN,
TIME_ZONE_NAME_LEN, TIME_ZONE_RULE_LEN, V2IP_SETTING_EXT_DOWNMIX,
};
use super::Rx;
const RC_NARROW_SIZE: usize = 3;
const RC_WIDE_SIZE: usize = 4;
const SET_VOLUME_UID_SINCE: u16 = 0x11;
const SET_VOLUME_LEGACY_SIZE: usize = 20;
const SET_VOLUME_SIZE: usize = 24;
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();
if payload.len() % BAY_CONFIG_SIZE != 0 {
return;
}
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);
}
}
const ROUTE_NONE: u16 = 0xFFFE;
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 unrouted = f.u16(4) == Some(ROUTE_NONE);
let audio_follows = f.u16(7) == Some(ROUTE_NONE);
let video = f.u16(4).and_then(|p| device.routed_source(p));
let audio = f.u16(7).and_then(|p| device.routed_source(p));
if let Some(bay) = state.bay_mut(BayUid::new(sender, sink_port)) {
if unrouted {
bay.set_video_source(None, ev);
} else if let Some(video) = video {
bay.set_video_source(Some(video), ev);
}
if audio_follows {
bay.set_audio_source(None, ev);
} else 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 len = f.payload().len();
let (target, port, at) = if f.protocol() >= SET_VOLUME_UID_SINCE {
if len < SET_VOLUME_SIZE {
return;
}
(rx.uid_or_zero(0), f.u16(16), 18)
} else {
if len < SET_VOLUME_LEGACY_SIZE {
return;
}
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)
};
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))
.map(|t| i8::from_le_bytes([t]))
.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);
if !takes_command(state, rx.sender(), target) {
return;
}
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;
};
let audio_follows = audio.is_unspecified();
apply_stream_route(state, sink, video, audio, ev);
if audio_follows {
if let Some(bay) = state.bay_mut(sink) {
bay.set_audio_source(None, ev);
}
}
}
const MANUAL_SWITCH_SIZE: usize = 40;
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() < MANUAL_SWITCH_SIZE || !takes_command(state, rx.sender(), target) {
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);
}
}
}
pub(super) 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 command_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)?;
takes_command(state, rx.sender(), subject).then_some(subject)
}
const MANAGEMENT_ONLY_SINCE: u16 = 0x2F;
pub(super) fn takes_command(state: &State, sender: DeviceUid, subject: DeviceUid) -> bool {
manages(state, sender, subject)
|| state
.device(subject)
.is_some_and(|d| d.hello.supported_protocol < MANAGEMENT_ONLY_SINCE)
}
pub(super) fn manages(state: &State, sender: DeviceUid, subject: DeviceUid) -> bool {
let Some(from) = state.device(sender) else {
return false;
};
if from.hello.features.has(DeviceFeature::MANAGER) {
return true;
}
match state.device(subject).map(|d| d.mesh_master) {
Some(master) if !master.is_zero() => master == sender,
_ => from.hello.features.has(DeviceFeature::MESH_MASTER),
}
}
fn scaling_flag_mask(state: &State, rx: &Rx<'_>) -> u8 {
if state
.device(rx.sender())
.is_some_and(Device::config_initialised)
{
SCALING_FLAGS_DEFINED
} else {
0
}
}
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;
const V2IP_SCHEDULE_AT: usize = V2IP_SETTINGS_AT + V2IP_SETTINGS_SIZE;
const V2IP_SCHEDULE_SIZE: usize = 28;
const V2IP_PTP_SETTINGS_AT: usize = V2IP_SCHEDULE_AT + V2IP_SCHEDULE_SIZE;
const V2IP_PTP_SETTINGS_SIZE: usize = 3;
const V2IP_POWER_SAVE_LEVEL_AT: usize = V2IP_PTP_SETTINGS_AT + V2IP_PTP_SETTINGS_SIZE;
const V2IP_VLAN_AT: usize = V2IP_SETTINGS_AT + 48;
const V2IP_VLAN_SIZE: usize = 16;
const V2IP_PTP_AT: usize = V2IP_VLAN_AT + V2IP_VLAN_SIZE;
const V2IP_PTP_SIZE: usize = 24;
const V2IP_PTP_MESSAGES_AT: usize = V2IP_PTP_AT + V2IP_PTP_SIZE;
const V2IP_PTP_MESSAGES_SIZE: usize = 8;
const V2IP_STREAM_TTL_AT: usize = 8;
const V2IP_SINK_ROTATION_AT: usize = 10;
const V2IP_SINK_ROTATION_STEP_AT: usize = 12;
const V2IP_VALID_EXT_AT: usize = V2IP_PTP_MESSAGES_AT + 16;
const V2IP_DOWNMIX_AT: usize = V2IP_VALID_EXT_AT + 4;
const V2IP_DOWNMIX_SIZE: usize = 5;
pub(super) fn ptp_messages(m: &[u8], valid: &mut V2ipDeviceSetting) -> V2ipPtpMessages {
if m.len() < V2IP_PTP_MESSAGES_SIZE {
*valid = valid.without(V2ipDeviceSetting::SETTINGS_BLOCK);
return V2ipPtpMessages::default();
}
let stream_ttl = m.get(V2IP_STREAM_TTL_AT).copied();
if stream_ttl.is_none() {
*valid = valid.without(V2ipDeviceSetting::STREAM_TTL);
}
let rotation = m.len() >= V2IP_SINK_ROTATION_STEP_AT + 2;
if !rotation {
*valid = valid.without(V2ipDeviceSetting::SINK_ROTATION);
}
let at = |i| if rotation { u16_at(m, i) } else { 0 };
V2ipPtpMessages {
log_announce_interval: m[0] as i8,
log_sync_interval: m[1] as i8,
log_delay_req_interval: m[2] as i8,
announce_timeout: m[3],
dscp: m[4],
sap_expire_min: m[5],
sap_interval_s: u16_at(m, 6),
stream_ttl: stream_ttl.unwrap_or(0),
sink_rotation: at(V2IP_SINK_ROTATION_AT),
sink_rotation_step: at(V2IP_SINK_ROTATION_STEP_AT),
}
}
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),
},
};
let whole_scaling = subject == rx.sender()
&& state
.device(subject)
.is_some_and(Device::config_initialised)
&& details.scaling.flags & SCALING_FLAG_OPTIONS_VALID != 0;
if let Some(device) = state.device_mut(subject) {
device.set_v2ip_details(details, whole_scaling, 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 mut 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,
auto_power_save: u16_at(s, 14),
power_save: V2ipPowerSaveSchedule::default(),
ptp_mode: V2ipPtpMode::OFF,
ptp_domain: 0,
ptp_priority1: 0,
power_save_level: V2ipPowerSaveLevel::NONE,
ptp_messages: V2ipPtpMessages::default(),
};
match p.get(V2IP_SCHEDULE_AT..V2IP_SCHEDULE_AT + V2IP_SCHEDULE_SIZE) {
Some(w) => {
for day in 0..7 {
frame.power_save.start[day] = u16_at(w, 2 * day);
frame.power_save.end[day] = u16_at(w, 14 + 2 * day);
}
}
None => {
frame.valid = frame.valid.without(V2ipDeviceSetting::POWER_SAVE_SCHEDULE);
}
}
match p.get(V2IP_PTP_SETTINGS_AT..V2IP_PTP_SETTINGS_AT + V2IP_PTP_SETTINGS_SIZE) {
Some(t) => {
frame.ptp_mode = V2ipPtpMode::from_wire(t[0]);
frame.ptp_domain = t[1];
frame.ptp_priority1 = t[2];
}
None => {
frame.valid = frame.valid.without(V2ipDeviceSetting::PTP_SETTINGS);
}
}
match p.get(V2IP_POWER_SAVE_LEVEL_AT) {
Some(level) => frame.power_save_level = V2ipPowerSaveLevel::from_wire(*level),
None => {
frame.valid = frame.valid.without(V2ipDeviceSetting::POWER_SAVE_LEVEL);
}
}
let block = p.get(V2IP_PTP_MESSAGES_AT..).unwrap_or_default();
frame.ptp_messages = ptp_messages(block, &mut frame.valid);
if let Some(device) = state.device_mut(subject) {
let frame = if subject == rx.sender() {
frame
} else {
frame.as_applied_to(&device.v2ip_settings.unwrap_or_default())
};
device.merge_v2ip_settings(frame, ev);
}
}
if let Some(d) = p.get(V2IP_DOWNMIX_AT..V2IP_DOWNMIX_AT + V2IP_DOWNMIX_SIZE) {
let carried = u32_at(p, V2IP_VALID_EXT_AT) & V2IP_SETTING_EXT_DOWNMIX != 0;
let frame = V2ipDownmix {
mode: V2ipDownmixMode::from_wire(d[0]),
level: V2ipDownmixLevel::from_wire(d[1]),
lfe: d[2],
state: d[3],
channels: d[4],
};
if let Some(device) = state.device_mut(subject) {
if subject == rx.sender() {
device.set_v2ip_downmix(carried.then_some(frame), ev);
} else if let Some(reported) = device.v2ip_downmix.filter(|_| carried) {
device.set_v2ip_downmix(Some(reported.written(frame)), ev);
}
}
}
if subject == rx.sender() {
if let Some(v) = p.get(V2IP_VLAN_AT..V2IP_VLAN_AT + V2IP_VLAN_SIZE) {
let flags = V2ipVlanFlag::from_bits(u16_at(v, 0));
if flags.has(V2ipVlanFlag::VALID) {
let vlan = V2ipVlan {
flags,
device: u16_at(v, 2),
port: [u16_at(v, 4), u16_at(v, 6), u16_at(v, 8)],
uplink: v[10],
active_uplink: v[11],
revert_s: v[12],
};
if let Some(device) = state.device_mut(subject) {
device.set_v2ip_vlan(vlan, ev);
}
}
}
}
if subject == rx.sender() {
if let Some(t) = p.get(V2IP_PTP_AT..V2IP_PTP_AT + V2IP_PTP_SIZE) {
let flags = V2ipPtpFlag::from_bits(t[0]);
let ptp = flags.has(V2ipPtpFlag::VALID).then(|| V2ipPtpState {
flags,
mode: V2ipPtpMode::from_wire(t[1]),
domain: t[2],
port_state: t[3],
lock: V2ipPtpLock::from_wire(t[4]),
clock_identity: eight(&t[8..16]),
grandmaster_identity: eight(&t[16..24]),
});
if let Some(device) = state.device_mut(subject) {
device.set_v2ip_ptp(ptp, ev);
}
}
}
}
const BAY_HIDE_SIZE: usize = 24;
pub(super) fn bay_hide(state: &mut State, rx: &Rx<'_>, ev: &mut Vec<Event>) {
let target = rx.uid_or_zero(0);
if rx.frame.payload().len() < BAY_HIDE_SIZE || !takes_command(state, rx.sender(), target) {
return;
}
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);
}
}
const BAY_STATUS_RECORD: usize = 32;
pub(super) fn bay_status(state: &mut State, rx: &Rx<'_>, ev: &mut Vec<Event>) {
for record in rx.frame.payload().chunks_exact(BAY_STATUS_RECORD) {
bay_status_record(state, rx.sender(), record, ev);
}
}
fn bay_status_record(state: &mut State, sender: DeviceUid, record: &[u8], ev: &mut Vec<Event>) {
let Some(port) = record.get(..2).map(|_| u16_at(record, 0)) else {
return;
};
let Some(device) = state.device(sender) else {
return;
};
if device.bay(port).is_none() {
return;
}
let is_v2ip = device.is_v2ip();
let features = record.get(24..28).map(|_| u32_at(record, 24));
let Some(status) = record
.get(20..24)
.map(|_| BayStatus::from_bits(u32_at(record, 20)))
else {
return;
};
let description = record.get(2..16).map(cstr);
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 p = rx.frame.payload();
if p.len() < MIRROR_SIZE {
return;
}
let Some(sink) = command_subject(state, rx, p) else {
return;
};
let Some(port) = state.device(sink).and_then(|d| d.first_output_port()) else {
return;
};
let master = uid_at(p, 16);
let none = master.is_zero() || master.as_bytes() == &[0xFF; 16] || master == sink;
let mirror = BayMirrorStatus {
target: (!none).then(|| BayUid::new(master, 0)),
};
if let Some(bay) = state.bay_mut(BayUid::new(sink, port)) {
bay.set_mirroring(mirror, ev);
}
}
const MIRROR_SIZE: usize = 32;
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 Vec<Event>) {
let f = &rx.frame;
let (Some(header), Some(first_port)) = (f.u16(0), f.u16(2)) else {
return;
};
let count = usize::from(header >> 1);
if f.payload().len() < 8 + 16 * count {
return;
}
let page: Vec<DeviceUid> = (0..count).filter_map(|i| f.uid(8 + 16 * i)).collect();
let Some(device) = state.device_mut(rx.sender()) else {
return;
};
for (bay, change) in device.set_v2ip_bay_mapping_page(first_port, &page) {
ev.push(match change {
MeshBayChange::Registered => Event::BayRegistered { bay },
MeshBayChange::Remapped => Event::NameChanged {
bay,
name: state
.bay(bay)
.map(|b| state.described(b).user_name().to_owned())
.unwrap_or_default(),
},
});
}
}
pub(super) fn time_zone(state: &mut State, rx: &Rx<'_>, ev: &mut Vec<Event>) {
let p = rx.frame.payload();
let (Some(zone), Some(rule)) = (
p.get(..TIME_ZONE_NAME_LEN),
p.get(TIME_ZONE_NAME_LEN..TIME_ZONE_NAME_LEN + TIME_ZONE_RULE_LEN),
) else {
return;
};
let time_zone = TimeZone {
zone: cstr(zone),
rule: cstr(rule),
};
if let Some(device) = state.device_mut(rx.sender()) {
device.set_time_zone(time_zone, ev);
}
}
pub(super) fn time(state: &mut State, rx: &Rx<'_>, _ev: &mut [Event]) {
let Some(utc) = rx.frame.u32(0) else {
return;
};
if let Some(device) = state.device_mut(rx.sender()) {
device.clock = Some((utc, rx.timestamp));
}
}
const TESTCARD_SIZE: usize = 56;
const TESTCARD_STATE: u8 = 2;
pub(super) fn v2ip_testcard(state: &mut State, rx: &Rx<'_>, ev: &mut Vec<Event>) {
let p = rx.frame.payload();
let Some(p) = p.get(..TESTCARD_SIZE) else {
return;
};
if p[16] != TESTCARD_STATE
|| uid_at(p, 0) != rx.sender()
|| p[18] > V2ipTestPattern::CARD.to_wire()
|| p[19] > V2ipToneMode::BEEP.to_wire()
{
return;
}
let testcard = V2ipTestcard {
flags: V2ipTestcardFlag::from_bits(p[17]),
pattern: V2ipTestPattern::from_wire(p[18]),
colour: u32_at(p, 20) & 0x00FF_FFFF,
tone: V2ipTestTone {
mode: V2ipToneMode::from_wire(p[19]),
freq: u16_at(p, 24),
level: p[26] as i8,
channels: p[27],
rate: u32_at(p, 28),
},
sync: V2ipTestSync {
period: u16_at(p, 32),
lead: u16_at(p, 34),
offset: u32_at(p, 36),
beep_ms: u16_at(p, 40),
},
frames: u32_at(p, 44),
periods: u32_at(p, 48),
marks: u32_at(p, 52),
};
if let Some(device) = state.device_mut(uid_at(p, 0)) {
device.set_v2ip_testcard(testcard, ev);
}
}
const TOPOLOGY_ENTRY: usize = 24;
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);
}
}
const FW_RTOS_LOADER_END: usize = 152;
const FW_MCU_END: usize = 200;
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 firmware_type = FirmwareType::from_wire(firmware_type);
let mut versions = vec![FirmwareVersion {
firmware_type,
timestamp,
version: version.clone(),
hash: f.u32(4).unwrap_or(0),
}];
let len = f.payload().len();
if firmware_type == FirmwareType::FPGA && len >= FW_RTOS_LOADER_END {
for (firmware_type, at) in [(FirmwareType::RTOS, 144), (FirmwareType::LOADER, 148)] {
versions.push(FirmwareVersion {
firmware_type,
timestamp,
version: version.clone(),
hash: f.u32(at).unwrap_or(0),
});
}
}
if firmware_type == FirmwareType::FPGA && len >= FW_MCU_END {
for (firmware_type, at, version_at) in [
(FirmwareType::MCU, 152, 168),
(FirmwareType::BOOTLOADER, 160, 184),
] {
versions.push(FirmwareVersion {
firmware_type,
timestamp: f.u32(at + 4).unwrap_or(0),
version: f.str(version_at, 16).unwrap_or_default(),
hash: f.u32(at).unwrap_or(0),
});
}
}
if let Some(device) = state.device_mut(rx.sender()) {
for version in versions {
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;
};
if rx.frame.uid(0) != Some(rx.sender()) {
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)
}
fn eight(p: &[u8]) -> [u8; 8] {
let mut out = [0; 8];
for (o, b) in out.iter_mut().zip(p) {
*o = *b;
}
out
}
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,
})
}