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}