Skip to main content

openipc_core/
realtek.rs

1/// Size of a Realtek USB RX descriptor before any PHY status and payload bytes.
2pub const RX_DESC_SIZE: usize = 24;
3/// Default native USB bulk-IN transfer size used by the receiver.
4pub const DEFAULT_RX_TRANSFER_SIZE: usize = 32 * 1024;
5
6/// Realtek USB RX descriptor layout family.
7///
8/// Jaguar1 is used by RTL8812AU/RTL8821AU and the existing RTL8814AU path in
9/// this project. Jaguar2 and Jaguar3 use the common 24-byte HalMAC 88xx
10/// descriptor while moving several fields from the Jaguar1 layout.
11#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
12pub enum RxDescriptorKind {
13    /// RTL8812AU/RTL8821AU/RTL8814AU-style descriptor layout.
14    #[default]
15    Jaguar1,
16    /// RTL8812BU/RTL8822BU common HalMAC descriptor layout.
17    Jaguar2,
18    /// RTL8812CU/EU and RTL8822CU/EU descriptor layout.
19    Jaguar3,
20}
21
22/// Type of packet carried by a Realtek RX descriptor.
23#[derive(Debug, Clone, Copy, PartialEq, Eq)]
24pub enum RxPacketType {
25    /// Normal received 802.11 frame.
26    NormalRx,
27    /// Command-to-host report generated by the chip firmware.
28    C2hPacket,
29}
30
31/// Parsed metadata from a Realtek USB RX descriptor.
32#[derive(Debug, Clone, Copy, PartialEq, Eq)]
33pub struct RxPacketAttrib {
34    /// Number of payload bytes following descriptor, PHY status, and shift bytes.
35    pub pkt_len: u16,
36    /// Whether PHY status bytes are present after the descriptor.
37    pub physt: bool,
38    /// Driver-info/PHY-status byte count.
39    pub drvinfo_sz: u8,
40    /// Extra alignment shift before payload bytes.
41    pub shift_sz: u8,
42    /// Whether the original 802.11 frame had QoS metadata.
43    pub qos: bool,
44    /// Realtek priority field.
45    pub priority: u8,
46    /// More-data bit from the descriptor.
47    pub mdata: bool,
48    /// 802.11 sequence number reported by the descriptor.
49    pub seq_num: u16,
50    /// 802.11 fragment number reported by the descriptor.
51    pub frag_num: u8,
52    /// More-fragments bit from the descriptor.
53    pub mfrag: bool,
54    /// Whether the chip reports the packet as decrypted.
55    pub bdecrypted: bool,
56    /// Realtek encryption type field.
57    pub encrypt: u8,
58    /// Hardware-reported CRC/FCS failure.
59    pub crc_err: bool,
60    /// Hardware-reported ICV failure.
61    pub icv_err: bool,
62    /// TSF low timestamp from the descriptor.
63    pub tsfl: u32,
64    /// Realtek data-rate code.
65    pub data_rate: u8,
66    /// Realtek bandwidth code.
67    pub bw: u8,
68    /// STBC flag from PHY status.
69    pub stbc: u8,
70    /// LDPC flag from PHY status.
71    pub ldpc: u8,
72    /// Short guard interval flag from PHY status.
73    pub sgi: u8,
74    /// Scrambler seed from PHY status.
75    pub scrambler: u8,
76    /// Per-path raw RSSI readings when available.
77    pub rssi: [u8; 4],
78    /// Per-path raw SNR readings when available.
79    pub snr: [i8; 4],
80    /// Per-path raw EVM readings when available.
81    pub evm: [i8; 4],
82    /// Whether this descriptor carries a received frame or a C2H report.
83    pub pkt_rpt_type: RxPacketType,
84}
85
86impl Default for RxPacketAttrib {
87    fn default() -> Self {
88        Self {
89            pkt_len: 0,
90            physt: false,
91            drvinfo_sz: 0,
92            shift_sz: 0,
93            qos: false,
94            priority: 0,
95            mdata: false,
96            seq_num: 0,
97            frag_num: 0,
98            mfrag: false,
99            bdecrypted: false,
100            encrypt: 0,
101            crc_err: false,
102            icv_err: false,
103            tsfl: 0,
104            data_rate: 0,
105            bw: 0,
106            stbc: 0,
107            ldpc: 0,
108            sgi: 0,
109            scrambler: 0,
110            rssi: [0; 4],
111            snr: [0; 4],
112            evm: [0; 4],
113            pkt_rpt_type: RxPacketType::NormalRx,
114        }
115    }
116}
117
118/// One packet extracted from a Realtek USB RX aggregate.
119#[derive(Debug, Clone, Copy)]
120pub struct RealtekRxPacket<'a> {
121    /// Parsed descriptor metadata.
122    pub attrib: RxPacketAttrib,
123    /// Borrowed packet bytes after descriptor, PHY status, and alignment shift.
124    pub data: &'a [u8],
125}
126
127/// Parsed command-to-host report carried in an RX aggregate.
128#[derive(Debug, Clone, Copy, PartialEq, Eq)]
129pub struct C2hPacket<'a> {
130    /// Firmware command/report identifier.
131    pub cmd_id: u8,
132    /// Optional sequence byte when the report includes one.
133    pub seq: Option<u8>,
134    /// Remaining C2H payload bytes.
135    pub payload: &'a [u8],
136}
137
138/// RTL8814A transmit status report extracted from a C2H packet.
139#[derive(Debug, Clone, Copy, PartialEq, Eq)]
140pub struct TxStatusReport8814 {
141    /// Offset at which this report was found in the C2H payload.
142    pub header_offset: usize,
143    /// Hardware queue selector.
144    pub queue_select: u8,
145    /// Whether the transmitted packet was broadcast.
146    pub packet_broadcast: bool,
147    /// Whether the packet exceeded the lifetime limit.
148    pub lifetime_over: bool,
149    /// Whether the packet exceeded the retry limit.
150    pub retry_over: bool,
151    /// MAC ID associated with the report.
152    pub mac_id: u8,
153    /// Number of data retries reported by hardware.
154    pub data_retry_count: u8,
155    /// Queue residency time in microseconds.
156    pub queue_time_us: u32,
157    /// Final data-rate code used by hardware.
158    pub final_data_rate: u8,
159}
160
161/// Error returned while parsing Realtek RX aggregates.
162#[derive(Debug, Clone, PartialEq, Eq)]
163pub enum AggregateError {
164    /// Fewer than `RX_DESC_SIZE` bytes were available for a descriptor.
165    DescriptorTooShort,
166    /// Descriptor payload length points beyond the remaining aggregate bytes.
167    InvalidPacketLength {
168        /// Packet length read from descriptor.
169        pkt_len: u16,
170        /// Descriptor + metadata + packet offset implied by the descriptor.
171        pkt_offset: usize,
172        /// Bytes remaining in the aggregate at the current descriptor.
173        remaining: usize,
174    },
175}
176
177impl std::fmt::Display for AggregateError {
178    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
179        match self {
180            Self::DescriptorTooShort => write!(f, "RX descriptor is shorter than {RX_DESC_SIZE} bytes"),
181            Self::InvalidPacketLength {
182                pkt_len,
183                pkt_offset,
184                remaining,
185            } => write!(
186                f,
187                "invalid RX packet length: pkt_len={pkt_len}, pkt_offset={pkt_offset}, remaining={remaining}"
188            ),
189        }
190    }
191}
192
193impl std::error::Error for AggregateError {}
194
195/// Parse a single Realtek RX descriptor.
196pub fn parse_rx_descriptor(desc: &[u8]) -> Result<RxPacketAttrib, AggregateError> {
197    parse_rx_descriptor_with_kind(desc, RxDescriptorKind::Jaguar1)
198}
199
200/// Parse a single Realtek RX descriptor using an explicit layout.
201pub fn parse_rx_descriptor_with_kind(
202    desc: &[u8],
203    kind: RxDescriptorKind,
204) -> Result<RxPacketAttrib, AggregateError> {
205    if desc.len() < RX_DESC_SIZE {
206        return Err(AggregateError::DescriptorTooShort);
207    }
208
209    let d0 = le32(desc, 0);
210    let d1 = le32(desc, 4);
211    let d2 = le32(desc, 8);
212    let d3 = le32(desc, 12);
213    let d4 = le32(desc, 16);
214    let d5 = le32(desc, 20);
215
216    let mut attrib = RxPacketAttrib {
217        pkt_len: bits(d0, 0, 14) as u16,
218        crc_err: bits(d0, 14, 1) != 0,
219        icv_err: bits(d0, 15, 1) != 0,
220        drvinfo_sz: (bits(d0, 16, 4) * 8) as u8,
221        shift_sz: bits(d0, 24, 2) as u8,
222        physt: bits(d0, 26, 1) != 0,
223        data_rate: bits(d3, 0, 7) as u8,
224        pkt_rpt_type: if bits(d2, 28, 1) != 0 {
225            RxPacketType::C2hPacket
226        } else {
227            RxPacketType::NormalRx
228        },
229        ..Default::default()
230    };
231
232    if kind == RxDescriptorKind::Jaguar1 {
233        attrib.encrypt = bits(d0, 20, 3) as u8;
234        attrib.qos = bits(d0, 23, 1) != 0;
235        attrib.bdecrypted = bits(d0, 27, 1) == 0;
236        attrib.priority = bits(d1, 8, 4) as u8;
237        attrib.mdata = bits(d1, 26, 1) != 0;
238        attrib.mfrag = bits(d1, 27, 1) != 0;
239        attrib.seq_num = bits(d2, 0, 12) as u16;
240        attrib.frag_num = bits(d2, 12, 4) as u8;
241        attrib.sgi = bits(d4, 0, 1) as u8;
242        attrib.ldpc = bits(d4, 1, 1) as u8;
243        attrib.stbc = bits(d4, 2, 1) as u8;
244        attrib.bw = bits(d4, 4, 2) as u8;
245        attrib.scrambler = bits(d4, 9, 7) as u8;
246        attrib.tsfl = d5;
247    }
248
249    Ok(attrib)
250}
251
252/// Split a Realtek USB bulk-IN transfer into packet descriptors and payloads.
253///
254/// Realtek devices aggregate one or more RX descriptors into each USB transfer.
255/// Each returned packet borrows from `buf`, so callers can parse metadata without
256/// copying frame bytes.
257pub fn parse_rx_aggregate(buf: &[u8]) -> Result<Vec<RealtekRxPacket<'_>>, AggregateError> {
258    parse_rx_aggregate_with_kind(buf, RxDescriptorKind::Jaguar1)
259}
260
261/// Split a Realtek USB bulk-IN transfer using an explicit RX descriptor layout.
262pub fn parse_rx_aggregate_with_kind(
263    buf: &[u8],
264    kind: RxDescriptorKind,
265) -> Result<Vec<RealtekRxPacket<'_>>, AggregateError> {
266    let mut packets = Vec::new();
267    let mut offset = 0usize;
268
269    while offset < buf.len() {
270        let remaining = buf.len() - offset;
271        if remaining < RX_DESC_SIZE {
272            break;
273        }
274
275        let desc = &buf[offset..offset + RX_DESC_SIZE];
276        let mut attrib = parse_rx_descriptor_with_kind(desc, kind)?;
277        let data_start =
278            offset + RX_DESC_SIZE + attrib.drvinfo_sz as usize + attrib.shift_sz as usize;
279        let pkt_offset = RX_DESC_SIZE
280            + attrib.drvinfo_sz as usize
281            + attrib.shift_sz as usize
282            + attrib.pkt_len as usize;
283        if attrib.pkt_len == 0 || pkt_offset > remaining {
284            return Err(AggregateError::InvalidPacketLength {
285                pkt_len: attrib.pkt_len,
286                pkt_offset,
287                remaining,
288            });
289        }
290
291        if attrib.pkt_rpt_type == RxPacketType::NormalRx {
292            let phy_start = offset + RX_DESC_SIZE;
293            let phy_end = phy_start + attrib.drvinfo_sz as usize;
294            parse_phy_status(&mut attrib, &buf[phy_start..phy_end]);
295        }
296
297        let data_end = data_start + attrib.pkt_len as usize;
298        packets.push(RealtekRxPacket {
299            attrib,
300            data: &buf[data_start..data_end],
301        });
302
303        let aligned = round_up_8(pkt_offset);
304        if aligned >= remaining {
305            break;
306        }
307        offset += aligned;
308    }
309
310    Ok(packets)
311}
312
313/// Parse a C2H report header from a Realtek RX packet payload.
314pub fn parse_c2h_packet(data: &[u8]) -> Option<C2hPacket<'_>> {
315    let (&cmd_id, rest) = data.split_first()?;
316    let seq = rest.first().copied();
317    let payload = if rest.len() > 1 { &rest[1..] } else { rest };
318    Some(C2hPacket {
319        cmd_id,
320        seq,
321        payload,
322    })
323}
324
325/// Parse RTL8814A TX status reports from a C2H payload.
326pub fn parse_8814_tx_status_reports(data: &[u8]) -> Vec<TxStatusReport8814> {
327    [1usize, 2usize]
328        .into_iter()
329        .filter_map(|offset| parse_8814_tx_status_report_at(data, offset))
330        .collect()
331}
332
333/// Parse one RTL8814A TX status report at a known payload offset.
334pub fn parse_8814_tx_status_report_at(
335    data: &[u8],
336    header_offset: usize,
337) -> Option<TxStatusReport8814> {
338    let h = data.get(header_offset..header_offset + 6)?;
339    let queue_time_raw = u16::from_le_bytes([h[3], h[4]]);
340    Some(TxStatusReport8814 {
341        header_offset,
342        queue_select: h[0] & 0x1f,
343        packet_broadcast: h[0] & (1 << 5) != 0,
344        lifetime_over: h[0] & (1 << 6) != 0,
345        retry_over: h[0] & (1 << 7) != 0,
346        mac_id: h[1],
347        data_retry_count: h[2] & 0x3f,
348        queue_time_us: u32::from(queue_time_raw) * 256,
349        final_data_rate: h[5],
350    })
351}
352
353const fn round_up_8(value: usize) -> usize {
354    (value + 7) & !7
355}
356
357fn le32(bytes: &[u8], offset: usize) -> u32 {
358    u32::from_le_bytes(
359        bytes[offset..offset + 4]
360            .try_into()
361            .expect("descriptor length checked"),
362    )
363}
364
365const fn bits(word: u32, offset: u8, len: u8) -> u32 {
366    if len == 32 {
367        word
368    } else {
369        (word >> offset) & ((1u32 << len) - 1)
370    }
371}
372
373fn parse_phy_status(attrib: &mut RxPacketAttrib, phy: &[u8]) {
374    if phy.len() < 2 {
375        return;
376    }
377
378    attrib.rssi[0] = phy[0];
379    attrib.rssi[1] = phy[1];
380
381    if phy.len() < 28 {
382        return;
383    }
384
385    attrib.rssi[2] = phy[23];
386    attrib.rssi[3] = phy[24];
387    attrib.snr[0] = phy[15] as i8;
388    attrib.snr[1] = phy[16] as i8;
389    attrib.snr[2] = phy[21] as i8;
390    attrib.snr[3] = phy[22] as i8;
391    attrib.evm[0] = phy[13] as i8;
392    attrib.evm[1] = phy[14] as i8;
393    attrib.evm[2] = phy[19] as i8;
394    attrib.evm[3] = phy[20] as i8;
395}
396
397#[cfg(test)]
398mod tests {
399    use super::*;
400
401    fn put_bits(word: &mut u32, offset: u8, len: u8, value: u32) {
402        let mask = ((1u32 << len) - 1) << offset;
403        *word = (*word & !mask) | ((value << offset) & mask);
404    }
405
406    fn descriptor(pkt_len: u16, drvinfo_units: u8, shift: u8, seq: u16) -> [u8; RX_DESC_SIZE] {
407        let mut desc = [0; RX_DESC_SIZE];
408        let mut d0 = 0u32;
409        put_bits(&mut d0, 0, 14, pkt_len as u32);
410        put_bits(&mut d0, 16, 4, drvinfo_units as u32);
411        put_bits(&mut d0, 24, 2, shift as u32);
412        let mut d2 = 0u32;
413        put_bits(&mut d2, 0, 12, seq as u32);
414        desc[0..4].copy_from_slice(&d0.to_le_bytes());
415        desc[8..12].copy_from_slice(&d2.to_le_bytes());
416        desc
417    }
418
419    fn jaguar3_descriptor(
420        pkt_len: u16,
421        drvinfo_units: u8,
422        shift: u8,
423        rx_rate: u8,
424        c2h: bool,
425    ) -> [u8; RX_DESC_SIZE] {
426        let mut desc = [0; RX_DESC_SIZE];
427        let mut d0 = 0u32;
428        put_bits(&mut d0, 0, 14, pkt_len as u32);
429        put_bits(&mut d0, 14, 1, 1);
430        put_bits(&mut d0, 15, 1, 1);
431        put_bits(&mut d0, 16, 4, drvinfo_units as u32);
432        put_bits(&mut d0, 24, 2, shift as u32);
433        let mut d2 = 0u32;
434        put_bits(&mut d2, 28, 1, u32::from(c2h));
435        let mut d3 = 0u32;
436        put_bits(&mut d3, 0, 7, rx_rate as u32);
437        desc[0..4].copy_from_slice(&d0.to_le_bytes());
438        desc[8..12].copy_from_slice(&d2.to_le_bytes());
439        desc[12..16].copy_from_slice(&d3.to_le_bytes());
440        desc
441    }
442
443    #[test]
444    fn parses_single_rx_packet() {
445        let mut aggregate = Vec::new();
446        aggregate.extend_from_slice(&descriptor(4, 0, 0, 77));
447        aggregate.extend_from_slice(&[1, 2, 3, 4]);
448
449        let packets = parse_rx_aggregate(&aggregate).unwrap();
450        assert_eq!(packets.len(), 1);
451        assert_eq!(packets[0].attrib.pkt_len, 4);
452        assert_eq!(packets[0].attrib.seq_num, 77);
453        assert_eq!(packets[0].data, &[1, 2, 3, 4]);
454    }
455
456    #[test]
457    fn ignores_short_tail_after_aligned_packet() {
458        let mut aggregate = Vec::new();
459        aggregate.extend_from_slice(&descriptor(4, 0, 0, 77));
460        aggregate.extend_from_slice(&[1, 2, 3, 4]);
461        aggregate.extend_from_slice(&[0, 0, 0, 0]);
462        aggregate.extend_from_slice(&[0xde, 0xad, 0xbe, 0xef]);
463
464        let packets = parse_rx_aggregate(&aggregate).unwrap();
465        assert_eq!(packets.len(), 1);
466        assert_eq!(packets[0].data, &[1, 2, 3, 4]);
467    }
468
469    #[test]
470    fn rejects_zero_length_descriptor() {
471        let aggregate = descriptor(0, 0, 0, 1);
472
473        let err = parse_rx_aggregate(&aggregate).unwrap_err();
474        assert_eq!(
475            err,
476            AggregateError::InvalidPacketLength {
477                pkt_len: 0,
478                pkt_offset: RX_DESC_SIZE,
479                remaining: RX_DESC_SIZE,
480            }
481        );
482    }
483
484    #[test]
485    fn rejects_descriptor_payload_past_transfer() {
486        let mut aggregate = Vec::new();
487        aggregate.extend_from_slice(&descriptor(8, 0, 0, 1));
488        aggregate.extend_from_slice(&[1, 2, 3, 4]);
489
490        let err = parse_rx_aggregate(&aggregate).unwrap_err();
491        assert_eq!(
492            err,
493            AggregateError::InvalidPacketLength {
494                pkt_len: 8,
495                pkt_offset: RX_DESC_SIZE + 8,
496                remaining: RX_DESC_SIZE + 4,
497            }
498        );
499    }
500
501    #[test]
502    fn surfaces_crc_and_icv_flags_from_jaguar1_descriptor() {
503        let mut desc = descriptor(4, 0, 0, 9);
504        let mut d0 = u32::from_le_bytes(desc[0..4].try_into().unwrap());
505        put_bits(&mut d0, 14, 1, 1);
506        put_bits(&mut d0, 15, 1, 1);
507        desc[0..4].copy_from_slice(&d0.to_le_bytes());
508
509        let mut aggregate = Vec::new();
510        aggregate.extend_from_slice(&desc);
511        aggregate.extend_from_slice(&[1, 2, 3, 4]);
512
513        let packets = parse_rx_aggregate(&aggregate).unwrap();
514        assert!(packets[0].attrib.crc_err);
515        assert!(packets[0].attrib.icv_err);
516        assert_eq!(packets[0].data, &[1, 2, 3, 4]);
517    }
518
519    #[test]
520    fn does_not_decode_payload_as_phy_status_when_drvinfo_absent() {
521        let mut payload = vec![0u8; 32];
522        payload[0] = 55;
523        payload[1] = 66;
524        payload[13] = 0x80;
525        payload[15] = 0x7f;
526        payload[23] = 77;
527
528        let mut aggregate = Vec::new();
529        aggregate.extend_from_slice(&descriptor(payload.len() as u16, 0, 0, 1));
530        aggregate.extend_from_slice(&payload);
531
532        let packets = parse_rx_aggregate(&aggregate).unwrap();
533        assert_eq!(packets[0].attrib.rssi, [0; 4]);
534        assert_eq!(packets[0].attrib.snr, [0; 4]);
535        assert_eq!(packets[0].attrib.evm, [0; 4]);
536        assert_eq!(packets[0].data, payload);
537    }
538
539    #[test]
540    fn parses_phy_status_only_from_driver_info_bytes() {
541        let mut phy = vec![0u8; 32];
542        phy[0] = 42;
543        phy[1] = 43;
544        phy[13] = 0xf0;
545        phy[14] = 0x0f;
546        phy[15] = 0x80;
547        phy[16] = 0x7f;
548        phy[19] = 0xee;
549        phy[20] = 0xdd;
550        phy[21] = 0xcc;
551        phy[22] = 0xbb;
552        phy[23] = 44;
553        phy[24] = 45;
554
555        let mut aggregate = Vec::new();
556        aggregate.extend_from_slice(&descriptor(3, 4, 1, 1));
557        aggregate.extend_from_slice(&phy);
558        aggregate.push(0xaa);
559        aggregate.extend_from_slice(&[1, 2, 3]);
560
561        let packets = parse_rx_aggregate(&aggregate).unwrap();
562        assert_eq!(packets[0].attrib.drvinfo_sz, 32);
563        assert_eq!(packets[0].attrib.shift_sz, 1);
564        assert_eq!(packets[0].attrib.rssi, [42, 43, 44, 45]);
565        assert_eq!(packets[0].attrib.snr, [-128, 127, -52, -69]);
566        assert_eq!(packets[0].attrib.evm, [-16, 15, -18, -35]);
567        assert_eq!(packets[0].data, &[1, 2, 3]);
568    }
569
570    #[test]
571    fn parses_c2h_packet_header() {
572        let packet = parse_c2h_packet(&[0x42, 0x7f, 1, 2]).unwrap();
573        assert_eq!(packet.cmd_id, 0x42);
574        assert_eq!(packet.seq, Some(0x7f));
575        assert_eq!(packet.payload, &[1, 2]);
576    }
577
578    #[test]
579    fn parses_8814_tx_status_at_devourer_offsets() {
580        let bytes = [0xaa, 0x83, 0x09, 0x25, 0x34, 0x12, 0x6c, 0xbb];
581        let reports = parse_8814_tx_status_reports(&bytes);
582        assert_eq!(reports.len(), 2);
583        assert_eq!(reports[0].header_offset, 1);
584        assert_eq!(reports[0].queue_select, 3);
585        assert!(reports[0].retry_over);
586        assert_eq!(reports[0].mac_id, 9);
587        assert_eq!(reports[0].data_retry_count, 0x25);
588        assert_eq!(reports[0].queue_time_us, 0x1234 * 256);
589        assert_eq!(reports[0].final_data_rate, 0x6c);
590    }
591
592    #[test]
593    fn advances_by_jaguar_eight_byte_alignment() {
594        let mut aggregate = Vec::new();
595        aggregate.extend_from_slice(&descriptor(5, 0, 0, 1));
596        aggregate.extend_from_slice(&[1, 2, 3, 4, 5]);
597        aggregate.extend_from_slice(&[0, 0, 0]);
598        aggregate.extend_from_slice(&descriptor(3, 0, 0, 2));
599        aggregate.extend_from_slice(&[6, 7, 8]);
600
601        let packets = parse_rx_aggregate(&aggregate).unwrap();
602        assert_eq!(packets.len(), 2);
603        assert_eq!(packets[0].data, &[1, 2, 3, 4, 5]);
604        assert_eq!(packets[1].data, &[6, 7, 8]);
605    }
606
607    #[test]
608    fn jaguar3_descriptor_matches_devourer_field_positions() {
609        let mut aggregate = Vec::new();
610        aggregate.extend_from_slice(&jaguar3_descriptor(4, 1, 2, 0x2c, false));
611        aggregate.extend_from_slice(&[41, 42, 0, 0, 0, 0, 0, 0]);
612        aggregate.extend_from_slice(&[0xaa, 0xbb]);
613        aggregate.extend_from_slice(&[1, 2, 3, 4]);
614
615        let packets = parse_rx_aggregate_with_kind(&aggregate, RxDescriptorKind::Jaguar3).unwrap();
616        assert_eq!(packets.len(), 1);
617        assert_eq!(packets[0].attrib.pkt_len, 4);
618        assert_eq!(packets[0].attrib.drvinfo_sz, 8);
619        assert_eq!(packets[0].attrib.shift_sz, 2);
620        assert_eq!(packets[0].attrib.data_rate, 0x2c);
621        assert!(packets[0].attrib.crc_err);
622        assert!(packets[0].attrib.icv_err);
623        assert_eq!(packets[0].attrib.pkt_rpt_type, RxPacketType::NormalRx);
624        assert_eq!(packets[0].data, &[1, 2, 3, 4]);
625    }
626
627    #[test]
628    fn jaguar3_c2h_bit_is_report_type() {
629        let mut aggregate = Vec::new();
630        aggregate.extend_from_slice(&jaguar3_descriptor(3, 0, 0, 0, true));
631        aggregate.extend_from_slice(&[0x61, 0x01, 0x02]);
632
633        let packets = parse_rx_aggregate_with_kind(&aggregate, RxDescriptorKind::Jaguar3).unwrap();
634        assert_eq!(packets.len(), 1);
635        assert_eq!(packets[0].attrib.pkt_rpt_type, RxPacketType::C2hPacket);
636        assert_eq!(packets[0].data, &[0x61, 0x01, 0x02]);
637    }
638
639    #[test]
640    fn c2h_payload_respects_drvinfo_and_shift_offsets() {
641        let mut desc = descriptor(3, 1, 2, 0);
642        let mut d2 = u32::from_le_bytes(desc[8..12].try_into().unwrap());
643        put_bits(&mut d2, 28, 1, 1);
644        desc[8..12].copy_from_slice(&d2.to_le_bytes());
645
646        let mut aggregate = Vec::new();
647        aggregate.extend_from_slice(&desc);
648        aggregate.extend_from_slice(&[0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48]);
649        aggregate.extend_from_slice(&[0xaa, 0xbb]);
650        aggregate.extend_from_slice(&[0x61, 0x07, 0xcc]);
651
652        let packets = parse_rx_aggregate(&aggregate).unwrap();
653        assert_eq!(packets.len(), 1);
654        assert_eq!(packets[0].attrib.pkt_rpt_type, RxPacketType::C2hPacket);
655        assert_eq!(packets[0].attrib.drvinfo_sz, 8);
656        assert_eq!(packets[0].attrib.shift_sz, 2);
657        assert_eq!(packets[0].data, &[0x61, 0x07, 0xcc]);
658        assert_eq!(
659            parse_c2h_packet(packets[0].data),
660            Some(C2hPacket {
661                cmd_id: 0x61,
662                seq: Some(0x07),
663                payload: &[0xcc],
664            })
665        );
666    }
667}