blob: 5903b706b8e04a2991f7a627bdf8438e13732a5a [file] [log] [blame]
// Copyright 2019 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 crate::common_utils::common::macros::{fx_err_and_bail, with_line};
use anyhow::{Context, Error};
use fidl::endpoints::create_request_stream;
use fidl_fuchsia_bluetooth_bredr::{
Attribute, Channel, ChannelMode, ChannelParameters, ConnectParameters,
ConnectionReceiverRequest, ConnectionReceiverRequestStream, DataElement, Information,
L2capParameters, ProfileAdvertiseRequest, ProfileDescriptor, ProfileMarker, ProfileProxy,
ProfileSearchRequest, ProtocolDescriptor, ProtocolIdentifier, SearchResultsRequest,
SearchResultsRequestStream, ServiceClassProfileIdentifier, ServiceDefinition,
};
use fuchsia_async as fasync;
use fuchsia_bluetooth::types::{PeerId, Uuid};
use fuchsia_component as component;
use fuchsia_sync::RwLock;
use futures::channel::oneshot;
use futures::select;
use futures::stream::StreamExt;
use futures::FutureExt;
use serde_json::value::Value;
use std::collections::HashMap;
use tracing::*;
#[derive(Debug)]
struct ProfileServerFacadeInner {
/// The current Profile Server Proxy
profile_server_proxy: Option<ProfileProxy>,
/// Total count of services advertised so far.
advertisement_count: usize,
/// Services currently active on the Profile Server Proxy
advertisement_stoppers: HashMap<usize, oneshot::Sender<()>>,
// Holds the channel so the connection remains open.
l2cap_channel_holder: Option<Channel>,
}
/// Perform Profile Server operations.
///
/// Note this object is shared among all threads created by the server.
#[derive(Debug)]
pub struct ProfileServerFacade {
inner: RwLock<ProfileServerFacadeInner>,
}
impl ProfileServerFacade {
pub fn new() -> ProfileServerFacade {
ProfileServerFacade {
inner: RwLock::new(ProfileServerFacadeInner {
profile_server_proxy: None,
advertisement_count: 0,
advertisement_stoppers: HashMap::new(),
l2cap_channel_holder: None,
}),
}
}
/// Creates a Profile Server Proxy.
pub fn create_profile_server_proxy(&self) -> Result<ProfileProxy, Error> {
let tag = "ProfileServerFacade::create_profile_server_proxy";
match self.inner.read().profile_server_proxy.clone() {
Some(profile_server_proxy) => {
info!(
tag = &with_line!(tag),
"Current profile server proxy: {:?}", profile_server_proxy
);
Ok(profile_server_proxy)
}
None => {
info!(tag = &with_line!(tag), "Setting new profile server proxy");
let profile_server_proxy =
component::client::connect_to_protocol::<ProfileMarker>();
if let Err(err) = profile_server_proxy {
fx_err_and_bail!(
&with_line!(tag),
format_err!("Failed to create profile server proxy: {}", err)
);
}
profile_server_proxy
}
}
}
/// Initialize the ProfileServer proxy.
pub async fn init_profile_server_proxy(&self) -> Result<(), Error> {
self.inner.write().profile_server_proxy = Some(self.create_profile_server_proxy()?);
Ok(())
}
/// Returns a list of String UUIDs from a Serde JSON list of Values.
///
/// # Arguments
/// * `uuid_list` - A serde json list of Values to parse.
/// Example input:
/// 'uuid_list': ["00000001-0000-1000-8000-00805F9B34FB"]
pub fn generate_service_class_uuids(&self, uuid_list: &Vec<Value>) -> Result<Vec<Uuid>, Error> {
let tag = "ProfileServerFacade::generate_service_class_uuids";
let mut service_class_uuid_list = Vec::new();
for raw_uuid in uuid_list {
let uuid = if let Some(u) = raw_uuid.as_str() {
u
} else {
fx_err_and_bail!(
&with_line!(tag),
format_err!("Unable to convert Value to String.")
)
};
let uuid: Uuid = match uuid.parse() {
Ok(uuid) => uuid,
Err(e) => {
fx_err_and_bail!(
&with_line!(tag),
format_err!("Unable to convert to Uuid: {:?}", e)
);
}
};
service_class_uuid_list.push(uuid);
}
Ok(service_class_uuid_list)
}
/// Returns a list of ProtocolDescriptors from a Serde JSON input.
///
/// Defined Protocol Identifiers for the Protocol Descriptor
/// We intentionally omit deprecated profile identifiers.
/// From Bluetooth Assigned Numbers:
/// https://www.bluetooth.com/specifications/assigned-numbers/service-discovery
///
/// # Arguments
/// * `protocol_descriptors`: A Json Representation of the ProtocolDescriptors
/// to set up. Example:
/// 'protocol_descriptors': [
/// {
/// 'protocol': 25, # u64 Representation of ProtocolIdentifier::AVDTP
/// 'params': [
/// {
/// 'data': 0x0103 # to indicate 1.3
/// },
/// {
/// 'data': 0x0105 # to indicate 1.5
/// }
/// ]
/// },
/// {
/// 'protocol': 1, # u64 Representation of ProtocolIdentifier::SDP
/// 'params': [{
/// 'data': 0x0019
/// }]
/// }
/// ]
pub fn generate_protocol_descriptors(
&self,
protocol_descriptors: &Vec<Value>,
) -> Result<Vec<ProtocolDescriptor>, Error> {
let tag = "ProfileServerFacade::generate_protocol_descriptors";
let mut protocol_descriptor_list = Vec::new();
for raw_protocol_descriptor in protocol_descriptors {
let protocol = match raw_protocol_descriptor["protocol"].as_u64() {
Some(p) => match p as u16 {
1 => ProtocolIdentifier::Sdp,
3 => ProtocolIdentifier::Rfcomm,
7 => ProtocolIdentifier::Att,
8 => ProtocolIdentifier::Obex,
15 => ProtocolIdentifier::Bnep,
17 => ProtocolIdentifier::Hidp,
18 => ProtocolIdentifier::HardcopyControlChannel,
20 => ProtocolIdentifier::HardcopyDataChannel,
22 => ProtocolIdentifier::HardcopyNotification,
23 => ProtocolIdentifier::Avctp,
25 => ProtocolIdentifier::Avdtp,
30 => ProtocolIdentifier::McapControlChannel,
31 => ProtocolIdentifier::McapDataChannel,
256 => ProtocolIdentifier::L2Cap,
_ => fx_err_and_bail!(
&with_line!(tag),
format!("Input protocol does not match supported protocols: {}", p)
),
},
None => fx_err_and_bail!(&with_line!(tag), "Value 'protocol' not found."),
};
let raw_params = if let Some(p) = raw_protocol_descriptor["params"].as_array() {
p
} else {
fx_err_and_bail!(&with_line!(tag), "Value 'params' not found or invalid type.")
};
let mut params = Vec::new();
for param in raw_params {
let data = if let Some(d) = param["data"].as_u64() {
d as u16
} else {
fx_err_and_bail!(&with_line!(tag), "Value 'data' not found or invalid type.")
};
params.push(DataElement::Uint16(data as u16));
}
protocol_descriptor_list
.push(ProtocolDescriptor { protocol: protocol, params: params });
}
Ok(protocol_descriptor_list)
}
/// Returns a list of ProfileDescriptors from a Serde JSON input.
///
/// Identifiers that are valid for Bluetooth Classes / Profiles
/// We intentionally omit classes and profile IDs that are unsupported, deprecated,
/// or reserved for use by Fuchsia Bluetooth.
/// From Bluetooth Assigned Numbers for SDP
/// https://www.bluetooth.com/specifications/assigned-numbers/service-discovery
///
/// # Arguments
/// * `profile_descriptors`: A Json Representation of the ProtocolDescriptors.
/// Example:
/// 'profile_descriptors': [{
/// 'profile_id': 0x110D, # Represents ServiceClassProfileIdentifier::AdvancedAudioDistribution
/// 'major_version': 1, # u64 representation of the major_version.
/// 'minor_version': 3, # u64 representation of the minor_version.
/// }],
pub fn generate_profile_descriptors(
&self,
profile_descriptors: &Vec<Value>,
) -> Result<Vec<ProfileDescriptor>, Error> {
let tag = "ProfileServerFacade::generate_profile_descriptors";
let mut profile_descriptor_list = Vec::new();
for raw_profile_descriptor in profile_descriptors.into_iter() {
let profile_id = if let Some(r) = raw_profile_descriptor.get("profile_id") {
match self.get_service_class_profile_identifier_from_id(r) {
Ok(id) => id,
Err(e) => fx_err_and_bail!(&with_line!(tag), e),
}
} else {
let log_err = "Invalid SDP search input. Missing 'profile_id'";
fx_err_and_bail!(&with_line!(tag), log_err)
};
let minor_version = if let Some(num) = raw_profile_descriptor["minor_version"].as_u64()
{
num as u8
} else {
let log_err = "Type of 'minor_version' incorrect or incorrect type.";
fx_err_and_bail!(&with_line!(tag), log_err)
};
let major_version = if let Some(num) = raw_profile_descriptor["major_version"].as_u64()
{
num as u8
} else {
let log_err = "Type of 'major_version' incorrect or incorrect type.";
fx_err_and_bail!(&with_line!(tag), log_err)
};
profile_descriptor_list.push(ProfileDescriptor {
profile_id,
minor_version,
major_version,
});
}
Ok(profile_descriptor_list)
}
/// Returns a list of Information objects from a Serde JSON input.
///
/// # Arguments
/// * `information_list`: A Json Representation of the Information objects.
/// Example:
/// 'information_list': [{
/// 'language': "en",
/// 'name': "A2DP",
/// 'description': "Advanced Audio Distribution Profile",
/// 'provider': "Fuchsia"
/// }],
pub fn generate_information(
&self,
information_list: &Vec<Value>,
) -> Result<Vec<Information>, Error> {
let tag = "ProfileServerFacade::generate_information";
let mut info_list = Vec::new();
for raw_information in information_list {
let language = if let Some(v) = raw_information["language"].as_str() {
Some(v.to_string())
} else {
let log_err = "Type of 'language' incorrect of invalid type.";
fx_err_and_bail!(&with_line!(tag), log_err)
};
let name = if let Some(v) = raw_information["name"].as_str() {
Some(v.to_string())
} else {
None
};
let description = if let Some(v) = raw_information["description"].as_str() {
Some(v.to_string())
} else {
None
};
let provider = if let Some(v) = raw_information["provider"].as_str() {
Some(v.to_string())
} else {
None
};
info_list.push(Information {
language,
name,
description,
provider,
..Default::default()
});
}
Ok(info_list)
}
/// Returns a list of Attributes from a Serde JSON input.
///
/// # Arguments
/// * `additional_attributes_list`: A Json Representation of the Attribute objects.
/// Example:
/// 'additional_attributes': [{
/// 'id': 201,
/// 'element': {
/// 'data': int(sig_uuid_constants['AVDTP'], 16)
/// }
/// }]
pub fn generate_additional_attributes(
&self,
additional_attributes_list: &Vec<Value>,
) -> Result<Vec<Attribute>, Error> {
let tag = "ProfileServerFacade::generate_additional_attributes";
let mut attribute_list = Vec::new();
for raw_attribute in additional_attributes_list {
let id = if let Some(v) = raw_attribute["id"].as_u64() {
v as u16
} else {
let log_err = "Type of 'id' incorrect or invalid type.";
fx_err_and_bail!(&with_line!(tag), log_err)
};
let raw_element = if let Some(e) = raw_attribute.get("element") {
e
} else {
let log_err = "Type of 'element' incorrect.";
fx_err_and_bail!(&with_line!(tag), log_err)
};
let data_element = if let Some(d) = raw_element["data"].as_u64() {
DataElement::Uint8(d as u8)
} else {
fx_err_and_bail!(&with_line!(tag), "Value 'data' not found.")
};
attribute_list.push(Attribute { id: id, element: data_element })
}
Ok(attribute_list)
}
/// Monitor the connection request stream, printing outputs when connections happen.
pub async fn monitor_connection_receiver(
mut requests: ConnectionReceiverRequestStream,
end_signal: oneshot::Receiver<()>,
) -> Result<(), Error> {
let tag = "ProfileServerFacade::monitor_connection_receiver";
let mut fused_end_signal = end_signal.fuse();
loop {
select! {
_ = fused_end_signal => {
info!("Ending advertisement on signal..");
return Ok(());
},
request = requests.next() => {
let request = match request {
None => {
let log_err = format_err!("Connection request stream ended");
fx_err_and_bail!(&with_line!(tag), log_err)
}
Some(Err(e)) => {
let log_err = format_err!("Error during connection request: {}", e);
fx_err_and_bail!(&with_line!(tag), log_err)
},
Some(Ok(r)) => r,
};
let ConnectionReceiverRequest::Connected { peer_id, channel, .. } = request else {
fx_err_and_bail!(&with_line!(tag), "unknown method")
};
let peer_id: PeerId = peer_id.into();
info!(
tag = &with_line!(tag),
"Connection from {}: {:?}!",
peer_id,
channel
);
}
}
}
}
/// Monitor the search results stream, printing logs when results are produced.
pub async fn monitor_search_results(
mut requests: SearchResultsRequestStream,
) -> Result<(), Error> {
let tag = "ProfileServerFacade::monitor_search_results";
while let Some(request) = requests.next().await {
let request = match request {
Err(e) => {
let log_err = format_err!("Error during search results request: {}", e);
fx_err_and_bail!(&with_line!(tag), log_err)
}
Ok(r) => r,
};
let SearchResultsRequest::ServiceFound { peer_id, protocol, attributes, responder } =
request
else {
fx_err_and_bail!(&with_line!(tag), "unknown method")
};
let peer_id: PeerId = peer_id.into();
info!(
tag = &with_line!(tag),
"Search Result: Peer {} with protocol {:?}: {:?}", peer_id, protocol, attributes
);
responder.send()?;
}
let log_err = format_err!("Search result request stream ended");
fx_err_and_bail!(&with_line!(tag), log_err)
}
/// Adds a service record based on a JSON dictrionary.
///
/// # Arguments:
/// * `args` : A Json object representing the service to add:
///Example Python dictionary pre JSON conversion
///args:
///{
/// 'service_class_uuids': ["0001"],
/// 'protocol_descriptors': [
/// {
/// 'protocol':
/// int(sig_uuid_constants['AVDTP'], 16),
/// 'params': [
/// {
/// 'data': 0x0103
/// }
/// ]
/// },
/// {
/// 'protocol': int(sig_uuid_constants['SDP'], 16),
/// 'params': [{
/// 'data': int(sig_uuid_constants['AVDTP'], 16),
/// }]
/// }
/// ],
/// 'profile_descriptors': [{
/// 'profile_id': int(sig_uuid_constants['AdvancedAudioDistribution'], 16),
/// 'major_version': 1,
/// 'minor_version': 3,
/// }],
/// 'additional_protocol_descriptors': [{
/// 'protocol': int(sig_uuid_constants['L2CAP'], 16),
/// 'params': [{
/// 'data': int(sig_uuid_constants['AVDTP'], 16),
/// }]
/// }],
/// 'information': [{
/// 'language': "en",
/// 'name': "A2DP",
/// 'description': "Advanced Audio Distribution Profile",
/// 'provider': "Fuchsia"
/// }],
/// 'additional_attributes': [{
/// 'id': 201,
/// 'element': {
/// 'data': int(sig_uuid_constants['AVDTP'], 16)
/// }
/// }]
///}
pub async fn add_service(&self, args: Value) -> Result<usize, Error> {
let tag = "ProfileServerFacade::write_sdp_record";
info!(tag = &with_line!(tag), "Writing SDP record");
let record_description = if let Some(r) = args.get("record") {
r
} else {
let log_err = "Invalid SDP record input. Missing 'record'";
fx_err_and_bail!(&with_line!(tag), log_err)
};
let service_class_uuids = if let Some(v) = record_description.get("service_class_uuids") {
if let Some(r) = v.as_array() {
self.generate_service_class_uuids(r)?
} else {
let log_err = "Invalid type for service_class_uuids in record input.";
fx_err_and_bail!(&with_line!(tag), log_err)
}
} else {
let log_err = "Invalid SDP record input. Missing 'service_class_uuids'";
fx_err_and_bail!(&with_line!(tag), log_err)
};
let protocol_descriptors = if let Some(v) = record_description.get("protocol_descriptors") {
if let Some(r) = v.as_array() {
self.generate_protocol_descriptors(r)?
} else {
let log_err = "Invalid type for protocol_descriptors in record input.";
fx_err_and_bail!(&with_line!(tag), log_err)
}
} else {
let log_err = "Invalid SDP record input. Missing 'protocol_descriptors'";
fx_err_and_bail!(&with_line!(tag), log_err)
};
let profile_descriptors = if let Some(v) = record_description.get("profile_descriptors") {
if let Some(r) = v.as_array() {
self.generate_profile_descriptors(r)?
} else {
let log_err = "Invalid type for profile_descriptors in record input.";
fx_err_and_bail!(&with_line!(tag), log_err)
}
} else {
let log_err = "Invalid SDP record input. Missing 'profile_descriptors'";
fx_err_and_bail!(&with_line!(tag), log_err)
};
let raw_additional_protocol_descriptors =
if let Some(v) = record_description.get("additional_protocol_descriptors") {
if let Some(arr) = v.as_array() {
Some(self.generate_protocol_descriptors(arr)?)
} else if v.is_null() {
None
} else {
let log_err =
"Invalid type for 'additional_protocol_descriptors'. Expected null or array.";
fx_err_and_bail!(&with_line!(tag), log_err)
}
} else {
let log_err = "Invalid SDP record input. Missing 'additional_protocol_descriptors'";
fx_err_and_bail!(&with_line!(tag), log_err)
};
let information = if let Some(v) = record_description.get("information") {
if let Some(r) = v.as_array() {
self.generate_information(r)?
} else {
let log_err = "Invalid type for information in record input.";
fx_err_and_bail!(&with_line!(tag), log_err)
}
} else {
let log_err = "Invalid SDP record input. Missing 'information'";
fx_err_and_bail!(&with_line!(tag), log_err)
};
let additional_attributes = if let Some(v) = record_description.get("additional_attributes")
{
if let Some(r) = v.as_array() {
Some(self.generate_additional_attributes(r)?)
} else {
None
}
} else {
let log_err = "Invalid SDP record input. Missing 'additional_attributes'";
fx_err_and_bail!(&with_line!(tag), log_err)
};
let service_defs = vec![ServiceDefinition {
service_class_uuids: Some(service_class_uuids.into_iter().map(Into::into).collect()),
protocol_descriptor_list: Some(protocol_descriptors),
profile_descriptors: Some(profile_descriptors),
additional_protocol_descriptor_lists: match raw_additional_protocol_descriptors {
Some(d) => Some(vec![d]),
None => None,
},
information: Some(information),
additional_attributes,
..Default::default()
}];
let (connect_client, connect_requests) =
create_request_stream().context("ConnectionReceiver creation")?;
match &self.inner.read().profile_server_proxy {
Some(server) => {
let _ = server.advertise(ProfileAdvertiseRequest {
services: Some(service_defs),
receiver: Some(connect_client),
..Default::default()
});
}
None => fx_err_and_bail!(&with_line!(tag), "No Server Proxy created."),
};
let (end_ad_sender, end_ad_receiver) = oneshot::channel::<()>();
let request_handler_fut =
Self::monitor_connection_receiver(connect_requests, end_ad_receiver);
fasync::Task::spawn(async move {
if let Err(err) = request_handler_fut.await {
error!(?err, "Connection receiver handler ended with error");
}
})
.detach();
let next = self.inner.write().advertisement_count + 1;
self.inner.write().advertisement_stoppers.insert(next, end_ad_sender);
self.inner.write().advertisement_count = next;
Ok(next)
}
/// Removes a remote service by id.
///
/// # Arguments:
/// * `service_id`: The service id to remove.
pub async fn remove_service(&self, service_id: usize) -> Result<(), Error> {
let tag = "ProfileServerFacade::remove_service";
match self.inner.write().advertisement_stoppers.remove(&service_id) {
Some(_) => Ok(()),
None => fx_err_and_bail!(&with_line!(tag), "Service ID not found"),
}
}
pub fn get_service_class_profile_identifier_from_id(
&self,
raw_profile_id: &Value,
) -> Result<ServiceClassProfileIdentifier, Error> {
let tag = "ProfileServerFacade::get_service_class_profile_identifier_from_id";
match raw_profile_id.as_u64().map(u16::try_from) {
Some(Ok(id)) => match ServiceClassProfileIdentifier::from_primitive(id) {
Some(id) => return Ok(id),
None => {
let log_err = format!("UUID {} not supported by profile server.", id);
fx_err_and_bail!(&with_line!(tag), log_err)
}
},
_ => fx_err_and_bail!(&with_line!(tag), "Type of raw_profile_id incorrect."),
};
}
pub async fn add_search(&self, args: Value) -> Result<(), Error> {
let tag = "ProfileServerFacade::add_search";
info!(tag = &with_line!(tag), "Adding Search");
let raw_attribute_list = if let Some(v) = args.get("attribute_list") {
if let Some(r) = v.as_array() {
r
} else {
let log_err = "Expected 'attribute_list' as an array.";
fx_err_and_bail!(&with_line!(tag), log_err)
}
} else {
let log_err = "Invalid SDP search input. Missing 'attribute_list'";
fx_err_and_bail!(&with_line!(tag), log_err)
};
let mut attribute_list = Vec::new();
for item in raw_attribute_list {
match item.as_u64() {
Some(v) => attribute_list.push(v as u16),
None => fx_err_and_bail!(
&with_line!(tag),
"Failed to convert value in attribute_list to u16."
),
};
}
let profile_id = if let Some(r) = args.get("profile_id") {
self.get_service_class_profile_identifier_from_id(r)?
} else {
let log_err = "Invalid SDP search input. Missing 'profile_id'";
fx_err_and_bail!(&with_line!(tag), log_err)
};
let (search_client, result_requests) =
create_request_stream().context("SearchResults creation")?;
match &self.inner.read().profile_server_proxy {
Some(server) => server.search(ProfileSearchRequest {
service_uuid: Some(profile_id),
attr_ids: Some(attribute_list),
results: Some(search_client),
..Default::default()
})?,
None => fx_err_and_bail!(&with_line!(tag), "No Server Proxy created."),
};
let search_fut = Self::monitor_search_results(result_requests);
fasync::Task::spawn(async move {
if let Err(err) = search_fut.await {
error!(?err, "Search result handler ended with error");
}
})
.detach();
Ok(())
}
/// Sends an outgoing l2cap connection request
///
/// # Arguments:
/// * `id`: String - The peer id to connect to.
/// * `psm`: u16 - The PSM value to connect to:
/// Valid PSM values: https://www.bluetooth.com/specifications/assigned-numbers/logical-link-control/
/// * `mode`: String - The channel mode to connect over
/// Available Values: BASIC, ERTM
pub async fn connect(&self, id: String, psm: u16, mode: &str) -> Result<(), Error> {
let tag = "ProfileServerFacade::connect";
let peer_id: PeerId = match id.parse() {
Ok(id) => id,
Err(_) => {
fx_err_and_bail!(
&with_line!(tag),
"Failed to convert value in attribute_list to u16."
);
}
};
let mode = match mode {
"BASIC" => ChannelMode::Basic,
"ERTM" => ChannelMode::EnhancedRetransmission,
_ => fx_err_and_bail!(&with_line!(tag), format!("Invalid mode: {:?}.", mode)),
};
let connection_result = match &self.inner.read().profile_server_proxy {
Some(server) => {
let l2cap_params = L2capParameters {
psm: Some(psm),
parameters: Some(ChannelParameters {
channel_mode: Some(mode),
..Default::default()
}),
..Default::default()
};
server.connect(&peer_id.into(), &ConnectParameters::L2cap(l2cap_params)).await?
}
None => fx_err_and_bail!(&with_line!(tag), "No Server Proxy created."),
};
match connection_result {
Ok(r) => self.inner.write().l2cap_channel_holder = Some(r),
Err(e) => {
fx_err_and_bail!(&with_line!(tag), format!("Failed to connect with error: {:?}", e))
}
};
Ok(())
}
/// Cleanup any Profile Server related objects.
pub async fn cleanup(&self) -> Result<(), Error> {
// Dropping these will signal the other end with an Err, which is enough.
self.inner.write().advertisement_stoppers.clear();
self.inner.write().advertisement_count = 0;
self.inner.write().l2cap_channel_holder = None;
self.inner.write().profile_server_proxy = None;
Ok(())
}
}