esp_radio/wifi/
csi.rs

1//! Wi-Fi Channel State Information (CSI).
2
3use alloc::boxed::Box;
4use core::marker::PhantomData;
5
6use esp_hal::time::{Duration, Instant};
7
8use super::{WifiError, c_types::c_void};
9#[cfg(any(wifi_mac_version = "2", wifi_mac_version = "3"))]
10use crate::sys::include::wifi_csi_acquire_config_t;
11use crate::{
12    sys::include::{
13        esp_wifi_set_csi,
14        esp_wifi_set_csi_config,
15        esp_wifi_set_csi_rx_cb,
16        wifi_csi_config_t,
17        wifi_csi_info_t,
18    },
19    wifi::{SecondaryChannel, esp_wifi_result},
20};
21
22/// CSI (Channel State Information) packet metadata and associated packet details.
23///
24/// This structure contains the raw CSI data, along with necessary metadata
25/// from the received Wi-Fi packet (MAC addresses, sequence number, packet headers).
26#[repr(transparent)]
27#[derive(Debug)]
28#[cfg_attr(feature = "defmt", derive(defmt::Format))]
29#[instability::unstable]
30pub struct WifiCsiInfo<'a> {
31    inner: *const wifi_csi_info_t,
32    _lt: PhantomData<&'a ()>,
33}
34
35impl<'a> WifiCsiInfo<'_> {
36    /// Received Signal Strength Indicator (RSSI) of the packet.
37    #[instability::unstable]
38    pub fn rssi(&self) -> i8 {
39        // Signed bitfields are broken in rust-bingen, see https://github.com/esp-rs/esp-wifi-sys/issues/482
40        // Hard-casting it from C-signed to i8 gives correct values, so no need for workaround
41        // here.
42        unsafe { (*self.inner).rx_ctrl.rssi() as i8 }
43    }
44
45    /// Data rate of the packet.
46    #[instability::unstable]
47    pub fn rate(&self) -> u8 {
48        unsafe { (*self.inner).rx_ctrl.rate() as u8 }
49    }
50
51    /// Protocol of the received packet, 0: non HT(11bg) packet; 1: HT(11n) packet; 3: VHT(11ac)
52    /// packet.
53    #[instability::unstable]
54    #[cfg(wifi_mac_version = "1")]
55    pub fn packet_mode(&self) -> u8 {
56        unsafe { (*self.inner).rx_ctrl.sig_mode() as u8 }
57    }
58
59    /// Modulation Coding Scheme. If is HT(11n) packet, shows the modulation, range from 0 to
60    /// 76(MSC0 ~ MCS76).
61    #[instability::unstable]
62    #[cfg(wifi_mac_version = "1")]
63    pub fn modulation_coding_scheme(&self) -> u8 {
64        unsafe { (*self.inner).rx_ctrl.mcs() as u8 }
65    }
66
67    /// Channel Bandwidth of the packet. 0: 20MHz; 1: 40MHz.
68    #[instability::unstable]
69    #[cfg(wifi_mac_version = "1")]
70    pub fn cwb(&self) -> bool {
71        unsafe { (*self.inner).rx_ctrl.cwb() != 0 }
72    }
73
74    /// Set to 1 indicates that channel estimate smoothing is recommended.
75    /// Set to 0 indicates that only per-carrier independent (unsmoothed) channel estimate is
76    /// recommended.
77    #[instability::unstable]
78    #[cfg(wifi_mac_version = "1")]
79    pub fn smoothing(&self) -> bool {
80        unsafe { (*self.inner).rx_ctrl.smoothing() != 0 }
81    }
82
83    /// Set to 1 indicates that the PPDU is not a sounding PPDU.
84    /// Set to 0 indicates that PPDU is a sounding PPDU.
85    #[instability::unstable]
86    #[cfg(wifi_mac_version = "1")]
87    pub fn not_sounding(&self) -> bool {
88        unsafe { (*self.inner).rx_ctrl.not_sounding() != 0 }
89    }
90
91    /// Aggregation. 0: MPDU packet; 1: AMPDU packet.
92    #[instability::unstable]
93    #[cfg(wifi_mac_version = "1")]
94    pub fn aggregation(&self) -> bool {
95        unsafe { (*self.inner).rx_ctrl.aggregation() != 0 }
96    }
97
98    /// Space Time Block Code(STBC). 0: non STBC packet; 1: STBC packet.
99    #[instability::unstable]
100    #[cfg(wifi_mac_version = "1")]
101    pub fn space_time_block_code(&self) -> u8 {
102        unsafe { (*self.inner).rx_ctrl.stbc() as u8 }
103    }
104
105    /// Forward Error Correction(FEC). Flag is set for 11n packets which are LDPC.
106    #[instability::unstable]
107    #[cfg(wifi_mac_version = "1")]
108    pub fn forward_error_correction_coding(&self) -> bool {
109        unsafe { (*self.inner).rx_ctrl.fec_coding() != 0 }
110    }
111
112    /// Short Guide Interval(SGI). 0: Long GI; 1: Short GI.
113    #[instability::unstable]
114    #[cfg(wifi_mac_version = "1")]
115    pub fn short_guide_interval(&self) -> bool {
116        unsafe { (*self.inner).rx_ctrl.sgi() != 0 }
117    }
118
119    /// Noise floor in dBm of Radio Frequency Module(RF).
120    #[instability::unstable]
121    pub fn noise_floor(&self) -> i8 {
122        unsafe { (*self.inner).rx_ctrl.noise_floor() as i8 }
123    }
124
125    /// The number of subframes aggregated in AMPDU.
126    #[instability::unstable]
127    #[cfg(wifi_mac_version = "1")]
128    pub fn ampdu_count(&self) -> u8 {
129        unsafe { (*self.inner).rx_ctrl.ampdu_cnt() as u8 }
130    }
131
132    /// Primary channel on which this packet is received.
133    #[instability::unstable]
134    pub fn channel(&self) -> u8 {
135        unsafe { (*self.inner).rx_ctrl.channel() as u8 }
136    }
137
138    /// [`SecondaryChannel`] on which this packet is received.
139    #[instability::unstable]
140    pub fn secondary_channel(&self) -> SecondaryChannel {
141        cfg_if::cfg_if! {
142            if #[cfg(wifi_mac_version = "1")] {
143                SecondaryChannel::from_raw(unsafe {
144                    (*self.inner).rx_ctrl.secondary_channel()
145                })
146            } else {
147                SecondaryChannel::from_raw(unsafe {
148                    (*self.inner).rx_ctrl.second()
149                })
150            }
151        }
152    }
153
154    /// The local time in microseconds when this packet is received. It is precise only if modem
155    /// sleep or light sleep is not enabled.
156    #[instability::unstable]
157    pub fn timestamp(&self) -> Instant {
158        let raw_ticks = unsafe { (*self.inner).rx_ctrl.timestamp() as u64 };
159
160        Instant::EPOCH + Duration::from_micros(raw_ticks)
161    }
162
163    /// Antenna number from which this packet is received.
164    /// 0: Wi-Fi antenna 0; 1: Wi-Fi antenna 1.
165    #[instability::unstable]
166    #[cfg(wifi_mac_version = "1")]
167    pub fn antenna(&self) -> u8 {
168        unsafe { (*self.inner).rx_ctrl.ant() as u8 }
169    }
170
171    /// The length of the reception MPDU.
172    #[instability::unstable]
173    pub fn signal_length(&self) -> u16 {
174        unsafe { (*self.inner).rx_ctrl.sig_len() as u16 }
175    }
176
177    /// State of the packet.
178    /// 0: no error; others: failure.
179    #[instability::unstable]
180    pub fn rx_state(&self) -> u8 {
181        unsafe { (*self.inner).rx_ctrl.rx_state() as u8 }
182    }
183
184    /// Indicate whether the reception frame is from interface 0.
185    #[instability::unstable]
186    #[cfg(esp32c6)]
187    pub fn rx_match0(&self) -> bool {
188        unsafe { (*self.inner).rx_ctrl.rxmatch0() != 0 }
189    }
190
191    /// Indicate whether the reception frame is from interface 1.
192    #[instability::unstable]
193    #[cfg(not(wifi_mac_version = "1"))]
194    pub fn rx_match1(&self) -> bool {
195        unsafe { (*self.inner).rx_ctrl.rxmatch1() != 0 }
196    }
197
198    /// Indicate whether the reception frame is from interface 2.
199    #[instability::unstable]
200    #[cfg(not(wifi_mac_version = "1"))]
201    pub fn rx_match2(&self) -> bool {
202        unsafe { (*self.inner).rx_ctrl.rxmatch2() != 0 }
203    }
204
205    /// Indicate whether the reception frame is from interface 3.
206    #[instability::unstable]
207    #[cfg(not(wifi_mac_version = "1"))]
208    pub fn rx_match3(&self) -> bool {
209        unsafe { (*self.inner).rx_ctrl.rxmatch3() != 0 }
210    }
211
212    /// HE-SIGA1 or HT-SIG.
213    #[instability::unstable]
214    #[cfg(not(wifi_mac_version = "1"))]
215    pub fn he_siga1(&self) -> u32 {
216        unsafe { (*self.inner).rx_ctrl.he_siga1 }
217    }
218
219    /// Reception state, 0: successful, others: failure.
220    #[instability::unstable]
221    #[cfg(not(wifi_mac_version = "1"))]
222    pub fn rx_end_state(&self) -> u8 {
223        unsafe { (*self.inner).rx_ctrl.rxend_state() as u8 }
224    }
225
226    /// HE-SIGA2.
227    #[instability::unstable]
228    #[cfg(not(wifi_mac_version = "1"))]
229    pub fn he_siga2(&self) -> u16 {
230        unsafe { (*self.inner).rx_ctrl.he_siga2 }
231    }
232
233    /// Indicate whether the reception is a group addressed frame.
234    #[instability::unstable]
235    #[cfg(not(wifi_mac_version = "1"))]
236    pub fn is_group(&self) -> bool {
237        unsafe { (*self.inner).rx_ctrl.is_group() != 0 }
238    }
239
240    /// The length of the channel information.
241    #[instability::unstable]
242    #[cfg(not(wifi_mac_version = "1"))]
243    pub fn rx_channel_estimate_length(&self) -> u32 {
244        unsafe { (*self.inner).rx_ctrl.rx_channel_estimate_len() }
245    }
246
247    /// Indicate the channel information is valid.
248    #[instability::unstable]
249    #[cfg(not(wifi_mac_version = "1"))]
250    pub fn rx_channel_estimate_info_valid(&self) -> bool {
251        unsafe { (*self.inner).rx_ctrl.rx_channel_estimate_info_vld() != 0 }
252    }
253
254    /// The format of the reception frame.
255    #[instability::unstable]
256    #[cfg(not(wifi_mac_version = "1"))]
257    pub fn cur_bb_format(&self) -> u8 {
258        unsafe { (*self.inner).rx_ctrl.cur_bb_format() as u8 }
259    }
260
261    /// Indicate whether the reception MPDU is a S-MPDU.
262    #[instability::unstable]
263    #[cfg(wifi_mac_version = "2")]
264    pub fn cur_single_mpdu(&self) -> bool {
265        unsafe { (*self.inner).rx_ctrl.cur_single_mpdu() != 0 }
266    }
267
268    /// The length of HE-SIGB.
269    #[instability::unstable]
270    #[cfg(wifi_mac_version = "2")]
271    pub fn he_sigb_length(&self) -> u8 {
272        unsafe { (*self.inner).rx_ctrl.he_sigb_len() as u8 }
273    }
274
275    /// The length of the reception MPDU excluding the FCS.
276    #[instability::unstable]
277    #[cfg(not(wifi_mac_version = "1"))]
278    pub fn dump_length(&self) -> u32 {
279        unsafe { (*self.inner).rx_ctrl.dump_len() }
280    }
281
282    /// Source MAC address of the CSI data.
283    #[instability::unstable]
284    pub fn mac(&self) -> &[u8; 6] {
285        unsafe { &(*self.inner).mac }
286    }
287
288    /// Destination MAC address of the CSI data.
289    #[instability::unstable]
290    pub fn destination_mac(&self) -> &[u8; 6] {
291        unsafe { &(*self.inner).dmac }
292    }
293
294    /// First four bytes of the CSI data is invalid or not, true indicates the first four bytes is
295    /// invalid due to hardware limitation.
296    #[instability::unstable]
297    pub fn first_word_invalid(&self) -> bool {
298        unsafe { (*self.inner).first_word_invalid }
299    }
300
301    /// Valid buffer of CSI data.
302    #[instability::unstable]
303    pub fn buf(&self) -> &[i8] {
304        unsafe {
305            if (*self.inner).buf.is_null() || (*self.inner).len == 0 {
306                &[]
307            } else {
308                core::slice::from_raw_parts((*self.inner).buf, (*self.inner).len as usize)
309            }
310        }
311    }
312
313    /// Header of the wifi packet.
314    #[instability::unstable]
315    pub fn header(&self) -> &[u8] {
316        unsafe {
317            if (*self.inner).hdr.is_null() {
318                &[]
319            } else {
320                const HDR_LEN: usize = 24;
321                core::slice::from_raw_parts((*self.inner).hdr, HDR_LEN)
322            }
323        }
324    }
325
326    /// Payload of the wifi packet.
327    #[instability::unstable]
328    pub fn payload(&self) -> &[u8] {
329        unsafe {
330            if (*self.inner).payload.is_null() || (*self.inner).payload_len == 0 {
331                &[]
332            } else {
333                core::slice::from_raw_parts(
334                    (*self.inner).payload,
335                    (*self.inner).payload_len as usize,
336                )
337            }
338        }
339    }
340
341    /// Rx sequence number of the wifi packet.
342    #[instability::unstable]
343    pub fn rx_sequence(&self) -> u16 {
344        unsafe { (*self.inner).rx_seq }
345    }
346}
347
348pub(crate) trait CsiCallback: FnMut(WifiCsiInfo<'_>) {}
349
350impl<T> CsiCallback for T where T: FnMut(WifiCsiInfo<'_>) {}
351
352unsafe extern "C" fn csi_rx_cb<C: CsiCallback>(ctx: *mut c_void, data: *mut wifi_csi_info_t) {
353    unsafe {
354        let csi_callback = &mut *(ctx as *mut C);
355
356        let data = WifiCsiInfo {
357            inner: data,
358            _lt: PhantomData,
359        };
360        csi_callback(data);
361    }
362}
363
364/// Channel state information (CSI) configuration.
365#[derive(Debug, Clone, PartialEq, Eq)]
366#[cfg_attr(feature = "defmt", derive(defmt::Format))]
367#[cfg(wifi_mac_version = "1")]
368#[instability::unstable]
369pub struct CsiConfig {
370    /// Enable to receive legacy long training field(lltf) data.
371    pub lltf_en: bool,
372    /// Enable to receive HT long training field(htltf) data.
373    pub htltf_en: bool,
374    /// Enable to receive space time block code HT long training
375    /// field(stbc-htltf2) data.
376    pub stbc_htltf2_en: bool,
377    /// Enable to generate htlft data by averaging lltf and ht_ltf data when
378    /// receiving HT packet. Otherwise, use ht_ltf data directly.
379    pub ltf_merge_en: bool,
380    /// Enable to turn on channel filter to smooth adjacent sub-carrier. Disable
381    /// it to keep independence of adjacent sub-carrier.
382    pub channel_filter_en: bool,
383    /// Manually scale the CSI data by left shifting or automatically scale the
384    /// CSI data. If set true, please set the shift bits. false: automatically.
385    /// true: manually.
386    pub manu_scale: bool,
387    /// Manually left shift bits of the scale of the CSI data. The range of the
388    /// left shift bits is 0~15.
389    pub shift: u8,
390    /// Enable to dump 802.11 ACK frame.
391    pub dump_ack_en: bool,
392}
393
394/// Channel state information (CSI) configuration.
395#[derive(Debug, Clone, PartialEq, Eq)]
396#[cfg_attr(feature = "defmt", derive(defmt::Format))]
397#[cfg(wifi_mac_version = "2")]
398#[instability::unstable]
399pub struct CsiConfig {
400    /// Enable to acquire CSI.
401    pub enable: u32,
402    /// Enable to acquire L-LTF when receiving a 11g PPDU.
403    pub acquire_csi_legacy: u32,
404    /// Enable to acquire HT-LTF when receiving an HT20 PPDU.
405    pub acquire_csi_ht20: u32,
406    /// Enable to acquire HT-LTF when receiving an HT40 PPDU.
407    pub acquire_csi_ht40: u32,
408    /// Enable to acquire HE-LTF when receiving an HE20 SU PPDU.
409    pub acquire_csi_su: u32,
410    /// Enable to acquire HE-LTF when receiving an HE20 MU PPDU.
411    pub acquire_csi_mu: u32,
412    /// Enable to acquire HE-LTF when receiving an HE20 DCM applied PPDU.
413    pub acquire_csi_dcm: u32,
414    /// Enable to acquire HE-LTF when receiving an HE20 Beamformed applied PPDU.
415    pub acquire_csi_beamformed: u32,
416    /// When receiving an STBC applied HE PPDU, 0- acquire the complete
417    /// HE-LTF1,  1- acquire the complete HE-LTF2, 2- sample evenly among the
418    /// HE-LTF1 and HE-LTF2.
419    pub acquire_csi_he_stbc: u32,
420    /// Value 0-3.
421    pub val_scale_cfg: u32,
422    /// Enable to dump 802.11 ACK frame, default disabled.
423    pub dump_ack_en: u32,
424    /// Reserved.
425    pub reserved: u32,
426}
427
428/// Channel state information (CSI) configuration.
429#[derive(Debug, Clone, PartialEq, Eq)]
430#[cfg_attr(feature = "defmt", derive(defmt::Format))]
431#[cfg(wifi_mac_version = "3")]
432#[instability::unstable]
433pub struct CsiConfig {
434    /// Enable to acquire CSI.
435    pub enable: u32,
436    /// Enable to acquire L-LTF.
437    pub acquire_csi_legacy: u32,
438    /// Enable to acquire L-LTF.
439    pub acquire_csi_force_lltf: bool,
440    /// Enable to acquire HT-LTF when receiving an HT20 PPDU.
441    pub acquire_csi_ht20: u32,
442    /// Enable to acquire HT-LTF when receiving an HT40 PPDU.
443    pub acquire_csi_ht40: u32,
444    /// Enable to acquire VHT-LTF when receiving an VHT20 PPDU.
445    pub acquire_csi_vht: bool,
446    /// Enable to acquire HE-LTF when receiving an HE20 SU PPDU.
447    pub acquire_csi_su: u32,
448    /// Enable to acquire HE-LTF when receiving an HE20 MU PPDU.
449    pub acquire_csi_mu: u32,
450    /// Enable to acquire HE-LTF when receiving an HE20 DCM applied PPDU.
451    pub acquire_csi_dcm: u32,
452    /// Enable to acquire HE-LTF when receiving an HE20 Beamformed applied PPDU.
453    pub acquire_csi_beamformed: u32,
454    /// When receiving an STBC applied HE PPDU, 0- acquire the complete
455    /// HE-LTF1,  1- acquire the complete HE-LTF2, 2- sample evenly among the
456    /// HE-LTF1 and HE-LTF2.
457    pub acquire_csi_he_stbc: u32,
458    /// Value 0-3.
459    pub val_scale_cfg: u32,
460    /// Enable to dump 802.11 ACK frame, default disabled.
461    pub dump_ack_en: u32,
462    /// Reserved.
463    pub reserved: u32,
464}
465
466impl CsiConfig {
467    /// Set CSI data configuration
468    pub(crate) fn apply_config(&self) -> Result<(), WifiError> {
469        let conf: wifi_csi_config_t = self.clone().into();
470        unsafe { esp_wifi_result!(esp_wifi_set_csi_config(&conf))? };
471        Ok(())
472    }
473
474    /// Register the RX callback function of CSI data. Each time a CSI data is
475    /// received, the callback function will be called.
476    pub(crate) fn set_receive_cb<C>(&mut self, cb: C) -> Result<(), WifiError>
477    where
478        C: CsiCallback,
479    {
480        let cb = Box::new(cb);
481        let cb_ptr = Box::into_raw(cb) as *mut c_void;
482
483        unsafe { esp_wifi_result!(esp_wifi_set_csi_rx_cb(Some(csi_rx_cb::<C>), cb_ptr))? };
484        Ok(())
485    }
486
487    /// Enable or disable CSI
488    pub(crate) fn set_csi(&self, enable: bool) -> Result<(), WifiError> {
489        // https://github.com/esp-rs/esp-wifi-sys/blob/2a466d96fe8119d49852fc794aea0216b106ba7b/esp-wifi-sys/headers/esp_wifi.h#L1241
490        unsafe { esp_wifi_result!(esp_wifi_set_csi(enable))? };
491        Ok(())
492    }
493}
494
495impl Default for CsiConfig {
496    #[cfg(wifi_mac_version = "1")]
497    fn default() -> Self {
498        Self {
499            lltf_en: true,
500            htltf_en: true,
501            stbc_htltf2_en: true,
502            ltf_merge_en: true,
503            channel_filter_en: true,
504            manu_scale: false,
505            shift: 0,
506            dump_ack_en: false,
507        }
508    }
509
510    #[cfg(wifi_mac_version = "2")]
511    fn default() -> Self {
512        // https://github.com/esp-rs/esp-wifi-sys/blob/2a466d96fe8119d49852fc794aea0216b106ba7b/esp-wifi-sys/headers/esp_wifi_he_types.h#L67-L82
513        Self {
514            enable: 1,
515            acquire_csi_legacy: 1,
516            acquire_csi_ht20: 1,
517            acquire_csi_ht40: 1,
518            acquire_csi_su: 1,
519            acquire_csi_mu: 1,
520            acquire_csi_dcm: 1,
521            acquire_csi_beamformed: 1,
522            acquire_csi_he_stbc: 2,
523            val_scale_cfg: 2,
524            dump_ack_en: 1,
525            reserved: 19,
526        }
527    }
528
529    #[cfg(wifi_mac_version = "3")]
530    fn default() -> Self {
531        // https://github.com/esp-rs/esp-wifi-sys/blob/2a466d96fe8119d49852fc794aea0216b106ba7b/esp-wifi-sys/headers/esp_wifi_he_types.h
532        Self {
533            enable: 1,
534            acquire_csi_legacy: 1,
535            acquire_csi_force_lltf: true,
536            acquire_csi_ht20: 1,
537            acquire_csi_ht40: 1,
538            acquire_csi_vht: true,
539            acquire_csi_su: 1,
540            acquire_csi_mu: 1,
541            acquire_csi_dcm: 1,
542            acquire_csi_beamformed: 1,
543            acquire_csi_he_stbc: 2,
544            val_scale_cfg: 2,
545            dump_ack_en: 1,
546            reserved: 0,
547        }
548    }
549}
550
551#[doc(hidden)]
552impl From<CsiConfig> for wifi_csi_config_t {
553    fn from(config: CsiConfig) -> Self {
554        #[cfg(wifi_mac_version = "1")]
555        {
556            wifi_csi_config_t {
557                lltf_en: config.lltf_en,
558                htltf_en: config.htltf_en,
559                stbc_htltf2_en: config.stbc_htltf2_en,
560                ltf_merge_en: config.ltf_merge_en,
561                channel_filter_en: config.channel_filter_en,
562                manu_scale: config.manu_scale,
563                shift: config.shift,
564                dump_ack_en: config.dump_ack_en,
565            }
566        }
567        #[cfg(wifi_mac_version = "2")]
568        {
569            wifi_csi_acquire_config_t {
570                _bitfield_align_1: [0; 0],
571                _bitfield_1: wifi_csi_acquire_config_t::new_bitfield_1(
572                    config.enable,
573                    config.acquire_csi_legacy,
574                    config.acquire_csi_ht20,
575                    config.acquire_csi_ht40,
576                    config.acquire_csi_su,
577                    config.acquire_csi_mu,
578                    config.acquire_csi_dcm,
579                    config.acquire_csi_beamformed,
580                    config.acquire_csi_he_stbc,
581                    config.val_scale_cfg,
582                    config.dump_ack_en,
583                    config.reserved,
584                ),
585            }
586        }
587        #[cfg(wifi_mac_version = "3")]
588        {
589            wifi_csi_acquire_config_t {
590                _bitfield_align_1: [0; 0],
591                _bitfield_1: wifi_csi_acquire_config_t::new_bitfield_1(
592                    config.enable,
593                    config.acquire_csi_legacy,
594                    config.acquire_csi_ht20,
595                    config.acquire_csi_ht40,
596                    config.acquire_csi_su,
597                    config.acquire_csi_mu,
598                    config.acquire_csi_dcm,
599                    config.acquire_csi_beamformed,
600                    config.acquire_csi_he_stbc,
601                    config.val_scale_cfg,
602                    config.dump_ack_en,
603                    config.reserved,
604                    0, // dump_ack_en
605                    0, // lltf_bit_mode
606                    0, // reserved
607                ),
608            }
609        }
610    }
611}