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}