wlan_mlme/
block_ack.rs

1// Copyright 2020 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5// TODO(https://fxbug.dev/42104687): Deny this lint once this code is exercised by the client state machine.
6//                        See `Associated::on_block_ack_frame` and https://fxbug.dev/42180615.
7#![allow(dead_code)]
8
9//! BlockAck API and state.
10//!
11//! This module provides a BlockAck state machine and a trait implemented by types that interact
12//! with BlockAck. To use the state machine, a type implementing `BlockAckTx` must be provided that
13//! transmits BlockAck frames emitted by the state machine.
14//!
15//! See IEEE Std 802.11-2016, 10.24.
16
17use crate::error::Error;
18use fidl_fuchsia_wlan_ieee80211 as fidl_ieee80211;
19use log::error;
20use wlan_common::append::Append;
21use wlan_common::buffer_reader::BufferReader;
22use wlan_common::buffer_writer::BufferWriter;
23use wlan_common::{frame_len, mac};
24use wlan_frame_writer::append_frame_to;
25use wlan_statemachine::*;
26use zerocopy::{Ref, SplitByteSlice};
27
28pub const ADDBA_REQ_FRAME_LEN: usize = frame_len!(mac::MgmtHdr, mac::ActionHdr, mac::AddbaReqHdr);
29pub const ADDBA_RESP_FRAME_LEN: usize = frame_len!(mac::MgmtHdr, mac::ActionHdr, mac::AddbaRespHdr);
30pub const DELBA_FRAME_LEN: usize = frame_len!(mac::MgmtHdr, mac::ActionHdr, mac::DelbaHdr);
31
32// TODO(https://fxbug.dev/42104687): Determine a better value.
33// TODO(https://fxbug.dev/42104064): Implement QoS policy engine. See the following parts of the specification:
34//
35//              - IEEE Std 802.11-2016, 3.1 (Traffic Identifier)
36//              - IEEE Std 802.11-2016, 5.1.1.1 (Data Service - General)
37//              - IEEE Std 802.11-2016, 9.4.2.30 (Access Policy)
38//              - IEEE Std 802.11-2016, 9.2.4.5.2 (TID Subfield)
39//
40//              A TID is from [0, 15] and is assigned to an MSDU in the layers above the MAC. [0,
41//              7] identify Traffic Categories (TCs) and [8, 15] identify parameterized TCs.
42const BLOCK_ACK_BUFFER_SIZE: u16 = 64;
43const BLOCK_ACK_TID: u16 = 0; // TODO(https://fxbug.dev/42104064): Implement QoS policy engine.
44
45/// BlockAck transmitter.
46///
47/// Types that implement this trait can transmit a BlockAck frame body. Typically, this involves
48/// embedding the frame body within a management frame with the appropriate metadata and differs
49/// based on the state and mode of a STA.
50///
51/// This trait is used to interact with `BlockAckState` and determines the output state during
52/// certain transitions. Moreover, this trait provides the necessary side effects of BlockAck state
53/// transitions (namely transmitting frames to clients).
54pub trait BlockAckTx {
55    /// Transmits a BlockAck frame with the given body.
56    ///
57    /// The `body` parameter does **not** include any management frame components. The frame length
58    /// `n` is the length of the entire management frame, including the management header, action
59    /// header, and BlockAck frame. This length can be used to allocate a buffer for the complete
60    /// management frame.
61    ///
62    /// # Errors
63    ///
64    /// An error should be returned if the frame cannot be constructed or transmitted.
65    fn send_block_ack_frame(&mut self, n: usize, body: &[u8]) -> Result<(), Error>;
66}
67
68/// Closed BlockAck state.
69///
70/// A BlockAck session is _closed_ when BlockAck is not in use and no peer has requested to
71/// establish or close a session. This is the initial state.
72#[derive(Debug, Default)]
73pub struct Closed;
74
75#[derive(Debug)]
76pub struct Establishing {
77    /// The dialog token transmitted to the recipient STA.
78    pub dialog_token: u8,
79}
80
81/// Established BlockAck state.
82///
83/// A BlockAck session is _established_ when BlockAck has been negotiated between peers by
84/// successfully exchanging ADDBA frames.
85#[derive(Debug)]
86pub struct Established {
87    /// Indicates whether the STA is the originator (initiator) or recipient of the BlockAck
88    /// session.
89    pub is_initiator: bool,
90}
91
92// TODO(https://fxbug.dev/42104687): BlockAck should be closed if the TID expires before BlockAck or QoS frames are
93//              received. The state machine must be driven by a timing mechanism to ensure that
94//              this happens. See IEEE Std 802.11-2016, 10.24.5.
95statemachine!(
96    /// BlockAck state machine.
97    ///
98    /// Models the state of a BlockAck session. A session is principally established or closed, but
99    /// also has intermediate states as a session is negotiated. Establishing and closing sessions
100    /// is done using an exchange of ADDBA and DELBA frames.
101    ///
102    /// A STA that initiates a BlockAck session is known as the _originator_ and its peer the
103    /// _recipient_. In infrastructure mode, both APs and clients may initiate a session.
104    #[derive(Debug)]
105    pub enum BlockAckState,
106    () => Closed,
107    Closed => [Establishing, Established],
108    Establishing => [Closed, Established],
109    Established => Closed,
110);
111
112impl BlockAckState {
113    /// Establishes a BlockAck session.
114    ///
115    /// Depending on its state, this function may send an ADDBA request frame to the remote peer in
116    /// order to await an affirmative ADDBA response frame from that peer.
117    ///
118    /// See IEEE Std 802.11-2016, 10.24.2.
119    #[allow(dead_code)] // TODO(https://fxbug.dev/42104687): Establish BlockAck sessions to increase throughput.
120    pub fn establish(self, tx: &mut impl BlockAckTx) -> Self {
121        match self {
122            BlockAckState::Closed(state) => {
123                // TODO(https://fxbug.dev/42104687): Examine `CapabilityInfo` of the remote peer.
124                // TODO(https://fxbug.dev/42104687): It appears there is no particular rule to choose the value for
125                //              `dialog_token`. Persist the dialog token for the BlockAck session
126                //              and find a proven way to generate tokens. See IEEE Std 802.11-2016,
127                //              9.6.5.2.
128                let dialog_token = 1;
129                let mut body = [0u8; ADDBA_REQ_FRAME_LEN];
130                let mut writer = BufferWriter::new(&mut body[..]);
131                match write_addba_req_body(&mut writer, dialog_token).and_then(|_| {
132                    tx.send_block_ack_frame(ADDBA_REQ_FRAME_LEN, writer.into_written())
133                }) {
134                    Ok(_) => state.transition_to(Establishing { dialog_token }).into(),
135                    Err(error) => {
136                        error!("error sending ADDBA request frame: {}", error);
137                        state.into()
138                    }
139                }
140            }
141            _ => self,
142        }
143    }
144
145    /// Closes a BlockAck session.
146    ///
147    /// This function sends a DELBA frame to the remote peer unless BlockAck is already closed.
148    /// Only initiator peers should attempt to explicitly close BlockAck sessions.
149    ///
150    /// See IEEE Std 802.11-2016, 10.24.5.
151    #[allow(dead_code)] // TODO(https://fxbug.dev/42104687): Implement the datagrams and transmission of DELBA frames.
152    pub fn close(self, tx: &mut impl BlockAckTx, reason_code: mac::ReasonCode) -> Self {
153        // This aggressively transitions to the `Closed` state. DELBA frames do not require an
154        // exchange (as ADDBA frames do). Note that per IEEE Std 802.11-2016, 10.24.5, only the
155        // initiator is meant to transmit DELBA frames. The other mechanism for closing BlockAck is
156        // an expiration of the TID before any BlockAck or QoS frames are received.
157        match self {
158            BlockAckState::Closed(_) => self,
159            _ => {
160                let is_initiator = match &self {
161                    &BlockAckState::Establishing(..) => true,
162                    &BlockAckState::Established(State {
163                        data: Established { is_initiator },
164                        ..
165                    }) => is_initiator,
166                    _ => false,
167                };
168                let mut body = [0u8; DELBA_FRAME_LEN];
169                let mut writer = BufferWriter::new(&mut body[..]);
170                match write_delba_body(&mut writer, is_initiator, reason_code)
171                    .and_then(|_| tx.send_block_ack_frame(DELBA_FRAME_LEN, writer.into_written()))
172                {
173                    Ok(_) => BlockAckState::from(State::new(Closed)),
174                    Err(error) => {
175                        error!("error sending DELBA frame: {}", error);
176                        self
177                    }
178                }
179            }
180        }
181    }
182
183    /// Reacts to a BlockAck frame.
184    ///
185    /// This function transitions the state machine in response to a BlockAck frame. In particular,
186    /// this function reacts to ADDBA and DELBA frames to begin and end BlockAck sessions.
187    ///
188    /// The `body` parameter must **not** include the management action byte. This value should be
189    /// parsed beforehand and removed from the frame body.
190    pub fn on_block_ack_frame<B: SplitByteSlice>(
191        self,
192        tx: &mut impl BlockAckTx,
193        action: mac::BlockAckAction,
194        body: B,
195    ) -> Self {
196        match self {
197            BlockAckState::Closed(state) => match action {
198                mac::BlockAckAction::ADDBA_REQUEST => {
199                    // Read the ADDBA request and send a response. If successful, transition to
200                    // `Established`. See IEEE Std 802.11-2016, 10.24.2.
201                    let mut frame = [0u8; ADDBA_RESP_FRAME_LEN];
202                    let mut writer = BufferWriter::new(&mut frame[..]);
203                    match read_addba_req_hdr(body)
204                        .and_then(|request| {
205                            write_addba_resp_body(&mut writer, request.dialog_token)
206                        })
207                        .and_then(|_| {
208                            tx.send_block_ack_frame(ADDBA_RESP_FRAME_LEN, writer.into_written())
209                        }) {
210                        Ok(_) => state.transition_to(Established { is_initiator: false }).into(),
211                        Err(error) => {
212                            error!("error sending ADDBA response frame: {}", error);
213                            state.into()
214                        }
215                    }
216                }
217                _ => state.into(),
218            },
219            BlockAckState::Establishing(state) => match action {
220                mac::BlockAckAction::ADDBA_RESPONSE => {
221                    // Read the ADDBA response. If successful and the response is affirmative,
222                    // transition to `Established`. If the response is negative, transition to
223                    // `Closed`. See IEEE Std 802.11-2016, 10.24.2.
224                    match read_addba_resp_hdr(state.dialog_token, body) {
225                        Ok(response) => {
226                            if { response.status } == fidl_ieee80211::StatusCode::Success.into() {
227                                state.transition_to(Established { is_initiator: true }).into()
228                            } else {
229                                // Transition to `Closed` if the remote peer sends a negative
230                                // response.
231                                state.transition_to(Closed).into()
232                            }
233                        }
234                        Err(error) => {
235                            error!("error processing ADDBA response frame: {}", error);
236                            // Transition to `Closed` if any errors occur.
237                            state.transition_to(Closed).into()
238                        }
239                    }
240                }
241                mac::BlockAckAction::DELBA => state.transition_to(Closed).into(),
242                _ => state.into(),
243            },
244            BlockAckState::Established(state) => match action {
245                mac::BlockAckAction::DELBA => {
246                    // TODO(https://fxbug.dev/42104687): Examine the DELBA frame as needed.  This is necessary for GCR
247                    //              modes, for example.
248                    if let Err(error) = read_delba_hdr(body) {
249                        error!("error processing DELBA frame: {}", error);
250                    }
251                    // See IEEE Std 802.11-2016, 10.24.5.
252                    state.transition_to(Closed).into()
253                }
254                _ => state.into(),
255            },
256        }
257    }
258}
259
260/// Writes the body of the management frame for an `ADDBA` request to the given buffer. The
261/// management header should be written to the buffer before using this function.
262///
263/// Note that the action header is part of the management frame body and is written by this
264/// function. The frame format is described by IEEE Std 802.11-2016, 9.6.5.2.
265pub fn write_addba_req_body<B: Append>(buffer: &mut B, dialog_token: u8) -> Result<(), Error> {
266    let body = mac::AddbaReqHdr {
267        action: mac::BlockAckAction::ADDBA_REQUEST,
268        dialog_token,
269        parameters: mac::BlockAckParameters(0)
270            .with_amsdu(true)
271            .with_policy(mac::BlockAckPolicy::IMMEDIATE)
272            .with_tid(BLOCK_ACK_TID)
273            .with_buffer_size(BLOCK_ACK_BUFFER_SIZE),
274        timeout: 0, // TODO(https://fxbug.dev/42104687): No timeout. Determine a better value.
275        starting_sequence_control: mac::BlockAckStartingSequenceControl(0)
276            .with_fragment_number(0) // Always zero. See IEEE Std 802.11-2016, 9.6.5.2.
277            .with_starting_sequence_number(1), // TODO(https://fxbug.dev/42104687): Determine a better value.
278    };
279    Ok(append_frame_to!(
280        buffer,
281        {
282            headers: {
283                mac::ActionHdr: &mac::ActionHdr {
284                    action: mac::ActionCategory::BLOCK_ACK,
285                },
286            },
287            body: body.as_bytes(),
288        }
289    )
290    .map(|_buffer| {})?)
291}
292
293/// Writes the body of the management frame for an `ADDBA` request to the given buffer. The
294/// management header should be written to the buffer before using this function.
295///
296/// Note that the action header is part fo the management frame body and is written by this
297/// function. The frame format is described by IEEE Std 802.11-2016, 9.6.5.3.
298pub fn write_addba_resp_body<B: Append>(buffer: &mut B, dialog_token: u8) -> Result<(), Error> {
299    let body = mac::AddbaRespHdr {
300        action: mac::BlockAckAction::ADDBA_RESPONSE,
301        dialog_token,
302        status: fidl_ieee80211::StatusCode::Success.into(),
303        parameters: mac::BlockAckParameters(0)
304            .with_amsdu(true)
305            .with_policy(mac::BlockAckPolicy::IMMEDIATE)
306            .with_tid(BLOCK_ACK_TID)
307            .with_buffer_size(BLOCK_ACK_BUFFER_SIZE),
308        timeout: 0, // TODO(https://fxbug.dev/42104687): No timeout. Determina a better value.
309    };
310    Ok(append_frame_to!(
311        buffer,
312        {
313            headers: {
314                mac::ActionHdr: &mac::ActionHdr {
315                    action: mac::ActionCategory::BLOCK_ACK,
316                },
317            },
318            body: body.as_bytes(),
319        }
320    )
321    .map(|_buffer| {})?)
322}
323
324pub fn write_delba_body<B: Append>(
325    buffer: &mut B,
326    is_initiator: bool,
327    reason_code: mac::ReasonCode,
328) -> Result<(), Error> {
329    let body = mac::DelbaHdr {
330        action: mac::BlockAckAction::DELBA,
331        parameters: mac::DelbaParameters(0).with_initiator(is_initiator).with_tid(BLOCK_ACK_TID),
332        reason_code,
333    };
334    Ok(append_frame_to!(
335        buffer,
336        {
337            headers: {
338                mac::ActionHdr: &mac::ActionHdr {
339                    action: mac::ActionCategory::BLOCK_ACK,
340                },
341            },
342            body: body.as_bytes(),
343        }
344    )
345    .map(|_buffer| {})?)
346}
347
348/// Reads an ADDBA request header from an ADDBA frame body.
349///
350/// This function and others in this module do **not** expect the management action byte to be
351/// present in the body. This value should be parsed and removed beforehand.
352///
353/// # Errors
354///
355/// Returns an error if the header cannot be parsed.
356fn read_addba_req_hdr<B: SplitByteSlice>(body: B) -> Result<Ref<B, mac::AddbaReqHdr>, Error> {
357    let mut reader = BufferReader::new(body);
358    reader.read::<mac::AddbaReqHdr>().ok_or_else(|| {
359        Error::Status("error reading ADDBA request header".to_string(), zx::Status::IO)
360    })
361}
362
363/// Reads an ADDBA response header from an ADDBA frame body.
364///
365/// This function and others in this module do **not** expect the management action byte to be
366/// present in the body. This value should be parsed and removed beforehand.
367///
368/// # Errors
369///
370/// Returns an error if the header cannot be parsed or if its dialog token is not the same as the
371/// given parameters.
372fn read_addba_resp_hdr<B: SplitByteSlice>(
373    dialog_token: u8,
374    body: B,
375) -> Result<Ref<B, mac::AddbaRespHdr>, Error> {
376    let mut reader = BufferReader::new(body);
377    reader
378        .read::<mac::AddbaRespHdr>()
379        .ok_or_else(|| {
380            Error::Status("error reading ADDBA response header".to_string(), zx::Status::IO)
381        })
382        .and_then(|response| {
383            if response.dialog_token == dialog_token {
384                Ok(response)
385            } else {
386                Err(Error::Status(
387                    "mismatched dialog token in ADDBA response header".to_string(),
388                    zx::Status::IO,
389                ))
390            }
391        })
392}
393
394/// Reads a DELBA header from a DELBA frame body.
395///
396/// This function and others in this module do **not** expect the management action byte to be
397/// present in the body. This value should be parsed and removed beforehand.
398///
399/// # Errors
400///
401/// Returns an error if the header cannot be parsed.
402fn read_delba_hdr<B: SplitByteSlice>(body: B) -> Result<Ref<B, mac::DelbaHdr>, Error> {
403    let mut reader = BufferReader::new(body);
404    reader
405        .read::<mac::DelbaHdr>()
406        .ok_or_else(|| Error::Status("error reading DELBA header".to_string(), zx::Status::IO))
407}
408
409#[cfg(test)]
410mod tests {
411    use super::*;
412    use wlan_common::append::TrackedAppend;
413    use wlan_common::assert_variant;
414    use wlan_statemachine as statemachine;
415
416    /// A STA that can send ADDBA frames (implements the `BlockAckTx` trait).
417    enum Station {
418        /// When in the this state, all transmissions succeed.
419        Up,
420        /// When in the this state, all transmissions fail.
421        Down,
422    }
423
424    impl BlockAckTx for Station {
425        fn send_block_ack_frame(&mut self, _: usize, _: &[u8]) -> Result<(), Error> {
426            match *self {
427                Station::Up => Ok(()),
428                Station::Down => {
429                    Err(Error::Status(format!("failed to transmit BlockAck frame"), zx::Status::IO))
430                }
431            }
432        }
433    }
434
435    /// Creates an ADDBA request body.
436    ///
437    /// Note that this is not a complete ADDBA request frame. This function exercises
438    /// `write_addba_req_body`.
439    fn addba_req_body(dialog_token: u8) -> (usize, [u8; ADDBA_RESP_FRAME_LEN]) {
440        let mut body = [0u8; ADDBA_RESP_FRAME_LEN];
441        let mut writer = BufferWriter::new(&mut body[..]);
442        super::write_addba_req_body(&mut writer, dialog_token).unwrap();
443        (writer.bytes_appended(), body)
444    }
445
446    /// Creates an ADDBA response body.
447    ///
448    /// Note that this is not a complete ADDBA response frame. This function exercises
449    /// `write_addba_resp_body`.
450    fn addba_resp_body(dialog_token: u8) -> (usize, [u8; ADDBA_RESP_FRAME_LEN]) {
451        let mut body = [0u8; ADDBA_RESP_FRAME_LEN];
452        let mut writer = BufferWriter::new(&mut body[..]);
453        super::write_addba_resp_body(&mut writer, dialog_token).unwrap();
454        (writer.bytes_appended(), body)
455    }
456
457    /// Creates a DELBA body.
458    ///
459    /// Note that this is not a complete DELBA frame. This function exercises `write_delba_body`.
460    fn delba_body(
461        is_initiator: bool,
462        reason_code: mac::ReasonCode,
463    ) -> (usize, [u8; DELBA_FRAME_LEN]) {
464        let mut body = [0u8; DELBA_FRAME_LEN];
465        let mut writer = BufferWriter::new(&mut body[..]);
466        super::write_delba_body(&mut writer, is_initiator, reason_code).unwrap();
467        (writer.bytes_appended(), body)
468    }
469
470    #[test]
471    fn request_establish_block_ack() {
472        let mut station = Station::Up;
473        let state = BlockAckState::from(State::new(Closed));
474        let state = state.establish(&mut station);
475        assert_variant!(state, BlockAckState::Establishing(_), "not in `Establishing` state");
476
477        let mut station = Station::Down;
478        let state = BlockAckState::from(State::new(Closed));
479        let state = state.establish(&mut station);
480        assert_variant!(state, BlockAckState::Closed(_), "not in `Closed` state");
481    }
482
483    #[test]
484    fn request_close_block_ack() {
485        let mut station = Station::Up;
486        let state = BlockAckState::from(statemachine::testing::new_state(Established {
487            is_initiator: true,
488        }));
489        let state = state.close(&mut station, fidl_ieee80211::ReasonCode::UnspecifiedReason.into());
490        assert_variant!(state, BlockAckState::Closed(_), "not in `Closed` state");
491    }
492
493    #[test]
494    fn respond_establish_block_ack() {
495        // Create a buffer describing an ADDBA request body and read the management action byte.
496        let (n, body) = addba_req_body(1);
497        let body = &body[..n];
498        let (_, body) = Ref::<_, mac::ActionHdr>::from_prefix(body).unwrap();
499
500        let mut station = Station::Up;
501        let state = BlockAckState::from(State::new(Closed));
502        let state =
503            state.on_block_ack_frame(&mut station, mac::BlockAckAction::ADDBA_REQUEST, body);
504        assert_variant!(state, BlockAckState::Established(_), "not in `Established` state");
505
506        let mut station = Station::Down;
507        let state = BlockAckState::from(State::new(Closed));
508        let state =
509            state.on_block_ack_frame(&mut station, mac::BlockAckAction::ADDBA_REQUEST, body);
510        assert_variant!(state, BlockAckState::Closed(_), "not in `Closed` state");
511    }
512
513    #[test]
514    fn respond_close_block_ack() {
515        // Create a buffer describing a DELBA body and read the management action byte.
516        let (n, body) = delba_body(true, fidl_ieee80211::ReasonCode::UnspecifiedReason.into());
517        let body = &body[..n];
518        let (_, body) = Ref::<_, mac::ActionHdr>::from_prefix(body).unwrap();
519
520        let mut station = Station::Up;
521        let state = BlockAckState::from(statemachine::testing::new_state(Established {
522            is_initiator: false,
523        }));
524        let state = state.on_block_ack_frame(&mut station, mac::BlockAckAction::DELBA, body);
525        assert_variant!(state, BlockAckState::Closed(_), "not in `Closed` state");
526    }
527
528    #[test]
529    fn write_addba_req_body() {
530        let (n, body) = addba_req_body(1);
531        let body = &body[..n];
532        assert_eq!(
533            body,
534            &[
535                // Action frame header (also part of ADDBA request frame)
536                0x03, // Action Category: block ack (0x03)
537                0x00, // block ack action: ADDBA request (0x00)
538                1,    // block ack dialog token
539                0b00000011, 0b00010000, // block ack parameters (u16)
540                0, 0, // block ack timeout (u16) (0: disabled)
541                0b00010000, 0, // block ack starting sequence number: fragment 0, sequence 1
542            ][..]
543        );
544    }
545
546    #[test]
547    fn write_addba_resp_body() {
548        let (n, body) = addba_resp_body(1);
549        let body = &body[..n];
550        assert_eq!(
551            body,
552            &[
553                // Action frame header (also part of ADDBA response frame)
554                0x03, // Action Category: block ack (0x03)
555                0x01, // block ack action: ADDBA response (0x01)
556                1,    // block ack dialog token
557                0, 0, // status
558                0b00000011, 0b00010000, // block ack parameters (u16)
559                0, 0, // block ack timeout (u16) (0: disabled)
560            ][..]
561        );
562    }
563
564    #[test]
565    fn write_delba_body() {
566        let (n, body) = delba_body(true, fidl_ieee80211::ReasonCode::UnspecifiedReason.into());
567        let body = &body[..n];
568        assert_eq!(
569            body,
570            &[
571                // Action frame header (also part of DELBA frame)
572                0x03, // action category: block ack (0x03)
573                0x02, // block ack action: DELBA (0x02)
574                0b00000000, 0b00001000, // DELBA block ack parameters (u16)
575                1, 0, // reason code (u16) (1: unspecified reason)
576            ][..]
577        );
578    }
579}