bt_rfcomm/frame/
command_response.rs

1// Copyright 2021 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
5use crate::frame::error::FrameParseError;
6use crate::frame::FrameTypeMarker;
7use crate::Role;
8
9/// The C/R bit in RFCOMM. This is used both at the frame level and the multiplexer
10/// channel command level. See RFCOMM 5.1.3 and 5.4.6, respectively.
11#[derive(Clone, Copy, Debug, PartialEq)]
12pub enum CommandResponse {
13    Command,
14    Response,
15}
16
17impl CommandResponse {
18    /// Classifies a frame type as a Command (C) or Response (R).
19    pub(crate) fn classify(
20        role: Role,
21        frame_type: FrameTypeMarker,
22        cr_bit: bool,
23    ) -> Result<Self, FrameParseError> {
24        use FrameTypeMarker::*;
25        // If the multiplexer has started, then the classification is determined by the frame type.
26        if role.is_multiplexer_started() {
27            let res = match (frame_type, role, cr_bit) {
28                // For UIH frames, it is determined by the role.
29                // This is defined in GSM 5.4.3.1, which is a subclause of GSM 5.2.1.2.
30                (UnnumberedInfoHeaderCheck, Role::Initiator, _) => Ok(CommandResponse::Command),
31                (UnnumberedInfoHeaderCheck, Role::Responder, _) => Ok(CommandResponse::Response),
32                (UnnumberedInfoHeaderCheck, role, _) => Err(FrameParseError::InvalidRole(role)),
33                // For all other frames, the logic is determined by both the role and C/R bit.
34                // See GSM 5.2.1.2 Table 1 for the exact mapping.
35                (_, Role::Initiator, true) | (_, Role::Responder, false) => {
36                    Ok(CommandResponse::Command)
37                }
38                (_, _, _) => Ok(CommandResponse::Response),
39            };
40            return res;
41        }
42
43        // Otherwise, the classification depends `cr_bit` of the mux startup frame.
44        match (frame_type, cr_bit) {
45            (SetAsynchronousBalancedMode, true) => Ok(CommandResponse::Command),
46            (SetAsynchronousBalancedMode, false) => Ok(CommandResponse::Response),
47            (DisconnectedMode | UnnumberedAcknowledgement, true) => Ok(CommandResponse::Response),
48            (DisconnectedMode | UnnumberedAcknowledgement, false) => Ok(CommandResponse::Command),
49            (frame_type, _) => Err(FrameParseError::InvalidFrameBeforeMuxStartup(frame_type)),
50        }
51    }
52}
53
54#[cfg(test)]
55mod tests {
56    use super::*;
57
58    use assert_matches::assert_matches;
59
60    #[test]
61    fn classify_frame_after_mux_startup() {
62        // Multiplexer started because a Role has been assigned.
63        let role = Role::Initiator;
64        let frame = FrameTypeMarker::SetAsynchronousBalancedMode;
65        assert_matches!(
66            CommandResponse::classify(role, frame, /* cr_bit= */ true),
67            Ok(CommandResponse::Command)
68        );
69
70        let role = Role::Initiator;
71        let frame = FrameTypeMarker::SetAsynchronousBalancedMode;
72        assert_matches!(
73            CommandResponse::classify(role, frame, /* cr_bit= */ false),
74            Ok(CommandResponse::Response)
75        );
76
77        let role = Role::Responder;
78        let frame = FrameTypeMarker::Disconnect;
79        assert_matches!(
80            CommandResponse::classify(role, frame, /* cr_bit= */ true),
81            Ok(CommandResponse::Response)
82        );
83
84        let role = Role::Responder;
85        let frame = FrameTypeMarker::DisconnectedMode;
86        assert_matches!(
87            CommandResponse::classify(role, frame, /* cr_bit= */ false),
88            Ok(CommandResponse::Command)
89        );
90    }
91
92    #[test]
93    fn classify_uih_frame_after_mux_startup() {
94        let frame = FrameTypeMarker::UnnumberedInfoHeaderCheck;
95
96        assert_matches!(
97            CommandResponse::classify(Role::Initiator, frame, /* cr_bit=*/ true),
98            Ok(CommandResponse::Command)
99        );
100
101        assert_matches!(
102            CommandResponse::classify(Role::Initiator, frame, /* cr_bit=*/ false),
103            Ok(CommandResponse::Command)
104        );
105
106        assert_matches!(
107            CommandResponse::classify(Role::Responder, frame, /* cr_bit=*/ true),
108            Ok(CommandResponse::Response)
109        );
110
111        assert_matches!(
112            CommandResponse::classify(Role::Responder, frame, /* cr_bit=*/ false),
113            Ok(CommandResponse::Response)
114        );
115    }
116
117    /// Tests classifying a SABM command when the multiplexer has not started. The classification
118    /// should simply be based on the CR bit.
119    #[test]
120    fn classify_sabm_before_mux_startup() {
121        // Mux not started.
122        let role = Role::Unassigned;
123        let frame = FrameTypeMarker::SetAsynchronousBalancedMode;
124        let cr_bit = true;
125        assert_matches!(
126            CommandResponse::classify(role, frame, cr_bit),
127            Ok(CommandResponse::Command)
128        );
129
130        // Mux not started.
131        let role = Role::Negotiating;
132        let frame = FrameTypeMarker::SetAsynchronousBalancedMode;
133        let cr_bit = false;
134        assert_matches!(
135            CommandResponse::classify(role, frame, cr_bit),
136            Ok(CommandResponse::Response)
137        );
138    }
139
140    /// Tests classifying a DM before multiplexer startup. Should be opposite of C/R bit.
141    #[test]
142    fn classify_dm_before_mux_startup() {
143        let frame = FrameTypeMarker::DisconnectedMode;
144
145        // Mux not started.
146        let role = Role::Negotiating;
147        let cr_bit = true;
148        assert_matches!(
149            CommandResponse::classify(role, frame, cr_bit),
150            Ok(CommandResponse::Response)
151        );
152
153        let role = Role::Unassigned;
154        let cr_bit = false;
155        assert_matches!(
156            CommandResponse::classify(role, frame, cr_bit),
157            Ok(CommandResponse::Command)
158        );
159    }
160
161    #[test]
162    fn classify_ua_before_mux_startup() {
163        let frame = FrameTypeMarker::UnnumberedAcknowledgement;
164
165        let role = Role::Unassigned;
166        let cr_bit = true;
167        assert_matches!(
168            CommandResponse::classify(role, frame, cr_bit),
169            Ok(CommandResponse::Response)
170        );
171
172        let role = Role::Negotiating;
173        let cr_bit = false;
174        assert_matches!(
175            CommandResponse::classify(role, frame, cr_bit),
176            Ok(CommandResponse::Command)
177        );
178    }
179
180    #[test]
181    fn classify_invalid_frame_before_mux_startup_is_error() {
182        // Disconnect can't be sent before startup.
183        let role = Role::Unassigned;
184        let frame = FrameTypeMarker::Disconnect;
185        let cr_bit = true;
186        assert_matches!(
187            CommandResponse::classify(role, frame, cr_bit),
188            Err(FrameParseError::InvalidFrameBeforeMuxStartup(_))
189        );
190
191        // UIH can't be sent before startup.
192        let role = Role::Unassigned;
193        let frame = FrameTypeMarker::UnnumberedInfoHeaderCheck;
194        let cr_bit = true;
195        assert_matches!(
196            CommandResponse::classify(role, frame, cr_bit),
197            Err(FrameParseError::InvalidFrameBeforeMuxStartup(_))
198        );
199    }
200}