Skip to main content

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