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, V2ipPrivacy, V2ipPrivacyStream, V2ipPtpMessages,
25 V2ipPtpState, V2ipRxStats, V2ipSinkSdp, V2ipStreamSource, V2ipStreamSources, V2ipTestSync,
26 V2ipTestTone, V2ipTestcard, V2ipTilingConfig, V2ipToneMode, V2ipTxStats, V2ipVlan, VctStatus,
27 MULTIVIEWER_INPUTS, 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/// How one of a V2IP source unit's streams is encrypted: the values of its
1569/// session description's `a=privacy` and `a=extmap` attributes. The key
1570/// itself never travels: every unit of a mesh holds the same one. This
1571/// library holds no key and decrypts nothing.
1572#[repr(C)]
1573#[derive(Clone, Copy)]
1574pub struct mxr_v2ip_privacy_stream_t {
1575 /// The stream's destination address, as a dotted quad.
1576 pub group: [c_char; MXR_IP_STRING_LEN],
1577 /// The stream's UDP port.
1578 pub port: u16,
1579 /// Steps whenever the parameters change.
1580 pub version: u8,
1581 /// Whether the stream is encrypted. Without it the stream goes out in the
1582 /// clear, and the fields below mean nothing.
1583 pub on: bool,
1584 /// The RTP header extension id of the full IV counter.
1585 pub full_id: u8,
1586 /// The RTP header extension id of the short IV counter.
1587 pub short_id: u8,
1588 /// The initialisation vector.
1589 pub iv: [u8; 8],
1590 /// The key generator.
1591 pub key_generator: [u8; 16],
1592 /// Names the mesh's key. A receiver holding another drops the stream.
1593 pub key_id: [u8; 8],
1594 /// The key version.
1595 pub key_version: u32,
1596}
1597
1598/// How a V2IP source unit's streams are encrypted, as it last reported it.
1599#[repr(C)]
1600#[derive(Clone, Copy)]
1601pub struct mxr_v2ip_privacy_t {
1602 /// The video stream.
1603 pub video: mxr_v2ip_privacy_stream_t,
1604 /// The audio stream.
1605 pub audio: mxr_v2ip_privacy_stream_t,
1606 /// The ancillary data stream.
1607 pub anc: mxr_v2ip_privacy_stream_t,
1608}
1609
1610fn privacy_stream(s: &V2ipPrivacyStream) -> mxr_v2ip_privacy_stream_t {
1611 let mut out = mxr_v2ip_privacy_stream_t {
1612 group: [0; MXR_IP_STRING_LEN],
1613 port: s.port,
1614 version: s.version,
1615 on: s.on,
1616 full_id: s.full_id,
1617 short_id: s.short_id,
1618 iv: s.iv,
1619 key_generator: s.key_generator,
1620 key_id: s.key_id,
1621 key_version: s.key_version,
1622 };
1623 put_ip(&mut out.group, Some(s.group));
1624 out
1625}
1626
1627/// Fills `out` with how a V2IP source unit's streams are encrypted, as it
1628/// last reported it.
1629///
1630/// Reports `MXR_ERR_NOT_REPORTED` until the unit has reported; it does when
1631/// that changes, at least once a minute, and when asked with
1632/// `mxr_request_v2ip_privacy()`. A change is announced through
1633/// `on_device_update`.
1634///
1635/// # Safety
1636///
1637/// `remote` is null or a live handle, and `out` points at a writable
1638/// `mxr_v2ip_privacy_t`.
1639#[no_mangle]
1640pub unsafe extern "C" fn mxr_v2ip_privacy(
1641 remote: *const mxr_remote_t,
1642 uid: mxr_uid_t,
1643 out: *mut mxr_v2ip_privacy_t,
1644) -> mxr_result_t {
1645 // SAFETY: the caller guarantees a live handle or null.
1646 let handle = unsafe { remote.as_ref() };
1647 with(handle, |r| {
1648 let value = r
1649 .remote
1650 .v2ip_privacy(uid.into())
1651 .map(|p: V2ipPrivacy| mxr_v2ip_privacy_t {
1652 video: privacy_stream(&p.video),
1653 audio: privacy_stream(&p.audio),
1654 anc: privacy_stream(&p.anc),
1655 });
1656 // SAFETY: the caller guarantees a writable struct or null.
1657 unsafe { fill(r, uid, out, "stream encryption", value) }
1658 })
1659}
1660
1661/// The logo slots an overlay sync can copy.
1662pub const MXR_OSD_SYNC_SLOTS: usize = 4;
1663/// Bytes in `mxr_osd_sync_report_t.reason`, its NUL included.
1664pub const MXR_OSD_SYNC_REASON_LEN: usize = 65;
1665
1666const _: () = assert!(MXR_OSD_SYNC_SLOTS == OSD_SYNC_SLOTS);
1667
1668/// One logo slot, as an overlay sync describes it.
1669#[repr(C)]
1670#[derive(Clone, Copy, Default)]
1671pub struct mxr_osd_sync_slot_t {
1672 /// The PNG's size in bytes; zero empties the slot.
1673 pub bytes: u32,
1674 /// The PNG's CRC-32.
1675 pub crc32: u32,
1676 /// The size it is shown at, in percent.
1677 pub percent: u8,
1678}
1679
1680/// The overlay sync a management unit last sent.
1681#[repr(C)]
1682#[derive(Clone, Copy)]
1683pub struct mxr_osd_sync_t {
1684 /// The sync's number at its source.
1685 pub seq: u16,
1686 /// The unit it is for, or zero for every unit.
1687 pub target: mxr_uid_t,
1688 /// `MXR_OSD_SYNC_WHAT_*` bits.
1689 pub what: u8,
1690 /// The source's HTTP port, zero meaning 80.
1691 pub http_port: u16,
1692 /// The source's address as a dotted quad, `0.0.0.0` meaning the
1693 /// sender's.
1694 pub source_ip: [c_char; MXR_IP_STRING_LEN],
1695 /// How many settings follow.
1696 pub records: u16,
1697 /// The logo slots, filled when `what` has
1698 /// `MXR_OSD_SYNC_WHAT_LOGO_SLOTS` and zero otherwise. A unit fetches a
1699 /// slot whose size or CRC differs from
1700 /// `http://source_ip:http_port/osd-sync/slotN.png`, N being 1 to 4.
1701 pub slots: [mxr_osd_sync_slot_t; MXR_OSD_SYNC_SLOTS],
1702}
1703
1704/// How a unit last reported getting on with an overlay sync.
1705#[repr(C)]
1706#[derive(Clone, Copy)]
1707pub struct mxr_osd_sync_report_t {
1708 /// The sync it reports on, numbered by the source that sent it.
1709 pub seq: u16,
1710 /// `MXR_OSD_SYNC_STATE_*`.
1711 pub state: u8,
1712 /// The settings it wrote.
1713 pub written: u16,
1714 /// The settings it refused.
1715 pub refused: u16,
1716 /// Why it failed or refused any.
1717 pub reason: [c_char; MXR_OSD_SYNC_REASON_LEN],
1718}
1719
1720/// Fills `out` with the overlay sync a management unit last sent.
1721///
1722/// Reports `MXR_ERR_NOT_REPORTED` until it has sent one. A new sync is
1723/// announced through `on_device_update`.
1724///
1725/// # Safety
1726///
1727/// `remote` is null or a live handle, and `out` points at a writable
1728/// `mxr_osd_sync_t`.
1729#[no_mangle]
1730pub unsafe extern "C" fn mxr_osd_sync(
1731 remote: *const mxr_remote_t,
1732 uid: mxr_uid_t,
1733 out: *mut mxr_osd_sync_t,
1734) -> mxr_result_t {
1735 // SAFETY: the caller guarantees a live handle or null.
1736 let handle = unsafe { remote.as_ref() };
1737 with(handle, |r| {
1738 let value = r.remote.osd_sync(uid.into()).map(|s: OsdSync| {
1739 let mut out = mxr_osd_sync_t {
1740 seq: s.seq,
1741 target: s.target.into(),
1742 what: s.what.bits(),
1743 http_port: s.http_port,
1744 source_ip: [0; MXR_IP_STRING_LEN],
1745 records: s.records,
1746 slots: [mxr_osd_sync_slot_t::default(); MXR_OSD_SYNC_SLOTS],
1747 };
1748 put_ip(&mut out.source_ip, Some(s.source_ip));
1749 for (o, slot) in out.slots.iter_mut().zip(s.slots.iter().flatten()) {
1750 *o = mxr_osd_sync_slot_t {
1751 bytes: slot.bytes,
1752 crc32: slot.crc32,
1753 percent: slot.percent,
1754 };
1755 }
1756 out
1757 });
1758 // SAFETY: the caller guarantees a writable struct or null.
1759 unsafe { fill(r, uid, out, "overlay sync", value) }
1760 })
1761}
1762
1763/// Fills `out` with how a unit last reported getting on with an overlay
1764/// sync. Match it to the sync by `seq` and the sync's source.
1765///
1766/// Reports `MXR_ERR_NOT_REPORTED` until the unit has reported. A change is
1767/// announced through `on_device_update`.
1768///
1769/// # Safety
1770///
1771/// `remote` is null or a live handle, and `out` points at a writable
1772/// `mxr_osd_sync_report_t`.
1773#[no_mangle]
1774pub unsafe extern "C" fn mxr_osd_sync_report(
1775 remote: *const mxr_remote_t,
1776 uid: mxr_uid_t,
1777 out: *mut mxr_osd_sync_report_t,
1778) -> mxr_result_t {
1779 // SAFETY: the caller guarantees a live handle or null.
1780 let handle = unsafe { remote.as_ref() };
1781 with(handle, |r| {
1782 let value = r
1783 .remote
1784 .osd_sync_report(uid.into())
1785 .map(|s: OsdSyncReport| {
1786 let mut out = mxr_osd_sync_report_t {
1787 seq: s.seq,
1788 state: s.state.to_wire(),
1789 written: s.written,
1790 refused: s.refused,
1791 reason: [0; MXR_OSD_SYNC_REASON_LEN],
1792 };
1793 put_str(&mut out.reason, &s.reason);
1794 out
1795 });
1796 // SAFETY: the caller guarantees a writable struct or null.
1797 unsafe { fill(r, uid, out, "overlay sync report", value) }
1798 })
1799}
1800
1801/// The power save levels a unit reports what it offers at.
1802pub const MXR_UNIT_POWER_SAVE_LEVELS: usize = 4;
1803
1804const _: () = assert!(MXR_UNIT_POWER_SAVE_LEVELS == mx_remote::UNIT_POWER_SAVE_LEVELS);
1805
1806/// What a unit offers at each power save level.
1807#[repr(C)]
1808#[derive(Clone, Copy)]
1809pub struct mxr_unit_power_save_t {
1810 /// `MXR_UNIT_POWER_SAVE_*` bits of each level, `level[i]` for
1811 /// `MXR_V2IP_POWER_SAVE_LEVEL_*` i + 1. All zero for a unit that predates
1812 /// them.
1813 pub level: [u8; MXR_UNIT_POWER_SAVE_LEVELS],
1814}
1815
1816/// Fills `out` with what a unit offers at each power save level, as its last
1817/// status report carried it.
1818///
1819/// Reports `MXR_ERR_NOT_REPORTED` until the unit has reported its status.
1820///
1821/// # Safety
1822///
1823/// `remote` is null or a live handle, and `out` points at a writable
1824/// `mxr_unit_power_save_t`.
1825#[no_mangle]
1826pub unsafe extern "C" fn mxr_unit_power_save(
1827 remote: *const mxr_remote_t,
1828 uid: mxr_uid_t,
1829 out: *mut mxr_unit_power_save_t,
1830) -> mxr_result_t {
1831 // SAFETY: the caller guarantees a live handle or null.
1832 let handle = unsafe { remote.as_ref() };
1833 with(handle, |r| {
1834 let value = r
1835 .remote
1836 .unit_status(uid.into())
1837 .map(|s| mxr_unit_power_save_t {
1838 level: s.power_save.map(|p| p.bits()),
1839 });
1840 // SAFETY: the caller guarantees a writable struct or null.
1841 unsafe { fill(r, uid, out, "unit status", value) }
1842 })
1843}
1844
1845/// A V2IP device's PTP state, as it reported it about itself.
1846#[repr(C)]
1847#[derive(Clone, Copy)]
1848pub struct mxr_v2ip_ptp_t {
1849 /// `MXR_V2IP_PTP_*` bits.
1850 pub flags: u8,
1851 /// `MXR_V2IP_PTP_MODE_*` the video processor runs.
1852 pub mode: u8,
1853 /// The domain the video processor runs in.
1854 pub domain: u8,
1855 /// The IEEE 1588 portState.
1856 pub port_state: u8,
1857 /// `MXR_V2IP_PTP_LOCK_*`.
1858 pub lock: u8,
1859 /// The device's own clock identity.
1860 pub clock_identity: [u8; 8],
1861 /// The clock identity of the grandmaster the device runs on, all zero
1862 /// while it runs on none.
1863 pub grandmaster_identity: [u8; 8],
1864}
1865
1866/// Fills `out` with the PTP state a V2IP device last reported about itself.
1867///
1868/// Reports `MXR_ERR_NOT_REPORTED` until the device has reported one, and while
1869/// it has none to report: no PTP on its video processor, or in power save. A
1870/// change is announced through `on_device_update`.
1871///
1872/// # Safety
1873///
1874/// `remote` is null or a live handle, and `out` points at a writable
1875/// `mxr_v2ip_ptp_t`.
1876#[no_mangle]
1877pub unsafe extern "C" fn mxr_v2ip_ptp(
1878 remote: *const mxr_remote_t,
1879 uid: mxr_uid_t,
1880 out: *mut mxr_v2ip_ptp_t,
1881) -> mxr_result_t {
1882 // SAFETY: the caller guarantees a live handle or null.
1883 let handle = unsafe { remote.as_ref() };
1884 with(handle, |r| {
1885 let value = r
1886 .remote
1887 .v2ip_ptp(uid.into())
1888 .map(|t: V2ipPtpState| mxr_v2ip_ptp_t {
1889 flags: t.flags.bits(),
1890 mode: t.mode.to_wire(),
1891 domain: t.domain,
1892 port_state: t.port_state,
1893 lock: t.lock.to_wire(),
1894 clock_identity: t.clock_identity,
1895 grandmaster_identity: t.grandmaster_identity,
1896 });
1897 // SAFETY: the caller guarantees a writable struct or null.
1898 unsafe { fill(r, uid, out, "PTP state", value) }
1899 })
1900}
1901
1902/// A V2IP sink's test tone.
1903#[repr(C)]
1904#[derive(Clone, Copy)]
1905pub struct mxr_v2ip_test_tone_t {
1906 /// `MXR_V2IP_TONE_MODE_*`.
1907 pub mode: u8,
1908 /// The level, in dBFS, `MXR_V2IP_TONE_LEVEL_MIN` to 0.
1909 pub level: i8,
1910 /// The channels it plays on, 1 to `MXR_V2IP_TONE_CHANNELS_MAX`; a line-up
1911 /// tone needs 2 or more.
1912 pub channels: u8,
1913 /// The frequency, `MXR_V2IP_TONE_FREQ_MIN` to `MXR_V2IP_TONE_FREQ_MAX` Hz.
1914 pub freq: u16,
1915 /// The sample rate: 44100, 48000 or 96000 Hz.
1916 pub rate: u32,
1917}
1918
1919/// A V2IP sink's lip-sync flash: a pattern frame is marked every `period`,
1920/// the frame `lead` after a mark flashes white, and a beep tone starts
1921/// `offset` sample periods after it.
1922#[repr(C)]
1923#[derive(Clone, Copy)]
1924pub struct mxr_v2ip_test_sync_t {
1925 /// Every this many pattern frames is marked, 0 for none.
1926 pub period: u16,
1927 /// The frame this many after a mark flashes; below `period`, or 0 without
1928 /// one.
1929 pub lead: u16,
1930 /// Sample periods between a mark and the beep, at most
1931 /// `MXR_V2IP_SYNC_OFFSET_MAX`.
1932 pub offset: u32,
1933 /// How long the beep lasts, `MXR_V2IP_SYNC_BEEP_MS_MIN` to
1934 /// `MXR_V2IP_SYNC_BEEP_MS_MAX` ms.
1935 pub beep_ms: u16,
1936}
1937
1938/// The test card a V2IP sink last reported. The counters run free and wrap.
1939#[repr(C)]
1940#[derive(Clone, Copy)]
1941pub struct mxr_v2ip_testcard_t {
1942 /// `MXR_V2IP_TESTCARD_*` bits.
1943 pub flags: u8,
1944 /// `MXR_V2IP_TEST_PATTERN_*`.
1945 pub pattern: u8,
1946 /// The colour of a flat pattern, `0xRRGGBB`.
1947 pub colour: u32,
1948 /// The tone.
1949 pub tone: mxr_v2ip_test_tone_t,
1950 /// The lip-sync flash.
1951 pub sync: mxr_v2ip_test_sync_t,
1952 /// Pattern frames sent.
1953 pub frames: u32,
1954 /// Sample periods since the tone started.
1955 pub periods: u32,
1956 /// Lip-sync marks the tone has seen.
1957 pub marks: u32,
1958}
1959
1960/// The lowest test tone frequency, in Hz.
1961pub const MXR_V2IP_TONE_FREQ_MIN: u16 = 20;
1962/// The highest test tone frequency, in Hz.
1963pub const MXR_V2IP_TONE_FREQ_MAX: u16 = 20000;
1964/// The quietest test tone level, in dBFS.
1965pub const MXR_V2IP_TONE_LEVEL_MIN: i8 = -60;
1966/// The most channels a test tone plays on.
1967pub const MXR_V2IP_TONE_CHANNELS_MAX: u8 = 8;
1968/// The largest lip-sync beep offset, in sample periods.
1969pub const MXR_V2IP_SYNC_OFFSET_MAX: u32 = 0xFFFFFF;
1970/// The shortest lip-sync beep, in milliseconds.
1971pub const MXR_V2IP_SYNC_BEEP_MS_MIN: u16 = 1;
1972/// The longest lip-sync beep, in milliseconds.
1973pub const MXR_V2IP_SYNC_BEEP_MS_MAX: u16 = 10000;
1974
1975const _: () = assert!(MXR_V2IP_TONE_FREQ_MIN == mx_remote::V2IP_TONE_FREQ_MIN);
1976const _: () = assert!(MXR_V2IP_TONE_FREQ_MAX == mx_remote::V2IP_TONE_FREQ_MAX);
1977const _: () = assert!(MXR_V2IP_TONE_LEVEL_MIN == mx_remote::V2IP_TONE_LEVEL_MIN);
1978const _: () = assert!(MXR_V2IP_TONE_CHANNELS_MAX == mx_remote::V2IP_TONE_CHANNELS_MAX);
1979const _: () = assert!(MXR_V2IP_SYNC_OFFSET_MAX == mx_remote::V2IP_SYNC_OFFSET_MAX);
1980const _: () = assert!(MXR_V2IP_SYNC_BEEP_MS_MIN == mx_remote::V2IP_SYNC_BEEP_MS_MIN);
1981const _: () = assert!(MXR_V2IP_SYNC_BEEP_MS_MAX == mx_remote::V2IP_SYNC_BEEP_MS_MAX);
1982
1983impl From<V2ipTestTone> for mxr_v2ip_test_tone_t {
1984 fn from(t: V2ipTestTone) -> Self {
1985 Self {
1986 mode: t.mode.to_wire(),
1987 level: t.level,
1988 channels: t.channels,
1989 freq: t.freq,
1990 rate: t.rate,
1991 }
1992 }
1993}
1994
1995impl From<mxr_v2ip_test_tone_t> for V2ipTestTone {
1996 fn from(t: mxr_v2ip_test_tone_t) -> Self {
1997 Self {
1998 mode: V2ipToneMode::from_wire(t.mode),
1999 freq: t.freq,
2000 level: t.level,
2001 channels: t.channels,
2002 rate: t.rate,
2003 }
2004 }
2005}
2006
2007impl From<V2ipTestSync> for mxr_v2ip_test_sync_t {
2008 fn from(s: V2ipTestSync) -> Self {
2009 Self {
2010 period: s.period,
2011 lead: s.lead,
2012 offset: s.offset,
2013 beep_ms: s.beep_ms,
2014 }
2015 }
2016}
2017
2018impl From<mxr_v2ip_test_sync_t> for V2ipTestSync {
2019 fn from(s: mxr_v2ip_test_sync_t) -> Self {
2020 Self {
2021 period: s.period,
2022 lead: s.lead,
2023 offset: s.offset,
2024 beep_ms: s.beep_ms,
2025 }
2026 }
2027}
2028
2029/// Fills `out` with the test card a V2IP sink last reported.
2030///
2031/// Reports `MXR_ERR_NOT_REPORTED` until the sink has answered
2032/// `mxr_request_v2ip_testcard()` or a change. A change is announced through
2033/// `on_device_update`.
2034///
2035/// # Safety
2036///
2037/// `remote` is null or a live handle, and `out` points at a writable
2038/// `mxr_v2ip_testcard_t`.
2039#[no_mangle]
2040pub unsafe extern "C" fn mxr_v2ip_testcard(
2041 remote: *const mxr_remote_t,
2042 uid: mxr_uid_t,
2043 out: *mut mxr_v2ip_testcard_t,
2044) -> mxr_result_t {
2045 // SAFETY: the caller guarantees a live handle or null.
2046 let handle = unsafe { remote.as_ref() };
2047 with(handle, |r| {
2048 let value = r
2049 .remote
2050 .v2ip_testcard(uid.into())
2051 .map(|t: V2ipTestcard| mxr_v2ip_testcard_t {
2052 flags: t.flags.bits(),
2053 pattern: t.pattern.to_wire(),
2054 colour: t.colour,
2055 tone: t.tone.into(),
2056 sync: t.sync.into(),
2057 frames: t.frames,
2058 periods: t.periods,
2059 marks: t.marks,
2060 });
2061 // SAFETY: the caller guarantees a writable struct or null.
2062 unsafe { fill(r, uid, out, "test card", value) }
2063 })
2064}
2065
2066/// Fills `out` with the window a sink is told to show.
2067///
2068/// # Safety
2069///
2070/// `remote` is null or a live handle, and `out` points at a writable
2071/// [`mxr_tiling_config_t`].
2072#[no_mangle]
2073pub unsafe extern "C" fn mxr_v2ip_tiling(
2074 remote: *const mxr_remote_t,
2075 uid: mxr_uid_t,
2076 out: *mut mxr_tiling_config_t,
2077) -> mxr_result_t {
2078 // SAFETY: the caller guarantees a live handle or null.
2079 let handle = unsafe { remote.as_ref() };
2080 with(handle, |r| {
2081 let value =
2082 r.remote
2083 .v2ip_tiling(uid.into())
2084 .map(|t: V2ipTilingConfig| mxr_tiling_config_t {
2085 target: t.target.into(),
2086 pos_x: t.pos_x,
2087 pos_y: t.pos_y,
2088 width: t.width,
2089 height: t.height,
2090 });
2091 // SAFETY: the caller guarantees a writable mxr_tiling_config_t or null.
2092 unsafe { fill(r, uid, out, "window", value) }
2093 })
2094}
2095
2096/// Fills `out` with what a multiviewer reports about itself.
2097///
2098/// # Safety
2099///
2100/// `remote` is null or a live handle, and `out` points at a writable
2101/// [`mxr_multiviewer_status_t`].
2102#[no_mangle]
2103pub unsafe extern "C" fn mxr_multiviewer_status(
2104 remote: *const mxr_remote_t,
2105 uid: mxr_uid_t,
2106 out: *mut mxr_multiviewer_status_t,
2107) -> mxr_result_t {
2108 // SAFETY: the caller guarantees a live handle or null.
2109 let handle = unsafe { remote.as_ref() };
2110 with(handle, |r| {
2111 let value = r.remote.multiviewer_status(uid.into()).map(multiviewer_of);
2112 // SAFETY: the caller guarantees a writable mxr_multiviewer_status_t or null.
2113 unsafe { fill(r, uid, out, "multiviewer status", value) }
2114 })
2115}
2116
2117/// Copies a multiviewer's report into the C shape.
2118fn multiviewer_of(s: MultiviewerStatus) -> mxr_multiviewer_status_t {
2119 let mut out = mxr_multiviewer_status_t {
2120 uid: s.uid.into(),
2121 mappings: [mxr_uid_t::default(); MXR_MULTIVIEWER_INPUTS],
2122 mcu_version: [0; MXR_NAME_LEN],
2123 scaler_version: [0; MXR_NAME_LEN],
2124 hw_view_mode: s.hw_view_mode,
2125 view_mode: s.view_mode.to_wire(),
2126 pip_position: s.pip_position.to_wire(),
2127 pip_size: s.pip_size.to_wire(),
2128 output_mode: s.output_mode.to_wire(),
2129 hdcp_mode: s.hdcp_mode.to_wire(),
2130 output_itc: s.output_itc.to_wire(),
2131 edid_template: s.edid_template.to_wire(),
2132 screen_mode: s.screen_mode,
2133 aspect_ratio: s.aspect_ratio.to_wire(),
2134 auto_switch: s.auto_switch.to_wire(),
2135 audio_source: s.audio_source.to_wire(),
2136 has_audio_volume: s.audio_volume.is_some(),
2137 audio_volume: s.audio_volume.unwrap_or(0),
2138 audio_muted: s.audio_muted.to_wire(),
2139 video_sources: [0; MXR_MULTIVIEWER_INPUTS],
2140 remote_control: s.remote_control.to_wire(),
2141 };
2142 for (slot, uid) in out.mappings.iter_mut().zip(s.mappings) {
2143 *slot = uid.into();
2144 }
2145 for (slot, source) in out.video_sources.iter_mut().zip(s.video_sources) {
2146 *slot = source.to_wire();
2147 }
2148 put_str(&mut out.mcu_version, &s.mcu_version);
2149 put_str(&mut out.scaler_version, &s.scaler_version);
2150 out
2151}
2152
2153/// Fills `out` with a ProAmp8's Dolby settings.
2154///
2155/// # Safety
2156///
2157/// `remote` is null or a live handle, and `out` points at a writable
2158/// [`mxr_dolby_settings_t`].
2159#[no_mangle]
2160pub unsafe extern "C" fn mxr_dolby_settings(
2161 remote: *const mxr_remote_t,
2162 uid: mxr_uid_t,
2163 out: *mut mxr_dolby_settings_t,
2164) -> mxr_result_t {
2165 // SAFETY: the caller guarantees a live handle or null.
2166 let handle = unsafe { remote.as_ref() };
2167 with(handle, |r| {
2168 let value = r
2169 .remote
2170 .dolby_settings(uid.into())
2171 .map(mxr_dolby_settings_t::from);
2172 // SAFETY: the caller guarantees a writable mxr_dolby_settings_t or null.
2173 unsafe { fill(r, uid, out, "Dolby settings", value) }
2174 })
2175}
2176
2177/// Fills `out` with a source bay's remote-control configuration.
2178///
2179/// # Safety
2180///
2181/// `remote` is null or a live handle, and `out` points at a writable
2182/// [`mxr_rc_settings_t`].
2183#[no_mangle]
2184pub unsafe extern "C" fn mxr_rc_settings(
2185 remote: *const mxr_remote_t,
2186 uid: mxr_uid_t,
2187 out: *mut mxr_rc_settings_t,
2188) -> mxr_result_t {
2189 // SAFETY: the caller guarantees a live handle or null.
2190 let handle = unsafe { remote.as_ref() };
2191 with(handle, |r| {
2192 let value = r.remote.rc_settings(uid.into()).map(rc_settings_of);
2193 // SAFETY: the caller guarantees a writable mxr_rc_settings_t or null.
2194 unsafe { fill(r, uid, out, "remote-control configuration", value) }
2195 })
2196}
2197
2198/// Copies a remote-control configuration into the C shape.
2199fn rc_settings_of(s: RcSettings) -> mxr_rc_settings_t {
2200 let mut out = mxr_rc_settings_t {
2201 target: s.target.into(),
2202 rc_target: s.rc_target,
2203 ip: [0; MXR_IP_STRING_LEN],
2204 cec_enabled: s.cec_enabled,
2205 cec_auto_on: s.cec_auto_on,
2206 forward_rc: s.forward_rc,
2207 forward_ir: s.forward_ir,
2208 rc_status: s.rc_status,
2209 status_name: [0; MXR_NAME_LEN],
2210 };
2211 put_ip(&mut out.ip, s.ip);
2212 put_str(&mut out.status_name, &s.status_name);
2213 out
2214}
2215
2216/// Writes the streams a device's source bays advertise, and returns how many
2217/// there are.
2218///
2219/// Returns the full count even when it exceeds `cap`, so calling with `cap`
2220/// zero sizes the buffer.
2221///
2222/// # Safety
2223///
2224/// `remote` is null or a live handle, and `out` is null or points at `cap`
2225/// writable [`mxr_stream_sources_t`].
2226#[no_mangle]
2227pub unsafe extern "C" fn mxr_v2ip_sources(
2228 remote: *const mxr_remote_t,
2229 uid: mxr_uid_t,
2230 out: *mut mxr_stream_sources_t,
2231 cap: usize,
2232) -> usize {
2233 guard(0, || {
2234 // SAFETY: the caller guarantees a live handle or null.
2235 let Some(r) = (unsafe { remote.as_ref() }) else {
2236 return no_handle();
2237 };
2238 let Some(sources) = r.remote.v2ip_sources(uid.into()) else {
2239 not_reported(r, uid, "V2IP stream sources");
2240 return 0;
2241 };
2242 // SAFETY: the caller guarantees cap writable elements at out.
2243 unsafe { copy_into(&sources, out, cap) }
2244 })
2245}
2246
2247/// Writes a device's network ports, and returns how many there are.
2248///
2249/// # Safety
2250///
2251/// `remote` is null or a live handle, and `out` is null or points at `cap`
2252/// writable [`mxr_network_port_t`].
2253#[no_mangle]
2254pub unsafe extern "C" fn mxr_network_status(
2255 remote: *const mxr_remote_t,
2256 uid: mxr_uid_t,
2257 out: *mut mxr_network_port_t,
2258 cap: usize,
2259) -> usize {
2260 guard(0, || {
2261 // SAFETY: the caller guarantees a live handle or null.
2262 let Some(r) = (unsafe { remote.as_ref() }) else {
2263 return no_handle();
2264 };
2265 let ports: Vec<mxr_network_port_t> = r
2266 .remote
2267 .network_status(uid.into())
2268 .iter()
2269 .map(port_of)
2270 .collect();
2271 // SAFETY: the caller guarantees cap writable elements at out.
2272 unsafe { copy_into(&ports, out, cap) }
2273 })
2274}
2275
2276/// Copies one port report into the C shape.
2277fn port_of(p: &NetworkPortStatus) -> mxr_network_port_t {
2278 let errors = p.errors.unwrap_or_default();
2279 let mut out = mxr_network_port_t {
2280 port: p.port,
2281 name: [0; MXR_NAME_LEN],
2282 link_speed: p.link_speed.to_wire(),
2283 link_full_duplex: p.link_full_duplex,
2284 ip: [0; MXR_IP_STRING_LEN],
2285 querier: [0; MXR_IP_STRING_LEN],
2286 has_mac_address: p.mac_address.is_some(),
2287 mac_address: p.mac_address.unwrap_or_default().0,
2288 has_errors: p.errors.is_some(),
2289 in_error: errors.in_error,
2290 in_fcs_error: errors.in_fcs_error,
2291 in_collision: errors.in_collision,
2292 out_deferred: errors.out_deferred,
2293 out_excessive: errors.out_excessive,
2294 polarity_error: errors.polarity_error,
2295 skew_warning: errors.skew_warning,
2296 length_warning: errors.length_warning,
2297 has_vct_status: p.vct_status.is_some(),
2298 vct_warning: [false; MXR_UTP_PAIRS],
2299 cable_status_count: p.cable_status.len().min(MXR_UTP_PAIRS),
2300 cable_status: [mxr_cable_status_t {
2301 polarity: false,
2302 pair: 0,
2303 skew: 0,
2304 length: 0,
2305 }; MXR_UTP_PAIRS],
2306 };
2307 put_str(&mut out.name, &p.name);
2308 put_ip(&mut out.ip, p.ip);
2309 put_ip(&mut out.querier, p.querier);
2310 if let Some(vct) = p.vct_status {
2311 for (slot, status) in out.vct_warning.iter_mut().zip(vct) {
2312 *slot = status == VctStatus::Warning;
2313 }
2314 }
2315 for (slot, cable) in out.cable_status.iter_mut().zip(&p.cable_status) {
2316 *slot = (*cable).into();
2317 }
2318 out
2319}
2320
2321/// Writes a device's view of the mesh topology, and returns how many entries
2322/// there are.
2323///
2324/// # Safety
2325///
2326/// `remote` is null or a live handle, and `out` is null or points at `cap`
2327/// writable [`mxr_topology_entry_t`].
2328#[no_mangle]
2329pub unsafe extern "C" fn mxr_topology(
2330 remote: *const mxr_remote_t,
2331 uid: mxr_uid_t,
2332 out: *mut mxr_topology_entry_t,
2333 cap: usize,
2334) -> usize {
2335 guard(0, || {
2336 // SAFETY: the caller guarantees a live handle or null.
2337 let Some(r) = (unsafe { remote.as_ref() }) else {
2338 return no_handle();
2339 };
2340 let topology = r.remote.topology(uid.into());
2341 // SAFETY: the caller guarantees cap writable elements at out.
2342 unsafe { copy_into(&topology, out, cap) }
2343 })
2344}
2345
2346/// Writes the firmware versions a device reports, and returns how many there
2347/// are.
2348///
2349/// # Safety
2350///
2351/// `remote` is null or a live handle, and `out` is null or points at `cap`
2352/// writable [`mxr_firmware_version_t`].
2353#[no_mangle]
2354pub unsafe extern "C" fn mxr_device_firmware(
2355 remote: *const mxr_remote_t,
2356 uid: mxr_uid_t,
2357 out: *mut mxr_firmware_version_t,
2358 cap: usize,
2359) -> usize {
2360 guard(0, || {
2361 // SAFETY: the caller guarantees a live handle or null.
2362 let Some(r) = (unsafe { remote.as_ref() }) else {
2363 return no_handle();
2364 };
2365 let versions: Vec<mxr_firmware_version_t> = r
2366 .remote
2367 .firmware(uid.into())
2368 .iter()
2369 .map(|(_, v)| firmware_of(v))
2370 .collect();
2371 // SAFETY: the caller guarantees cap writable elements at out.
2372 unsafe { copy_into(&versions, out, cap) }
2373 })
2374}
2375
2376/// Copies one firmware report into the C shape.
2377fn firmware_of(v: &FirmwareVersion) -> mxr_firmware_version_t {
2378 let mut out = mxr_firmware_version_t {
2379 firmware_type: v.firmware_type.to_wire(),
2380 timestamp: v.timestamp,
2381 hash: v.hash,
2382 version: [0; MXR_VERSION_LEN],
2383 };
2384 put_str(&mut out.version, &v.version);
2385 out
2386}
2387
2388/// Writes a device's audio endpoints, in the order it reported them, and
2389/// returns how many there are.
2390///
2391/// # Safety
2392///
2393/// `remote` is null or a live handle, and `out` is null or points at `cap`
2394/// writable [`mxr_audio_endpoint_t`].
2395#[no_mangle]
2396pub unsafe extern "C" fn mxr_audio_endpoints(
2397 remote: *const mxr_remote_t,
2398 uid: mxr_uid_t,
2399 out: *mut mxr_audio_endpoint_t,
2400 cap: usize,
2401) -> usize {
2402 guard(0, || {
2403 // SAFETY: the caller guarantees a live handle or null.
2404 let Some(r) = (unsafe { remote.as_ref() }) else {
2405 return no_handle();
2406 };
2407 let Some(endpoints) = r.remote.audio_endpoints(uid.into()) else {
2408 not_reported(r, uid, "audio endpoints");
2409 return 0;
2410 };
2411 let list: Vec<mxr_audio_endpoint_t> = endpoints.list().map(Into::into).collect();
2412 // SAFETY: the caller guarantees cap writable elements at out.
2413 unsafe { copy_into(&list, out, cap) }
2414 })
2415}
2416
2417/// Fills `out` with the state an audio endpoint reports, on the bits of its
2418/// features: `MXR_AUDIO_MUTE` while it is muted, `MXR_AUDIO_TRIGGER` while
2419/// its trigger is active, and `MXR_AUDIO_AUDIO_LOCK` while its source is
2420/// locked. Beside `mxr_audio_endpoint_t` rather than in it, so that struct
2421/// keeps its size.
2422///
2423/// Reports `MXR_ERR_NOT_REPORTED` until the device has reported its audio
2424/// endpoints, and `MXR_ERR_NOT_FOUND` for an endpoint it did not report.
2425///
2426/// # Safety
2427///
2428/// `remote` is null or a live handle, and `out` points at a writable
2429/// `uint32_t`.
2430#[no_mangle]
2431pub unsafe extern "C" fn mxr_audio_endpoint_status(
2432 remote: *const mxr_remote_t,
2433 uid: mxr_uid_t,
2434 endpoint: u8,
2435 out: *mut u32,
2436) -> mxr_result_t {
2437 // SAFETY: the caller guarantees a live handle or null.
2438 let handle = unsafe { remote.as_ref() };
2439 with(handle, |r| {
2440 if out.is_null() {
2441 return null_out("audio endpoint status");
2442 }
2443 let Some(endpoints) = r.remote.audio_endpoints(uid.into()) else {
2444 return not_reported(r, uid, "audio endpoints");
2445 };
2446 let Some(status) = endpoints.status(endpoint) else {
2447 return fail(
2448 mxr_result_t::MXR_ERR_NOT_FOUND,
2449 "the device reported no such audio endpoint",
2450 );
2451 };
2452 // SAFETY: the caller guarantees a writable uint32_t or null.
2453 unsafe { fill(r, uid, out, "audio endpoint status", Some(status.bits())) }
2454 })
2455}
2456
2457/// Writes the endpoints hanging off one audio endpoint, and returns how many
2458/// there are.
2459///
2460/// # Safety
2461///
2462/// `remote` is null or a live handle, and `out` is null or points at `cap`
2463/// writable bytes.
2464#[no_mangle]
2465pub unsafe extern "C" fn mxr_audio_endpoint_children(
2466 remote: *const mxr_remote_t,
2467 uid: mxr_uid_t,
2468 endpoint: u8,
2469 out: *mut u8,
2470 cap: usize,
2471) -> usize {
2472 guard(0, || {
2473 // SAFETY: the caller guarantees a live handle or null.
2474 let Some(r) = (unsafe { remote.as_ref() }) else {
2475 return no_handle();
2476 };
2477 let children = match r.remote.audio_endpoints(uid.into()) {
2478 Some(endpoints) => match endpoints.get(endpoint) {
2479 Some(e) => e.children.clone(),
2480 None => {
2481 fail(
2482 mxr_result_t::MXR_ERR_NOT_FOUND,
2483 &format!("the device has no audio endpoint {endpoint}"),
2484 );
2485 return 0;
2486 }
2487 },
2488 None => {
2489 not_reported(r, uid, "audio endpoints");
2490 return 0;
2491 }
2492 };
2493 // SAFETY: the caller guarantees cap writable bytes at out.
2494 unsafe { copy_into(&children, out, cap) }
2495 })
2496}
2497
2498/// Reports a null handle from a call whose answer is a count.
2499fn no_handle() -> usize {
2500 fail(
2501 mxr_result_t::MXR_ERR_INVALID_ARGUMENT,
2502 "the client handle is null",
2503 );
2504 0
2505}
2506
2507#[cfg(test)]
2508mod tests {
2509 use std::ffi::CStr;
2510 use std::net::Ipv4Addr;
2511
2512 use mx_remote::V2ipPrivacyStream;
2513
2514 use super::privacy_stream;
2515
2516 /// Each field lands in its own C field, every one of them distinct.
2517 #[test]
2518 fn a_privacy_stream_keeps_every_field() {
2519 let s = V2ipPrivacyStream {
2520 group: Ipv4Addr::new(239, 1, 2, 3),
2521 port: 0x1388,
2522 version: 7,
2523 on: true,
2524 full_id: 3,
2525 short_id: 4,
2526 iv: [0x10; 8],
2527 key_generator: [0x20; 16],
2528 key_id: [0x30; 8],
2529 key_version: 0x0102_0304,
2530 };
2531 let c = privacy_stream(&s);
2532 // SAFETY: put_ip writes a NUL-terminated string into the array.
2533 let group = unsafe { CStr::from_ptr(c.group.as_ptr()) };
2534 assert_eq!(group.to_str(), Ok("239.1.2.3"));
2535 assert_eq!((c.port, c.version, c.on), (0x1388, 7, true));
2536 assert_eq!((c.full_id, c.short_id), (3, 4));
2537 assert_eq!(
2538 (c.iv, c.key_generator, c.key_id),
2539 (s.iv, s.key_generator, s.key_id)
2540 );
2541 assert_eq!(c.key_version, 0x0102_0304);
2542 }
2543}