blob: 668f975e0c7e76d2c2456b7c91217bfc33563511 [file] [log] [blame]
// Copyright 2021 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
use anyhow::format_err;
use core::fmt::{self, Display};
use crate::frame::FrameParseError;
use crate::{RfcommError, Role};
/// Identifier for a direct link connection (DLC) between devices.
///
/// Use the `TryFrom<u8>` implementation to construct a valid DLCI.
///
/// The DLCI is 6 bits wide and consists of a direction bit and a 5-bit Server Channel number.
/// DLCIs 1 and 62-63 are reserved and never used in RFCOMM.
/// See RFCOMM 5.4.
#[derive(Clone, Copy, Hash, Eq, Debug, PartialEq)]
pub struct DLCI(u8);
impl DLCI {
/// The control channel for the RFCOMM Multiplexer.
pub const MUX_CONTROL_DLCI: DLCI = DLCI(0);
/// The minimum user-space DLCI.
const MIN_USER_DLCI: DLCI = DLCI(2);
/// The maximum user-space DLCI.
const MAX_USER_DLCI: DLCI = DLCI(61);
pub fn is_mux_control(&self) -> bool {
*self == Self::MUX_CONTROL_DLCI
}
pub fn is_user(&self) -> bool {
self.0 >= Self::MIN_USER_DLCI.0 && self.0 <= Self::MAX_USER_DLCI.0
}
/// Returns Ok(()) if the DLCI belongs to the side of the session with the
/// given `role` - this is only applicable to User DLCIs.
///
/// The DLCI space is divided into two equal parts. RFCOMM 5.2 states:
/// "...this partitions the DLCI value space such that server applications on the non-
/// initiating device are reachable on DLCIs 2,4,6,...,60, and server applications on
/// the initiating device are reachable on DLCIs 3,5,7,...,61."
pub fn validate(&self, role: Role) -> Result<(), RfcommError> {
if !self.is_user() {
return Err(RfcommError::InvalidDLCI(*self));
}
let valid_bit = match role {
Role::Responder => 0,
Role::Initiator => 1,
role => {
return Err(RfcommError::InvalidRole(role));
}
};
if self.0 % 2 == valid_bit {
Ok(())
} else {
Err(RfcommError::InvalidDLCI(*self))
}
}
/// Returns true if the DLCI is initiated by this device.
/// Returns an Error if the provided `role` is invalid or if the DLCI is not
/// a user DLCI.
pub fn initiator(&self, role: Role) -> Result<bool, RfcommError> {
if !self.is_user() {
return Err(RfcommError::InvalidDLCI(*self));
}
// A DLCI is considered initiated by us if the direction bit is the same as the expected
// direction bit associated with the role of the remote peer. See RFCOMM 5.4 for the
// expected value of the direction bit for a particular DLCI.
match role.opposite_role() {
Role::Responder => Ok(self.0 % 2 == 0),
Role::Initiator => Ok(self.0 % 2 == 1),
role => {
return Err(RfcommError::InvalidRole(role));
}
}
}
}
impl TryFrom<u8> for DLCI {
type Error = FrameParseError;
fn try_from(value: u8) -> Result<DLCI, Self::Error> {
if value != DLCI::MUX_CONTROL_DLCI.0
&& (value < DLCI::MIN_USER_DLCI.0 || value > DLCI::MAX_USER_DLCI.0)
{
return Err(FrameParseError::InvalidDLCI(value));
}
Ok(DLCI(value))
}
}
impl From<DLCI> for u8 {
fn from(value: DLCI) -> u8 {
value.0
}
}
impl TryFrom<DLCI> for ServerChannel {
type Error = RfcommError;
fn try_from(dlci: DLCI) -> Result<ServerChannel, Self::Error> {
if !dlci.is_user() {
return Err(RfcommError::InvalidDLCI(dlci));
}
// The ServerChannel is the upper 5 bits of the 6-bit DLCI. See RFCOMM 5.4.
ServerChannel::try_from(dlci.0 >> 1)
}
}
impl Display for DLCI {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(formatter, "{}", self.0)
}
}
/// The Server Channel number associated with an RFCOMM channel.
///
/// Use the provided `u8::try_from` implementation to construct a valid ServerChannel.
///
/// Server Channels are 5 bits wide; they are the 5 most significant bits of the
/// DLCI.
/// Server Channels 0 and 31 are reserved. See RFCOMM 5.4 for the definition and
/// usage.
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
pub struct ServerChannel(u8);
impl ServerChannel {
const MAX: ServerChannel = ServerChannel(30);
const MIN: ServerChannel = ServerChannel(1);
/// Returns an iterator over all the Server Channels.
pub fn all() -> impl Iterator<Item = ServerChannel> {
(Self::MIN.0..=Self::MAX.0).map(|x| ServerChannel(x))
}
/// Converts the ServerChannel to a DLCI for the provided `role`.
/// Defined in RFCOMM 5.4.
pub fn to_dlci(&self, role: Role) -> Result<DLCI, RfcommError> {
let direction_bit = match role {
Role::Initiator => 1,
Role::Responder => 0,
r => {
return Err(RfcommError::InvalidRole(r));
}
};
let v = (self.0 << 1) | direction_bit;
DLCI::try_from(v).map_err(RfcommError::from)
}
}
impl TryFrom<u8> for ServerChannel {
type Error = RfcommError;
fn try_from(src: u8) -> Result<ServerChannel, Self::Error> {
if src < Self::MIN.0 || src > Self::MAX.0 {
return Err(RfcommError::Other(format_err!("Out of range: {:?}", src).into()));
}
Ok(ServerChannel(src))
}
}
impl From<ServerChannel> for u8 {
fn from(value: ServerChannel) -> u8 {
value.0
}
}
impl Display for ServerChannel {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(formatter, "{}", self.0)
}
}
#[cfg(test)]
mod tests {
use super::*;
use assert_matches::assert_matches;
#[test]
fn test_create_dlci() {
let v1 = 10;
let dlci = DLCI::try_from(v1);
let expected_sc1 = ServerChannel::try_from(5).unwrap();
assert!(dlci.is_ok());
assert_eq!(ServerChannel::try_from(dlci.unwrap()).unwrap(), expected_sc1);
let v2 = 0;
let dlci = DLCI::try_from(v2).unwrap();
assert_matches!(ServerChannel::try_from(dlci), Err(RfcommError::InvalidDLCI(_)));
let v3 = 2;
let dlci = DLCI::try_from(v3);
let expected_sc3 = ServerChannel::try_from(1).unwrap();
assert!(dlci.is_ok());
assert_eq!(ServerChannel::try_from(dlci.unwrap()).unwrap(), expected_sc3);
let v4 = 61;
let dlci = DLCI::try_from(v4);
let expected_sc4 = ServerChannel::try_from(30).unwrap();
assert!(dlci.is_ok());
assert_eq!(ServerChannel::try_from(dlci.unwrap()).unwrap(), expected_sc4);
let v5 = 1;
let dlci = DLCI::try_from(v5);
assert!(dlci.is_err());
let v6 = 62;
let dlci = DLCI::try_from(v6);
assert!(dlci.is_err());
let v7 = 63;
let dlci = DLCI::try_from(v7);
assert!(dlci.is_err());
}
#[test]
fn validate_dlci_as_initiator_role() {
let role = Role::Initiator;
let dlci = DLCI::MUX_CONTROL_DLCI;
assert_matches!(dlci.validate(role), Err(RfcommError::InvalidDLCI(_)));
let dlci = DLCI::MIN_USER_DLCI;
assert_matches!(dlci.validate(role), Err(RfcommError::InvalidDLCI(_)));
let dlci = DLCI::try_from(9).unwrap();
assert!(dlci.validate(role).is_ok());
}
#[test]
fn validate_dlci_as_responder_role() {
let role = Role::Responder;
let dlci = DLCI::MUX_CONTROL_DLCI;
assert_matches!(dlci.validate(role), Err(RfcommError::InvalidDLCI(_)));
let dlci = DLCI::try_from(7).unwrap();
assert_matches!(dlci.validate(role), Err(RfcommError::InvalidDLCI(_)));
let dlci = DLCI::try_from(10).unwrap();
assert!(dlci.validate(role).is_ok());
}
#[test]
fn validate_dlci_with_invalid_role_returns_error() {
let role = Role::Unassigned;
let dlci = DLCI::try_from(10).unwrap();
assert_matches!(dlci.validate(role), Err(RfcommError::InvalidRole(_)));
let role = Role::Negotiating;
let dlci = DLCI::try_from(11).unwrap();
assert_matches!(dlci.validate(role), Err(RfcommError::InvalidRole(_)));
}
#[test]
fn dlci_check_is_initiator() {
let dlci = DLCI::MUX_CONTROL_DLCI;
assert_matches!(dlci.initiator(Role::Initiator), Err(RfcommError::InvalidDLCI(_)));
assert_matches!(dlci.initiator(Role::Responder), Err(RfcommError::InvalidDLCI(_)));
let dlci = DLCI::try_from(20).unwrap();
assert_matches!(dlci.initiator(Role::Initiator), Ok(true));
assert_matches!(dlci.initiator(Role::Responder), Ok(false));
let dlci = DLCI::try_from(25).unwrap();
assert_matches!(dlci.initiator(Role::Initiator), Ok(false));
assert_matches!(dlci.initiator(Role::Responder), Ok(true));
}
#[test]
fn dlci_check_as_initiator_with_invalid_role_returns_error() {
let role = Role::Unassigned;
let dlci = DLCI::try_from(10).unwrap();
assert_matches!(dlci.initiator(role), Err(RfcommError::InvalidRole(_)));
let role = Role::Negotiating;
let dlci = DLCI::try_from(11).unwrap();
assert_matches!(dlci.initiator(role), Err(RfcommError::InvalidRole(_)));
}
#[test]
fn convert_server_channel_to_dlci_invalid_role() {
let invalid_role = Role::Unassigned;
let server_channel = ServerChannel::try_from(10).unwrap();
assert_matches!(server_channel.to_dlci(invalid_role), Err(_));
let invalid_role = Role::Negotiating;
let server_channel = ServerChannel::try_from(13).unwrap();
assert_matches!(server_channel.to_dlci(invalid_role), Err(_));
}
#[test]
fn convert_server_channel_to_dlci_success() {
let server_channel = ServerChannel::try_from(5).unwrap();
let expected_dlci = DLCI::try_from(11).unwrap();
assert_eq!(server_channel.to_dlci(Role::Initiator).unwrap(), expected_dlci);
let expected_dlci = DLCI::try_from(10).unwrap();
assert_eq!(server_channel.to_dlci(Role::Responder).unwrap(), expected_dlci);
let server_channel = ServerChannel::MIN;
let expected_dlci = DLCI::try_from(2).unwrap();
assert_eq!(server_channel.to_dlci(Role::Responder).unwrap(), expected_dlci);
let server_channel = ServerChannel::MAX;
let expected_dlci = DLCI::try_from(61).unwrap();
assert_eq!(server_channel.to_dlci(Role::Initiator).unwrap(), expected_dlci);
}
#[test]
fn server_channel_from_primitive() {
let normal = 10;
let sc = ServerChannel::try_from(normal);
assert!(sc.is_ok());
let invalid = 0;
let sc = ServerChannel::try_from(invalid);
assert_matches!(sc, Err(_));
let too_large = 31;
let sc = ServerChannel::try_from(too_large);
assert_matches!(sc, Err(_));
let u8_max = std::u8::MAX;
let sc = ServerChannel::try_from(u8_max);
assert_matches!(sc, Err(_));
}
}