blob: 05fbbc105aadd6339e10f25be58d2f6a19dc5a7c [file]
/*
* Copyright 2019 HIMSA II K/S - www.himsa.com. Represented by EHIMA -
* www.ehima.com
*
* 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.
*/
/*
* This module contains API of the audio stream control protocol.
*/
#include "client_parser.h"
#include <base/strings/string_number_conversions.h>
#include <bluetooth/log.h>
#include <endian.h>
#include <hardware/bt_common_types.h>
#include <hardware/bt_gatt_types.h>
#include <map>
#include <numeric>
#include "internal_include/bt_trace.h"
#include "le_audio_types.h"
#include "le_audio_utils.h"
#include "os/log.h"
#include "stack/include/bt_types.h"
using bluetooth::le_audio::types::acs_ac_record;
namespace bluetooth::le_audio {
namespace client_parser {
namespace ascs {
static std::map<uint8_t, std::string> ase_state_map_string = {
{kAseStateIdle, "Idle"},
{kAseStateCodecConfigured, "Codec Configured"},
{kAseStateQosConfigured, "QoS Configured"},
{kAseStateEnabling, "Enabling"},
{kAseStateStreaming, "Streaming"},
{kAseStateDisabling, "Disabling"},
{kAseStateReleasing, "Releasing"},
};
static std::map<uint8_t, std::string> ctp_opcode_map_string = {
{kCtpOpcodeCodecConfiguration, "Config Codec"},
{kCtpOpcodeQosConfiguration, "Config QoS"},
{kCtpOpcodeEnable, "Enable"},
{kCtpOpcodeReceiverStartReady, "Receiver Start Ready"},
{kCtpOpcodeDisable, "Disable"},
{kCtpOpcodeReceiverStopReady, "Receiver Stop Ready"},
{kCtpOpcodeUpdateMetadata, "Update Metadata"},
{kCtpOpcodeRelease, "Release"},
};
static std::map<uint8_t, std::string> ctp_configuration_reason_map_string = {
{kCtpResponseNoReason, ""},
{kCtpResponseCodecId, "Codec ID"},
{kCtpResponseCodecSpecificConfiguration, "Codec specific configuration"},
{kCtpResponseSduInterval, "SDU interval"},
{kCtpResponseFraming, "Framing"},
{kCtpResponsePhy, "PHY"},
{kCtpResponseMaximumSduSize, "Maximum SDU size"},
{kCtpResponseRetransmissionNumber, "Retransmission number"},
{kCtpResponseMaxTransportLatency, "Max Transport latency"},
{kCtpResponsePresentationDelay, "Presentation delay"},
{kCtpResponseInvalidAseCisMapping, "Invalid ASE CIS mapping"},
};
static std::map<uint8_t, std::string> ctp_response_code_map_string = {
{kCtpResponseCodeSuccess, "Success"},
{kCtpResponseCodeUnsupportedOpcode, "Unsupported Opcode"},
{kCtpResponseCodeInvalidLength, "Invalid Length"},
{kCtpResponseCodeInvalidAseId, "Invalid ASE ID"},
{kCtpResponseCodeInvalidAseStateMachineTransition,
"Invalid ASE State Machine Transition"},
{kCtpResponseCodeInvalidAseDirection, "Invalid ASE Direction"},
{kCtpResponseCodeUnsupportedAudioCapabilities,
"Unsupported Audio Capabilities"},
{kCtpResponseCodeUnsupportedConfigurationParameterValue,
"Unsupported Configuration Parameter Value"},
{kCtpResponseCodeRejectedConfigurationParameterValue,
"Rejected Configuration Parameter Value"},
{kCtpResponseCodeInvalidConfigurationParameterValue,
"Invalid Configuration Parameter Value"},
{kCtpResponseCodeUnsupportedMetadata, "Unsupported Metadata"},
{kCtpResponseCodeRejectedMetadata, "Rejected Metadata"},
{kCtpResponseCodeInvalidMetadata, "Invalid Metadata"},
{kCtpResponseCodeInsufficientResources, "Insufficient Resources"},
{kCtpResponseCodeUnspecifiedError, "Unspecified Error"},
};
static std::map<uint8_t, std::string> ctp_metadata_reason_map_string = {
{kCtpMetadataResponsePreferredAudioContexts, "Preferred Audio Contexts"},
{kCtpMetadataResponseStreamingAudioContexts, "Streaming Audio Contexts"},
{kCtpMetadataResponseProgramInfo, "Program Info"},
{kCtpMetadataResponseLanguage, "Language"},
{kCtpMetadataResponseCcidList, "CCID List"},
{kCtpMetadataResponseParentalRating, "Parental Rating"},
{kCtpMetadataResponseProgramInfoUri, "Program Info URI"},
{kCtpMetadataResponseExtendedMetadata, "Extended Metadata"},
{kCtpMetadataResponseVendorSpecific, "Vendor Specific"},
};
static std::map<uint8_t, std::map<uint8_t, std::string>*>
ctp_response_code_map = {
{kCtpResponseCodeUnsupportedConfigurationParameterValue,
&ctp_configuration_reason_map_string},
{kCtpResponseCodeRejectedConfigurationParameterValue,
&ctp_configuration_reason_map_string},
{kCtpResponseCodeInvalidConfigurationParameterValue,
&ctp_configuration_reason_map_string},
{kCtpResponseCodeUnsupportedMetadata, &ctp_metadata_reason_map_string},
{kCtpResponseCodeRejectedMetadata, &ctp_metadata_reason_map_string},
{kCtpResponseCodeInvalidMetadata, &ctp_metadata_reason_map_string},
};
bool ParseAseStatusHeader(ase_rsp_hdr& arh, uint16_t len,
const uint8_t* value) {
if (len < kAseRspHdrMinLen) {
log::error("wrong len of ASE char (header): {}", static_cast<int>(len));
return false;
}
STREAM_TO_UINT8(arh.id, value);
STREAM_TO_UINT8(arh.state, value);
log::info("ASE status: \tASE id: 0x{:x}\tASE state: {} (0x{:x})", arh.id,
ase_state_map_string[arh.state], arh.state);
return true;
}
bool ParseAseStatusCodecConfiguredStateParams(
struct ase_codec_configured_state_params& rsp, uint16_t len,
const uint8_t* value) {
uint8_t codec_spec_conf_len;
if (len < kAseStatusCodecConfMinLen) {
log::error("Wrong len of codec conf status (Codec conf header)");
return false;
}
STREAM_TO_UINT8(rsp.framing, value);
STREAM_TO_UINT8(rsp.preferred_phy, value);
STREAM_TO_UINT8(rsp.preferred_retrans_nb, value);
STREAM_TO_UINT16(rsp.max_transport_latency, value);
STREAM_TO_UINT24(rsp.pres_delay_min, value);
STREAM_TO_UINT24(rsp.pres_delay_max, value);
STREAM_TO_UINT24(rsp.preferred_pres_delay_min, value);
STREAM_TO_UINT24(rsp.preferred_pres_delay_max, value);
STREAM_TO_UINT8(rsp.codec_id.coding_format, value);
STREAM_TO_UINT16(rsp.codec_id.vendor_company_id, value);
STREAM_TO_UINT16(rsp.codec_id.vendor_codec_id, value);
STREAM_TO_UINT8(codec_spec_conf_len, value);
len -= kAseStatusCodecConfMinLen;
if (len != codec_spec_conf_len) {
log::error("Wrong len of codec conf status (Codec spec conf)");
return false;
}
if (codec_spec_conf_len)
rsp.codec_spec_conf =
std::vector<uint8_t>(value, value + codec_spec_conf_len);
log::info(
"Codec configuration\n\tFraming: 0x{:x}\n\tPreferred PHY: "
"0x{:x}\n\tPreferred retransmission number: 0x{:x}\n\tMax transport "
"latency: 0x{:x}\n\tPresence delay min: 0x{:x}\n\tPresence delay max: "
"0x{:x}\n\tPreferredPresentationDelayMin: "
"0x{:x}\n\tPreferredPresentationDelayMax: 0x{:x}\n\tCoding format: "
"0x{:x}\n\tVendor codec company ID: 0x{:x}\n\tVendor codec ID: "
"0x{:x}\n\tCodec specific conf len: {}\n\tCodec specific conf: {}",
rsp.framing, rsp.preferred_phy, rsp.preferred_retrans_nb,
rsp.max_transport_latency, rsp.pres_delay_min, rsp.pres_delay_max,
rsp.preferred_pres_delay_min, rsp.preferred_pres_delay_max,
rsp.codec_id.coding_format, rsp.codec_id.vendor_company_id,
rsp.codec_id.vendor_codec_id, (int)codec_spec_conf_len,
base::HexEncode(rsp.codec_spec_conf.data(), rsp.codec_spec_conf.size()));
return true;
}
bool ParseAseStatusQosConfiguredStateParams(
struct ase_qos_configured_state_params& rsp, uint16_t len,
const uint8_t* value) {
if (len != kAseStatusCodecQosConfMinLen) {
log::error("Wrong len of ASE characteristic (QOS conf header)");
return false;
}
STREAM_TO_UINT8(rsp.cig_id, value);
STREAM_TO_UINT8(rsp.cis_id, value);
STREAM_TO_UINT24(rsp.sdu_interval, value);
STREAM_TO_UINT8(rsp.framing, value);
STREAM_TO_UINT8(rsp.phy, value);
STREAM_TO_UINT16(rsp.max_sdu, value);
STREAM_TO_UINT8(rsp.retrans_nb, value);
STREAM_TO_UINT16(rsp.max_transport_latency, value);
STREAM_TO_UINT24(rsp.pres_delay, value);
log::info(
"Codec QoS Configured\n\tCIG: 0x{:x}\n\tCIS: 0x{:x}\n\tSDU interval: "
"0x{:x}\n\tFraming: 0x{:x}\n\tPHY: 0x{:x}\n\tMax SDU: "
"0x{:x}\n\tRetransmission number: 0x{:x}\n\tMax transport latency: "
"0x{:x}\n\tPresentation delay: 0x{:x}",
rsp.cig_id, rsp.cis_id, rsp.sdu_interval, rsp.framing, rsp.phy,
rsp.max_sdu, rsp.retrans_nb, rsp.max_transport_latency, rsp.pres_delay);
return true;
}
bool ParseAseStatusTransientStateParams(struct ase_transient_state_params& rsp,
uint16_t len, const uint8_t* value) {
uint8_t metadata_len;
if (len < kAseStatusTransMinLen) {
log::error("Wrong len of ASE characteristic (metadata)");
return false;
}
STREAM_TO_UINT8(rsp.cig_id, value);
STREAM_TO_UINT8(rsp.cis_id, value);
STREAM_TO_UINT8(metadata_len, value);
len -= kAseStatusTransMinLen;
if (len != metadata_len) {
log::error("Wrong len of ASE characteristic (metadata)");
return false;
}
if (metadata_len > 0)
rsp.metadata = std::vector<uint8_t>(value, value + metadata_len);
log::info(
"Status enabling/streaming/disabling\n\tCIG: 0x{:x}\n\tCIS: "
"0x{:x}\n\tMetadata: {}",
rsp.cig_id, rsp.cis_id,
base::HexEncode(rsp.metadata.data(), rsp.metadata.size()));
return true;
}
bool ParseAseCtpNotification(struct ctp_ntf& ntf, uint16_t len,
const uint8_t* value) {
uint8_t num_entries;
if (len < kCtpNtfMinLen) {
log::error("Wrong len of ASE control point notification: {}", (int)len);
return false;
}
STREAM_TO_UINT8(ntf.op, value);
STREAM_TO_UINT8(num_entries, value);
if (len != kCtpNtfMinLen + (num_entries * kCtpAseEntryMinLen)) {
log::error("Wrong len of ASE control point notification (ASE IDs)");
return false;
}
for (int i = 0; i < num_entries; i++) {
struct ctp_ase_entry entry;
STREAM_TO_UINT8(entry.ase_id, value);
STREAM_TO_UINT8(entry.response_code, value);
STREAM_TO_UINT8(entry.reason, value);
ntf.entries.push_back(std::move(entry));
}
log::info(
"Control point notification\n\tOpcode: {} (0x{:x})\n\tNum ASE IDs: {}",
ctp_opcode_map_string[ntf.op], ntf.op, (int)num_entries);
for (size_t i = 0; i < num_entries; i++)
log::info("\n\tASE ID[0x{:x}] response: {} (0x{:x}) reason: {} (0x{:x})",
ntf.entries[i].ase_id,
ctp_response_code_map_string[ntf.entries[i].response_code],
ntf.entries[i].response_code,
((ctp_response_code_map.count(ntf.entries[i].response_code) != 0)
? (*ctp_response_code_map[ntf.entries[i].response_code])
[ntf.entries[i].reason]
: ""),
ntf.entries[i].reason);
return true;
}
bool PrepareAseCtpCodecConfig(const std::vector<struct ctp_codec_conf>& confs,
std::vector<uint8_t>& value) {
if (confs.size() == 0) return false;
std::stringstream conf_ents_str;
size_t msg_len = std::accumulate(
confs.begin(), confs.end(),
confs.size() * kCtpCodecConfMinLen + kAseNumSize + kCtpOpSize,
[&conf_ents_str](size_t cur_len, auto const& conf) {
if (utils::IsCodecUsingLtvFormat(conf.codec_id)) {
types::LeAudioLtvMap ltv;
if (ltv.Parse(conf.codec_config.data(), conf.codec_config.size())) {
for (const auto& [type, value] : ltv.Values()) {
conf_ents_str
<< "\ttype: " << std::to_string(type)
<< "\tlen: " << std::to_string(value.size())
<< "\tdata: " << base::HexEncode(value.data(), value.size())
<< "\n";
}
return cur_len + conf.codec_config.size();
}
log::error("Error parsing codec configuration LTV data.");
}
conf_ents_str << "\t"
<< base::HexEncode(conf.codec_config.data(),
conf.codec_config.size());
return cur_len + conf.codec_config.size();
});
value.resize(msg_len);
uint8_t* msg = value.data();
UINT8_TO_STREAM(msg, kCtpOpcodeCodecConfiguration);
UINT8_TO_STREAM(msg, confs.size());
for (const struct ctp_codec_conf& conf : confs) {
UINT8_TO_STREAM(msg, conf.ase_id);
UINT8_TO_STREAM(msg, conf.target_latency);
UINT8_TO_STREAM(msg, conf.target_phy);
UINT8_TO_STREAM(msg, conf.codec_id.coding_format);
UINT16_TO_STREAM(msg, conf.codec_id.vendor_company_id);
UINT16_TO_STREAM(msg, conf.codec_id.vendor_codec_id);
UINT8_TO_STREAM(msg, conf.codec_config.size());
ARRAY_TO_STREAM(msg, conf.codec_config.data(),
static_cast<int>(conf.codec_config.size()));
log::info(
"Codec configuration\n\tAse id: 0x{:x}\n\tTarget latency: "
"0x{:x}\n\tTarget PHY: 0x{:x}\n\tCoding format: 0x{:x}\n\tVendor codec "
"company ID: 0x{:x}\n\tVendor codec ID: 0x{:x}\n\tCodec config len: "
"{}\n\tCodec spec conf: \n{}",
conf.ase_id, conf.target_latency, conf.target_phy,
conf.codec_id.coding_format, conf.codec_id.vendor_company_id,
conf.codec_id.vendor_codec_id,
static_cast<int>(conf.codec_config.size()), conf_ents_str.str());
}
return true;
}
bool PrepareAseCtpConfigQos(const std::vector<struct ctp_qos_conf>& confs,
std::vector<uint8_t>& value) {
if (confs.size() == 0) return false;
value.resize(confs.size() * kCtpQosConfMinLen + kAseNumSize + kCtpOpSize);
uint8_t* msg = value.data();
UINT8_TO_STREAM(msg, kCtpOpcodeQosConfiguration);
UINT8_TO_STREAM(msg, confs.size());
for (const struct ctp_qos_conf& conf : confs) {
UINT8_TO_STREAM(msg, conf.ase_id);
UINT8_TO_STREAM(msg, conf.cig);
UINT8_TO_STREAM(msg, conf.cis);
UINT24_TO_STREAM(msg, conf.sdu_interval);
UINT8_TO_STREAM(msg, conf.framing);
UINT8_TO_STREAM(msg, conf.phy);
UINT16_TO_STREAM(msg, conf.max_sdu);
UINT8_TO_STREAM(msg, conf.retrans_nb);
UINT16_TO_STREAM(msg, conf.max_transport_latency);
UINT24_TO_STREAM(msg, conf.pres_delay);
log::info(
"QoS configuration\n\tAse id: 0x{:x}\n\tcig: 0x{:x}\n\tCis: "
"0x{:x}\n\tSDU interval: 0x{:x}\n\tFraming: 0x{:x}\n\tPhy: "
"0x{:x}\n\tMax sdu size: 0x{:x}\n\tRetrans nb: 0x{:x}\n\tMax Transport "
"latency: 0x{:x}\n\tPres delay: 0x{:x}",
conf.ase_id, conf.cig, conf.cis, conf.sdu_interval, conf.framing,
conf.phy, conf.max_sdu, conf.retrans_nb, conf.max_transport_latency,
conf.pres_delay);
}
return true;
}
bool PrepareAseCtpEnable(const std::vector<struct ctp_enable>& confs,
std::vector<uint8_t>& value) {
if (confs.size() == 0) return false;
if (confs.size() > UINT8_MAX) {
log::error("To many ASEs to update metadata");
return false;
}
uint16_t msg_len = confs.size() * kCtpEnableMinLen + kAseNumSize + kCtpOpSize;
for (auto& conf : confs) {
if (msg_len > GATT_MAX_ATTR_LEN) {
log::error("Message length above GATT maximum");
return false;
}
if (conf.metadata.size() > UINT8_MAX) {
log::error("ase[{}] metadata length is invalid", conf.ase_id);
return false;
}
msg_len += conf.metadata.size();
}
value.resize(msg_len);
uint8_t* msg = value.data();
UINT8_TO_STREAM(msg, kCtpOpcodeEnable);
UINT8_TO_STREAM(msg, confs.size());
for (const struct ctp_enable& conf : confs) {
UINT8_TO_STREAM(msg, conf.ase_id);
UINT8_TO_STREAM(msg, conf.metadata.size());
ARRAY_TO_STREAM(msg, conf.metadata.data(),
static_cast<int>(conf.metadata.size()));
log::info("Enable\n\tAse id: 0x{:x}\n\tMetadata: {}", conf.ase_id,
base::HexEncode(conf.metadata.data(), conf.metadata.size()));
}
return true;
}
bool PrepareAseCtpAudioReceiverStartReady(const std::vector<uint8_t>& ase_ids,
std::vector<uint8_t>& value) {
if (ase_ids.size() == 0) return false;
value.resize(ase_ids.size() * kAseIdSize + kAseNumSize + kCtpOpSize);
uint8_t* msg = value.data();
UINT8_TO_STREAM(msg, kCtpOpcodeReceiverStartReady);
UINT8_TO_STREAM(msg, ase_ids.size());
for (const uint8_t& id : ase_ids) {
UINT8_TO_STREAM(msg, id);
log::info("ReceiverStartReady\n\tAse id: 0x{:x}", id);
}
return true;
}
bool PrepareAseCtpDisable(const std::vector<uint8_t>& ase_ids,
std::vector<uint8_t>& value) {
if (ase_ids.size() == 0) return false;
value.resize(ase_ids.size() * kAseIdSize + kAseNumSize + kCtpOpSize);
uint8_t* msg = value.data();
UINT8_TO_STREAM(msg, kCtpOpcodeDisable);
UINT8_TO_STREAM(msg, ase_ids.size());
for (const uint8_t& id : ase_ids) {
UINT8_TO_STREAM(msg, id);
log::info("Disable\n\tAse id: 0x{:x}", id);
}
return true;
}
bool PrepareAseCtpAudioReceiverStopReady(const std::vector<uint8_t>& ase_ids,
std::vector<uint8_t>& value) {
if (ase_ids.size() == 0) return false;
value.resize(ase_ids.size() * kAseIdSize + kAseNumSize + kCtpOpSize);
uint8_t* msg = value.data();
UINT8_TO_STREAM(msg, kCtpOpcodeReceiverStopReady);
UINT8_TO_STREAM(msg, ase_ids.size());
for (const uint8_t& ase_id : ase_ids) {
UINT8_TO_STREAM(msg, ase_id);
log::info("ReceiverStopReady\n\tAse id: 0x{:x}", ase_id);
}
return true;
}
bool PrepareAseCtpUpdateMetadata(
const std::vector<struct ctp_update_metadata>& confs,
std::vector<uint8_t>& value) {
if (confs.size() == 0) return false;
if (confs.size() > UINT8_MAX) {
log::error("To many ASEs to update metadata");
return false;
}
uint16_t msg_len =
confs.size() * kCtpUpdateMetadataMinLen + kAseNumSize + kCtpOpSize;
for (auto& conf : confs) {
if (msg_len > GATT_MAX_ATTR_LEN) {
log::error("Message length above GATT maximum");
return false;
}
if (conf.metadata.size() > UINT8_MAX) {
log::error("ase[{}] metadata length is invalid", conf.ase_id);
return false;
}
msg_len += conf.metadata.size();
}
value.resize(msg_len);
uint8_t* msg = value.data();
UINT8_TO_STREAM(msg, kCtpOpcodeUpdateMetadata);
UINT8_TO_STREAM(msg, confs.size());
for (const struct ctp_update_metadata& conf : confs) {
UINT8_TO_STREAM(msg, conf.ase_id);
UINT8_TO_STREAM(msg, conf.metadata.size());
ARRAY_TO_STREAM(msg, conf.metadata.data(),
static_cast<int>(conf.metadata.size()));
log::info("Update Metadata\n\tAse id: 0x{:x}\n\tMetadata: {}", conf.ase_id,
base::HexEncode(conf.metadata.data(), conf.metadata.size()));
}
return true;
}
bool PrepareAseCtpRelease(const std::vector<uint8_t>& ase_ids,
std::vector<uint8_t>& value) {
if (ase_ids.size() == 0) return true;
value.resize(ase_ids.size() * kAseIdSize + kAseNumSize + kCtpOpSize);
uint8_t* msg = value.data();
UINT8_TO_STREAM(msg, kCtpOpcodeRelease);
UINT8_TO_STREAM(msg, ase_ids.size());
for (const uint8_t& ase_id : ase_ids) {
UINT8_TO_STREAM(msg, ase_id);
log::info("Release\n\tAse id: 0x{:x}", ase_id);
}
return true;
}
} // namespace ascs
namespace pacs {
int ParseSinglePac(std::vector<struct acs_ac_record>& pac_recs, uint16_t len,
const uint8_t* value) {
struct acs_ac_record rec;
uint8_t codec_spec_cap_len, metadata_len;
if (len < kAcsPacRecordMinLen) {
log::error("Wrong len of PAC record ({}!={})", len, kAcsPacRecordMinLen);
pac_recs.clear();
return -1;
}
STREAM_TO_UINT8(rec.codec_id.coding_format, value);
STREAM_TO_UINT16(rec.codec_id.vendor_company_id, value);
STREAM_TO_UINT16(rec.codec_id.vendor_codec_id, value);
STREAM_TO_UINT8(codec_spec_cap_len, value);
len -= kAcsPacRecordMinLen - kAcsPacMetadataLenLen;
if (len < codec_spec_cap_len + kAcsPacMetadataLenLen) {
log::error("Wrong len of PAC record (codec specific capabilities) ({}!={})",
len, codec_spec_cap_len + kAcsPacMetadataLenLen);
pac_recs.clear();
return -1;
}
rec.codec_spec_caps_raw.assign(value, value + codec_spec_cap_len);
if (utils::IsCodecUsingLtvFormat(rec.codec_id)) {
bool parsed;
rec.codec_spec_caps =
types::LeAudioLtvMap::Parse(value, codec_spec_cap_len, parsed);
if (!parsed) return -1;
}
value += codec_spec_cap_len;
len -= codec_spec_cap_len;
STREAM_TO_UINT8(metadata_len, value);
len -= kAcsPacMetadataLenLen;
if (len < metadata_len) {
log::error("Wrong len of PAC record (metadata) ({}!={})", len,
metadata_len);
pac_recs.clear();
return -1;
}
rec.metadata = std::vector<uint8_t>(value, value + metadata_len);
value += metadata_len;
len -= metadata_len;
pac_recs.push_back(std::move(rec));
return len;
}
bool ParsePacs(std::vector<struct acs_ac_record>& pac_recs, uint16_t len,
const uint8_t* value) {
if (len < kAcsPacDiscoverRspMinLen) {
log::error("Wrong len of PAC characteristic ({}!={})", len,
kAcsPacDiscoverRspMinLen);
return false;
}
uint8_t pac_rec_nb;
STREAM_TO_UINT8(pac_rec_nb, value);
len -= kAcsPacDiscoverRspMinLen;
pac_recs.reserve(pac_rec_nb);
for (int i = 0; i < pac_rec_nb; i++) {
int remaining_len = ParseSinglePac(pac_recs, len, value);
if (remaining_len < 0) return false;
value += (len - remaining_len);
len = remaining_len;
}
return true;
}
bool ParseAudioLocations(types::AudioLocations& audio_locations, uint16_t len,
const uint8_t* value) {
if (len != kAudioLocationsRspMinLen) {
log::error("Wrong len of Audio Location characteristic");
return false;
}
STREAM_TO_UINT32(audio_locations, value);
log::info("Audio locations: {}", audio_locations.to_string());
return true;
}
bool ParseSupportedAudioContexts(
types::BidirectionalPair<types::AudioContexts>& contexts, uint16_t len,
const uint8_t* value) {
if (len != kAseAudioSuppContRspMinLen) {
log::error("Wrong len of Audio Supported Context characteristic");
return false;
}
STREAM_TO_UINT16(contexts.sink.value_ref(), value);
STREAM_TO_UINT16(contexts.source.value_ref(), value);
log::info(
"Supported Audio Contexts: \n\tSupported Sink Contexts: {}\n\tSupported "
"Source Contexts: {}",
contexts.sink.to_string(), contexts.source.to_string());
return true;
}
bool ParseAvailableAudioContexts(
types::BidirectionalPair<types::AudioContexts>& contexts, uint16_t len,
const uint8_t* value) {
if (len != kAseAudioAvailRspMinLen) {
log::error("Wrong len of Audio Availability characteristic");
return false;
}
STREAM_TO_UINT16(contexts.sink.value_ref(), value);
STREAM_TO_UINT16(contexts.source.value_ref(), value);
log::info(
"Available Audio Contexts: \n\tAvailable Sink Contexts: {}\n\tAvailable "
"Source Contexts: {}",
contexts.sink.to_string(), contexts.source.to_string());
return true;
}
} // namespace pacs
namespace tmap {
bool ParseTmapRole(std::bitset<16>& role, uint16_t len, const uint8_t* value) {
if (len != kTmapRoleLen) {
log::error(
", Wrong len of Telephony Media Audio Profile Role, characteristic");
return false;
}
STREAM_TO_UINT16(role, value);
log::info(", Telephony Media Audio Profile Role:\n\tRole: {}",
role.to_string());
return true;
}
} // namespace tmap
} // namespace client_parser
} // namespace bluetooth::le_audio