1pub const RX_DESC_SIZE: usize = 24;
3pub const DEFAULT_RX_TRANSFER_SIZE: usize = 32 * 1024;
5
6#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
12pub enum RxDescriptorKind {
13 #[default]
15 Jaguar1,
16 Jaguar2,
18 Jaguar3,
20}
21
22#[derive(Debug, Clone, Copy, PartialEq, Eq)]
24pub enum RxPacketType {
25 NormalRx,
27 C2hPacket,
29}
30
31#[derive(Debug, Clone, Copy, PartialEq, Eq)]
33pub struct RxPacketAttrib {
34 pub pkt_len: u16,
36 pub physt: bool,
38 pub drvinfo_sz: u8,
40 pub shift_sz: u8,
42 pub qos: bool,
44 pub priority: u8,
46 pub mdata: bool,
48 pub seq_num: u16,
50 pub frag_num: u8,
52 pub mfrag: bool,
54 pub bdecrypted: bool,
56 pub encrypt: u8,
58 pub crc_err: bool,
60 pub icv_err: bool,
62 pub tsfl: u32,
64 pub data_rate: u8,
66 pub bw: u8,
68 pub stbc: u8,
70 pub ldpc: u8,
72 pub sgi: u8,
74 pub scrambler: u8,
76 pub rssi: [u8; 4],
78 pub snr: [i8; 4],
80 pub evm: [i8; 4],
82 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#[derive(Debug, Clone, Copy)]
120pub struct RealtekRxPacket<'a> {
121 pub attrib: RxPacketAttrib,
123 pub data: &'a [u8],
125}
126
127#[derive(Debug, Clone, Copy, PartialEq, Eq)]
129pub struct C2hPacket<'a> {
130 pub cmd_id: u8,
132 pub seq: Option<u8>,
134 pub payload: &'a [u8],
136}
137
138#[derive(Debug, Clone, Copy, PartialEq, Eq)]
140pub struct TxStatusReport8814 {
141 pub header_offset: usize,
143 pub queue_select: u8,
145 pub packet_broadcast: bool,
147 pub lifetime_over: bool,
149 pub retry_over: bool,
151 pub mac_id: u8,
153 pub data_retry_count: u8,
155 pub queue_time_us: u32,
157 pub final_data_rate: u8,
159}
160
161#[derive(Debug, Clone, PartialEq, Eq)]
163pub enum AggregateError {
164 DescriptorTooShort,
166 InvalidPacketLength {
168 pkt_len: u16,
170 pkt_offset: usize,
172 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
195pub fn parse_rx_descriptor(desc: &[u8]) -> Result<RxPacketAttrib, AggregateError> {
197 parse_rx_descriptor_with_kind(desc, RxDescriptorKind::Jaguar1)
198}
199
200pub 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
252pub fn parse_rx_aggregate(buf: &[u8]) -> Result<Vec<RealtekRxPacket<'_>>, AggregateError> {
258 parse_rx_aggregate_with_kind(buf, RxDescriptorKind::Jaguar1)
259}
260
261pub 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], kind);
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
313pub 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
325pub 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
333pub 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], kind: RxDescriptorKind) {
374 if kind != RxDescriptorKind::Jaguar1 {
375 parse_phy_status_jaguar23(attrib, phy, kind);
376 return;
377 }
378 if phy.len() < 2 {
379 return;
380 }
381
382 attrib.rssi[0] = phy[0];
383 attrib.rssi[1] = phy[1];
384
385 if phy.len() < 28 {
386 return;
387 }
388
389 attrib.rssi[2] = phy[23];
390 attrib.rssi[3] = phy[24];
391 attrib.snr[0] = phy[15] as i8;
392 attrib.snr[1] = phy[16] as i8;
393 attrib.snr[2] = phy[21] as i8;
394 attrib.snr[3] = phy[22] as i8;
395 attrib.evm[0] = phy[13] as i8;
396 attrib.evm[1] = phy[14] as i8;
397 attrib.evm[2] = phy[19] as i8;
398 attrib.evm[3] = phy[20] as i8;
399}
400
401fn parse_phy_status_jaguar23(attrib: &mut RxPacketAttrib, phy: &[u8], kind: RxDescriptorKind) {
402 if phy.len() < 28 {
403 return;
404 }
405 let is_cck = if kind == RxDescriptorKind::Jaguar3 {
406 phy[0] & 0x0f == 0
407 } else {
408 attrib.data_rate <= 3
409 };
410 if is_cck {
411 attrib.rssi[0] = phy[1];
412 return;
413 }
414 attrib.rssi.copy_from_slice(&phy[1..5]);
415 attrib.ldpc = (phy[7] >> 5) & 1;
416 attrib.stbc = (phy[7] >> 6) & 1;
417 let l_rxsc = phy[5] & 0x0f;
418 let ht_rxsc = (phy[5] >> 4) & 0x0f;
419 let rxsc = if (4..=11).contains(&attrib.data_rate) {
420 l_rxsc
421 } else {
422 ht_rxsc
423 };
424 attrib.bw = if rxsc >= 13 {
425 2
426 } else if rxsc >= 9 {
427 1
428 } else {
429 0
430 };
431
432 if kind == RxDescriptorKind::Jaguar2 || phy[0] & 0x0f == 1 {
434 for path in 0..4 {
435 attrib.evm[path] = phy[16 + path] as i8;
436 attrib.snr[path] = phy[24 + path] as i8;
437 }
438 }
439}
440
441#[cfg(test)]
442mod tests {
443 use super::*;
444
445 fn put_bits(word: &mut u32, offset: u8, len: u8, value: u32) {
446 let mask = ((1u32 << len) - 1) << offset;
447 *word = (*word & !mask) | ((value << offset) & mask);
448 }
449
450 fn descriptor(pkt_len: u16, drvinfo_units: u8, shift: u8, seq: u16) -> [u8; RX_DESC_SIZE] {
451 let mut desc = [0; RX_DESC_SIZE];
452 let mut d0 = 0u32;
453 put_bits(&mut d0, 0, 14, pkt_len as u32);
454 put_bits(&mut d0, 16, 4, drvinfo_units as u32);
455 put_bits(&mut d0, 24, 2, shift as u32);
456 let mut d2 = 0u32;
457 put_bits(&mut d2, 0, 12, seq as u32);
458 desc[0..4].copy_from_slice(&d0.to_le_bytes());
459 desc[8..12].copy_from_slice(&d2.to_le_bytes());
460 desc
461 }
462
463 fn jaguar3_descriptor(
464 pkt_len: u16,
465 drvinfo_units: u8,
466 shift: u8,
467 rx_rate: u8,
468 c2h: bool,
469 ) -> [u8; RX_DESC_SIZE] {
470 let mut desc = [0; RX_DESC_SIZE];
471 let mut d0 = 0u32;
472 put_bits(&mut d0, 0, 14, pkt_len as u32);
473 put_bits(&mut d0, 14, 1, 1);
474 put_bits(&mut d0, 15, 1, 1);
475 put_bits(&mut d0, 16, 4, drvinfo_units as u32);
476 put_bits(&mut d0, 24, 2, shift as u32);
477 let mut d2 = 0u32;
478 put_bits(&mut d2, 28, 1, u32::from(c2h));
479 let mut d3 = 0u32;
480 put_bits(&mut d3, 0, 7, rx_rate as u32);
481 desc[0..4].copy_from_slice(&d0.to_le_bytes());
482 desc[8..12].copy_from_slice(&d2.to_le_bytes());
483 desc[12..16].copy_from_slice(&d3.to_le_bytes());
484 desc
485 }
486
487 #[test]
488 fn parses_single_rx_packet() {
489 let mut aggregate = Vec::new();
490 aggregate.extend_from_slice(&descriptor(4, 0, 0, 77));
491 aggregate.extend_from_slice(&[1, 2, 3, 4]);
492
493 let packets = parse_rx_aggregate(&aggregate).unwrap();
494 assert_eq!(packets.len(), 1);
495 assert_eq!(packets[0].attrib.pkt_len, 4);
496 assert_eq!(packets[0].attrib.seq_num, 77);
497 assert_eq!(packets[0].data, &[1, 2, 3, 4]);
498 }
499
500 #[test]
501 fn ignores_short_tail_after_aligned_packet() {
502 let mut aggregate = Vec::new();
503 aggregate.extend_from_slice(&descriptor(4, 0, 0, 77));
504 aggregate.extend_from_slice(&[1, 2, 3, 4]);
505 aggregate.extend_from_slice(&[0, 0, 0, 0]);
506 aggregate.extend_from_slice(&[0xde, 0xad, 0xbe, 0xef]);
507
508 let packets = parse_rx_aggregate(&aggregate).unwrap();
509 assert_eq!(packets.len(), 1);
510 assert_eq!(packets[0].data, &[1, 2, 3, 4]);
511 }
512
513 #[test]
514 fn rejects_zero_length_descriptor() {
515 let aggregate = descriptor(0, 0, 0, 1);
516
517 let err = parse_rx_aggregate(&aggregate).unwrap_err();
518 assert_eq!(
519 err,
520 AggregateError::InvalidPacketLength {
521 pkt_len: 0,
522 pkt_offset: RX_DESC_SIZE,
523 remaining: RX_DESC_SIZE,
524 }
525 );
526 }
527
528 #[test]
529 fn rejects_descriptor_payload_past_transfer() {
530 let mut aggregate = Vec::new();
531 aggregate.extend_from_slice(&descriptor(8, 0, 0, 1));
532 aggregate.extend_from_slice(&[1, 2, 3, 4]);
533
534 let err = parse_rx_aggregate(&aggregate).unwrap_err();
535 assert_eq!(
536 err,
537 AggregateError::InvalidPacketLength {
538 pkt_len: 8,
539 pkt_offset: RX_DESC_SIZE + 8,
540 remaining: RX_DESC_SIZE + 4,
541 }
542 );
543 }
544
545 #[test]
546 fn surfaces_crc_and_icv_flags_from_jaguar1_descriptor() {
547 let mut desc = descriptor(4, 0, 0, 9);
548 let mut d0 = u32::from_le_bytes(desc[0..4].try_into().unwrap());
549 put_bits(&mut d0, 14, 1, 1);
550 put_bits(&mut d0, 15, 1, 1);
551 desc[0..4].copy_from_slice(&d0.to_le_bytes());
552
553 let mut aggregate = Vec::new();
554 aggregate.extend_from_slice(&desc);
555 aggregate.extend_from_slice(&[1, 2, 3, 4]);
556
557 let packets = parse_rx_aggregate(&aggregate).unwrap();
558 assert!(packets[0].attrib.crc_err);
559 assert!(packets[0].attrib.icv_err);
560 assert_eq!(packets[0].data, &[1, 2, 3, 4]);
561 }
562
563 #[test]
564 fn does_not_decode_payload_as_phy_status_when_drvinfo_absent() {
565 let mut payload = vec![0u8; 32];
566 payload[0] = 55;
567 payload[1] = 66;
568 payload[13] = 0x80;
569 payload[15] = 0x7f;
570 payload[23] = 77;
571
572 let mut aggregate = Vec::new();
573 aggregate.extend_from_slice(&descriptor(payload.len() as u16, 0, 0, 1));
574 aggregate.extend_from_slice(&payload);
575
576 let packets = parse_rx_aggregate(&aggregate).unwrap();
577 assert_eq!(packets[0].attrib.rssi, [0; 4]);
578 assert_eq!(packets[0].attrib.snr, [0; 4]);
579 assert_eq!(packets[0].attrib.evm, [0; 4]);
580 assert_eq!(packets[0].data, payload);
581 }
582
583 #[test]
584 fn parses_phy_status_only_from_driver_info_bytes() {
585 let mut phy = vec![0u8; 32];
586 phy[0] = 42;
587 phy[1] = 43;
588 phy[13] = 0xf0;
589 phy[14] = 0x0f;
590 phy[15] = 0x80;
591 phy[16] = 0x7f;
592 phy[19] = 0xee;
593 phy[20] = 0xdd;
594 phy[21] = 0xcc;
595 phy[22] = 0xbb;
596 phy[23] = 44;
597 phy[24] = 45;
598
599 let mut aggregate = Vec::new();
600 aggregate.extend_from_slice(&descriptor(3, 4, 1, 1));
601 aggregate.extend_from_slice(&phy);
602 aggregate.push(0xaa);
603 aggregate.extend_from_slice(&[1, 2, 3]);
604
605 let packets = parse_rx_aggregate(&aggregate).unwrap();
606 assert_eq!(packets[0].attrib.drvinfo_sz, 32);
607 assert_eq!(packets[0].attrib.shift_sz, 1);
608 assert_eq!(packets[0].attrib.rssi, [42, 43, 44, 45]);
609 assert_eq!(packets[0].attrib.snr, [-128, 127, -52, -69]);
610 assert_eq!(packets[0].attrib.evm, [-16, 15, -18, -35]);
611 assert_eq!(packets[0].data, &[1, 2, 3]);
612 }
613
614 #[test]
615 fn parses_c2h_packet_header() {
616 let packet = parse_c2h_packet(&[0x42, 0x7f, 1, 2]).unwrap();
617 assert_eq!(packet.cmd_id, 0x42);
618 assert_eq!(packet.seq, Some(0x7f));
619 assert_eq!(packet.payload, &[1, 2]);
620 }
621
622 #[test]
623 fn parses_8814_tx_status_at_devourer_offsets() {
624 let bytes = [0xaa, 0x83, 0x09, 0x25, 0x34, 0x12, 0x6c, 0xbb];
625 let reports = parse_8814_tx_status_reports(&bytes);
626 assert_eq!(reports.len(), 2);
627 assert_eq!(reports[0].header_offset, 1);
628 assert_eq!(reports[0].queue_select, 3);
629 assert!(reports[0].retry_over);
630 assert_eq!(reports[0].mac_id, 9);
631 assert_eq!(reports[0].data_retry_count, 0x25);
632 assert_eq!(reports[0].queue_time_us, 0x1234 * 256);
633 assert_eq!(reports[0].final_data_rate, 0x6c);
634 }
635
636 #[test]
637 fn advances_by_jaguar_eight_byte_alignment() {
638 let mut aggregate = Vec::new();
639 aggregate.extend_from_slice(&descriptor(5, 0, 0, 1));
640 aggregate.extend_from_slice(&[1, 2, 3, 4, 5]);
641 aggregate.extend_from_slice(&[0, 0, 0]);
642 aggregate.extend_from_slice(&descriptor(3, 0, 0, 2));
643 aggregate.extend_from_slice(&[6, 7, 8]);
644
645 let packets = parse_rx_aggregate(&aggregate).unwrap();
646 assert_eq!(packets.len(), 2);
647 assert_eq!(packets[0].data, &[1, 2, 3, 4, 5]);
648 assert_eq!(packets[1].data, &[6, 7, 8]);
649 }
650
651 #[test]
652 fn jaguar3_descriptor_matches_devourer_field_positions() {
653 let mut aggregate = Vec::new();
654 aggregate.extend_from_slice(&jaguar3_descriptor(4, 1, 2, 0x2c, false));
655 aggregate.extend_from_slice(&[41, 42, 0, 0, 0, 0, 0, 0]);
656 aggregate.extend_from_slice(&[0xaa, 0xbb]);
657 aggregate.extend_from_slice(&[1, 2, 3, 4]);
658
659 let packets = parse_rx_aggregate_with_kind(&aggregate, RxDescriptorKind::Jaguar3).unwrap();
660 assert_eq!(packets.len(), 1);
661 assert_eq!(packets[0].attrib.pkt_len, 4);
662 assert_eq!(packets[0].attrib.drvinfo_sz, 8);
663 assert_eq!(packets[0].attrib.shift_sz, 2);
664 assert_eq!(packets[0].attrib.data_rate, 0x2c);
665 assert!(packets[0].attrib.crc_err);
666 assert!(packets[0].attrib.icv_err);
667 assert_eq!(packets[0].attrib.pkt_rpt_type, RxPacketType::NormalRx);
668 assert_eq!(packets[0].data, &[1, 2, 3, 4]);
669 }
670
671 #[test]
672 fn jaguar3_c2h_bit_is_report_type() {
673 let mut aggregate = Vec::new();
674 aggregate.extend_from_slice(&jaguar3_descriptor(3, 0, 0, 0, true));
675 aggregate.extend_from_slice(&[0x61, 0x01, 0x02]);
676
677 let packets = parse_rx_aggregate_with_kind(&aggregate, RxDescriptorKind::Jaguar3).unwrap();
678 assert_eq!(packets.len(), 1);
679 assert_eq!(packets[0].attrib.pkt_rpt_type, RxPacketType::C2hPacket);
680 assert_eq!(packets[0].data, &[0x61, 0x01, 0x02]);
681 }
682
683 #[test]
684 fn jaguar3_type1_phy_status_uses_jgr3_offsets() {
685 let mut phy = [0u8; 32];
686 phy[0] = 1;
687 phy[1..5].copy_from_slice(&[71, 57, 0, 0]);
688 phy[5] = 13 << 4;
689 phy[7] = (1 << 5) | (1 << 6);
690 phy[16..20].copy_from_slice(&[0xd5, 0x80, 0, 0]);
691 phy[24..28].copy_from_slice(&[43, 25, 0, 0]);
692 let mut aggregate = Vec::new();
693 aggregate.extend_from_slice(&jaguar3_descriptor(3, 4, 0, 13, false));
694 aggregate.extend_from_slice(&phy);
695 aggregate.extend_from_slice(&[1, 2, 3]);
696
697 let packet = parse_rx_aggregate_with_kind(&aggregate, RxDescriptorKind::Jaguar3)
698 .unwrap()
699 .remove(0);
700 assert_eq!(packet.attrib.rssi, [71, 57, 0, 0]);
701 assert_eq!(packet.attrib.evm[0], -43);
702 assert_eq!(packet.attrib.snr[..2], [43, 25]);
703 assert_eq!(packet.attrib.bw, 2);
704 assert_eq!((packet.attrib.ldpc, packet.attrib.stbc), (1, 1));
705 }
706
707 #[test]
708 fn jaguar3_non_type1_page_does_not_decode_evm_or_snr() {
709 let mut phy = [0u8; 32];
710 phy[0] = 2;
711 phy[1] = 64;
712 phy[16] = 0x80;
713 phy[24] = 0x7f;
714 let mut aggregate = Vec::new();
715 aggregate.extend_from_slice(&jaguar3_descriptor(1, 4, 0, 13, false));
716 aggregate.extend_from_slice(&phy);
717 aggregate.push(0xaa);
718 let packet = parse_rx_aggregate_with_kind(&aggregate, RxDescriptorKind::Jaguar3)
719 .unwrap()
720 .remove(0);
721 assert_eq!(packet.attrib.rssi[0], 64);
722 assert_eq!(packet.attrib.evm, [0; 4]);
723 assert_eq!(packet.attrib.snr, [0; 4]);
724 }
725
726 #[test]
727 fn c2h_payload_respects_drvinfo_and_shift_offsets() {
728 let mut desc = descriptor(3, 1, 2, 0);
729 let mut d2 = u32::from_le_bytes(desc[8..12].try_into().unwrap());
730 put_bits(&mut d2, 28, 1, 1);
731 desc[8..12].copy_from_slice(&d2.to_le_bytes());
732
733 let mut aggregate = Vec::new();
734 aggregate.extend_from_slice(&desc);
735 aggregate.extend_from_slice(&[0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48]);
736 aggregate.extend_from_slice(&[0xaa, 0xbb]);
737 aggregate.extend_from_slice(&[0x61, 0x07, 0xcc]);
738
739 let packets = parse_rx_aggregate(&aggregate).unwrap();
740 assert_eq!(packets.len(), 1);
741 assert_eq!(packets[0].attrib.pkt_rpt_type, RxPacketType::C2hPacket);
742 assert_eq!(packets[0].attrib.drvinfo_sz, 8);
743 assert_eq!(packets[0].attrib.shift_sz, 2);
744 assert_eq!(packets[0].data, &[0x61, 0x07, 0xcc]);
745 assert_eq!(
746 parse_c2h_packet(packets[0].data),
747 Some(C2hPacket {
748 cmd_id: 0x61,
749 seq: Some(0x07),
750 payload: &[0xcc],
751 })
752 );
753 }
754}