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