// Copyright 2023 The Android Open Source Project // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License.
usecrate::ffi; usecrate::packets::{hci, llcp}; use std::collections::HashMap; use std::convert::{TryFrom, TryInto};
// Description of CIS configuration parameters: // // - ISO_Interval (multiple of 1.25ms) // ISO_Interval is the time between the CIS anchor points of adjacent CIS // events. ISO_Interval is equal for all CISes in a CIG. // - Sub_Interval (ms) // Sub_Interval is the time between start of two consecutive // subevents of a CIS // - NSE // NSE is the maximum number of subevents in each CIS event. // - BN (Burst Number) // BN is the number of payloads expected in each CIS event. // Each CIS event has NSE - BN retransmission slots. // - FT (Flush Timeout) // The Flush Timeout (FT) parameter is the maximum number of CIS events // that may be used to transmit (and retransmit) a given payload // - Framed // Framed indicates whether the CIS carries framed or unframed data; the // value shall be the same in both directions. // Unframed PDUs shall only be used when the ISO_Interval is equal to // or an integer multiple of the SDU_Interval. // // For the purpose of emulating the CISes, the intervals between CIS subevents, // and different CIS events are ignored, leading to a number of approximations: // // - CIG_Sync_Delay = ISO_Interval // The CIG synchronization point is the same as the next event anchor // - CIS_Sync_Delay = ISO_Interval // All CISes start on the CIG anchor. // - BN = NSE // Unless otherwise specified in test commands. // No retransmission slots. // - FT = 1 // All PDUs are sent within one event. // - Sub_Interval = ISO_Interval / NSE
#[allow(non_camel_case_types)] type microseconds = u32;
#[allow(non_camel_case_types)] type slots = u16;
/// CIG configuration. #[derive(Clone, Debug)] struct CigConfig { // CIG parameters.
iso_interval: slots,
sdu_interval_c_to_p: microseconds,
sdu_interval_p_to_c: microseconds,
ft_c_to_p: u8,
ft_p_to_c: u8,
framed: bool, // True when the CIG can still be configured.
configurable: bool,
}
impl CisParameters { fn new(cig_config: &CigConfig, cis_config: &CisConfig) -> CisParameters { let bn_c_to_p: u8 = cis_config
.bn_c_to_p
.unwrap_or_else(|| cis_config.max_sdu_c_to_p.div_ceil(251).try_into().unwrap()); let bn_p_to_c: u8 = cis_config
.bn_p_to_c
.unwrap_or_else(|| cis_config.max_sdu_p_to_c.div_ceil(251).try_into().unwrap()); let nse = cis_config
.nse
.unwrap_or(std::cmp::max(bn_c_to_p, bn_p_to_c)); let max_pdu_c_to_p = cis_config.max_pdu_c_to_p.unwrap_or(251); let max_pdu_p_to_c = cis_config.max_pdu_p_to_c.unwrap_or(251); let sub_interval = (cig_config.iso_interval as u32 * 1250) / nse as u32;
// Select one phy in the enabled mask with // the priority LE 2M > LE 1M > LE Coded. fn select_phy(phys: u8) -> u8 { match phys { 0x2 | 0x3 | 0x6 | 0x7 => 0x2, 0x1 | 0x5 => 0x1, 0x4 => 0x4, 0x0 => panic!(), // Not allowed by parameter LE Set Cig Parameters validation
_ => unreachable!(),
}
}
let phy_c_to_p = select_phy(cis_config.phy_c_to_p); let phy_p_to_c = select_phy(cis_config.phy_p_to_c);
/// ISO manager state. pubstruct IsoManager { /// CIG configuration.
cig_config: HashMap<u8, CigConfig>, /// CIS configuration.
cis_config: HashMap<(u8, u8), CisConfig>, /// Mapping from ACL connection handle to connection role.
acl_connections: HashMap<u16, hci::Role>, /// Mapping from CIS connection handle to a CIS connection /// opened as central (initiated a LL_CIS_REQ) or peripheral /// (accepted with LL_CIS_RSP). Central connections are in the /// configuration state as long as HCI LE Create CIS has not been /// invoked.
cis_connections: HashMap<u16, Cis>, /// Pending CIS connection requests, initiated from the command /// HCI LE Create CIS.
cis_connection_requests: Vec<CisRequest>, /// Link layer callbacks.
ops: ffi::ControllerOps,
}
// Notify of CIS disconnection for connected or initiated CISes. for cis inself.cis_connections.values() { if cis.acl_connection_handle != Some(acl_connection_handle) { continue;
}
match cis.state { // When the CIS is fully connected, disconnection is notified // with Disconnection Complete event.
CisState::Connected => self.send_hci_event(hci::DisconnectionComplete {
status: hci::ErrorCode::Success,
connection_handle: cis.cis_connection_handle,
reason,
}),
// When the CIS connection has been locally initiated, disconnection // is notified with LE Cis Established event with error status. // Similarly, when a remote CIS connection request has been accepted // by the Host, the status must be notified with LE Cis Established.
CisState::PendingRsp | CisState::PendingInd => { self.send_hci_event(hci::LeCisEstablishedV1 {
status: reason,
connection_handle: cis.cis_connection_handle,
cig_sync_delay: 0,
cis_sync_delay: 0,
transport_latency_c_to_p: 0,
transport_latency_p_to_c: 0,
phy_c_to_p: hci::SecondaryPhyType::NoPackets,
phy_p_to_c: hci::SecondaryPhyType::NoPackets,
nse: 0,
bn_p_to_c: 0,
bn_c_to_p: 0,
ft_p_to_c: 0,
ft_c_to_p: 0,
max_pdu_p_to_c: 0,
max_pdu_c_to_p: 0,
iso_interval: 0,
})
}
_ => (),
}
}
// Send LE Cis Established events with an error reason for pending CIS // connection requests. for cis in &self.cis_connection_requests { if cis.acl_connection_handle != acl_connection_handle { continue;
}
// Drop peripheral CIS connections on the removed ACL connection. self.cis_connections.retain(|_, cis| {
cis.role == hci::Role::Central
|| cis.acl_connection_handle != Some(acl_connection_handle)
});
// Reset the state of central CIS connections. for cis inself.cis_connections.values_mut() { if cis.acl_connection_handle == Some(acl_connection_handle) {
cis.acl_connection_handle = None;
cis.state = CisState::Configuration;
}
}
// Returns the first unused handle in the range 0xe00..0xefe. fn new_cis_connection_handle(&self) -> u16 {
(0xe00..0xefe)
.find(|handle| !self.cis_connections.contains_key(handle))
.unwrap()
}
// Insert a new CIS connection, optionally allocating an handle for the // selected CIG_Id, CIS_Id, Role triplet. Returns the handle for the connection. fn new_cis_connection(&mutself, cig_id: u8, cis_id: u8, role: hci::Role) -> u16 { let cis_connection_handle = self
.cis_connections
.values()
.filter_map(|cis| {
(cis.cig_id == cig_id && cis.cis_id == cis_id && cis.role == role)
.then_some(cis.cis_connection_handle)
})
.next();
cis_connection_handle.unwrap_or_else(|| { let cis_connection_handle = self.new_cis_connection_handle(); self.cis_connections.insert(
cis_connection_handle,
Cis {
cig_id,
cis_id,
role,
cis_connection_handle,
state: CisState::Configuration,
acl_connection_handle: None,
parameters: None,
iso_data_path_c_to_p: None,
iso_data_path_p_to_c: None,
},
);
cis_connection_handle
})
}
// If the Host issues this command when the CIG is not in the configurable // state, the Controller shall return the error code // Command Disallowed (0x0C). if !self
.cig_config
.get(&cig_id)
.map(|cig| cig.configurable)
.unwrap_or(true)
{
println!("CIG ({}) is no longer in the configurable state", cig_id); returnself.send_hci_event(command_complete(hci::ErrorCode::CommandDisallowed));
}
for cis in cis_config { let cis_id = cis.cis_id; let cis_connection = self.cis_connections.values().find(|cis| {
cis.cis_id == cis_id && cis.cig_id == cig_id && cis.role == hci::Role::Central
}); let (iso_data_path_c_to_p, iso_data_path_p_to_c) = cis_connection
.map(|cis| (cis.iso_data_path_c_to_p, cis.iso_data_path_p_to_c))
.unwrap_or((None, None));
// The PHY_C_To_P[i] parameter identifies which PHY to use for transmission // from the Central to the Peripheral. The Host shall set at least one bit in this // parameter and the Controller shall pick a PHY from the bits that are set. if cis.phy_c_to_p == 0 || cis.phy_p_to_c == 0 {
println!( "CIS ({}) does not configure PHys ({:x}, {:x})",
cis.cis_id, cis.phy_c_to_p, cis.phy_p_to_c
); returnself.send_hci_event(command_complete(
hci::ErrorCode::UnsupportedFeatureOrParameterValue,
));
}
// If the Host sets, in the PHY_C_To_P[i] or PHY_P_To_C[i] parameters, a bit // for a PHY that the Controller does not support, including a bit that is // reserved for future use, the Controller shall return the error code // Unsupported Feature or Parameter Value (0x11). if (cis.phy_c_to_p & !self.supported_phys()) != 0
|| (cis.phy_p_to_c & !self.supported_phys()) != 0
{
println!( "CIS ({}) configures unsupported PHYs ({:x}, {:x})",
cis.cis_id, cis.phy_c_to_p, cis.phy_p_to_c
); returnself.send_hci_event(command_complete(
hci::ErrorCode::UnsupportedFeatureOrParameterValue,
));
}
// If a CIS configuration that is being modified has a data path set in // the Central to Peripheral direction and the Host has specified // that Max_SDU_C_To_P[i] shall be set to zero, the Controller shall // return the error code Command Disallowed (0x0C). if cis.max_sdu_c_to_p == 0 && iso_data_path_c_to_p.is_some() {
println!( "CIS ({}) has a data path for C->P but Max_SDU_C_To_P is zero",
cis.cis_id
); returnself.send_hci_event(command_complete(hci::ErrorCode::CommandDisallowed));
}
// If a CIS configuration that is being modified has a data path set in the // Peripheral to Central direction and the Host has specified that // Max_SDU_P_To_C[i] shall be set to zero, the Controller shall return // the error code Command Disallowed (0x0C). if cis.max_sdu_p_to_c == 0 && iso_data_path_p_to_c.is_some() {
println!( "CIS ({}) has a data path for P->C but Max_SDU_P_To_C is zero",
cis.cis_id
); returnself.send_hci_event(command_complete(hci::ErrorCode::CommandDisallowed));
}
if cis.max_sdu_c_to_p > 0xfff || cis.max_sdu_p_to_c > 0xfff {
println!( "invalid Max_SDU C->P ({}) or Max_SDU P->C ({}) for CIS ({})",
cis.max_sdu_c_to_p, cis.max_sdu_p_to_c, cis_id
); returnself
.send_hci_event(command_complete(hci::ErrorCode::InvalidHciCommandParameters));
}
}
// If the Host specifies an invalid combination of CIS parameters, the // Controller shall return the error code Unsupported Feature or // Parameter Value (0x11). if (configures_c_to_p && !(0xff..=0xf_ffff).contains(&sdu_interval_c_to_p))
|| (configures_p_to_c && !(0xff..=0xf_ffff).contains(&sdu_interval_p_to_c))
{
println!( "invalid SDU_Interval C->P ({}) or SDU_Interval P->C ({})",
sdu_interval_c_to_p, sdu_interval_p_to_c
); returnself
.send_hci_event(command_complete(hci::ErrorCode::InvalidHciCommandParameters));
} if (configures_c_to_p && !(0x5..=0xfa0).contains(&max_transport_latency_c_to_p))
|| (configures_p_to_c && !(0x5..=0xfa0).contains(&max_transport_latency_p_to_c))
{
println!( "invalid Max_Transport_Latency C->P ({}) or Max_Transport_Latency P->C ({})",
max_transport_latency_c_to_p, max_transport_latency_p_to_c
); returnself
.send_hci_event(command_complete(hci::ErrorCode::InvalidHciCommandParameters));
}
let Some(iso_interval) = iso_interval(
sdu_interval_c_to_p,
sdu_interval_p_to_c,
framed,
max_transport_latency_c_to_p as u32 * 1000,
max_transport_latency_p_to_c as u32 * 1000,
) else {
println!( "ISO_Interval cannot be chosen that fulfills the requirement from the CIG parameters"); returnself.send_hci_event(command_complete(
hci::ErrorCode::UnsupportedFeatureOrParameterValue,
));
};
// If the Status return parameter is non-zero, then the state of the CIG // and its CIS configurations shall not be changed by the command. // If the CIG did not already exist, it shall not be created. let cig = self.cig_config.entry(cig_id).or_default(); letmut cis_connection_handles = vec![];
cig.iso_interval = iso_interval;
cig.sdu_interval_c_to_p = sdu_interval_c_to_p;
cig.sdu_interval_p_to_c = sdu_interval_p_to_c;
cig.ft_c_to_p = 1;
cig.ft_p_to_c = 1;
cig.framed = framed;
// If the Host issues this command when the CIG is not in the configurable // state, the Controller shall return the error code // Command Disallowed (0x0C). if !self
.cig_config
.get(&cig_id)
.map(|cig| cig.configurable)
.unwrap_or(true)
{
println!("CIG ({}) is no longer in the configurable state", cig_id); returnself.send_hci_event(command_complete(hci::ErrorCode::CommandDisallowed));
}
for cis in cis_config { let cis_id = cis.cis_id; let cis_connection = self.cis_connections.values().find(|cis| {
cis.cis_id == cis_id && cis.cig_id == cig_id && cis.role == hci::Role::Central
}); let (iso_data_path_c_to_p, iso_data_path_p_to_c) = cis_connection
.map(|cis| (cis.iso_data_path_c_to_p, cis.iso_data_path_p_to_c))
.unwrap_or((None, None));
// The PHY_C_To_P[i] parameter identifies which PHY to use for transmission // from the Central to the Peripheral. The Host shall set at least one bit in this // parameter and the Controller shall pick a PHY from the bits that are set. if cis.phy_c_to_p == 0 || cis.phy_p_to_c == 0 {
println!( "CIS ({}) does not configure PHys ({:x}, {:x})",
cis.cis_id, cis.phy_c_to_p, cis.phy_p_to_c
); returnself.send_hci_event(command_complete(
hci::ErrorCode::UnsupportedFeatureOrParameterValue,
));
}
// If the Host sets, in the PHY_C_To_P[i] or PHY_P_To_C[i] parameters, a bit // for a PHY that the Controller does not support, including a bit that is // reserved for future use, the Controller shall return the error code // Unsupported Feature or Parameter Value (0x11). if (cis.phy_c_to_p & !self.supported_phys()) != 0
|| (cis.phy_p_to_c & !self.supported_phys()) != 0
{
println!( "CIS ({}) configures unsupported PHYs ({:x}, {:x})",
cis.cis_id, cis.phy_c_to_p, cis.phy_p_to_c
); returnself.send_hci_event(command_complete(
hci::ErrorCode::UnsupportedFeatureOrParameterValue,
));
}
// If a CIS configuration that is being modified has a data path set in // the Central to Peripheral direction and the Host has specified // that Max_SDU_C_To_P[i] shall be set to zero, the Controller shall // return the error code Command Disallowed (0x0C). if cis.max_sdu_c_to_p == 0 && iso_data_path_c_to_p.is_some() {
println!( "CIS ({}) has a data path for C->P but Max_SDU_C_To_P is zero",
cis.cis_id
); returnself.send_hci_event(command_complete(hci::ErrorCode::CommandDisallowed));
}
// If a CIS configuration that is being modified has a data path set in the // Peripheral to Central direction and the Host has specified that // Max_SDU_P_To_C[i] shall be set to zero, the Controller shall return // the error code Command Disallowed (0x0C). if cis.max_sdu_p_to_c == 0 && iso_data_path_p_to_c.is_some() {
println!( "CIS ({}) has a data path for P->C but Max_SDU_P_To_C is zero",
cis.cis_id
); returnself.send_hci_event(command_complete(hci::ErrorCode::CommandDisallowed));
}
if cis.max_sdu_c_to_p > 0xfff || cis.max_sdu_p_to_c > 0xfff {
println!( "invalid Max_SDU C->P ({}) or Max_SDU P->C ({}) for CIS ({})",
cis.max_sdu_c_to_p, cis.max_sdu_p_to_c, cis_id
); returnself
.send_hci_event(command_complete(hci::ErrorCode::InvalidHciCommandParameters));
}
}
// If the Host specifies an invalid combination of CIS parameters, the // Controller shall return the error code Unsupported Feature or // Parameter Value (0x11). if (configures_c_to_p && !(0xff..=0xf_ffff).contains(&sdu_interval_c_to_p))
|| (configures_p_to_c && !(0xff..=0xf_ffff).contains(&sdu_interval_p_to_c))
{
println!( "invalid SDU_Interval C->P ({}) or SDU_Interval P->C ({})",
sdu_interval_c_to_p, sdu_interval_p_to_c
); returnself
.send_hci_event(command_complete(hci::ErrorCode::InvalidHciCommandParameters));
}
// If the Status return parameter is non-zero, then the state of the CIG // and its CIS configurations shall not be changed by the command. // If the CIG did not already exist, it shall not be created. let cig = self.cig_config.entry(cig_id).or_default(); letmut cis_connection_handles = vec![];
cig.iso_interval = iso_interval;
cig.sdu_interval_c_to_p = sdu_interval_c_to_p;
cig.sdu_interval_p_to_c = sdu_interval_p_to_c;
cig.ft_c_to_p = ft_c_to_p;
cig.ft_p_to_c = ft_p_to_c;
cig.framed = framed;
// If the Host issues this command with a CIG_ID that does not exist, the // Controller shall return the error code Unknown Connection Identifier (0x02). if !self.cig_config.contains_key(&cig_id) {
println!("CIG ({}) does not exist", cig_id); returnself.send_hci_event(command_complete(hci::ErrorCode::UnknownConnection));
}
// If the Host tries to remove a CIG which is in the active state, // then the Controller shall return the error code // Command Disallowed (0x0C). ifself.cis_connections.values().any(|cis| {
cis.role == hci::Role::Central
&& cis.cig_id == cig_id
&& cis.state != CisState::Configuration
}) {
println!("CIG ({}) cannot be removed as it is in active state", cig_id); returnself.send_hci_event(command_complete(hci::ErrorCode::CommandDisallowed));
}
let command_status = |status| hci::LeCreateCisStatus { status, num_hci_command_packets: 1 };
for cis_config in cis_config { matchself.acl_connections.get(&cis_config.acl_connection_handle) { // If any ACL_Connection_Handle[i] is not the handle of an existing ACL // connection, the Controller shall return the error code Unknown Connection // Identifier (0x02).
None => {
println!( "cannot create LE CIS with unknown ACL connection handle {}",
cis_config.acl_connection_handle
); returnself.send_hci_event(command_status(hci::ErrorCode::UnknownConnection));
} // If the Host issues this command on an ACL_Connection_Handle where the // Controller is the Peripheral, the Controller shall return the error code // Command Disallowed (0x0C).
Some(hci::Role::Peripheral) => {
println!( "the ACL connection handle {} is for a peripheral connection",
cis_config.acl_connection_handle
); returnself.send_hci_event(command_status(
hci::ErrorCode::InvalidHciCommandParameters,
));
}
Some(hci::Role::Central) => (),
}
// If any CIS_Connection_Handle[i] is not the handle of a CIS or CIS // configuration, the Controller shall return the error code Unknown Connection // Identifier (0x02). let Some(cis) = self.cis_connections.get(&cis_config.cis_connection_handle) else {
println!( "cannot create LE CIS with unknown CIS connection handle {}",
cis_config.cis_connection_handle
); returnself.send_hci_event(command_status(hci::ErrorCode::UnknownConnection));
};
// If the Host attempts to create a CIS that has already been created, the // Controller shall return the error code Connection Already Exists (0x0B). if cis.state != CisState::Configuration {
println!( "cannot create LE CIS with CIS connection handle {} as it is already connected",
cis_config.cis_connection_handle
); returnself
.send_hci_event(command_status(hci::ErrorCode::ConnectionAlreadyExists));
}
// If two different elements of the CIS_Connection_Handle arrayed parameter // identify the same CIS, the Controller shall return the error code // Invalid HCI Command Parameters (0x12). if cis_connection_requests
.iter()
.any(|request| request.cis_connection_handle == cis_config.cis_connection_handle)
{
println!( "the CIS connection handle {} is requested twice",
cis_config.cis_connection_handle
); returnself
.send_hci_event(command_status(hci::ErrorCode::InvalidHciCommandParameters));
}
// If the Host issues this command before all the HCI_LE_CIS_Established // events from the previous use of the command have been generated, the // Controller shall return the error code Command Disallowed (0x0C). if !self.cis_connection_requests.is_empty() {
println!("another LE Create CIS request is already pending"); returnself.send_hci_event(command_status(hci::ErrorCode::CommandDisallowed));
}
// If the Host issues this command when the Connected Isochronous Stream // (Host Support) feature bit (see [Vol 6] Part B, Section 4.6.27) is not set, // the Controller shall return the error code Command Disallowed (0x0C). if !self.connected_isochronous_stream_host_support() {
println!("the feature bit Connected Isochronous Stream (Host Support) is not set"); returnself.send_hci_event(command_status(hci::ErrorCode::CommandDisallowed));
}
// Tag the CIG as no longer configurable. for cis_request in &cis_connection_requests { self.cig_config
.get_mut(&cis_request.cig_id)
.unwrap()
.configurable = false;
}
// Send the first connection request. self.deque_cis_connection_request(); self.send_hci_event(command_status(hci::ErrorCode::Success))
}
pubfn hci_le_accept_cis_request(&mutself, packet: hci::LeAcceptCisRequest) { let connection_handle: u16 = packet.connection_handle();
let command_status =
|status| hci::LeAcceptCisRequestStatus { status, num_hci_command_packets: 1 };
// If the Peripheral’s Host issues this command with a // Connection_Handle that does not exist, or the Connection_Handle // is not for a CIS, the Controller shall return the error code // Unknown Connection Identifier (0x02). if !self.cis_connections.contains_key(&connection_handle) {
println!( "cannot accept LE CIS request with invalid connection handle {}",
connection_handle
); returnself.send_hci_event(command_status(hci::ErrorCode::UnknownConnection));
}
let cis = self.cis_connections.get_mut(&connection_handle).unwrap();
// If the Central’s Host issues this command, the Controller shall // return the error code Command Disallowed (0x0C). if cis.role == hci::Role::Central {
println!( "cannot accept LE CIS request with central connection handle {}",
connection_handle
); returnself.send_hci_event(command_status(hci::ErrorCode::CommandDisallowed));
}
// If the Peripheral's Host issues this command with a Connection_Handle // for a CIS that has already been established or that already has an // HCI_LE_Accept_CIS_Request or HCI_LE_Reject_CIS_Request command in progress, // the Controller shall return the error code Command Disallowed (0x0C). if cis.state != CisState::PendingAccept {
println!( "cannot accept LE CIS request for non-pending connection handle {}",
connection_handle
); returnself.send_hci_event(command_status(hci::ErrorCode::CommandDisallowed));
}
// Update local state.
cis.state = CisState::PendingInd;
// Send back LL_CIS_RSP to accept the request. let acl_connection_handle = cis.acl_connection_handle.unwrap(); self.send_llcp_packet(
acl_connection_handle,
llcp::CisRsp { cis_offset_min: 0, cis_offset_max: 0xffffff, conn_event_count: 0 },
);
// If the Peripheral’s Host issues this command with a // Connection_Handle that does not exist, or the Connection_Handle // is not for a CIS, the Controller shall return the error code // Unknown Connection Identifier (0x02). if !self.cis_connections.contains_key(&connection_handle) {
println!( "cannot accept LE CIS request with invalid connection handle {}",
connection_handle
); returnself.send_hci_event(command_complete(hci::ErrorCode::UnknownConnection));
}
let cis = self.cis_connections.get(&connection_handle).unwrap();
// If the Central’s Host issues this command, the Controller shall // return the error code Command Disallowed (0x0C). if cis.role == hci::Role::Central {
println!( "cannot accept LE CIS request with central connection handle {}",
connection_handle
); returnself.send_hci_event(command_complete(hci::ErrorCode::CommandDisallowed));
}
// If the Peripheral's Host issues this command with a Connection_Handle // for a CIS that has already been established or that already has an // HCI_LE_Accept_CIS_Request or HCI_LE_Reject_CIS_Request command in progress, // the Controller shall return the error code Command Disallowed (0x0C). if cis.state != CisState::PendingAccept {
println!( "cannot accept LE CIS request for non-pending connection handle {}",
connection_handle
); returnself.send_hci_event(command_complete(hci::ErrorCode::CommandDisallowed));
}
// Update local state. let acl_connection_handle = cis.acl_connection_handle.unwrap(); self.cis_connections.remove(&connection_handle);
// Send back LL_CIS_RSP to reject the request. let error_code = if packet.reason() == hci::ErrorCode::Success {
hci::ErrorCode::RemoteUserTerminatedConnection
} else {
packet.reason()
}; self.send_llcp_packet(
acl_connection_handle,
llcp::RejectExtInd {
reject_opcode: llcp::Opcode::LlCisReq as u8,
error_code: error_code as u8,
},
);
// If the Host attempts to set a data path with a Connection Handle that does not // exist or that is not for a CIS, CIS configuration, or BIS, the Controller shall // return the error code Unknown Connection Identifier (0x02). let Some(cis) = self.cis_connections.get_mut(&connection_handle) else {
println!("the CIS connection handle 0x{:x} is not assigned", connection_handle); returnself.send_hci_event(command_complete(hci::ErrorCode::CommandDisallowed));
};
let (c_to_p_direction, p_to_c_direction) = if cis.role == hci::Role::Central {
(hci::DataPathDirection::Output, hci::DataPathDirection::Input)
} else {
(hci::DataPathDirection::Input, hci::DataPathDirection::Output)
};
// If the Host issues this command more than once for the same // Connection_Handle and direction before issuing the HCI_LE_Remove_ISO_Data_- // Path command for that Connection_Handle and direction, the Controller shall // return the error code Command Disallowed (0x0C). if cis.iso_data_path_c_to_p.is_some() && packet.data_path_direction() == c_to_p_direction {
println!("C->P ISO data path already configured for ({}, {})", cis.cig_id, cis.cis_id); returnself.send_hci_event(command_complete(hci::ErrorCode::CommandDisallowed));
} if cis.iso_data_path_p_to_c.is_some() && packet.data_path_direction() == p_to_c_direction {
println!("P->C ISO data path already configured for ({}, {})", cis.cig_id, cis.cis_id); returnself.send_hci_event(command_complete(hci::ErrorCode::CommandDisallowed));
}
// If the Host issues this command for a CIS on a Peripheral before it has issued // the HCI_LE_Accept_CIS_Request command for that CIS, then the Controller // shall return the error code Command Disallowed (0x0C). if cis.role == hci::Role::Peripheral && cis.state == CisState::PendingAccept {
println!("setup ISO data path sent before accepting the CIS request"); returnself.send_hci_event(command_complete(hci::ErrorCode::CommandDisallowed));
}
// If the Host issues this command for a vendor-specific data transport path that // has not been configured using the HCI_Configure_Data_Path command, the // Controller shall return the error code Command Disallowed (0x0C).
// If the Host attempts to set an output data path using a connection handle that is // for an Isochronous Broadcaster, for an input data path on a Synchronized // Receiver, or for a data path for the direction on a unidirectional CIS where BN // is set to 0, the Controller shall return the error code Command Disallowed // (0x0C).
// If the Host issues this command with Codec_Configuration_Length non-zero // and Codec_ID set to transparent air mode, the Controller shall return the error // code Invalid HCI Command Parameters (0x12). if !codec_configuration.is_empty() && packet.codec_id() == 0x3 {
println!("Codec Configuration is not empty and Codec ID is for transparent air mode"); returnself
.send_hci_event(command_complete(hci::ErrorCode::InvalidHciCommandParameters));
}
// If the Host issues this command with codec-related parameters that exceed the // bandwidth and latency allowed on the established CIS or BIS identified by the // Connection_Handle parameter, the Controller shall return the error code // Invalid HCI Command Parameters (0x12).
// If the Host issues this command with a Connection_Handle that does not exist // or is not for a CIS, CIS configuration, or BIS, the Controller shall return the // error code Unknown Connection Identifier (0x02). let Some(cis) = self.cis_connections.get_mut(&connection_handle) else {
println!("the CIS connection handle 0x{:x} is not assigned", connection_handle); returnself.send_hci_event(command_complete(hci::ErrorCode::CommandDisallowed));
};
let (remove_c_to_p, remove_p_to_c) = if cis.role == hci::Role::Central {
(remove_output_data_path, remove_input_data_path)
} else {
(remove_input_data_path, remove_output_data_path)
};
// If the Host issues this command for a data path that has not been set up (using // the HCI_LE_Setup_ISO_Data_Path command), the Controller shall return the // error code Command Disallowed (0x0C) if cis.iso_data_path_c_to_p.is_none() && remove_c_to_p {
println!("attempted to remove Iso Data Path C->P but it is not configured"); returnself.send_hci_event(command_complete(hci::ErrorCode::CommandDisallowed));
} if cis.iso_data_path_p_to_c.is_none() && remove_p_to_c {
println!("attempted to remove Iso Data Path P->C but it is not configured"); returnself.send_hci_event(command_complete(hci::ErrorCode::CommandDisallowed));
}
if remove_c_to_p {
cis.iso_data_path_c_to_p = None;
} if remove_p_to_c {
cis.iso_data_path_p_to_c = None;
}
pubfn hci_disconnect(&mutself, packet: hci::Disconnect) { let connection_handle: u16 = packet.connection_handle(); let command_status = |status| hci::DisconnectStatus { status, num_hci_command_packets: 1};
let Some(cis) = self.cis_connections.get(&connection_handle).cloned() else { returnself.send_hci_event(command_status(hci::ErrorCode::UnknownConnection));
};
// If, on the Central, the Host issues this command before issuing the // HCI_LE_Create_CIS command for the same CIS, then the Controller shall // return the error code Command Disallowed (0x0C). // If, on the Peripheral, the Host issues this command before the Controller has // generated the HCI_LE_CIS_Established event for that CIS, then the Controller // shall return the error code Command Disallowed (0x0C). if !matches!(cis.state, CisState::Connected | CisState::PendingRsp) {
println!( "cannot disconnect CIS connection with handle {} because it is not connected",
connection_handle
); returnself.send_hci_event(command_status(hci::ErrorCode::CommandDisallowed));
}
iflet Some(cis_connection_handle) = cis_connection_handle { self.send_hci_event(hci::DisconnectionComplete {
status: hci::ErrorCode::Success,
connection_handle: cis_connection_handle,
reason: hci::ErrorCode::try_from(packet.error_code()).unwrap(),
}); self.cis_connections.remove(&cis_connection_handle);
} else {
println!("skipping out of place packet LL_CIS_TERMINATE_IND");
}
}
}
/// Derive a valid ISO_Interval for a CIG based on the /// LE Set Cig Parameters command input. SDU_Interval, Max_Transport_Latency are /// provided microseconds. fn iso_interval(
sdu_interval_c_to_p: microseconds,
sdu_interval_p_to_c: microseconds,
framed: bool,
max_transport_latency_c_to_p: microseconds,
max_transport_latency_p_to_c: microseconds,
) -> Option<slots> { if framed { let iso_interval = std::cmp::max(sdu_interval_c_to_p, sdu_interval_p_to_c);
Some(iso_interval.div_ceil(1250) as u16)
} else { // Unframed PDUs shall only be used when the ISO_Interval is equal to // or an integer multiple of the SDU_Interval and a constant time offset // alignment is maintained between the SDU generation and the timing in // the isochronous transport. let iso_interval = num_integer::lcm( 1250, match (sdu_interval_c_to_p, sdu_interval_p_to_c) {
(0, 0) => panic!(),
(0, _) => sdu_interval_p_to_c,
(_, 0) => sdu_interval_c_to_p,
_ => num_integer::lcm(sdu_interval_c_to_p, sdu_interval_p_to_c),
},
); let min_transport_latency_c_to_p = 2 * iso_interval - sdu_interval_c_to_p; let min_transport_latency_p_to_c = 2 * iso_interval - sdu_interval_p_to_c;
/// Compute the transport latency for a CIG based on the /// configuration parameters. CIG_Sync_Delay, SDU_Interval are provided /// in microseconds, ISO_Interval in multiple of 1.25ms, fn transport_latency(
cig_sync_delay: microseconds,
iso_interval: slots,
ft: u8,
sdu_interval: microseconds,
framed: bool,
) -> microseconds { let iso_interval = iso_interval as u32 * 1250; if framed {
cig_sync_delay + ft as u32 * iso_interval + sdu_interval
} else {
cig_sync_delay + ft as u32 * iso_interval - sdu_interval
}
}
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.