bt_rfcomm/
dlci.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 anyhow::format_err;
6use core::fmt::{self, Display};
7
8use crate::frame::FrameParseError;
9use crate::{RfcommError, Role};
10
11/// Identifier for a direct link connection (DLC) between devices.
12///
13/// Use the `TryFrom<u8>` implementation to construct a valid DLCI.
14///
15/// The DLCI is 6 bits wide and consists of a direction bit and a 5-bit Server Channel number.
16/// DLCIs 1 and 62-63 are reserved and never used in RFCOMM.
17/// See RFCOMM 5.4.
18#[derive(Clone, Copy, Hash, Eq, Debug, PartialEq)]
19pub struct DLCI(u8);
20
21impl DLCI {
22    /// The control channel for the RFCOMM Multiplexer.
23    pub const MUX_CONTROL_DLCI: DLCI = DLCI(0);
24    /// The minimum user-space DLCI.
25    const MIN_USER_DLCI: DLCI = DLCI(2);
26    /// The maximum user-space DLCI.
27    const MAX_USER_DLCI: DLCI = DLCI(61);
28
29    pub fn is_mux_control(&self) -> bool {
30        *self == Self::MUX_CONTROL_DLCI
31    }
32
33    pub fn is_user(&self) -> bool {
34        self.0 >= Self::MIN_USER_DLCI.0 && self.0 <= Self::MAX_USER_DLCI.0
35    }
36
37    /// Returns Ok(()) if the DLCI belongs to the side of the session with the
38    /// given `role` - this is only applicable to User DLCIs.
39    ///
40    /// The DLCI space is divided into two equal parts. RFCOMM 5.2 states:
41    /// "...this partitions the DLCI value space such that server applications on the non-
42    /// initiating device are reachable on DLCIs 2,4,6,...,60, and server applications on
43    /// the initiating device are reachable on DLCIs 3,5,7,...,61."
44    pub fn validate(&self, role: Role) -> Result<(), RfcommError> {
45        if !self.is_user() {
46            return Err(RfcommError::InvalidDLCI(*self));
47        }
48
49        let valid_bit = match role {
50            Role::Responder => 0,
51            Role::Initiator => 1,
52            role => {
53                return Err(RfcommError::InvalidRole(role));
54            }
55        };
56
57        if self.0 % 2 == valid_bit {
58            Ok(())
59        } else {
60            Err(RfcommError::InvalidDLCI(*self))
61        }
62    }
63
64    /// Returns true if the DLCI is initiated by this device.
65    /// Returns an Error if the provided `role` is invalid or if the DLCI is not
66    /// a user DLCI.
67    pub fn initiator(&self, role: Role) -> Result<bool, RfcommError> {
68        if !self.is_user() {
69            return Err(RfcommError::InvalidDLCI(*self));
70        }
71
72        // A DLCI is considered initiated by us if the direction bit is the same as the expected
73        // direction bit associated with the role of the remote peer. See RFCOMM 5.4 for the
74        // expected value of the direction bit for a particular DLCI.
75        match role.opposite_role() {
76            Role::Responder => Ok(self.0 % 2 == 0),
77            Role::Initiator => Ok(self.0 % 2 == 1),
78            role => {
79                return Err(RfcommError::InvalidRole(role));
80            }
81        }
82    }
83}
84
85impl TryFrom<u8> for DLCI {
86    type Error = FrameParseError;
87
88    fn try_from(value: u8) -> Result<DLCI, Self::Error> {
89        if value != DLCI::MUX_CONTROL_DLCI.0
90            && (value < DLCI::MIN_USER_DLCI.0 || value > DLCI::MAX_USER_DLCI.0)
91        {
92            return Err(FrameParseError::InvalidDLCI(value));
93        }
94        Ok(DLCI(value))
95    }
96}
97
98impl From<DLCI> for u8 {
99    fn from(value: DLCI) -> u8 {
100        value.0
101    }
102}
103
104impl TryFrom<DLCI> for ServerChannel {
105    type Error = RfcommError;
106
107    fn try_from(dlci: DLCI) -> Result<ServerChannel, Self::Error> {
108        if !dlci.is_user() {
109            return Err(RfcommError::InvalidDLCI(dlci));
110        }
111
112        // The ServerChannel is the upper 5 bits of the 6-bit DLCI. See RFCOMM 5.4.
113        ServerChannel::try_from(dlci.0 >> 1)
114    }
115}
116
117impl Display for DLCI {
118    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
119        write!(formatter, "{}", self.0)
120    }
121}
122
123/// The Server Channel number associated with an RFCOMM channel.
124///
125/// Use the provided `u8::try_from` implementation to construct a valid ServerChannel.
126///
127/// Server Channels are 5 bits wide; they are the 5 most significant bits of the
128/// DLCI.
129/// Server Channels 0 and 31 are reserved. See RFCOMM 5.4 for the definition and
130/// usage.
131#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
132pub struct ServerChannel(u8);
133
134impl ServerChannel {
135    const MAX: ServerChannel = ServerChannel(30);
136    const MIN: ServerChannel = ServerChannel(1);
137
138    /// Returns an iterator over all the Server Channels.
139    pub fn all() -> impl Iterator<Item = ServerChannel> {
140        (Self::MIN.0..=Self::MAX.0).map(|x| ServerChannel(x))
141    }
142
143    /// Converts the ServerChannel to a DLCI for the provided `role`.
144    /// Defined in RFCOMM 5.4.
145    pub fn to_dlci(&self, role: Role) -> Result<DLCI, RfcommError> {
146        let direction_bit = match role {
147            Role::Initiator => 1,
148            Role::Responder => 0,
149            r => {
150                return Err(RfcommError::InvalidRole(r));
151            }
152        };
153
154        let v = (self.0 << 1) | direction_bit;
155        DLCI::try_from(v).map_err(RfcommError::from)
156    }
157}
158
159impl TryFrom<u8> for ServerChannel {
160    type Error = RfcommError;
161    fn try_from(src: u8) -> Result<ServerChannel, Self::Error> {
162        if src < Self::MIN.0 || src > Self::MAX.0 {
163            return Err(RfcommError::Other(format_err!("Out of range: {:?}", src).into()));
164        }
165        Ok(ServerChannel(src))
166    }
167}
168
169impl From<ServerChannel> for u8 {
170    fn from(value: ServerChannel) -> u8 {
171        value.0
172    }
173}
174
175impl Display for ServerChannel {
176    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
177        write!(formatter, "{}", self.0)
178    }
179}
180
181#[cfg(test)]
182mod tests {
183    use super::*;
184
185    use assert_matches::assert_matches;
186
187    #[test]
188    fn test_create_dlci() {
189        let v1 = 10;
190        let dlci = DLCI::try_from(v1);
191        let expected_sc1 = ServerChannel::try_from(5).unwrap();
192        assert!(dlci.is_ok());
193        assert_eq!(ServerChannel::try_from(dlci.unwrap()).unwrap(), expected_sc1);
194
195        let v2 = 0;
196        let dlci = DLCI::try_from(v2).unwrap();
197        assert_matches!(ServerChannel::try_from(dlci), Err(RfcommError::InvalidDLCI(_)));
198
199        let v3 = 2;
200        let dlci = DLCI::try_from(v3);
201        let expected_sc3 = ServerChannel::try_from(1).unwrap();
202        assert!(dlci.is_ok());
203        assert_eq!(ServerChannel::try_from(dlci.unwrap()).unwrap(), expected_sc3);
204
205        let v4 = 61;
206        let dlci = DLCI::try_from(v4);
207        let expected_sc4 = ServerChannel::try_from(30).unwrap();
208        assert!(dlci.is_ok());
209        assert_eq!(ServerChannel::try_from(dlci.unwrap()).unwrap(), expected_sc4);
210
211        let v5 = 1;
212        let dlci = DLCI::try_from(v5);
213        assert!(dlci.is_err());
214
215        let v6 = 62;
216        let dlci = DLCI::try_from(v6);
217        assert!(dlci.is_err());
218
219        let v7 = 63;
220        let dlci = DLCI::try_from(v7);
221        assert!(dlci.is_err());
222    }
223
224    #[test]
225    fn validate_dlci_as_initiator_role() {
226        let role = Role::Initiator;
227
228        let dlci = DLCI::MUX_CONTROL_DLCI;
229        assert_matches!(dlci.validate(role), Err(RfcommError::InvalidDLCI(_)));
230
231        let dlci = DLCI::MIN_USER_DLCI;
232        assert_matches!(dlci.validate(role), Err(RfcommError::InvalidDLCI(_)));
233
234        let dlci = DLCI::try_from(9).unwrap();
235        assert!(dlci.validate(role).is_ok());
236    }
237
238    #[test]
239    fn validate_dlci_as_responder_role() {
240        let role = Role::Responder;
241
242        let dlci = DLCI::MUX_CONTROL_DLCI;
243        assert_matches!(dlci.validate(role), Err(RfcommError::InvalidDLCI(_)));
244
245        let dlci = DLCI::try_from(7).unwrap();
246        assert_matches!(dlci.validate(role), Err(RfcommError::InvalidDLCI(_)));
247
248        let dlci = DLCI::try_from(10).unwrap();
249        assert!(dlci.validate(role).is_ok());
250    }
251
252    #[test]
253    fn validate_dlci_with_invalid_role_returns_error() {
254        let role = Role::Unassigned;
255        let dlci = DLCI::try_from(10).unwrap();
256        assert_matches!(dlci.validate(role), Err(RfcommError::InvalidRole(_)));
257
258        let role = Role::Negotiating;
259        let dlci = DLCI::try_from(11).unwrap();
260        assert_matches!(dlci.validate(role), Err(RfcommError::InvalidRole(_)));
261    }
262
263    #[test]
264    fn dlci_check_is_initiator() {
265        let dlci = DLCI::MUX_CONTROL_DLCI;
266        assert_matches!(dlci.initiator(Role::Initiator), Err(RfcommError::InvalidDLCI(_)));
267        assert_matches!(dlci.initiator(Role::Responder), Err(RfcommError::InvalidDLCI(_)));
268
269        let dlci = DLCI::try_from(20).unwrap();
270        assert_matches!(dlci.initiator(Role::Initiator), Ok(true));
271        assert_matches!(dlci.initiator(Role::Responder), Ok(false));
272
273        let dlci = DLCI::try_from(25).unwrap();
274        assert_matches!(dlci.initiator(Role::Initiator), Ok(false));
275        assert_matches!(dlci.initiator(Role::Responder), Ok(true));
276    }
277
278    #[test]
279    fn dlci_check_as_initiator_with_invalid_role_returns_error() {
280        let role = Role::Unassigned;
281        let dlci = DLCI::try_from(10).unwrap();
282        assert_matches!(dlci.initiator(role), Err(RfcommError::InvalidRole(_)));
283
284        let role = Role::Negotiating;
285        let dlci = DLCI::try_from(11).unwrap();
286        assert_matches!(dlci.initiator(role), Err(RfcommError::InvalidRole(_)));
287    }
288
289    #[test]
290    fn convert_server_channel_to_dlci_invalid_role() {
291        let invalid_role = Role::Unassigned;
292        let server_channel = ServerChannel::try_from(10).unwrap();
293        assert_matches!(server_channel.to_dlci(invalid_role), Err(_));
294
295        let invalid_role = Role::Negotiating;
296        let server_channel = ServerChannel::try_from(13).unwrap();
297        assert_matches!(server_channel.to_dlci(invalid_role), Err(_));
298    }
299
300    #[test]
301    fn convert_server_channel_to_dlci_success() {
302        let server_channel = ServerChannel::try_from(5).unwrap();
303        let expected_dlci = DLCI::try_from(11).unwrap();
304        assert_eq!(server_channel.to_dlci(Role::Initiator).unwrap(), expected_dlci);
305
306        let expected_dlci = DLCI::try_from(10).unwrap();
307        assert_eq!(server_channel.to_dlci(Role::Responder).unwrap(), expected_dlci);
308
309        let server_channel = ServerChannel::MIN;
310        let expected_dlci = DLCI::try_from(2).unwrap();
311        assert_eq!(server_channel.to_dlci(Role::Responder).unwrap(), expected_dlci);
312
313        let server_channel = ServerChannel::MAX;
314        let expected_dlci = DLCI::try_from(61).unwrap();
315        assert_eq!(server_channel.to_dlci(Role::Initiator).unwrap(), expected_dlci);
316    }
317
318    #[test]
319    fn server_channel_from_primitive() {
320        let normal = 10;
321        let sc = ServerChannel::try_from(normal);
322        assert!(sc.is_ok());
323
324        let invalid = 0;
325        let sc = ServerChannel::try_from(invalid);
326        assert_matches!(sc, Err(_));
327
328        let too_large = 31;
329        let sc = ServerChannel::try_from(too_large);
330        assert_matches!(sc, Err(_));
331
332        let u8_max = std::u8::MAX;
333        let sc = ServerChannel::try_from(u8_max);
334        assert_matches!(sc, Err(_));
335    }
336}