mx_remote_ffi/subsystems.rs
1// Author: Lars Op den Kamp (lars@opdenkamp-it.nl)
2// Copyright (c) 2026 Op den Kamp IT Solutions
3
4//! What a device reports about one of its subsystems.
5//!
6//! These are the values that do not fit in a device or bay snapshot: streams,
7//! statistics, the audio tree, the network ports. Each has an event that says
8//! it moved and a call here that says what it is now, which is why the events
9//! carry only an identifier - what they would carry instead is this, and a
10//! copy taken at event time could only be staler than a read.
11//!
12//! A call returns `MXR_ERR_NOT_REPORTED` when the device exists but has not
13//! sent that subsystem, which is a different answer from a device that has
14//! never been heard from at all.
15
16use std::ffi::c_char;
17use std::net::Ipv4Addr;
18use std::time::UNIX_EPOCH;
19
20use mx_remote::{
21 AmpDolbySettings, AudioEndpoint, DeviceV2ipDetails, DeviceV2ipSink, FirmwareVersion,
22 MultiviewerStatus, NetworkPortStatus, OsdMessage, OsdSync, OsdSyncReport, RcSettings,
23 StreamKind, TopologyEntry, UnitStatus, UtpCableStatus, V2ipDecoderDetail, V2ipDeviceSettings,
24 V2ipDeviceStats, V2ipDownmix, V2ipDownmixLevel, V2ipDownmixMode, V2ipFpgaFeature, V2ipPrivacy,
25 V2ipPrivacyStream, V2ipPtpMessages, V2ipPtpState, V2ipRxStats, V2ipSinkSdp, V2ipStreamSource,
26 V2ipStreamSources, V2ipTestSync, V2ipTestTone, V2ipTestcard, V2ipTilingConfig, V2ipToneMode,
27 V2ipTxStats, V2ipVlan, VctStatus, MULTIVIEWER_INPUTS, OSD_MESSAGE_TEXT_MAX, OSD_SYNC_SLOTS,
28 V2IP_DOWNMIX_LFE_MAX,
29};
30
31use crate::abi::{fail, guard, mxr_result_t, mxr_uid_t, put_str};
32use crate::bits::{
33 MXR_V2IP_DOWNMIX_ALWAYS, MXR_V2IP_DOWNMIX_LEVEL_UNSCALED, MXR_V2IP_DOWNMIX_LFE_MAX,
34};
35use crate::control::mxr_audio_format_t;
36use crate::info::{copy_into, not_heard_from, null_out, MXR_NAME_LEN, MXR_VERSION_LEN};
37use crate::remote::{mxr_remote_t, with, MXR_IP_STRING_LEN};
38
39/// How many inputs a multiviewer has.
40///
41/// Written as a literal because the generated header needs one, and checked
42/// against the core crate's value below so the two cannot drift apart.
43pub const MXR_MULTIVIEWER_INPUTS: usize = 4;
44
45const _: () = assert!(MXR_MULTIVIEWER_INPUTS == MULTIVIEWER_INPUTS);
46
47/// How many pairs a UTP cable diagnostic covers.
48pub const MXR_UTP_PAIRS: usize = 4;
49
50/// Which of a OneIP device's streams an address describes.
51#[repr(i32)]
52#[derive(Clone, Copy, Debug, PartialEq, Eq)]
53pub enum mxr_stream_kind_t {
54 /// The video stream.
55 MXR_STREAM_VIDEO = 0,
56 /// The audio stream.
57 MXR_STREAM_AUDIO = 1,
58 /// The ancillary-data stream.
59 MXR_STREAM_ANC = 2,
60 /// The audio-return stream.
61 MXR_STREAM_ARC = 3,
62}
63
64impl From<StreamKind> for mxr_stream_kind_t {
65 fn from(kind: StreamKind) -> Self {
66 match kind {
67 StreamKind::Video => Self::MXR_STREAM_VIDEO,
68 StreamKind::Audio => Self::MXR_STREAM_AUDIO,
69 StreamKind::Anc => Self::MXR_STREAM_ANC,
70 StreamKind::Arc => Self::MXR_STREAM_ARC,
71 }
72 }
73}
74
75/// One multicast stream address.
76#[repr(C)]
77#[derive(Clone, Copy)]
78pub struct mxr_stream_source_t {
79 /// Which stream this address is for.
80 pub kind: mxr_stream_kind_t,
81 /// The multicast group, as a dotted quad.
82 pub ip: [c_char; MXR_IP_STRING_LEN],
83 /// The destination UDP port.
84 pub port: u16,
85 /// Whether this carries a usable address: a multicast group and a non-zero
86 /// port, both. A slot a device has not filled in is not an error, so this
87 /// is what separates an address from an empty slot.
88 pub valid: bool,
89}
90
91impl From<V2ipStreamSource> for mxr_stream_source_t {
92 fn from(s: V2ipStreamSource) -> Self {
93 let mut out = Self {
94 kind: s.kind.into(),
95 ip: [0; MXR_IP_STRING_LEN],
96 port: s.port,
97 valid: s.is_valid(),
98 };
99 put_str(&mut out.ip, &s.ip.to_string());
100 out
101 }
102}
103
104/// The streams one OneIP source advertises.
105#[repr(C)]
106#[derive(Clone, Copy)]
107pub struct mxr_stream_sources_t {
108 /// The originating device, zero when it is not known.
109 pub uid: mxr_uid_t,
110 /// The video stream.
111 pub video: mxr_stream_source_t,
112 /// The audio stream.
113 pub audio: mxr_stream_source_t,
114 /// The ancillary-data stream.
115 pub anc: mxr_stream_source_t,
116 /// Whether an audio-return stream is advertised.
117 pub has_arc: bool,
118 /// The audio-return stream, meaningful only when `has_arc` is set.
119 pub arc: mxr_stream_source_t,
120}
121
122impl From<V2ipStreamSources> for mxr_stream_sources_t {
123 fn from(s: V2ipStreamSources) -> Self {
124 Self {
125 uid: s.uid.into(),
126 video: s.video.into(),
127 audio: s.audio.into(),
128 anc: s.anc.into(),
129 has_arc: s.arc.is_some(),
130 arc: s.arc.unwrap_or_default().into(),
131 }
132 }
133}
134
135/// A OneIP device's own encoder configuration.
136#[repr(C)]
137#[derive(Clone, Copy)]
138pub struct mxr_v2ip_details_t {
139 /// The video stream this device sources.
140 pub video: mxr_stream_source_t,
141 /// The audio stream this device sources.
142 pub audio: mxr_stream_source_t,
143 /// The ancillary-data stream this device sources.
144 pub anc: mxr_stream_source_t,
145 /// The audio-return stream this device sources.
146 pub arc: mxr_stream_source_t,
147 /// Encoder rate in units of 10Mb/s, or -1 when no rate has been reported.
148 pub tx_rate: i16,
149 /// DSCP marking for the video stream, or -1 when unmarked.
150 pub dscp_video: i16,
151 /// DSCP marking for the audio stream, or -1 when unmarked.
152 pub dscp_audio: i16,
153 /// DSCP marking for the ancillary-data stream, or -1 when unmarked.
154 pub dscp_anc: i16,
155 /// The signal type the output scales to.
156 pub scaling_mode: u16,
157 /// Refresh rate in Hz.
158 pub scaling_refresh: u16,
159 /// `MXR_SCALING_FLAG_*` bits. Bits outside those are undefined and are not
160 /// reliably zero: firmware predating the fix builds this frame over an
161 /// uninitialised stack local.
162 pub scaling_flags: u8,
163}
164
165/// Set when the frame carries a scaling mode and refresh rate.
166pub const MXR_SCALING_FLAG_MODE_VALID: u8 = 1 << 0;
167/// Set when the frame carries the scaling options.
168pub const MXR_SCALING_FLAG_OPTIONS_VALID: u8 = 1 << 1;
169/// Set when the frame carries the second group of scaling options.
170///
171/// Firmware with those options sets this on every configuration it sends about
172/// itself, so it doubles as the report that the device has them at all. It is
173/// read only from a sender that says it initialises its configuration: on one
174/// that does not, this bit is uninitialised stack and the settings behind it
175/// would be invented rather than misread.
176pub const MXR_SCALING_FLAG_OPTIONS2_VALID: u8 = 1 << 4;
177/// Set when the output follows its source's format instead of a fixed one.
178pub const MXR_SCALING_FLAG_MATCH_SOURCE: u8 = 1 << 5;
179/// Set when the output declines 4:2:0 rather than scaling it.
180pub const MXR_SCALING_FLAG_SKIP_420: u8 = 1 << 6;
181/// Set when the output scales automatically.
182pub const MXR_SCALING_FLAG_AUTO_SCALING: u8 = 1 << 7;
183
184/// The streams a OneIP sink is subscribed to.
185///
186/// **Addresses that read as unset mean "no route, or the sink could not work
187/// one out" - never "definitely not subscribed".** This is the one part of a
188/// device configuration with no validity marker of its own, so a sender with
189/// nothing to say sends zeros and every receiver stores them. A sender leaves
190/// it empty whenever its own stream configuration does not resolve, which
191/// covers more than having no route: a selected source whose record has not
192/// arrived yet, the state after a restart at either end, missing audio bay
193/// configuration, or a stream failing its validity check.
194///
195/// Expect it rather than guard against it. Any scaling change makes the device
196/// rebuild and rebroadcast this block - so the empty reading turns up most
197/// often during exactly the no-signal troubleshooting that prompted the change.
198/// A device's periodic report puts a real route back within a minute of it
199/// having one, so a reader that needs certainty should wait one out rather than
200/// treat the first empty reading as an answer.
201///
202/// Only the device's own report sets this. A controller writing another
203/// device's configuration sends the block zeroed, and that frame is ignored.
204#[repr(C)]
205#[derive(Clone, Copy)]
206pub struct mxr_v2ip_sink_t {
207 /// The streams the sink subscribes to.
208 pub addresses: mxr_stream_sources_t,
209 /// Whether the sender reported a resolved audio format.
210 pub has_audio_format: bool,
211 /// The audio format, meaningful only when `has_audio_format` is set.
212 pub audio_format: mxr_audio_format_t,
213}
214
215/// Transmitter stream statistics.
216#[repr(C)]
217#[derive(Clone, Copy)]
218pub struct mxr_v2ip_tx_stats_t {
219 /// Video packets sent.
220 pub video: u32,
221 /// Audio packets sent.
222 pub audio: u32,
223 /// Ancillary-data packets sent.
224 pub anc: u32,
225 /// Times the stream went down.
226 pub stream_down: u32,
227 /// Transmit overflows.
228 pub overflow: u32,
229}
230
231impl From<V2ipTxStats> for mxr_v2ip_tx_stats_t {
232 fn from(s: V2ipTxStats) -> Self {
233 Self {
234 video: s.video,
235 audio: s.audio,
236 anc: s.anc,
237 stream_down: s.stream_down,
238 overflow: s.overflow,
239 }
240 }
241}
242
243/// Receiver stream statistics.
244#[repr(C)]
245#[derive(Clone, Copy)]
246pub struct mxr_v2ip_rx_stats_t {
247 /// Video packets received.
248 pub video_total: u32,
249 /// Video packets dropped.
250 pub video_dropped: u32,
251 /// Video sequence errors.
252 pub video_seq_errors: u32,
253 /// Watchdog timeouts.
254 pub wdt_timeout: u32,
255 /// Audio packets received.
256 pub audio_total: u32,
257 /// Audio packets dropped.
258 pub audio_dropped: u32,
259 /// Audio sequence errors.
260 pub audio_seq_errors: u32,
261 /// Ancillary-data packets received.
262 pub anc_total: u32,
263 /// Ancillary-data packets dropped.
264 pub anc_dropped: u32,
265 /// Ancillary-data sequence errors.
266 pub anc_seq_errors: u32,
267 /// The decoder's health state: 0 unknown, 1 healthy, 2 bad, 3 starting.
268 ///
269 /// Only healthy and bad are verdicts. Reading failure as "not healthy"
270 /// counts a decoder that is merely coming up as one that failed, which is
271 /// what every sink reports for a moment after a route change.
272 pub decoder_state: u8,
273}
274
275impl From<V2ipRxStats> for mxr_v2ip_rx_stats_t {
276 fn from(s: V2ipRxStats) -> Self {
277 Self {
278 video_total: s.video_total,
279 video_dropped: s.video_dropped,
280 video_seq_errors: s.video_seq_errors,
281 wdt_timeout: s.wdt_timeout,
282 audio_total: s.audio_total,
283 audio_dropped: s.audio_dropped,
284 audio_seq_errors: s.audio_seq_errors,
285 anc_total: s.anc_total,
286 anc_dropped: s.anc_dropped,
287 anc_seq_errors: s.anc_seq_errors,
288 decoder_state: s.decoder_state.to_wire(),
289 }
290 }
291}
292
293/// What a statistics report says about a sink's decoder.
294#[repr(i32)]
295#[derive(Clone, Copy, Debug, PartialEq, Eq)]
296pub enum mxr_v2ip_decoder_detail_t {
297 /// The report carried no decoder block: the sender's firmware predates it.
298 MXR_V2IP_DECODER_ABSENT = 0,
299 /// The block is there and the decoder has never answered.
300 MXR_V2IP_DECODER_NEVER_ANSWERED = 1,
301 /// The block carries a reading.
302 MXR_V2IP_DECODER_ANSWERED = 2,
303}
304
305/// What a sink's decoder recovered from the codestream it is being given.
306///
307/// This is what the decoder understood, read ahead of the scaler: the geometry
308/// is unrounded and is not what the display is being sent. Every field but
309/// `detail` is zero unless `detail` is `MXR_V2IP_DECODER_ANSWERED`.
310///
311/// `detail` follows the sink being configured rather than the sink being
312/// enabled, so a sink that is switched off still reports - as reason 10, or
313/// from an older sender as reason 1, which is the same reading a sink whose
314/// source has died produces. Nothing here answers whether a sink is enabled.
315///
316/// Colour depth is absent on purpose and will stay absent: the video processor
317/// answers that from a driver constant rather than from the codestream, so
318/// there is no reading to carry. Assert depth at the encoder's input bay
319/// instead.
320#[repr(C)]
321#[derive(Clone, Copy)]
322pub struct mxr_v2ip_decoder_t {
323 /// Which of the three states this report is in.
324 pub detail: mxr_v2ip_decoder_detail_t,
325 /// The primary cause of the state the decoder is in, by the sender's own
326 /// names: 0 OK, 1 NO_PACKETS, 2 PACKETS_DEGRADED, 3 NO_FORMAT, 4
327 /// FORMAT_MISMATCH, 5 FORMAT_REJECTED, 6 DECODER_BLOCKED, 7
328 /// SWITCH_PENDING, 8 PTP_UNLOCKED, 9 TX_BRIDGE_UNLOCKED, 10 IDLE.
329 /// Firmware adds causes, so an unrecognised value is passed through as it
330 /// arrived.
331 ///
332 /// The primary cause only, and the numbers are identities rather than
333 /// ranks: several causes can be true at once and a fixed priority order in
334 /// the firmware decides which lands here. Classify on `flags`, which
335 /// carries all of them; a test against this field asks which cause won
336 /// instead. Reason 10 is the exception and is read here: it outranks the
337 /// whole word, and testing it first is what stops a switched-off sink
338 /// being reported as broken. Reason 9 is the one most often hidden here —
339 /// see `flags`.
340 ///
341 /// A pending switch is a step in an operation someone asked for rather
342 /// than a fault, and PTP unlocked costs audio alone: audio cannot enable
343 /// and the picture is unaffected, so reporting it as a fault puts an
344 /// overlay over a good picture. Reason 10 says the sink is switched off,
345 /// and carries no implication about `width` and `height`: those are read
346 /// before any cause is decided, so a switched-off sink still detecting a
347 /// codestream reports a real geometry. An older sender reports reason 1
348 /// for the same sink, so an absent reason 10 is not evidence a sink is
349 /// enabled - nothing here answers that, which comes from
350 /// `mxr_v2ip_details()` or the device's HTTP status.
351 pub reason: u8,
352 /// The converter watchdog is holding the stream back.
353 pub blocking: bool,
354 /// The recovered picture width, and 0 when none was recovered.
355 pub width: u16,
356 /// The recovered picture height, and 0 when none was recovered.
357 pub height: u16,
358 /// The recovered colour space: 0 RGB, 1 YCbCr 4:4:4, 2 YCbCr 4:2:2,
359 /// 3 YCbCr 4:2:0, 255 the decoder cannot name it.
360 ///
361 /// No value here means "no signal": a decoder with nothing to decode
362 /// reports 0, which is indistinguishable from a real RGB reading. A zero
363 /// `width` or `height` is what says the decoder recovered nothing - which
364 /// is not the same as the sink being switched off, and does not imply it. The 255
365 /// is its own value rather than the 0xF a signal report uses for an
366 /// unknown colour space.
367 pub format: u16,
368 /// How many readings the sink has stored. Monotonic, wrapping at 65535
369 /// after some 36 hours, and never reset.
370 ///
371 /// A sink reads its video processor every two seconds and reports every
372 /// second, so roughly every other report repeats a reading already seen:
373 /// a frame arriving says nothing about how fresh the values in it are.
374 /// This counter moves only when a reading is stored, so a processor that
375 /// stopped answering leaves it still rather than implying a refresh.
376 ///
377 /// After pointing a sink at something else, wait for this to advance by
378 /// two before trusting the geometry. It ticks when a reply lands rather
379 /// than when a query is sent, so the first tick can carry an answer the
380 /// processor read fractionally before the switch; the second cannot,
381 /// because at most one query is outstanding at a time.
382 pub updates: u16,
383 /// Every cause that applies, as bit N for reason N, where `reason` carries
384 /// the primary one. Bit 0 is cleared by the sender, so an empty word means
385 /// nothing beyond the primary cause applies.
386 ///
387 /// This is what to classify on, once reason 10 has been ruled out. That
388 /// cause outranks the whole word and leaves the bits below it set - a sink
389 /// switched off while running keeps the bits the decoder genuinely saw on
390 /// the way down - so a fault mask over `flags` reports a deliberately
391 /// disabled sink as broken.
392 ///
393 /// A cause that is true can be missing from
394 /// `reason` and present here: reason 9, the pipeline rebuilding after the
395 /// transmitter bridge stayed unlocked, sits below every input-side cause,
396 /// so a pipeline restarting in a loop shows an input-side cause in
397 /// `reason` and bit 9 here alone - always, rather than briefly. Bit 9
398 /// needs a sustained five seconds to appear at all, and sustained across
399 /// reports it means a restart loop rather than one event, because the
400 /// sender's debounce restarts each time it elapses.
401 ///
402 /// Reasons 3 and 4 are the two arms of one decision and never appear
403 /// together.
404 pub flags: u32,
405 /// How many times the converter watchdog has triggered.
406 pub blocked_count: u32,
407}
408
409impl From<V2ipDecoderDetail> for mxr_v2ip_decoder_t {
410 fn from(detail: V2ipDecoderDetail) -> Self {
411 let empty = Self {
412 detail: mxr_v2ip_decoder_detail_t::MXR_V2IP_DECODER_ABSENT,
413 reason: 0,
414 blocking: false,
415 width: 0,
416 height: 0,
417 format: 0,
418 updates: 0,
419 flags: 0,
420 blocked_count: 0,
421 };
422 match detail {
423 V2ipDecoderDetail::Absent => empty,
424 V2ipDecoderDetail::NeverAnswered => Self {
425 detail: mxr_v2ip_decoder_detail_t::MXR_V2IP_DECODER_NEVER_ANSWERED,
426 ..empty
427 },
428 V2ipDecoderDetail::Answered(r) => Self {
429 detail: mxr_v2ip_decoder_detail_t::MXR_V2IP_DECODER_ANSWERED,
430 reason: r.reason.to_wire(),
431 blocking: r.blocking,
432 width: r.width,
433 height: r.height,
434 format: r.format.to_wire(),
435 updates: r.updates,
436 flags: r.flags,
437 blocked_count: r.blocked_count,
438 },
439 }
440 }
441}
442
443/// A device's OneIP statistics, cumulative and over the last minute.
444#[repr(C)]
445#[derive(Clone, Copy)]
446pub struct mxr_v2ip_stats_t {
447 /// Transmit totals since boot.
448 pub tx: mxr_v2ip_tx_stats_t,
449 /// Transmit counts over the last minute.
450 pub tx_per_minute: mxr_v2ip_tx_stats_t,
451 /// Receive totals since boot.
452 pub rx: mxr_v2ip_rx_stats_t,
453 /// Receive counts over the last minute.
454 pub rx_per_minute: mxr_v2ip_rx_stats_t,
455 /// What the sink's decoder recovered from the codestream it is decoding.
456 pub decoder: mxr_v2ip_decoder_t,
457}
458
459/// The window a sink is currently told to show.
460///
461/// This is the pollable view of a sink's window, not the persisted video wall
462/// setting: on a sink running the wall module a write here is transient,
463/// because that module pushes its own target window back within about a
464/// second.
465#[repr(C)]
466#[derive(Clone, Copy)]
467pub struct mxr_tiling_config_t {
468 /// The sink this window belongs to.
469 pub target: mxr_uid_t,
470 /// Window origin, horizontal.
471 pub pos_x: u16,
472 /// Window origin, vertical.
473 pub pos_y: u16,
474 /// Window width.
475 pub width: u16,
476 /// Window height.
477 pub height: u16,
478}
479
480/// What a multiviewer reports about itself.
481#[repr(C)]
482#[derive(Clone, Copy)]
483pub struct mxr_multiviewer_status_t {
484 /// The multiviewer.
485 pub uid: mxr_uid_t,
486 /// The source device mapped to each input.
487 pub mappings: [mxr_uid_t; MXR_MULTIVIEWER_INPUTS],
488 /// The MCU firmware version.
489 pub mcu_version: [c_char; MXR_NAME_LEN],
490 /// The scaler firmware version.
491 pub scaler_version: [c_char; MXR_NAME_LEN],
492 /// The view mode the hardware reports, which is its own numbering rather
493 /// than `view_mode`'s.
494 pub hw_view_mode: u8,
495 /// The window layout.
496 pub view_mode: u8,
497 /// Which corner the picture-in-picture window sits in.
498 pub pip_position: u8,
499 /// The size of the picture-in-picture window.
500 pub pip_size: u8,
501 /// The output resolution.
502 pub output_mode: u8,
503 /// The HDCP mode.
504 pub hdcp_mode: u8,
505 /// The IT content flag.
506 pub output_itc: u8,
507 /// The EDID presented to sources.
508 pub edid_template: u8,
509 /// How a two, three or four screen layout is arranged: 1 large, 2 small,
510 /// 0 in a single or picture-in-picture layout or while the hardware has
511 /// not said.
512 pub screen_mode: u8,
513 /// How a source is fitted into its window.
514 pub aspect_ratio: u8,
515 /// Whether automatic source switching is on.
516 pub auto_switch: u8,
517 /// Which window the audio is taken from.
518 pub audio_source: u8,
519 /// Whether a volume has been reported.
520 pub has_audio_volume: bool,
521 /// The output volume.
522 pub audio_volume: u8,
523 /// Whether the output is muted.
524 pub audio_muted: u8,
525 /// The source shown in each window.
526 pub video_sources: [u8; MXR_MULTIVIEWER_INPUTS],
527 /// Which window remote control is forwarded to.
528 pub remote_control: u8,
529}
530
531/// One node of a device's audio tree.
532#[repr(C)]
533#[derive(Clone, Copy)]
534pub struct mxr_audio_endpoint_t {
535 /// The endpoint's identifier on its device.
536 pub id: u8,
537 /// What the endpoint can do, as `MXR_AUDIO_*` bits.
538 pub features: u32,
539 /// Whether the endpoint carries a stream address.
540 pub has_address: bool,
541 /// The stream address, meaningful only when `has_address` is set.
542 pub address: mxr_stream_source_t,
543 /// The endpoint this one hangs off, or -1 at a root.
544 pub parent: i16,
545 /// How many children this endpoint has; read them with
546 /// `mxr_audio_endpoint_children()`.
547 pub child_count: usize,
548 /// Whether the device reported which inputs are selectable.
549 pub has_inputs_available: bool,
550 /// Bitmask of the endpoints this one may be switched to.
551 pub inputs_available: u32,
552 /// Whether the device reported which input is selected.
553 pub has_inputs_routed: bool,
554 /// Bitmask of the endpoint this one is listening to.
555 pub inputs_routed: u32,
556 /// The device at the other end of the link, zero when unlinked.
557 pub linked_device: mxr_uid_t,
558 /// The endpoint at the other end of the link, or -1 when unlinked.
559 pub linked_endpoint: i16,
560}
561
562impl From<&AudioEndpoint> for mxr_audio_endpoint_t {
563 fn from(e: &AudioEndpoint) -> Self {
564 Self {
565 id: e.id,
566 features: e.features.bits(),
567 has_address: e.address.is_some(),
568 address: e.address.unwrap_or_default().into(),
569 // An endpoint id is a byte on the wire, so -1 cannot collide.
570 parent: e.parent.map_or(-1, i16::from),
571 child_count: e.children.len(),
572 has_inputs_available: e.inputs_available.is_some(),
573 inputs_available: e.inputs_available.unwrap_or(0),
574 has_inputs_routed: e.inputs_routed.is_some(),
575 inputs_routed: e.inputs_routed.unwrap_or(0),
576 linked_device: e.linked_device.into(),
577 linked_endpoint: e.linked_endpoint.map_or(-1, i16::from),
578 }
579 }
580}
581
582/// The diagnostic result for one UTP cable pair.
583#[repr(C)]
584#[derive(Clone, Copy)]
585pub struct mxr_cable_status_t {
586 /// Whether the pair is wired with normal polarity.
587 pub polarity: bool,
588 /// Which pair this describes.
589 pub pair: u8,
590 /// Measured skew.
591 pub skew: u32,
592 /// Measured length.
593 pub length: u32,
594}
595
596impl From<UtpCableStatus> for mxr_cable_status_t {
597 fn from(c: UtpCableStatus) -> Self {
598 Self {
599 polarity: c.polarity,
600 pair: c.pair,
601 skew: c.skew,
602 length: c.length,
603 }
604 }
605}
606
607/// The link state and diagnostics of one network port.
608#[repr(C)]
609#[derive(Clone, Copy)]
610pub struct mxr_network_port_t {
611 /// Port number.
612 pub port: u16,
613 /// Port name.
614 pub name: [c_char; MXR_NAME_LEN],
615 /// Negotiated link speed.
616 pub link_speed: u8,
617 /// Whether the link negotiated full duplex.
618 pub link_full_duplex: bool,
619 /// The port's own address, empty when it has not reported one.
620 pub ip: [c_char; MXR_IP_STRING_LEN],
621 /// The IGMP querier the port sees, empty when it sees none.
622 pub querier: [c_char; MXR_IP_STRING_LEN],
623 /// Whether the port reported a hardware address.
624 pub has_mac_address: bool,
625 /// The hardware address, meaningful only when `has_mac_address` is set.
626 pub mac_address: [u8; 6],
627 /// Whether the port reported link errors.
628 pub has_errors: bool,
629 /// Input errors.
630 pub in_error: bool,
631 /// Input frame check errors.
632 pub in_fcs_error: bool,
633 /// Input collisions.
634 pub in_collision: bool,
635 /// Deferred transmissions.
636 pub out_deferred: bool,
637 /// Excessive transmissions.
638 pub out_excessive: bool,
639 /// Polarity errors.
640 pub polarity_error: bool,
641 /// Skew warning.
642 pub skew_warning: bool,
643 /// Length warning.
644 pub length_warning: bool,
645 /// Whether the port reported a virtual cable test.
646 pub has_vct_status: bool,
647 /// Whether each pair raised a warning, meaningful only when
648 /// `has_vct_status` is set.
649 pub vct_warning: [bool; MXR_UTP_PAIRS],
650 /// How many entries of `cable_status` the port filled in.
651 pub cable_status_count: usize,
652 /// Cable diagnostics per pair.
653 pub cable_status: [mxr_cable_status_t; MXR_UTP_PAIRS],
654}
655
656/// One device a unit found behind its switch ports, in its topology report.
657#[repr(C)]
658#[derive(Clone, Copy)]
659pub struct mxr_topology_entry_t {
660 /// The device this entry describes.
661 pub uid: mxr_uid_t,
662 /// The sender's switch ports this device's MAC address was learnt on, one
663 /// bit per port.
664 pub mask: u32,
665}
666
667impl From<TopologyEntry> for mxr_topology_entry_t {
668 fn from(e: TopologyEntry) -> Self {
669 Self {
670 uid: e.uid.into(),
671 mask: e.mask,
672 }
673 }
674}
675
676/// One firmware component a device reports.
677#[repr(C)]
678#[derive(Clone, Copy)]
679pub struct mxr_firmware_version_t {
680 /// Which component this describes: 0 the unit's firmware, 1 the video
681 /// processor's FPGA image, 2 its RTOS image, 3 its loader image, 4 the
682 /// unit's bootloader.
683 pub firmware_type: u8,
684 /// When the image was built or installed, in seconds since the Unix epoch,
685 /// or 0 when it carries no time.
686 pub timestamp: u32,
687 /// Checksum of the image.
688 pub hash: u32,
689 /// Human-readable version string.
690 pub version: [c_char; MXR_VERSION_LEN],
691}
692
693/// A ProAmp8's Dolby settings.
694#[repr(C)]
695#[derive(Clone, Copy)]
696pub struct mxr_dolby_settings_t {
697 /// 0 = standard, 1 = 3-zone Dolby, 2 = 4-zone Dolby.
698 pub mode: u8,
699 /// Whether PCM is up-mixed to 5.1 rather than passed through.
700 pub pcm_upmix: bool,
701 /// Whether a Dolby stream was detected.
702 pub dolby_detected: bool,
703 /// Whether up-mixing is currently running.
704 pub pcm_upmix_active: bool,
705}
706
707impl From<AmpDolbySettings> for mxr_dolby_settings_t {
708 fn from(s: AmpDolbySettings) -> Self {
709 Self {
710 mode: s.mode,
711 pcm_upmix: s.pcm_upmix,
712 dolby_detected: s.dolby_detected,
713 pcm_upmix_active: s.pcm_upmix_active,
714 }
715 }
716}
717
718/// The remote-control configuration of a source bay.
719#[repr(C)]
720#[derive(Clone, Copy)]
721pub struct mxr_rc_settings_t {
722 /// The device this configuration belongs to.
723 pub target: mxr_uid_t,
724 /// The control method, as the wire value.
725 ///
726 /// Zero is infrared, a method a bay really uses, so it is not a stand-in
727 /// for "not reported". Check that `mxr_rc_settings()` returned `MXR_OK`
728 /// before reading this: a device that has not sent its settings yet
729 /// leaves the struct as the caller allocated it, and a zeroed one then
730 /// reads as a bay set to infrared. `mxr_bay_info_t` answers the same
731 /// question with a `has_rc_type` flag beside its `rc_type`.
732 pub rc_target: u8,
733 /// The control target's address, empty when unset.
734 pub ip: [c_char; MXR_IP_STRING_LEN],
735 /// Whether CEC is enabled.
736 pub cec_enabled: bool,
737 /// Whether CEC powers the sink on automatically.
738 pub cec_auto_on: bool,
739 /// Whether remote-control commands are forwarded.
740 pub forward_rc: bool,
741 /// Whether infrared is forwarded.
742 pub forward_ir: bool,
743 /// The driver state on the source, as the wire value. One above the last
744 /// this library knows is passed through as it arrived.
745 pub rc_status: u8,
746 /// The driver-reported status string, empty when unknown.
747 pub status_name: [c_char; MXR_NAME_LEN],
748}
749
750/// Writes an address into a fixed-width field, leaving it empty when there is
751/// none.
752fn put_ip(dst: &mut [c_char], ip: Option<Ipv4Addr>) {
753 put_str(dst, &ip.map(|ip| ip.to_string()).unwrap_or_default());
754}
755
756/// Declares a getter for one subsystem of a device.
757///
758/// Each has the same three answers - no such device, the device has not sent
759/// this, here it is - and writing them out once keeps a getter that answers
760/// differently visible as one.
761/// Writes a subsystem reading through `out`, or reports why there is none.
762///
763/// # Safety
764///
765/// `out` is null or points at a writable `T`.
766unsafe fn fill<T>(
767 r: &mxr_remote_t,
768 uid: mxr_uid_t,
769 out: *mut T,
770 what: &str,
771 value: Option<T>,
772) -> mxr_result_t {
773 if out.is_null() {
774 return null_out(what);
775 }
776 match value {
777 Some(value) => {
778 // SAFETY: the caller guarantees a writable T, and it is not null.
779 unsafe { *out = value };
780 mxr_result_t::MXR_OK
781 }
782 None => not_reported(r, uid, what),
783 }
784}
785
786/// Reports why a subsystem read found nothing: no such device, or a device
787/// that has not sent this.
788fn not_reported(r: &mxr_remote_t, uid: mxr_uid_t, what: &str) -> mxr_result_t {
789 if r.remote.device(uid.into()).is_none() {
790 return not_heard_from(uid);
791 }
792 fail(
793 mxr_result_t::MXR_ERR_NOT_REPORTED,
794 &format!("the device has reported no {what}"),
795 )
796}
797
798/// Fills `out` with a device's OneIP statistics.
799///
800/// A device sends these only while subscribed; see
801/// `mxr_subscribe_v2ip_stats()`.
802///
803/// # Safety
804///
805/// `remote` is null or a live handle, and `out` points at a writable
806/// [`mxr_v2ip_stats_t`].
807#[no_mangle]
808pub unsafe extern "C" fn mxr_v2ip_stats(
809 remote: *const mxr_remote_t,
810 uid: mxr_uid_t,
811 out: *mut mxr_v2ip_stats_t,
812) -> mxr_result_t {
813 // SAFETY: the caller guarantees a live handle or null.
814 let handle = unsafe { remote.as_ref() };
815 with(handle, |r| {
816 let value = r
817 .remote
818 .v2ip_stats(uid.into())
819 .map(|s: V2ipDeviceStats| mxr_v2ip_stats_t {
820 tx: s.tx.into(),
821 tx_per_minute: s.tx_per_minute.into(),
822 rx: s.rx.into(),
823 rx_per_minute: s.rx_per_minute.into(),
824 decoder: s.decoder.into(),
825 });
826 // SAFETY: the caller guarantees a writable mxr_v2ip_stats_t or null.
827 unsafe { fill(r, uid, out, "OneIP statistics", value) }
828 })
829}
830
831/// Fills `out` with a OneIP device's own encoder configuration.
832///
833/// # Safety
834///
835/// `remote` is null or a live handle, and `out` points at a writable
836/// [`mxr_v2ip_details_t`].
837#[no_mangle]
838pub unsafe extern "C" fn mxr_v2ip_details(
839 remote: *const mxr_remote_t,
840 uid: mxr_uid_t,
841 out: *mut mxr_v2ip_details_t,
842) -> mxr_result_t {
843 // SAFETY: the caller guarantees a live handle or null.
844 let handle = unsafe { remote.as_ref() };
845 with(handle, |r| {
846 let value = r
847 .remote
848 .v2ip_details(uid.into())
849 .map(|d: DeviceV2ipDetails| mxr_v2ip_details_t {
850 video: d.video.into(),
851 audio: d.audio.into(),
852 anc: d.anc.into(),
853 arc: d.arc.into(),
854 // A rate and a marking are both bytes on the wire, so -1 cannot
855 // collide with a value a device could report.
856 tx_rate: d.tx_rate.map_or(-1, i16::from),
857 dscp_video: d.dscp.video.map_or(-1, i16::from),
858 dscp_audio: d.dscp.audio.map_or(-1, i16::from),
859 dscp_anc: d.dscp.anc.map_or(-1, i16::from),
860 scaling_mode: d.scaling.mode.to_wire(),
861 scaling_refresh: d.scaling.refresh,
862 scaling_flags: d.scaling.flags,
863 });
864 // SAFETY: the caller guarantees a writable mxr_v2ip_details_t or null.
865 unsafe { fill(r, uid, out, "OneIP encoder configuration", value) }
866 })
867}
868
869/// Fills `out` with the streams a OneIP sink is subscribed to.
870///
871/// # Safety
872///
873/// `remote` is null or a live handle, and `out` points at a writable
874/// [`mxr_v2ip_sink_t`].
875#[no_mangle]
876pub unsafe extern "C" fn mxr_v2ip_sink(
877 remote: *const mxr_remote_t,
878 uid: mxr_uid_t,
879 out: *mut mxr_v2ip_sink_t,
880) -> mxr_result_t {
881 // SAFETY: the caller guarantees a live handle or null.
882 let handle = unsafe { remote.as_ref() };
883 with(handle, |r| {
884 let value = r
885 .remote
886 .v2ip_sink(uid.into())
887 .map(|s: DeviceV2ipSink| mxr_v2ip_sink_t {
888 addresses: s.addresses.into(),
889 has_audio_format: s.audio_fmt.is_some(),
890 audio_format: {
891 let f = s.audio_fmt.unwrap_or_default();
892 mxr_audio_format_t {
893 sample_rate: f.sample_rate,
894 channels: f.channels,
895 }
896 },
897 });
898 // SAFETY: the caller guarantees a writable mxr_v2ip_sink_t or null.
899 unsafe { fill(r, uid, out, "OneIP sink route", value) }
900 })
901}
902
903/// Fills `out` with what a OneIP device's video processor supports.
904///
905/// Reports `MXR_ERR_NOT_REPORTED` while the device has not said: a processor
906/// that has yet to answer and one with none of the optional commands send the
907/// same empty mask, so neither is reported as a capability set.
908///
909/// # Safety
910///
911/// `remote` is null or a live handle, and `out` points at a writable
912/// `uint64_t`.
913#[no_mangle]
914pub unsafe extern "C" fn mxr_v2ip_features(
915 remote: *const mxr_remote_t,
916 uid: mxr_uid_t,
917 out: *mut u64,
918) -> mxr_result_t {
919 // SAFETY: the caller guarantees a live handle or null.
920 let handle = unsafe { remote.as_ref() };
921 with(handle, |r| {
922 let value = r
923 .remote
924 .v2ip_features(uid.into())
925 .map(V2ipFpgaFeature::bits);
926 // SAFETY: the caller guarantees a writable uint64_t or null.
927 unsafe { fill(r, uid, out, "processor features", value) }
928 })
929}
930
931/// A OneIP device's settings.
932///
933/// A setting is reported only when its `MXR_V2IP_SETTING_*` bit is set in
934/// `valid`, and a device reports every setting it has.
935#[repr(C)]
936#[derive(Clone, Copy)]
937pub struct mxr_v2ip_device_settings_t {
938 /// `MXR_V2IP_SETTING_*` bits of the settings reported so far.
939 pub valid: u32,
940 /// `MXR_V2IP_SETTING_*` values of the on/off settings among `valid`.
941 pub flags: u32,
942 /// The infrared profiles stored on the device, bit n for profile n, when
943 /// `valid` has `MXR_V2IP_SETTING_IR_PROFILES`.
944 pub ir_profiles: u32,
945 /// The global infrared port's profile, when `valid` has
946 /// `MXR_V2IP_SETTING_IR_PROFILE`.
947 pub ir_profile: i8,
948 /// The output infrared port's profile, when `valid` has
949 /// `MXR_V2IP_SETTING_IR_PROFILE_SINK`. `MXR_V2IP_IR_PROFILE_NOT_SET` means
950 /// it follows the global one.
951 pub ir_profile_sink: i8,
952}
953
954/// The output infrared port follows the global one.
955pub const MXR_V2IP_IR_PROFILE_NOT_SET: i8 = -1;
956/// One past the highest infrared profile.
957pub const MXR_V2IP_IR_PROFILE_MAX: i8 = 18;
958
959// The header carries these as literals, so they are held to the core's here.
960const _: () = assert!(MXR_V2IP_IR_PROFILE_NOT_SET == mx_remote::V2IP_IR_PROFILE_NOT_SET);
961const _: () = assert!(MXR_V2IP_IR_PROFILE_MAX == mx_remote::V2IP_IR_PROFILE_MAX);
962
963/// Fills `out` with a OneIP device's settings.
964///
965/// Reports `MXR_ERR_NOT_REPORTED` until the device has reported any. A change
966/// is announced through `on_device_update`.
967///
968/// # Safety
969///
970/// `remote` is null or a live handle, and `out` points at a writable
971/// `mxr_v2ip_device_settings_t`.
972#[no_mangle]
973pub unsafe extern "C" fn mxr_v2ip_device_settings(
974 remote: *const mxr_remote_t,
975 uid: mxr_uid_t,
976 out: *mut mxr_v2ip_device_settings_t,
977) -> mxr_result_t {
978 // SAFETY: the caller guarantees a live handle or null.
979 let handle = unsafe { remote.as_ref() };
980 with(handle, |r| {
981 let value = r
982 .remote
983 .v2ip_device_settings(uid.into())
984 .map(|s: V2ipDeviceSettings| mxr_v2ip_device_settings_t {
985 valid: s.valid.bits(),
986 flags: s.flags.bits(),
987 ir_profiles: s.ir_profiles,
988 ir_profile: s.ir_profile,
989 ir_profile_sink: s.ir_profile_sink,
990 });
991 // SAFETY: the caller guarantees a writable struct or null.
992 unsafe { fill(r, uid, out, "device settings", value) }
993 })
994}
995
996/// A OneIP device's power save settings, beside `mxr_v2ip_device_settings_t`
997/// rather than in it so that struct keeps its size.
998///
999/// Which of them the device has reported is in that struct's `valid`.
1000#[repr(C)]
1001#[derive(Clone, Copy)]
1002pub struct mxr_v2ip_power_save_t {
1003 /// The idle minutes before the device powers down by itself, 0 for never,
1004 /// when `valid` has `MXR_V2IP_SETTING_AUTO_POWER_SAVE`.
1005 pub auto_minutes: u16,
1006 /// When each day's power save window starts, Monday first, in minutes
1007 /// after midnight in the device's time zone, when `valid` has
1008 /// `MXR_V2IP_SETTING_POWER_SAVE_SCHEDULE`.
1009 pub start: [u16; 7],
1010 /// When each day's window ends. A window ending before it starts runs
1011 /// past midnight, and one ending where it starts means none that day.
1012 pub end: [u16; 7],
1013}
1014
1015/// Minutes in a day: every power save time is below this.
1016pub const MXR_V2IP_MINUTES_PER_DAY: u16 = 1440;
1017
1018const _: () = assert!(MXR_V2IP_MINUTES_PER_DAY == mx_remote::V2IP_MINUTES_PER_DAY);
1019
1020/// Fills `out` with a OneIP device's power save settings.
1021///
1022/// Reports `MXR_ERR_NOT_REPORTED` until the device has reported any settings;
1023/// `mxr_v2ip_device_settings()` says which of these it has.
1024///
1025/// # Safety
1026///
1027/// `remote` is null or a live handle, and `out` points at a writable
1028/// `mxr_v2ip_power_save_t`.
1029#[no_mangle]
1030pub unsafe extern "C" fn mxr_v2ip_power_save(
1031 remote: *const mxr_remote_t,
1032 uid: mxr_uid_t,
1033 out: *mut mxr_v2ip_power_save_t,
1034) -> mxr_result_t {
1035 // SAFETY: the caller guarantees a live handle or null.
1036 let handle = unsafe { remote.as_ref() };
1037 with(handle, |r| {
1038 let value = r
1039 .remote
1040 .v2ip_device_settings(uid.into())
1041 .map(|s: V2ipDeviceSettings| mxr_v2ip_power_save_t {
1042 auto_minutes: s.auto_power_save,
1043 start: s.power_save.start,
1044 end: s.power_save.end,
1045 });
1046 // SAFETY: the caller guarantees a writable struct or null.
1047 unsafe { fill(r, uid, out, "device settings", value) }
1048 })
1049}
1050
1051/// A OneIP device's PTP settings, beside `mxr_v2ip_device_settings_t` rather
1052/// than in it so that struct keeps its size.
1053///
1054/// Which of them the device has reported is in that struct's `valid`.
1055#[repr(C)]
1056#[derive(Clone, Copy)]
1057pub struct mxr_v2ip_ptp_settings_t {
1058 /// `MXR_V2IP_PTP_MODE_*`, when `valid` has `MXR_V2IP_SETTING_PTP_MODE`.
1059 pub mode: u8,
1060 /// The domain, at most `MXR_V2IP_PTP_DOMAIN_MAX`, when `valid` has
1061 /// `MXR_V2IP_SETTING_PTP_DOMAIN`.
1062 pub domain: u8,
1063 /// The priority1 the device announces as a grandmaster, lower winning,
1064 /// when `valid` has `MXR_V2IP_SETTING_PTP_PRIORITY1`.
1065 pub priority1: u8,
1066}
1067
1068/// The highest PTP domain a OneIP device runs in.
1069pub const MXR_V2IP_PTP_DOMAIN_MAX: u8 = 127;
1070
1071const _: () = assert!(MXR_V2IP_PTP_DOMAIN_MAX == mx_remote::V2IP_PTP_DOMAIN_MAX);
1072
1073/// Fills `out` with a OneIP device's PTP settings.
1074///
1075/// Reports `MXR_ERR_NOT_REPORTED` until the device has reported any settings;
1076/// `mxr_v2ip_device_settings()` says which of these it has.
1077///
1078/// # Safety
1079///
1080/// `remote` is null or a live handle, and `out` points at a writable
1081/// `mxr_v2ip_ptp_settings_t`.
1082#[no_mangle]
1083pub unsafe extern "C" fn mxr_v2ip_ptp_settings(
1084 remote: *const mxr_remote_t,
1085 uid: mxr_uid_t,
1086 out: *mut mxr_v2ip_ptp_settings_t,
1087) -> mxr_result_t {
1088 // SAFETY: the caller guarantees a live handle or null.
1089 let handle = unsafe { remote.as_ref() };
1090 with(handle, |r| {
1091 let value = r
1092 .remote
1093 .v2ip_device_settings(uid.into())
1094 .map(|s: V2ipDeviceSettings| mxr_v2ip_ptp_settings_t {
1095 mode: s.ptp_mode.to_wire(),
1096 domain: s.ptp_domain,
1097 priority1: s.ptp_priority1,
1098 });
1099 // SAFETY: the caller guarantees a writable struct or null.
1100 unsafe { fill(r, uid, out, "device settings", value) }
1101 })
1102}
1103
1104/// Bytes in `mxr_time_zone_t.zone`, its NUL included.
1105pub const MXR_TIME_ZONE_NAME_LEN: usize = 48;
1106/// Bytes in `mxr_time_zone_t.rule`, its NUL included.
1107pub const MXR_TIME_ZONE_RULE_LEN: usize = 64;
1108
1109const _: () = assert!(MXR_TIME_ZONE_NAME_LEN == mx_remote::TIME_ZONE_NAME_LEN);
1110const _: () = assert!(MXR_TIME_ZONE_RULE_LEN == mx_remote::TIME_ZONE_RULE_LEN);
1111
1112/// The time zone a device announces for its mesh. An empty name and rule mean
1113/// the controller has none, and the devices keep UTC.
1114#[repr(C)]
1115#[derive(Clone, Copy)]
1116pub struct mxr_time_zone_t {
1117 /// The IANA name, such as `Europe/Amsterdam`.
1118 pub zone: [c_char; MXR_TIME_ZONE_NAME_LEN],
1119 /// The POSIX TZ rule the devices keep time by.
1120 pub rule: [c_char; MXR_TIME_ZONE_RULE_LEN],
1121}
1122
1123/// Fills `out` with the time zone a device announced for its mesh.
1124///
1125/// Reports `MXR_ERR_NOT_REPORTED` until it has announced one; the mesh
1126/// controller does with every periodic broadcast. A change is announced
1127/// through `on_device_update`.
1128///
1129/// # Safety
1130///
1131/// `remote` is null or a live handle, and `out` points at a writable
1132/// `mxr_time_zone_t`.
1133#[no_mangle]
1134pub unsafe extern "C" fn mxr_time_zone(
1135 remote: *const mxr_remote_t,
1136 uid: mxr_uid_t,
1137 out: *mut mxr_time_zone_t,
1138) -> mxr_result_t {
1139 // SAFETY: the caller guarantees a live handle or null.
1140 let handle = unsafe { remote.as_ref() };
1141 with(handle, |r| {
1142 let value = r.remote.time_zone(uid.into()).map(|tz| {
1143 let mut out = mxr_time_zone_t {
1144 zone: [0; MXR_TIME_ZONE_NAME_LEN],
1145 rule: [0; MXR_TIME_ZONE_RULE_LEN],
1146 };
1147 put_str(&mut out.zone, &tz.zone);
1148 put_str(&mut out.rule, &tz.rule);
1149 out
1150 });
1151 // SAFETY: the caller guarantees a writable struct or null.
1152 unsafe { fill(r, uid, out, "time zone", value) }
1153 })
1154}
1155
1156/// Fills `out` with a device's clock as of now, in seconds since 1970 UTC:
1157/// the time it last announced, advanced by how long ago that arrived.
1158///
1159/// Reports `MXR_ERR_NOT_REPORTED` until it has announced one; the mesh
1160/// controller does with every periodic broadcast once its clock is set.
1161///
1162/// # Safety
1163///
1164/// `remote` is null or a live handle, and `out` points at a writable
1165/// `uint64_t`.
1166#[no_mangle]
1167pub unsafe extern "C" fn mxr_device_clock(
1168 remote: *const mxr_remote_t,
1169 uid: mxr_uid_t,
1170 out: *mut u64,
1171) -> mxr_result_t {
1172 // SAFETY: the caller guarantees a live handle or null.
1173 let handle = unsafe { remote.as_ref() };
1174 with(handle, |r| {
1175 let value = r
1176 .remote
1177 .device_clock(uid.into())
1178 .and_then(|t| t.duration_since(UNIX_EPOCH).ok())
1179 .map(|d| d.as_secs());
1180 // SAFETY: the caller guarantees a writable uint64_t or null.
1181 unsafe { fill(r, uid, out, "clock", value) }
1182 })
1183}
1184
1185/// VLAN tagging on a OneIP device's uplink.
1186///
1187/// Only the device knows what it runs, so this is what it reported about
1188/// itself. A change written to it is applied at once and carries
1189/// `MXR_V2IP_VLAN_PENDING` until the mesh controller confirms it; unconfirmed,
1190/// the device reverts it.
1191#[repr(C)]
1192#[derive(Clone, Copy)]
1193pub struct mxr_v2ip_vlan_t {
1194 /// `MXR_V2IP_VLAN_*` bits.
1195 pub flags: u16,
1196 /// The VLAN id of the device's own traffic, 0 for untagged.
1197 pub device: u16,
1198 /// The VLAN id each downlink port carries, 0 to share the device's
1199 /// untagged traffic: the SFP port, the UTP port with PoE, then the UTP
1200 /// port.
1201 pub port: [u16; MXR_V2IP_VLAN_PORTS],
1202 /// The port pinned as the uplink as 1 + its index in `port`, 0 to detect
1203 /// it.
1204 pub uplink: u8,
1205 /// Reported by the device: the port in use as the uplink, as 1 + its
1206 /// index in `port`.
1207 pub active_uplink: u8,
1208 /// Reported by the device: the seconds before a pending configuration is
1209 /// reverted.
1210 pub revert_s: u8,
1211}
1212
1213/// The ports in `mxr_v2ip_vlan_t::port`.
1214pub const MXR_V2IP_VLAN_PORTS: usize = 3;
1215/// The index of the SFP port in `mxr_v2ip_vlan_t::port`.
1216pub const MXR_V2IP_VLAN_PORT_SFP: usize = 0;
1217/// The highest VLAN id: 0 means untagged, and 4095 is reserved by IEEE 802.1Q.
1218pub const MXR_V2IP_VLAN_ID_MAX: u16 = 4094;
1219
1220const _: () = assert!(MXR_V2IP_VLAN_PORTS == mx_remote::V2IP_VLAN_PORTS);
1221const _: () = assert!(MXR_V2IP_VLAN_PORT_SFP == mx_remote::V2IP_VLAN_PORT_SFP);
1222const _: () = assert!(MXR_V2IP_VLAN_ID_MAX == mx_remote::V2IP_VLAN_ID_MAX);
1223
1224/// Fills `out` with the VLAN configuration a OneIP device last reported.
1225///
1226/// Reports `MXR_ERR_NOT_REPORTED` until the device has reported one. A change
1227/// is announced through `on_device_update`.
1228///
1229/// # Safety
1230///
1231/// `remote` is null or a live handle, and `out` points at a writable
1232/// `mxr_v2ip_vlan_t`.
1233#[no_mangle]
1234pub unsafe extern "C" fn mxr_v2ip_vlan(
1235 remote: *const mxr_remote_t,
1236 uid: mxr_uid_t,
1237 out: *mut mxr_v2ip_vlan_t,
1238) -> mxr_result_t {
1239 // SAFETY: the caller guarantees a live handle or null.
1240 let handle = unsafe { remote.as_ref() };
1241 with(handle, |r| {
1242 let value = r
1243 .remote
1244 .v2ip_vlan(uid.into())
1245 .map(|v: V2ipVlan| mxr_v2ip_vlan_t {
1246 flags: v.flags.bits(),
1247 device: v.device,
1248 port: v.port,
1249 uplink: v.uplink,
1250 active_uplink: v.active_uplink,
1251 revert_s: v.revert_s,
1252 });
1253 // SAFETY: the caller guarantees a writable struct or null.
1254 unsafe { fill(r, uid, out, "VLAN configuration", value) }
1255 })
1256}
1257
1258/// The most temperatures a unit reports.
1259pub const MXR_UNIT_MAX_TEMPERATURES: usize = 6;
1260/// Bytes in `mxr_unit_status_t.status_message`, its NUL included.
1261pub const MXR_UNIT_STATUS_MESSAGE_LEN: usize = 49;
1262
1263const _: () = assert!(MXR_UNIT_MAX_TEMPERATURES == mx_remote::UNIT_MAX_TEMPERATURES);
1264
1265/// A unit's health and IP configuration, as it last reported them.
1266/// Addresses are dotted quads.
1267#[repr(C)]
1268#[derive(Clone, Copy)]
1269pub struct mxr_unit_status_t {
1270 /// `MXR_UNIT_*` bits.
1271 pub flags: u32,
1272 /// Seconds since the unit booted.
1273 pub uptime: u32,
1274 /// How often the unit has booted, when `flags` has `MXR_UNIT_BOOT_COUNT`.
1275 pub boot_count: u32,
1276 /// How often the unit has rebooted.
1277 pub reboot_count: u32,
1278 /// The encoder's health, a mask of the unit's encoder status bits.
1279 pub encoder_health: u32,
1280 /// The decoder's health, a mask of the unit's decoder status and state
1281 /// bits.
1282 pub decoder_health: u32,
1283 /// The address in use.
1284 pub ip: [c_char; MXR_IP_STRING_LEN],
1285 /// The netmask in use.
1286 pub netmask: [c_char; MXR_IP_STRING_LEN],
1287 /// The gateway in use.
1288 pub gateway: [c_char; MXR_IP_STRING_LEN],
1289 /// The primary DNS server in use.
1290 pub dns1: [c_char; MXR_IP_STRING_LEN],
1291 /// The secondary DNS server in use.
1292 pub dns2: [c_char; MXR_IP_STRING_LEN],
1293 /// The fan speed in RPM, when `flags` has `MXR_UNIT_FAN_SPEED`.
1294 pub fan_rpm: u16,
1295 /// The fan duty cycle, in percent.
1296 pub fan_duty: u8,
1297 /// The unit's system status.
1298 pub status: u8,
1299 /// Seconds before a pending IP configuration is reverted.
1300 pub net_revert_s: u8,
1301 /// How many of `temperatures` are reported.
1302 pub nb_temperatures: u8,
1303 /// Temperatures, in degrees Celsius.
1304 pub temperatures: [i8; MXR_UNIT_MAX_TEMPERATURES],
1305 /// The message of the current system status.
1306 pub status_message: [c_char; MXR_UNIT_STATUS_MESSAGE_LEN],
1307}
1308
1309/// Fills `out` with the health and IP configuration a unit last reported.
1310///
1311/// Reports `MXR_ERR_NOT_REPORTED` until the unit has reported them. A change
1312/// is announced through `on_device_update`.
1313///
1314/// # Safety
1315///
1316/// `remote` is null or a live handle, and `out` points at a writable
1317/// `mxr_unit_status_t`.
1318#[no_mangle]
1319pub unsafe extern "C" fn mxr_unit_status(
1320 remote: *const mxr_remote_t,
1321 uid: mxr_uid_t,
1322 out: *mut mxr_unit_status_t,
1323) -> mxr_result_t {
1324 // SAFETY: the caller guarantees a live handle or null.
1325 let handle = unsafe { remote.as_ref() };
1326 with(handle, |r| {
1327 let value = r.remote.unit_status(uid.into()).map(|s: UnitStatus| {
1328 let mut out = mxr_unit_status_t {
1329 flags: s.flags.bits(),
1330 uptime: s.uptime,
1331 boot_count: s.boot_count,
1332 reboot_count: s.reboot_count,
1333 encoder_health: s.encoder_health,
1334 decoder_health: s.decoder_health,
1335 ip: [0; MXR_IP_STRING_LEN],
1336 netmask: [0; MXR_IP_STRING_LEN],
1337 gateway: [0; MXR_IP_STRING_LEN],
1338 dns1: [0; MXR_IP_STRING_LEN],
1339 dns2: [0; MXR_IP_STRING_LEN],
1340 fan_rpm: s.fan_rpm,
1341 fan_duty: s.fan_duty,
1342 status: s.status,
1343 net_revert_s: s.net_revert_s,
1344 nb_temperatures: s.temperatures.len() as u8,
1345 temperatures: [0; MXR_UNIT_MAX_TEMPERATURES],
1346 status_message: [0; MXR_UNIT_STATUS_MESSAGE_LEN],
1347 };
1348 put_ip(&mut out.ip, Some(s.network.ip));
1349 put_ip(&mut out.netmask, Some(s.network.netmask));
1350 put_ip(&mut out.gateway, Some(s.network.gateway));
1351 put_ip(&mut out.dns1, Some(s.network.dns1));
1352 put_ip(&mut out.dns2, Some(s.network.dns2));
1353 for (slot, t) in out.temperatures.iter_mut().zip(&s.temperatures) {
1354 *slot = *t;
1355 }
1356 put_str(&mut out.status_message, &s.status_message);
1357 out
1358 });
1359 // SAFETY: the caller guarantees a writable struct or null.
1360 unsafe { fill(r, uid, out, "unit status", value) }
1361 })
1362}
1363
1364/// Fills `out` with the level a OneIP device powers down to in power save,
1365/// `MXR_V2IP_POWER_SAVE_LEVEL_*`.
1366///
1367/// Reports `MXR_ERR_NOT_REPORTED` until the device has reported any settings;
1368/// `mxr_v2ip_device_settings()` says whether it has this one.
1369///
1370/// # Safety
1371///
1372/// `remote` is null or a live handle, and `out` points at a writable byte.
1373#[no_mangle]
1374pub unsafe extern "C" fn mxr_v2ip_power_save_level(
1375 remote: *const mxr_remote_t,
1376 uid: mxr_uid_t,
1377 out: *mut u8,
1378) -> mxr_result_t {
1379 // SAFETY: the caller guarantees a live handle or null.
1380 let handle = unsafe { remote.as_ref() };
1381 with(handle, |r| {
1382 let value = r
1383 .remote
1384 .v2ip_device_settings(uid.into())
1385 .map(|s: V2ipDeviceSettings| s.power_save_level.to_wire());
1386 // SAFETY: the caller guarantees a writable byte or null.
1387 unsafe { fill(r, uid, out, "device settings", value) }
1388 })
1389}
1390
1391/// How a OneIP device paces its PTP messages, its SAP timings, its streams'
1392/// TTL and its output's rotation, which a frame carries in the same block.
1393/// Intervals are base-2 logarithms of seconds, so -3 is 125 ms and 1 is 2 s.
1394/// Each value is meaningful only behind its `MXR_V2IP_SETTING_PTP_*`,
1395/// `MXR_V2IP_SETTING_SAP_*`, `MXR_V2IP_SETTING_STREAM_TTL` or
1396/// `MXR_V2IP_SETTING_SINK_ROTATION` bit in
1397/// `mxr_v2ip_device_settings_t`'s `valid`.
1398#[repr(C)]
1399#[derive(Clone, Copy)]
1400pub struct mxr_v2ip_ptp_messages_t {
1401 /// Between announce messages.
1402 pub log_announce_interval: i8,
1403 /// Between sync messages.
1404 pub log_sync_interval: i8,
1405 /// Between delay requests.
1406 pub log_delay_req_interval: i8,
1407 /// The announce intervals before a silent master is dropped.
1408 pub announce_timeout: u8,
1409 /// The DSCP of the messages, at most 63.
1410 pub dscp: u8,
1411 /// The minutes after which a heard SAP announcement is forgotten.
1412 pub sap_expire_min: u8,
1413 /// The seconds between the device's SAP announcements.
1414 pub sap_interval_s: u16,
1415 /// The TTL of the device's streams, 1 to 255. A device whose video
1416 /// processor cannot change it keeps the setting and sends
1417 /// `MXR_V2IP_STREAM_TTL_DEFAULT`.
1418 pub stream_ttl: u8,
1419 /// The degrees the device's HDMI output is turned by, clockwise, at most
1420 /// `MXR_V2IP_SINK_ROTATION_MAX`.
1421 pub sink_rotation: u16,
1422 /// The step the output turns by: the device takes only an angle that is
1423 /// a multiple of it, and 0 alone when it is 0. Only the device reports
1424 /// it; a write ignores it.
1425 pub sink_rotation_step: u16,
1426}
1427
1428impl From<V2ipPtpMessages> for mxr_v2ip_ptp_messages_t {
1429 fn from(m: V2ipPtpMessages) -> Self {
1430 Self {
1431 log_announce_interval: m.log_announce_interval,
1432 log_sync_interval: m.log_sync_interval,
1433 log_delay_req_interval: m.log_delay_req_interval,
1434 announce_timeout: m.announce_timeout,
1435 dscp: m.dscp,
1436 sap_expire_min: m.sap_expire_min,
1437 sap_interval_s: m.sap_interval_s,
1438 stream_ttl: m.stream_ttl,
1439 sink_rotation: m.sink_rotation,
1440 sink_rotation_step: m.sink_rotation_step,
1441 }
1442 }
1443}
1444
1445impl From<mxr_v2ip_ptp_messages_t> for V2ipPtpMessages {
1446 fn from(m: mxr_v2ip_ptp_messages_t) -> Self {
1447 Self {
1448 log_announce_interval: m.log_announce_interval,
1449 log_sync_interval: m.log_sync_interval,
1450 log_delay_req_interval: m.log_delay_req_interval,
1451 announce_timeout: m.announce_timeout,
1452 dscp: m.dscp,
1453 sap_expire_min: m.sap_expire_min,
1454 sap_interval_s: m.sap_interval_s,
1455 stream_ttl: m.stream_ttl,
1456 sink_rotation: m.sink_rotation,
1457 sink_rotation_step: m.sink_rotation_step,
1458 }
1459 }
1460}
1461
1462/// Fills `out` with how a OneIP device paces its PTP messages.
1463///
1464/// Reports `MXR_ERR_NOT_REPORTED` until the device has reported any settings;
1465/// `mxr_v2ip_device_settings()` says which of these it has.
1466///
1467/// # Safety
1468///
1469/// `remote` is null or a live handle, and `out` points at a writable
1470/// `mxr_v2ip_ptp_messages_t`.
1471#[no_mangle]
1472pub unsafe extern "C" fn mxr_v2ip_ptp_messages(
1473 remote: *const mxr_remote_t,
1474 uid: mxr_uid_t,
1475 out: *mut mxr_v2ip_ptp_messages_t,
1476) -> mxr_result_t {
1477 // SAFETY: the caller guarantees a live handle or null.
1478 let handle = unsafe { remote.as_ref() };
1479 with(handle, |r| {
1480 let value = r
1481 .remote
1482 .v2ip_device_settings(uid.into())
1483 .map(|s: V2ipDeviceSettings| s.ptp_messages.into());
1484 // SAFETY: the caller guarantees a writable struct or null.
1485 unsafe { fill(r, uid, out, "device settings", value) }
1486 })
1487}
1488
1489/// How a OneIP output mixes 5.1 and 7.1 PCM down to stereo, and whether it
1490/// does now.
1491#[repr(C)]
1492#[derive(Clone, Copy)]
1493pub struct mxr_v2ip_downmix_t {
1494 /// When the output mixes down, an `MXR_V2IP_DOWNMIX_*` mode.
1495 pub mode: u8,
1496 /// How loud the mix is, an `MXR_V2IP_DOWNMIX_LEVEL_*`.
1497 pub level: u8,
1498 /// The percent the LFE channel is mixed in at, at most
1499 /// `MXR_V2IP_DOWNMIX_LFE_MAX`; 0 leaves it out.
1500 pub lfe: u8,
1501 /// Why the stream is or is not mixed down, as the device's own code. Only
1502 /// the device reports it; a write ignores it.
1503 pub state: u8,
1504 /// The stream's channels while it is mixed down, else 0. Only the device
1505 /// reports it; a write ignores it.
1506 pub channels: u8,
1507}
1508
1509// The header carries these as literals, so they are held to the core's here.
1510const _: () = assert!(MXR_V2IP_DOWNMIX_ALWAYS == V2ipDownmixMode::ALWAYS.to_wire());
1511const _: () = assert!(MXR_V2IP_DOWNMIX_LEVEL_UNSCALED == V2ipDownmixLevel::UNSCALED.to_wire());
1512const _: () = assert!(MXR_V2IP_DOWNMIX_LFE_MAX == V2IP_DOWNMIX_LFE_MAX);
1513
1514impl From<V2ipDownmix> for mxr_v2ip_downmix_t {
1515 fn from(d: V2ipDownmix) -> Self {
1516 Self {
1517 mode: d.mode.to_wire(),
1518 level: d.level.to_wire(),
1519 lfe: d.lfe,
1520 state: d.state,
1521 channels: d.channels,
1522 }
1523 }
1524}
1525
1526impl From<mxr_v2ip_downmix_t> for V2ipDownmix {
1527 fn from(d: mxr_v2ip_downmix_t) -> Self {
1528 Self {
1529 mode: V2ipDownmixMode::from_wire(d.mode),
1530 level: V2ipDownmixLevel::from_wire(d.level),
1531 lfe: d.lfe,
1532 state: d.state,
1533 channels: d.channels,
1534 }
1535 }
1536}
1537
1538/// Fills `out` with how a OneIP device's output mixes down to stereo.
1539///
1540/// Reports `MXR_ERR_NOT_REPORTED` while the device reports no downmix: until
1541/// its video processor has answered for it, and always for one whose output
1542/// cannot mix down. A change is announced through `on_device_update`.
1543///
1544/// # Safety
1545///
1546/// `remote` is null or a live handle, and `out` points at a writable
1547/// `mxr_v2ip_downmix_t`.
1548#[no_mangle]
1549pub unsafe extern "C" fn mxr_v2ip_downmix(
1550 remote: *const mxr_remote_t,
1551 uid: mxr_uid_t,
1552 out: *mut mxr_v2ip_downmix_t,
1553) -> mxr_result_t {
1554 // SAFETY: the caller guarantees a live handle or null.
1555 let handle = unsafe { remote.as_ref() };
1556 with(handle, |r| {
1557 let value = r.remote.v2ip_downmix(uid.into()).map(Into::into);
1558 // SAFETY: the caller guarantees a writable struct or null.
1559 unsafe { fill(r, uid, out, "downmix", value) }
1560 })
1561}
1562
1563/// The longest session description a OneIP output takes, in bytes.
1564pub const MXR_V2IP_SDP_MAX: usize = 2048;
1565/// Bytes in `mxr_v2ip_sink_sdp_t.name`, its NUL included.
1566pub const MXR_V2IP_SDP_NAME_LEN: usize = 48;
1567/// Bytes in `mxr_v2ip_sink_sdp_t.layout`, its NUL included.
1568pub const MXR_V2IP_SDP_LAYOUT_LEN: usize = 8;
1569
1570const _: () = assert!(MXR_V2IP_SDP_MAX == mx_remote::V2IP_SDP_MAX);
1571
1572/// What a OneIP output plays from a session description, as it last reported
1573/// it. The stream's fields are zero while it plays none.
1574#[repr(C)]
1575#[derive(Clone, Copy)]
1576pub struct mxr_v2ip_sink_sdp_t {
1577 /// `MXR_V2IP_SDP_FLAG_*` bits.
1578 pub flags: u8,
1579 /// What the output shows while it plays audio alone.
1580 pub screen: u8,
1581 /// `MXR_V2IP_SDP_ENCODING_*`.
1582 pub encoding: u8,
1583 /// The channel count.
1584 pub channels: u8,
1585 /// The RTP payload type.
1586 pub payload_type: u8,
1587 /// `MXR_V2IP_SDP_MEDIA_CLOCK_*`.
1588 pub media_clock: u8,
1589 /// `MXR_V2IP_SDP_REF_CLOCK_*`.
1590 pub ref_clock: u8,
1591 /// `MXR_V2IP_SDP_CLOCK_MODE_*`, with `MXR_V2IP_SDP_FLAG_CLOCK`.
1592 pub clock_mode: u8,
1593 /// `MXR_V2IP_SDP_CLOCK_STATE_*`, with `MXR_V2IP_SDP_FLAG_CLOCK`.
1594 pub clock_state: u8,
1595 /// The destination UDP port.
1596 pub port: u16,
1597 /// The sample rate, in Hz.
1598 pub rate_hz: u32,
1599 /// The media clock offset.
1600 pub media_clock_offset: u32,
1601 /// The multicast group, as a dotted quad.
1602 pub group: [c_char; MXR_IP_STRING_LEN],
1603 /// The session name.
1604 pub name: [c_char; MXR_V2IP_SDP_NAME_LEN],
1605 /// The channel grouping it plays, empty without one.
1606 pub layout: [c_char; MXR_V2IP_SDP_LAYOUT_LEN],
1607}
1608
1609/// Fills `out` with what a OneIP output last reported playing from a session
1610/// description.
1611///
1612/// Reports `MXR_ERR_NOT_REPORTED` until the output has reported; it does when
1613/// that changes, at least once a minute, and when asked with
1614/// `mxr_request_v2ip_sink_sdp()`. A change is announced through
1615/// `on_device_update`.
1616///
1617/// # Safety
1618///
1619/// `remote` is null or a live handle, and `out` points at a writable
1620/// `mxr_v2ip_sink_sdp_t`.
1621#[no_mangle]
1622pub unsafe extern "C" fn mxr_v2ip_sink_sdp(
1623 remote: *const mxr_remote_t,
1624 uid: mxr_uid_t,
1625 out: *mut mxr_v2ip_sink_sdp_t,
1626) -> mxr_result_t {
1627 // SAFETY: the caller guarantees a live handle or null.
1628 let handle = unsafe { remote.as_ref() };
1629 with(handle, |r| {
1630 let value = r.remote.v2ip_sink_sdp(uid.into()).map(|s: V2ipSinkSdp| {
1631 let mut out = mxr_v2ip_sink_sdp_t {
1632 flags: s.flags.bits(),
1633 screen: s.screen,
1634 encoding: s.encoding.to_wire(),
1635 channels: s.channels,
1636 payload_type: s.payload_type,
1637 media_clock: s.media_clock.to_wire(),
1638 ref_clock: s.ref_clock.to_wire(),
1639 clock_mode: s.clock_mode.to_wire(),
1640 clock_state: s.clock_state.to_wire(),
1641 port: s.port,
1642 rate_hz: s.rate_hz,
1643 media_clock_offset: s.media_clock_offset,
1644 group: [0; MXR_IP_STRING_LEN],
1645 name: [0; MXR_V2IP_SDP_NAME_LEN],
1646 layout: [0; MXR_V2IP_SDP_LAYOUT_LEN],
1647 };
1648 put_ip(&mut out.group, Some(s.group));
1649 put_str(&mut out.name, &s.name);
1650 put_str(&mut out.layout, &s.layout);
1651 out
1652 });
1653 // SAFETY: the caller guarantees a writable struct or null.
1654 unsafe { fill(r, uid, out, "session description status", value) }
1655 })
1656}
1657
1658/// How one of a OneIP source unit's streams is encrypted: the values of its
1659/// session description's `a=privacy` and `a=extmap` attributes. The key
1660/// itself never travels: every unit of a mesh holds the same one. This
1661/// library holds no key and decrypts nothing.
1662#[repr(C)]
1663#[derive(Clone, Copy)]
1664pub struct mxr_v2ip_privacy_stream_t {
1665 /// The stream's destination address, as a dotted quad.
1666 pub group: [c_char; MXR_IP_STRING_LEN],
1667 /// The stream's UDP port.
1668 pub port: u16,
1669 /// Steps whenever the parameters change.
1670 pub version: u8,
1671 /// Whether the stream is encrypted. Without it the stream goes out in the
1672 /// clear, and the fields below mean nothing.
1673 pub on: bool,
1674 /// The RTP header extension id of the full IV counter.
1675 pub full_id: u8,
1676 /// The RTP header extension id of the short IV counter.
1677 pub short_id: u8,
1678 /// The initialisation vector.
1679 pub iv: [u8; 8],
1680 /// The key generator.
1681 pub key_generator: [u8; 16],
1682 /// Names the mesh's key. A receiver holding another drops the stream.
1683 pub key_id: [u8; 8],
1684 /// The key version.
1685 pub key_version: u32,
1686}
1687
1688/// How a OneIP source unit's streams are encrypted, as it last reported it.
1689#[repr(C)]
1690#[derive(Clone, Copy)]
1691pub struct mxr_v2ip_privacy_t {
1692 /// The video stream.
1693 pub video: mxr_v2ip_privacy_stream_t,
1694 /// The audio stream.
1695 pub audio: mxr_v2ip_privacy_stream_t,
1696 /// The ancillary data stream.
1697 pub anc: mxr_v2ip_privacy_stream_t,
1698}
1699
1700fn privacy_stream(s: &V2ipPrivacyStream) -> mxr_v2ip_privacy_stream_t {
1701 let mut out = mxr_v2ip_privacy_stream_t {
1702 group: [0; MXR_IP_STRING_LEN],
1703 port: s.port,
1704 version: s.version,
1705 on: s.on,
1706 full_id: s.full_id,
1707 short_id: s.short_id,
1708 iv: s.iv,
1709 key_generator: s.key_generator,
1710 key_id: s.key_id,
1711 key_version: s.key_version,
1712 };
1713 put_ip(&mut out.group, Some(s.group));
1714 out
1715}
1716
1717/// Fills `out` with how a OneIP source unit's streams are encrypted, as it
1718/// last reported it.
1719///
1720/// Reports `MXR_ERR_NOT_REPORTED` until the unit has reported; it does when
1721/// that changes, at least once a minute, and when asked with
1722/// `mxr_request_v2ip_privacy()`. A change is announced through
1723/// `on_device_update`.
1724///
1725/// # Safety
1726///
1727/// `remote` is null or a live handle, and `out` points at a writable
1728/// `mxr_v2ip_privacy_t`.
1729#[no_mangle]
1730pub unsafe extern "C" fn mxr_v2ip_privacy(
1731 remote: *const mxr_remote_t,
1732 uid: mxr_uid_t,
1733 out: *mut mxr_v2ip_privacy_t,
1734) -> mxr_result_t {
1735 // SAFETY: the caller guarantees a live handle or null.
1736 let handle = unsafe { remote.as_ref() };
1737 with(handle, |r| {
1738 let value = r
1739 .remote
1740 .v2ip_privacy(uid.into())
1741 .map(|p: V2ipPrivacy| mxr_v2ip_privacy_t {
1742 video: privacy_stream(&p.video),
1743 audio: privacy_stream(&p.audio),
1744 anc: privacy_stream(&p.anc),
1745 });
1746 // SAFETY: the caller guarantees a writable struct or null.
1747 unsafe { fill(r, uid, out, "stream encryption", value) }
1748 })
1749}
1750
1751/// The logo slots an overlay sync can copy.
1752pub const MXR_OSD_SYNC_SLOTS: usize = 4;
1753/// Bytes in `mxr_osd_sync_report_t.reason`, its NUL included.
1754pub const MXR_OSD_SYNC_REASON_LEN: usize = 65;
1755
1756const _: () = assert!(MXR_OSD_SYNC_SLOTS == OSD_SYNC_SLOTS);
1757
1758/// One logo slot, as an overlay sync describes it.
1759#[repr(C)]
1760#[derive(Clone, Copy, Default)]
1761pub struct mxr_osd_sync_slot_t {
1762 /// The PNG's size in bytes; zero empties the slot.
1763 pub bytes: u32,
1764 /// The PNG's CRC-32.
1765 pub crc32: u32,
1766 /// The size it is shown at, in percent.
1767 pub percent: u8,
1768}
1769
1770/// The overlay sync a management unit last sent.
1771#[repr(C)]
1772#[derive(Clone, Copy)]
1773pub struct mxr_osd_sync_t {
1774 /// The sync's number at its source.
1775 pub seq: u16,
1776 /// The unit it is for, or zero for every unit.
1777 pub target: mxr_uid_t,
1778 /// `MXR_OSD_SYNC_WHAT_*` bits.
1779 pub what: u8,
1780 /// The source's HTTP port, zero meaning 80.
1781 pub http_port: u16,
1782 /// The source's address as a dotted quad, `0.0.0.0` meaning the
1783 /// sender's.
1784 pub source_ip: [c_char; MXR_IP_STRING_LEN],
1785 /// How many settings follow.
1786 pub records: u16,
1787 /// The logo slots, filled when `what` has
1788 /// `MXR_OSD_SYNC_WHAT_LOGO_SLOTS` and zero otherwise. A unit fetches a
1789 /// slot whose size or CRC differs from
1790 /// `http://source_ip:http_port/osd-sync/slotN.png`, N being 1 to 4.
1791 pub slots: [mxr_osd_sync_slot_t; MXR_OSD_SYNC_SLOTS],
1792}
1793
1794/// How a unit last reported getting on with an overlay sync.
1795#[repr(C)]
1796#[derive(Clone, Copy)]
1797pub struct mxr_osd_sync_report_t {
1798 /// The sync it reports on, numbered by the source that sent it.
1799 pub seq: u16,
1800 /// `MXR_OSD_SYNC_STATE_*`.
1801 pub state: u8,
1802 /// The settings it wrote.
1803 pub written: u16,
1804 /// The settings it refused.
1805 pub refused: u16,
1806 /// Why it failed or refused any.
1807 pub reason: [c_char; MXR_OSD_SYNC_REASON_LEN],
1808}
1809
1810/// Fills `out` with the overlay sync a management unit last sent.
1811///
1812/// Reports `MXR_ERR_NOT_REPORTED` until it has sent one. A new sync is
1813/// announced through `on_device_update`.
1814///
1815/// # Safety
1816///
1817/// `remote` is null or a live handle, and `out` points at a writable
1818/// `mxr_osd_sync_t`.
1819#[no_mangle]
1820pub unsafe extern "C" fn mxr_osd_sync(
1821 remote: *const mxr_remote_t,
1822 uid: mxr_uid_t,
1823 out: *mut mxr_osd_sync_t,
1824) -> mxr_result_t {
1825 // SAFETY: the caller guarantees a live handle or null.
1826 let handle = unsafe { remote.as_ref() };
1827 with(handle, |r| {
1828 let value = r.remote.osd_sync(uid.into()).map(|s: OsdSync| {
1829 let mut out = mxr_osd_sync_t {
1830 seq: s.seq,
1831 target: s.target.into(),
1832 what: s.what.bits(),
1833 http_port: s.http_port,
1834 source_ip: [0; MXR_IP_STRING_LEN],
1835 records: s.records,
1836 slots: [mxr_osd_sync_slot_t::default(); MXR_OSD_SYNC_SLOTS],
1837 };
1838 put_ip(&mut out.source_ip, Some(s.source_ip));
1839 for (o, slot) in out.slots.iter_mut().zip(s.slots.iter().flatten()) {
1840 *o = mxr_osd_sync_slot_t {
1841 bytes: slot.bytes,
1842 crc32: slot.crc32,
1843 percent: slot.percent,
1844 };
1845 }
1846 out
1847 });
1848 // SAFETY: the caller guarantees a writable struct or null.
1849 unsafe { fill(r, uid, out, "overlay sync", value) }
1850 })
1851}
1852
1853/// Fills `out` with how a unit last reported getting on with an overlay
1854/// sync. Match it to the sync by `seq` and the sync's source.
1855///
1856/// Reports `MXR_ERR_NOT_REPORTED` until the unit has reported. A change is
1857/// announced through `on_device_update`.
1858///
1859/// # Safety
1860///
1861/// `remote` is null or a live handle, and `out` points at a writable
1862/// `mxr_osd_sync_report_t`.
1863#[no_mangle]
1864pub unsafe extern "C" fn mxr_osd_sync_report(
1865 remote: *const mxr_remote_t,
1866 uid: mxr_uid_t,
1867 out: *mut mxr_osd_sync_report_t,
1868) -> mxr_result_t {
1869 // SAFETY: the caller guarantees a live handle or null.
1870 let handle = unsafe { remote.as_ref() };
1871 with(handle, |r| {
1872 let value = r
1873 .remote
1874 .osd_sync_report(uid.into())
1875 .map(|s: OsdSyncReport| {
1876 let mut out = mxr_osd_sync_report_t {
1877 seq: s.seq,
1878 state: s.state.to_wire(),
1879 written: s.written,
1880 refused: s.refused,
1881 reason: [0; MXR_OSD_SYNC_REASON_LEN],
1882 };
1883 put_str(&mut out.reason, &s.reason);
1884 out
1885 });
1886 // SAFETY: the caller guarantees a writable struct or null.
1887 unsafe { fill(r, uid, out, "overlay sync report", value) }
1888 })
1889}
1890
1891/// Bytes in `mxr_osd_message_t.text`, its NUL included.
1892pub const MXR_OSD_MESSAGE_TEXT_LEN: usize = 256;
1893
1894const _: () = assert!(MXR_OSD_MESSAGE_TEXT_LEN == OSD_MESSAGE_TEXT_MAX + 1);
1895
1896/// The message on a unit's output, as the unit last reported it.
1897#[repr(C)]
1898#[derive(Clone, Copy)]
1899pub struct mxr_osd_message_t {
1900 /// `MXR_OSD_MESSAGE_*` bits. Without `MXR_OSD_MESSAGE_SET` the other
1901 /// fields are empty.
1902 pub flags: u8,
1903 /// The logo shown with it: 0 the built-in logo, -1 none, 1 to 5 a slot.
1904 pub logo: i8,
1905 /// `MXR_OSD_MESSAGE_PRIORITY_*`.
1906 pub priority: u8,
1907 /// Its markup.
1908 pub text: [c_char; MXR_OSD_MESSAGE_TEXT_LEN],
1909}
1910
1911/// Fills `out` with the message on a unit's output, as the unit last
1912/// reported it to this client.
1913///
1914/// Reports `MXR_ERR_NOT_REPORTED` until it has. A unit reports it only when
1915/// asked with `mxr_request_osd_message()` or written to, and a change is
1916/// announced through `on_device_update`.
1917///
1918/// # Safety
1919///
1920/// `remote` is null or a live handle, and `out` points at a writable
1921/// `mxr_osd_message_t`.
1922#[no_mangle]
1923pub unsafe extern "C" fn mxr_osd_message(
1924 remote: *const mxr_remote_t,
1925 uid: mxr_uid_t,
1926 out: *mut mxr_osd_message_t,
1927) -> mxr_result_t {
1928 // SAFETY: the caller guarantees a live handle or null.
1929 let handle = unsafe { remote.as_ref() };
1930 with(handle, |r| {
1931 let value = r.remote.osd_message(uid.into()).map(|m: OsdMessage| {
1932 let mut out = mxr_osd_message_t {
1933 flags: m.flags.bits(),
1934 logo: m.logo,
1935 priority: m.priority.to_wire(),
1936 text: [0; MXR_OSD_MESSAGE_TEXT_LEN],
1937 };
1938 put_str(&mut out.text, &m.text);
1939 out
1940 });
1941 // SAFETY: the caller guarantees a writable struct or null.
1942 unsafe { fill(r, uid, out, "output message", value) }
1943 })
1944}
1945
1946/// The power save levels a unit reports what it offers at.
1947pub const MXR_UNIT_POWER_SAVE_LEVELS: usize = 4;
1948
1949const _: () = assert!(MXR_UNIT_POWER_SAVE_LEVELS == mx_remote::UNIT_POWER_SAVE_LEVELS);
1950
1951/// What a unit offers at each power save level.
1952#[repr(C)]
1953#[derive(Clone, Copy)]
1954pub struct mxr_unit_power_save_t {
1955 /// `MXR_UNIT_POWER_SAVE_*` bits of each level, `level[i]` for
1956 /// `MXR_V2IP_POWER_SAVE_LEVEL_*` i + 1. All zero for a unit that predates
1957 /// them.
1958 pub level: [u8; MXR_UNIT_POWER_SAVE_LEVELS],
1959}
1960
1961/// Fills `out` with what a unit offers at each power save level, as its last
1962/// status report carried it.
1963///
1964/// Reports `MXR_ERR_NOT_REPORTED` until the unit has reported its status.
1965///
1966/// # Safety
1967///
1968/// `remote` is null or a live handle, and `out` points at a writable
1969/// `mxr_unit_power_save_t`.
1970#[no_mangle]
1971pub unsafe extern "C" fn mxr_unit_power_save(
1972 remote: *const mxr_remote_t,
1973 uid: mxr_uid_t,
1974 out: *mut mxr_unit_power_save_t,
1975) -> mxr_result_t {
1976 // SAFETY: the caller guarantees a live handle or null.
1977 let handle = unsafe { remote.as_ref() };
1978 with(handle, |r| {
1979 let value = r
1980 .remote
1981 .unit_status(uid.into())
1982 .map(|s| mxr_unit_power_save_t {
1983 level: s.power_save.map(|p| p.bits()),
1984 });
1985 // SAFETY: the caller guarantees a writable struct or null.
1986 unsafe { fill(r, uid, out, "unit status", value) }
1987 })
1988}
1989
1990/// A OneIP device's PTP state, as it reported it about itself.
1991#[repr(C)]
1992#[derive(Clone, Copy)]
1993pub struct mxr_v2ip_ptp_t {
1994 /// `MXR_V2IP_PTP_*` bits.
1995 pub flags: u8,
1996 /// `MXR_V2IP_PTP_MODE_*` the video processor runs.
1997 pub mode: u8,
1998 /// The domain the video processor runs in.
1999 pub domain: u8,
2000 /// The IEEE 1588 portState.
2001 pub port_state: u8,
2002 /// `MXR_V2IP_PTP_LOCK_*`.
2003 pub lock: u8,
2004 /// The device's own clock identity.
2005 pub clock_identity: [u8; 8],
2006 /// The clock identity of the grandmaster the device runs on, all zero
2007 /// while it runs on none.
2008 pub grandmaster_identity: [u8; 8],
2009}
2010
2011/// Fills `out` with the PTP state a OneIP device last reported about itself.
2012///
2013/// Reports `MXR_ERR_NOT_REPORTED` until the device has reported one, and while
2014/// it has none to report: no PTP on its video processor, or in power save. A
2015/// change is announced through `on_device_update`.
2016///
2017/// # Safety
2018///
2019/// `remote` is null or a live handle, and `out` points at a writable
2020/// `mxr_v2ip_ptp_t`.
2021#[no_mangle]
2022pub unsafe extern "C" fn mxr_v2ip_ptp(
2023 remote: *const mxr_remote_t,
2024 uid: mxr_uid_t,
2025 out: *mut mxr_v2ip_ptp_t,
2026) -> mxr_result_t {
2027 // SAFETY: the caller guarantees a live handle or null.
2028 let handle = unsafe { remote.as_ref() };
2029 with(handle, |r| {
2030 let value = r
2031 .remote
2032 .v2ip_ptp(uid.into())
2033 .map(|t: V2ipPtpState| mxr_v2ip_ptp_t {
2034 flags: t.flags.bits(),
2035 mode: t.mode.to_wire(),
2036 domain: t.domain,
2037 port_state: t.port_state,
2038 lock: t.lock.to_wire(),
2039 clock_identity: t.clock_identity,
2040 grandmaster_identity: t.grandmaster_identity,
2041 });
2042 // SAFETY: the caller guarantees a writable struct or null.
2043 unsafe { fill(r, uid, out, "PTP state", value) }
2044 })
2045}
2046
2047/// A OneIP sink's test tone.
2048#[repr(C)]
2049#[derive(Clone, Copy)]
2050pub struct mxr_v2ip_test_tone_t {
2051 /// `MXR_V2IP_TONE_MODE_*`.
2052 pub mode: u8,
2053 /// The level, in dBFS, `MXR_V2IP_TONE_LEVEL_MIN` to 0.
2054 pub level: i8,
2055 /// The channels it plays on, 1 to `MXR_V2IP_TONE_CHANNELS_MAX`; a line-up
2056 /// tone needs 2 or more.
2057 pub channels: u8,
2058 /// The frequency, `MXR_V2IP_TONE_FREQ_MIN` to `MXR_V2IP_TONE_FREQ_MAX` Hz.
2059 pub freq: u16,
2060 /// The sample rate: 44100, 48000 or 96000 Hz.
2061 pub rate: u32,
2062}
2063
2064/// A OneIP sink's lip-sync flash: a pattern frame is marked every `period`,
2065/// the frame `lead` after a mark flashes white, and a beep tone starts
2066/// `offset` sample periods after it.
2067#[repr(C)]
2068#[derive(Clone, Copy)]
2069pub struct mxr_v2ip_test_sync_t {
2070 /// Every this many pattern frames is marked, 0 for none.
2071 pub period: u16,
2072 /// The frame this many after a mark flashes; below `period`, or 0 without
2073 /// one.
2074 pub lead: u16,
2075 /// Sample periods between a mark and the beep, at most
2076 /// `MXR_V2IP_SYNC_OFFSET_MAX`.
2077 pub offset: u32,
2078 /// How long the beep lasts, `MXR_V2IP_SYNC_BEEP_MS_MIN` to
2079 /// `MXR_V2IP_SYNC_BEEP_MS_MAX` ms.
2080 pub beep_ms: u16,
2081}
2082
2083/// The test card a OneIP sink last reported. The counters run free and wrap.
2084#[repr(C)]
2085#[derive(Clone, Copy)]
2086pub struct mxr_v2ip_testcard_t {
2087 /// `MXR_V2IP_TESTCARD_*` bits.
2088 pub flags: u8,
2089 /// `MXR_V2IP_TEST_PATTERN_*`.
2090 pub pattern: u8,
2091 /// The colour of a flat pattern, `0xRRGGBB`.
2092 pub colour: u32,
2093 /// The tone.
2094 pub tone: mxr_v2ip_test_tone_t,
2095 /// The lip-sync flash.
2096 pub sync: mxr_v2ip_test_sync_t,
2097 /// Pattern frames sent.
2098 pub frames: u32,
2099 /// Sample periods since the tone started.
2100 pub periods: u32,
2101 /// Lip-sync marks the tone has seen.
2102 pub marks: u32,
2103}
2104
2105/// The lowest test tone frequency, in Hz.
2106pub const MXR_V2IP_TONE_FREQ_MIN: u16 = 20;
2107/// The highest test tone frequency, in Hz.
2108pub const MXR_V2IP_TONE_FREQ_MAX: u16 = 20000;
2109/// The quietest test tone level, in dBFS.
2110pub const MXR_V2IP_TONE_LEVEL_MIN: i8 = -60;
2111/// The most channels a test tone plays on.
2112pub const MXR_V2IP_TONE_CHANNELS_MAX: u8 = 8;
2113/// The largest lip-sync beep offset, in sample periods.
2114pub const MXR_V2IP_SYNC_OFFSET_MAX: u32 = 0xFFFFFF;
2115/// The shortest lip-sync beep, in milliseconds.
2116pub const MXR_V2IP_SYNC_BEEP_MS_MIN: u16 = 1;
2117/// The longest lip-sync beep, in milliseconds.
2118pub const MXR_V2IP_SYNC_BEEP_MS_MAX: u16 = 10000;
2119
2120const _: () = assert!(MXR_V2IP_TONE_FREQ_MIN == mx_remote::V2IP_TONE_FREQ_MIN);
2121const _: () = assert!(MXR_V2IP_TONE_FREQ_MAX == mx_remote::V2IP_TONE_FREQ_MAX);
2122const _: () = assert!(MXR_V2IP_TONE_LEVEL_MIN == mx_remote::V2IP_TONE_LEVEL_MIN);
2123const _: () = assert!(MXR_V2IP_TONE_CHANNELS_MAX == mx_remote::V2IP_TONE_CHANNELS_MAX);
2124const _: () = assert!(MXR_V2IP_SYNC_OFFSET_MAX == mx_remote::V2IP_SYNC_OFFSET_MAX);
2125const _: () = assert!(MXR_V2IP_SYNC_BEEP_MS_MIN == mx_remote::V2IP_SYNC_BEEP_MS_MIN);
2126const _: () = assert!(MXR_V2IP_SYNC_BEEP_MS_MAX == mx_remote::V2IP_SYNC_BEEP_MS_MAX);
2127
2128impl From<V2ipTestTone> for mxr_v2ip_test_tone_t {
2129 fn from(t: V2ipTestTone) -> Self {
2130 Self {
2131 mode: t.mode.to_wire(),
2132 level: t.level,
2133 channels: t.channels,
2134 freq: t.freq,
2135 rate: t.rate,
2136 }
2137 }
2138}
2139
2140impl From<mxr_v2ip_test_tone_t> for V2ipTestTone {
2141 fn from(t: mxr_v2ip_test_tone_t) -> Self {
2142 Self {
2143 mode: V2ipToneMode::from_wire(t.mode),
2144 freq: t.freq,
2145 level: t.level,
2146 channels: t.channels,
2147 rate: t.rate,
2148 }
2149 }
2150}
2151
2152impl From<V2ipTestSync> for mxr_v2ip_test_sync_t {
2153 fn from(s: V2ipTestSync) -> Self {
2154 Self {
2155 period: s.period,
2156 lead: s.lead,
2157 offset: s.offset,
2158 beep_ms: s.beep_ms,
2159 }
2160 }
2161}
2162
2163impl From<mxr_v2ip_test_sync_t> for V2ipTestSync {
2164 fn from(s: mxr_v2ip_test_sync_t) -> Self {
2165 Self {
2166 period: s.period,
2167 lead: s.lead,
2168 offset: s.offset,
2169 beep_ms: s.beep_ms,
2170 }
2171 }
2172}
2173
2174/// Fills `out` with the test card a OneIP sink last reported.
2175///
2176/// Reports `MXR_ERR_NOT_REPORTED` until the sink has answered
2177/// `mxr_request_v2ip_testcard()` or a change. A change is announced through
2178/// `on_device_update`.
2179///
2180/// # Safety
2181///
2182/// `remote` is null or a live handle, and `out` points at a writable
2183/// `mxr_v2ip_testcard_t`.
2184#[no_mangle]
2185pub unsafe extern "C" fn mxr_v2ip_testcard(
2186 remote: *const mxr_remote_t,
2187 uid: mxr_uid_t,
2188 out: *mut mxr_v2ip_testcard_t,
2189) -> mxr_result_t {
2190 // SAFETY: the caller guarantees a live handle or null.
2191 let handle = unsafe { remote.as_ref() };
2192 with(handle, |r| {
2193 let value = r
2194 .remote
2195 .v2ip_testcard(uid.into())
2196 .map(|t: V2ipTestcard| mxr_v2ip_testcard_t {
2197 flags: t.flags.bits(),
2198 pattern: t.pattern.to_wire(),
2199 colour: t.colour,
2200 tone: t.tone.into(),
2201 sync: t.sync.into(),
2202 frames: t.frames,
2203 periods: t.periods,
2204 marks: t.marks,
2205 });
2206 // SAFETY: the caller guarantees a writable struct or null.
2207 unsafe { fill(r, uid, out, "test card", value) }
2208 })
2209}
2210
2211/// Fills `out` with the window a sink is told to show.
2212///
2213/// # Safety
2214///
2215/// `remote` is null or a live handle, and `out` points at a writable
2216/// [`mxr_tiling_config_t`].
2217#[no_mangle]
2218pub unsafe extern "C" fn mxr_v2ip_tiling(
2219 remote: *const mxr_remote_t,
2220 uid: mxr_uid_t,
2221 out: *mut mxr_tiling_config_t,
2222) -> mxr_result_t {
2223 // SAFETY: the caller guarantees a live handle or null.
2224 let handle = unsafe { remote.as_ref() };
2225 with(handle, |r| {
2226 let value =
2227 r.remote
2228 .v2ip_tiling(uid.into())
2229 .map(|t: V2ipTilingConfig| mxr_tiling_config_t {
2230 target: t.target.into(),
2231 pos_x: t.pos_x,
2232 pos_y: t.pos_y,
2233 width: t.width,
2234 height: t.height,
2235 });
2236 // SAFETY: the caller guarantees a writable mxr_tiling_config_t or null.
2237 unsafe { fill(r, uid, out, "window", value) }
2238 })
2239}
2240
2241/// Fills `out` with what a multiviewer reports about itself.
2242///
2243/// # Safety
2244///
2245/// `remote` is null or a live handle, and `out` points at a writable
2246/// [`mxr_multiviewer_status_t`].
2247#[no_mangle]
2248pub unsafe extern "C" fn mxr_multiviewer_status(
2249 remote: *const mxr_remote_t,
2250 uid: mxr_uid_t,
2251 out: *mut mxr_multiviewer_status_t,
2252) -> mxr_result_t {
2253 // SAFETY: the caller guarantees a live handle or null.
2254 let handle = unsafe { remote.as_ref() };
2255 with(handle, |r| {
2256 let value = r.remote.multiviewer_status(uid.into()).map(multiviewer_of);
2257 // SAFETY: the caller guarantees a writable mxr_multiviewer_status_t or null.
2258 unsafe { fill(r, uid, out, "multiviewer status", value) }
2259 })
2260}
2261
2262/// Copies a multiviewer's report into the C shape.
2263fn multiviewer_of(s: MultiviewerStatus) -> mxr_multiviewer_status_t {
2264 let mut out = mxr_multiviewer_status_t {
2265 uid: s.uid.into(),
2266 mappings: [mxr_uid_t::default(); MXR_MULTIVIEWER_INPUTS],
2267 mcu_version: [0; MXR_NAME_LEN],
2268 scaler_version: [0; MXR_NAME_LEN],
2269 hw_view_mode: s.hw_view_mode,
2270 view_mode: s.view_mode.to_wire(),
2271 pip_position: s.pip_position.to_wire(),
2272 pip_size: s.pip_size.to_wire(),
2273 output_mode: s.output_mode.to_wire(),
2274 hdcp_mode: s.hdcp_mode.to_wire(),
2275 output_itc: s.output_itc.to_wire(),
2276 edid_template: s.edid_template.to_wire(),
2277 screen_mode: s.screen_mode,
2278 aspect_ratio: s.aspect_ratio.to_wire(),
2279 auto_switch: s.auto_switch.to_wire(),
2280 audio_source: s.audio_source.to_wire(),
2281 has_audio_volume: s.audio_volume.is_some(),
2282 audio_volume: s.audio_volume.unwrap_or(0),
2283 audio_muted: s.audio_muted.to_wire(),
2284 video_sources: [0; MXR_MULTIVIEWER_INPUTS],
2285 remote_control: s.remote_control.to_wire(),
2286 };
2287 for (slot, uid) in out.mappings.iter_mut().zip(s.mappings) {
2288 *slot = uid.into();
2289 }
2290 for (slot, source) in out.video_sources.iter_mut().zip(s.video_sources) {
2291 *slot = source.to_wire();
2292 }
2293 put_str(&mut out.mcu_version, &s.mcu_version);
2294 put_str(&mut out.scaler_version, &s.scaler_version);
2295 out
2296}
2297
2298/// Fills `out` with a ProAmp8's Dolby settings.
2299///
2300/// # Safety
2301///
2302/// `remote` is null or a live handle, and `out` points at a writable
2303/// [`mxr_dolby_settings_t`].
2304#[no_mangle]
2305pub unsafe extern "C" fn mxr_dolby_settings(
2306 remote: *const mxr_remote_t,
2307 uid: mxr_uid_t,
2308 out: *mut mxr_dolby_settings_t,
2309) -> mxr_result_t {
2310 // SAFETY: the caller guarantees a live handle or null.
2311 let handle = unsafe { remote.as_ref() };
2312 with(handle, |r| {
2313 let value = r
2314 .remote
2315 .dolby_settings(uid.into())
2316 .map(mxr_dolby_settings_t::from);
2317 // SAFETY: the caller guarantees a writable mxr_dolby_settings_t or null.
2318 unsafe { fill(r, uid, out, "Dolby settings", value) }
2319 })
2320}
2321
2322/// Fills `out` with a source bay's remote-control configuration.
2323///
2324/// # Safety
2325///
2326/// `remote` is null or a live handle, and `out` points at a writable
2327/// [`mxr_rc_settings_t`].
2328#[no_mangle]
2329pub unsafe extern "C" fn mxr_rc_settings(
2330 remote: *const mxr_remote_t,
2331 uid: mxr_uid_t,
2332 out: *mut mxr_rc_settings_t,
2333) -> mxr_result_t {
2334 // SAFETY: the caller guarantees a live handle or null.
2335 let handle = unsafe { remote.as_ref() };
2336 with(handle, |r| {
2337 let value = r.remote.rc_settings(uid.into()).map(rc_settings_of);
2338 // SAFETY: the caller guarantees a writable mxr_rc_settings_t or null.
2339 unsafe { fill(r, uid, out, "remote-control configuration", value) }
2340 })
2341}
2342
2343/// Copies a remote-control configuration into the C shape.
2344fn rc_settings_of(s: RcSettings) -> mxr_rc_settings_t {
2345 let mut out = mxr_rc_settings_t {
2346 target: s.target.into(),
2347 rc_target: s.rc_target,
2348 ip: [0; MXR_IP_STRING_LEN],
2349 cec_enabled: s.cec_enabled,
2350 cec_auto_on: s.cec_auto_on,
2351 forward_rc: s.forward_rc,
2352 forward_ir: s.forward_ir,
2353 rc_status: s.rc_status,
2354 status_name: [0; MXR_NAME_LEN],
2355 };
2356 put_ip(&mut out.ip, s.ip);
2357 put_str(&mut out.status_name, &s.status_name);
2358 out
2359}
2360
2361/// Writes the streams a device's source bays advertise, and returns how many
2362/// there are.
2363///
2364/// Returns the full count even when it exceeds `cap`, so calling with `cap`
2365/// zero sizes the buffer.
2366///
2367/// # Safety
2368///
2369/// `remote` is null or a live handle, and `out` is null or points at `cap`
2370/// writable [`mxr_stream_sources_t`].
2371#[no_mangle]
2372pub unsafe extern "C" fn mxr_v2ip_sources(
2373 remote: *const mxr_remote_t,
2374 uid: mxr_uid_t,
2375 out: *mut mxr_stream_sources_t,
2376 cap: usize,
2377) -> usize {
2378 guard(0, || {
2379 // SAFETY: the caller guarantees a live handle or null.
2380 let Some(r) = (unsafe { remote.as_ref() }) else {
2381 return no_handle();
2382 };
2383 let Some(sources) = r.remote.v2ip_sources(uid.into()) else {
2384 not_reported(r, uid, "OneIP stream sources");
2385 return 0;
2386 };
2387 // SAFETY: the caller guarantees cap writable elements at out.
2388 unsafe { copy_into(&sources, out, cap) }
2389 })
2390}
2391
2392/// Writes a device's network ports, and returns how many there are.
2393///
2394/// # Safety
2395///
2396/// `remote` is null or a live handle, and `out` is null or points at `cap`
2397/// writable [`mxr_network_port_t`].
2398#[no_mangle]
2399pub unsafe extern "C" fn mxr_network_status(
2400 remote: *const mxr_remote_t,
2401 uid: mxr_uid_t,
2402 out: *mut mxr_network_port_t,
2403 cap: usize,
2404) -> usize {
2405 guard(0, || {
2406 // SAFETY: the caller guarantees a live handle or null.
2407 let Some(r) = (unsafe { remote.as_ref() }) else {
2408 return no_handle();
2409 };
2410 let ports: Vec<mxr_network_port_t> = r
2411 .remote
2412 .network_status(uid.into())
2413 .iter()
2414 .map(port_of)
2415 .collect();
2416 // SAFETY: the caller guarantees cap writable elements at out.
2417 unsafe { copy_into(&ports, out, cap) }
2418 })
2419}
2420
2421/// Copies one port report into the C shape.
2422fn port_of(p: &NetworkPortStatus) -> mxr_network_port_t {
2423 let errors = p.errors.unwrap_or_default();
2424 let mut out = mxr_network_port_t {
2425 port: p.port,
2426 name: [0; MXR_NAME_LEN],
2427 link_speed: p.link_speed.to_wire(),
2428 link_full_duplex: p.link_full_duplex,
2429 ip: [0; MXR_IP_STRING_LEN],
2430 querier: [0; MXR_IP_STRING_LEN],
2431 has_mac_address: p.mac_address.is_some(),
2432 mac_address: p.mac_address.unwrap_or_default().0,
2433 has_errors: p.errors.is_some(),
2434 in_error: errors.in_error,
2435 in_fcs_error: errors.in_fcs_error,
2436 in_collision: errors.in_collision,
2437 out_deferred: errors.out_deferred,
2438 out_excessive: errors.out_excessive,
2439 polarity_error: errors.polarity_error,
2440 skew_warning: errors.skew_warning,
2441 length_warning: errors.length_warning,
2442 has_vct_status: p.vct_status.is_some(),
2443 vct_warning: [false; MXR_UTP_PAIRS],
2444 cable_status_count: p.cable_status.len().min(MXR_UTP_PAIRS),
2445 cable_status: [mxr_cable_status_t {
2446 polarity: false,
2447 pair: 0,
2448 skew: 0,
2449 length: 0,
2450 }; MXR_UTP_PAIRS],
2451 };
2452 put_str(&mut out.name, &p.name);
2453 put_ip(&mut out.ip, p.ip);
2454 put_ip(&mut out.querier, p.querier);
2455 if let Some(vct) = p.vct_status {
2456 for (slot, status) in out.vct_warning.iter_mut().zip(vct) {
2457 *slot = status == VctStatus::Warning;
2458 }
2459 }
2460 for (slot, cable) in out.cable_status.iter_mut().zip(&p.cable_status) {
2461 *slot = (*cable).into();
2462 }
2463 out
2464}
2465
2466/// Writes a device's view of the mesh topology, and returns how many entries
2467/// there are.
2468///
2469/// # Safety
2470///
2471/// `remote` is null or a live handle, and `out` is null or points at `cap`
2472/// writable [`mxr_topology_entry_t`].
2473#[no_mangle]
2474pub unsafe extern "C" fn mxr_topology(
2475 remote: *const mxr_remote_t,
2476 uid: mxr_uid_t,
2477 out: *mut mxr_topology_entry_t,
2478 cap: usize,
2479) -> usize {
2480 guard(0, || {
2481 // SAFETY: the caller guarantees a live handle or null.
2482 let Some(r) = (unsafe { remote.as_ref() }) else {
2483 return no_handle();
2484 };
2485 let topology = r.remote.topology(uid.into());
2486 // SAFETY: the caller guarantees cap writable elements at out.
2487 unsafe { copy_into(&topology, out, cap) }
2488 })
2489}
2490
2491/// Writes the firmware versions a device reports, and returns how many there
2492/// are.
2493///
2494/// # Safety
2495///
2496/// `remote` is null or a live handle, and `out` is null or points at `cap`
2497/// writable [`mxr_firmware_version_t`].
2498#[no_mangle]
2499pub unsafe extern "C" fn mxr_device_firmware(
2500 remote: *const mxr_remote_t,
2501 uid: mxr_uid_t,
2502 out: *mut mxr_firmware_version_t,
2503 cap: usize,
2504) -> usize {
2505 guard(0, || {
2506 // SAFETY: the caller guarantees a live handle or null.
2507 let Some(r) = (unsafe { remote.as_ref() }) else {
2508 return no_handle();
2509 };
2510 let versions: Vec<mxr_firmware_version_t> = r
2511 .remote
2512 .firmware(uid.into())
2513 .iter()
2514 .map(|(_, v)| firmware_of(v))
2515 .collect();
2516 // SAFETY: the caller guarantees cap writable elements at out.
2517 unsafe { copy_into(&versions, out, cap) }
2518 })
2519}
2520
2521/// Copies one firmware report into the C shape.
2522fn firmware_of(v: &FirmwareVersion) -> mxr_firmware_version_t {
2523 let mut out = mxr_firmware_version_t {
2524 firmware_type: v.firmware_type.to_wire(),
2525 timestamp: v.timestamp,
2526 hash: v.hash,
2527 version: [0; MXR_VERSION_LEN],
2528 };
2529 put_str(&mut out.version, &v.version);
2530 out
2531}
2532
2533/// Writes a device's audio endpoints, in the order it reported them, and
2534/// returns how many there are.
2535///
2536/// # Safety
2537///
2538/// `remote` is null or a live handle, and `out` is null or points at `cap`
2539/// writable [`mxr_audio_endpoint_t`].
2540#[no_mangle]
2541pub unsafe extern "C" fn mxr_audio_endpoints(
2542 remote: *const mxr_remote_t,
2543 uid: mxr_uid_t,
2544 out: *mut mxr_audio_endpoint_t,
2545 cap: usize,
2546) -> usize {
2547 guard(0, || {
2548 // SAFETY: the caller guarantees a live handle or null.
2549 let Some(r) = (unsafe { remote.as_ref() }) else {
2550 return no_handle();
2551 };
2552 let Some(endpoints) = r.remote.audio_endpoints(uid.into()) else {
2553 not_reported(r, uid, "audio endpoints");
2554 return 0;
2555 };
2556 let list: Vec<mxr_audio_endpoint_t> = endpoints.list().map(Into::into).collect();
2557 // SAFETY: the caller guarantees cap writable elements at out.
2558 unsafe { copy_into(&list, out, cap) }
2559 })
2560}
2561
2562/// Fills `out` with the state an audio endpoint reports, on the bits of its
2563/// features: `MXR_AUDIO_MUTE` while it is muted, `MXR_AUDIO_TRIGGER` while
2564/// its trigger is active, and `MXR_AUDIO_AUDIO_LOCK` while its source is
2565/// locked. Beside `mxr_audio_endpoint_t` rather than in it, so that struct
2566/// keeps its size.
2567///
2568/// Reports `MXR_ERR_NOT_REPORTED` until the device has reported its audio
2569/// endpoints, and `MXR_ERR_NOT_FOUND` for an endpoint it did not report.
2570///
2571/// # Safety
2572///
2573/// `remote` is null or a live handle, and `out` points at a writable
2574/// `uint32_t`.
2575#[no_mangle]
2576pub unsafe extern "C" fn mxr_audio_endpoint_status(
2577 remote: *const mxr_remote_t,
2578 uid: mxr_uid_t,
2579 endpoint: u8,
2580 out: *mut u32,
2581) -> mxr_result_t {
2582 // SAFETY: the caller guarantees a live handle or null.
2583 let handle = unsafe { remote.as_ref() };
2584 with(handle, |r| {
2585 if out.is_null() {
2586 return null_out("audio endpoint status");
2587 }
2588 let Some(endpoints) = r.remote.audio_endpoints(uid.into()) else {
2589 return not_reported(r, uid, "audio endpoints");
2590 };
2591 let Some(status) = endpoints.status(endpoint) else {
2592 return fail(
2593 mxr_result_t::MXR_ERR_NOT_FOUND,
2594 "the device reported no such audio endpoint",
2595 );
2596 };
2597 // SAFETY: the caller guarantees a writable uint32_t or null.
2598 unsafe { fill(r, uid, out, "audio endpoint status", Some(status.bits())) }
2599 })
2600}
2601
2602/// Writes the endpoints hanging off one audio endpoint, and returns how many
2603/// there are.
2604///
2605/// # Safety
2606///
2607/// `remote` is null or a live handle, and `out` is null or points at `cap`
2608/// writable bytes.
2609#[no_mangle]
2610pub unsafe extern "C" fn mxr_audio_endpoint_children(
2611 remote: *const mxr_remote_t,
2612 uid: mxr_uid_t,
2613 endpoint: u8,
2614 out: *mut u8,
2615 cap: usize,
2616) -> usize {
2617 guard(0, || {
2618 // SAFETY: the caller guarantees a live handle or null.
2619 let Some(r) = (unsafe { remote.as_ref() }) else {
2620 return no_handle();
2621 };
2622 let children = match r.remote.audio_endpoints(uid.into()) {
2623 Some(endpoints) => match endpoints.get(endpoint) {
2624 Some(e) => e.children.clone(),
2625 None => {
2626 fail(
2627 mxr_result_t::MXR_ERR_NOT_FOUND,
2628 &format!("the device has no audio endpoint {endpoint}"),
2629 );
2630 return 0;
2631 }
2632 },
2633 None => {
2634 not_reported(r, uid, "audio endpoints");
2635 return 0;
2636 }
2637 };
2638 // SAFETY: the caller guarantees cap writable bytes at out.
2639 unsafe { copy_into(&children, out, cap) }
2640 })
2641}
2642
2643/// Reports a null handle from a call whose answer is a count.
2644fn no_handle() -> usize {
2645 fail(
2646 mxr_result_t::MXR_ERR_INVALID_ARGUMENT,
2647 "the client handle is null",
2648 );
2649 0
2650}
2651
2652#[cfg(test)]
2653mod tests {
2654 use std::ffi::CStr;
2655 use std::net::Ipv4Addr;
2656
2657 use mx_remote::{V2ipDownmix, V2ipDownmixLevel, V2ipDownmixMode, V2ipPrivacyStream};
2658
2659 use super::{mxr_v2ip_downmix_t, privacy_stream};
2660
2661 /// Each field of a downmix lands in its own C field and back, every one of
2662 /// them distinct.
2663 #[test]
2664 fn a_downmix_keeps_every_field() {
2665 let d = V2ipDownmix {
2666 mode: V2ipDownmixMode::ALWAYS,
2667 level: V2ipDownmixLevel::UNSCALED,
2668 lfe: 70,
2669 state: 3,
2670 channels: 6,
2671 };
2672 let c = mxr_v2ip_downmix_t::from(d);
2673 assert_eq!(
2674 (c.mode, c.level, c.lfe, c.state, c.channels),
2675 (2, 1, 70, 3, 6)
2676 );
2677 assert_eq!(V2ipDownmix::from(c), d);
2678 }
2679
2680 /// Each field lands in its own C field, every one of them distinct.
2681 #[test]
2682 fn a_privacy_stream_keeps_every_field() {
2683 let s = V2ipPrivacyStream {
2684 group: Ipv4Addr::new(239, 1, 2, 3),
2685 port: 0x1388,
2686 version: 7,
2687 on: true,
2688 full_id: 3,
2689 short_id: 4,
2690 iv: [0x10; 8],
2691 key_generator: [0x20; 16],
2692 key_id: [0x30; 8],
2693 key_version: 0x0102_0304,
2694 };
2695 let c = privacy_stream(&s);
2696 // SAFETY: put_ip writes a NUL-terminated string into the array.
2697 let group = unsafe { CStr::from_ptr(c.group.as_ptr()) };
2698 assert_eq!(group.to_str(), Ok("239.1.2.3"));
2699 assert_eq!((c.port, c.version, c.on), (0x1388, 7, true));
2700 assert_eq!((c.full_id, c.short_id), (3, 4));
2701 assert_eq!(
2702 (c.iv, c.key_generator, c.key_id),
2703 (s.iv, s.key_generator, s.key_id)
2704 );
2705 assert_eq!(c.key_version, 0x0102_0304);
2706 }
2707}