Skip to main content

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