| // Copyright 2026 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. | |
| #![allow(dead_code)] | |
| use bt_ascs::client::{ASE_CONTROL_POINT_UUID, SINK_ASE_UUID, SOURCE_ASE_UUID}; | |
| use bt_ascs::server::ASCS_UUID; | |
| use bt_ascs::types::{ | |
| AseAdditionalParameters, AseId, AseState, AudioDirection, AudioStreamEndpoint, | |
| }; | |
| use bt_common::core::{CodecId, CodingFormat}; | |
| use bt_common::packet_encoding::Encodable; | |
| use bt_gatt::client::FromCharacteristic; | |
| use bt_gatt::test_utils::{FakeClient, FakePeerService}; | |
| use bt_gatt::types::{ | |
| AttributePermissions, Characteristic, CharacteristicProperties, CharacteristicProperty, Handle, | |
| }; | |
| use bt_pacs::{PacRecord, SourcePac, PACS_UUID}; | |
| pub const SOURCE_PAC_HANDLE: Handle = Handle(1); | |
| pub const SINK_ASE_HANDLE: Handle = Handle(2); | |
| pub const CONTROL_POINT_HANDLE: Handle = Handle(3); | |
| pub const SOURCE_ASE_HANDLE: Handle = Handle(4); | |
| pub const DEFAULT_SINK_ASE_ID: AseId = AseId(1); | |
| pub const DEFAULT_SOURCE_ASE_ID: AseId = AseId(2); | |
| /// Creates a `FakePeerService` configured as a PACS service with a single | |
| /// Source PAC record for LC3. | |
| pub fn create_pacs_service() -> FakePeerService { | |
| let mut pacs_service = FakePeerService::new(); | |
| let pac_record = PacRecord { | |
| codec_id: CodecId::Assigned(CodingFormat::Lc3), | |
| codec_specific_capabilities: vec![], | |
| metadata: vec![], | |
| }; | |
| let mut pac_buf = vec![0; 1 + pac_record.encoded_len()]; | |
| pac_buf[0] = 1; // 1 record | |
| pac_record.encode(&mut pac_buf[1..]).unwrap(); | |
| let source_pac_chr = Characteristic { | |
| handle: SOURCE_PAC_HANDLE, | |
| uuid: SourcePac::UUID, | |
| properties: CharacteristicProperty::Read.into(), | |
| permissions: AttributePermissions::default(), | |
| descriptors: vec![], | |
| }; | |
| pacs_service.add_characteristic(source_pac_chr, pac_buf); | |
| pacs_service | |
| } | |
| /// Sets up a PACS service in `client` with a single Source PAC record for LC3. | |
| /// Adds the service to `client` and returns the `FakePeerService`. | |
| pub fn setup_pacs_service(client: &mut FakeClient) -> FakePeerService { | |
| let pacs_service = create_pacs_service(); | |
| client.add_service(PACS_UUID, true, pacs_service.clone()); | |
| pacs_service | |
| } | |
| /// Creates a `FakePeerService` configured as an ASCS service with | |
| /// `sink_ase_count` sink ASEs, `source_ase_count` source ASEs, and a Control | |
| /// Point initialized with `control_point_val`. | |
| pub fn create_ascs_service( | |
| sink_ase_count: usize, | |
| source_ase_count: usize, | |
| control_point_val: Vec<u8>, | |
| ) -> FakePeerService { | |
| let mut ascs_service = FakePeerService::new(); | |
| let control_point_chr = Characteristic { | |
| handle: CONTROL_POINT_HANDLE, | |
| uuid: ASE_CONTROL_POINT_UUID, | |
| properties: CharacteristicProperties::WRITE_NOTIFY, | |
| permissions: AttributePermissions::default(), | |
| descriptors: vec![], | |
| }; | |
| ascs_service.add_characteristic(control_point_chr, control_point_val); | |
| // ASE ids and handles are allocated from iterators shared by every endpoint | |
| // characteristic, so sinks and sources can never collide with each other. | |
| // The handle reserved for the control point characteristic is skipped. | |
| let mut ase_ids = (1..).map(AseId); | |
| let mut handles = (SINK_ASE_HANDLE.0..).map(Handle).filter(|h| *h != CONTROL_POINT_HANDLE); | |
| let directions = std::iter::repeat_n(AudioDirection::Sink, sink_ase_count) | |
| .chain(std::iter::repeat_n(AudioDirection::Source, source_ase_count)); | |
| for direction in directions { | |
| let endpoint = AudioStreamEndpoint { | |
| handle: Handle(0), | |
| direction, | |
| ase_id: ase_ids.next().expect("ase ids available"), | |
| state: AseState::Idle, | |
| additional: AseAdditionalParameters::None, | |
| }; | |
| let mut ase_buf = vec![0; endpoint.encoded_len()]; | |
| endpoint.encode(&mut ase_buf).unwrap(); | |
| let uuid = match direction { | |
| AudioDirection::Sink => SINK_ASE_UUID, | |
| AudioDirection::Source => SOURCE_ASE_UUID, | |
| }; | |
| let ase_chr = Characteristic { | |
| handle: handles.next().expect("handles available"), | |
| uuid, | |
| properties: CharacteristicProperties::READ_NOTIFY, | |
| permissions: AttributePermissions::default(), | |
| descriptors: vec![], | |
| }; | |
| ascs_service.add_characteristic(ase_chr, ase_buf); | |
| } | |
| ascs_service | |
| } | |
| /// Sets up an ASCS service in `client` with `sink_ase_count` sink ASEs, | |
| /// `source_ase_count` source ASEs, and an empty Control Point. Adds the service | |
| /// to `client` and returns the `FakePeerService`. | |
| pub fn setup_ascs_service( | |
| client: &mut FakeClient, | |
| sink_ase_count: usize, | |
| source_ase_count: usize, | |
| ) -> FakePeerService { | |
| setup_ascs_service_with_cp_value(client, sink_ase_count, source_ase_count, vec![]) | |
| } | |
| /// Sets up an ASCS service in `client` with `sink_ase_count` sink ASEs, | |
| /// `source_ase_count` source ASEs, and a Control Point initialized with | |
| /// `control_point_val`. Adds the service to `client` and returns the | |
| /// `FakePeerService`. | |
| pub fn setup_ascs_service_with_cp_value( | |
| client: &mut FakeClient, | |
| sink_ase_count: usize, | |
| source_ase_count: usize, | |
| control_point_val: Vec<u8>, | |
| ) -> FakePeerService { | |
| let ascs_service = create_ascs_service(sink_ase_count, source_ase_count, control_point_val); | |
| client.add_service(ASCS_UUID, true, ascs_service.clone()); | |
| ascs_service | |
| } |