blob: cb61e57484c430384789657ed7796ff4bb572bfb [file]
// Copyright 2023 The Pigweed Authors
//
// 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
//
// https://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.
#include <numeric>
#include "lib/stdcompat/utility.h"
#include "pw_unit_test/framework.h"
// clang-format off
// All emboss headers are listed (even if they don't have explicit tests) to
// ensure they are compiled.
#include "pw_bluetooth/emboss_util.h"
#include "pw_bluetooth/att.emb.h" // IWYU pragma: keep
#include "pw_bluetooth/hci_commands.emb.h" // IWYU pragma: keep
#include "pw_bluetooth/hci_common.emb.h"
#include "pw_bluetooth/hci_data.emb.h"
#include "pw_bluetooth/hci_events.emb.h" // IWYU pragma: keep
#include "pw_bluetooth/hci_h4.emb.h" // IWYU pragma: keep
#include "pw_bluetooth/hci_test.emb.h"
#include "pw_bluetooth/hci_android.emb.h" // IWYU pragma: keep
#include "pw_bluetooth/l2cap_frames.emb.h" // IWYU pragma: keep
#include "pw_bluetooth/rfcomm_frames.emb.h" // IWYU pragma: keep
#include "pw_bluetooth/avctp_avrcp.emb.h" // IWYU pragma: keep
// clang-format on
namespace pw::bluetooth {
namespace {
// Examples are used in docs.rst.
TEST(EmbossExamples, MakeView) {
// DOCSTAG: [pw_bluetooth-examples-make_view]
std::array<uint8_t, 4> buffer = {0x00, 0x01, 0x02, 0x03};
auto view = emboss::MakeTestCommandPacketView(&buffer);
EXPECT_TRUE(view.IsComplete());
EXPECT_EQ(view.payload().Read(), 0x03);
// DOCSTAG: [pw_bluetooth-examples-make_view]
}
TEST(EmbossTest, MakeView) {
std::array<uint8_t, 4> buffer = {0x00, 0x01, 0x02, 0x03};
auto view = emboss::MakeTestCommandPacketView(&buffer);
EXPECT_TRUE(view.IsComplete());
EXPECT_EQ(view.payload().Read(), 0x03);
}
static void InitializeIsoPacket(const emboss::IsoDataFramePacketWriter& view,
emboss::TsFlag ts_flag,
emboss::IsoDataPbFlag pb_flag,
size_t sdu_fragment_size) {
view.header().connection_handle().Write(0x123);
view.header().ts_flag().Write(ts_flag);
view.header().pb_flag().Write(pb_flag);
size_t optional_fields_total_size = 0;
if (ts_flag == emboss::TsFlag::TIMESTAMP_PRESENT) {
optional_fields_total_size += 4;
}
if ((pb_flag == emboss::IsoDataPbFlag::FIRST_FRAGMENT) ||
(pb_flag == emboss::IsoDataPbFlag::COMPLETE_SDU)) {
optional_fields_total_size += 4;
}
view.header().data_total_length().Write(sdu_fragment_size +
optional_fields_total_size);
}
// This definition has a mix of full-width values and bitfields and includes
// conditional bitfields. Let's add this to verify that the structure itself
// doesn't get changed incorrectly and that emboss' size calculation matches
// ours.
TEST(EmbossTest, CheckIsoPacketSize) {
std::array<uint8_t, 2048> buffer{};
const size_t kSduFragmentSize = 100;
auto view = emboss::MakeIsoDataFramePacketView(&buffer);
InitializeIsoPacket(view,
emboss::TsFlag::TIMESTAMP_NOT_PRESENT,
emboss::IsoDataPbFlag::FIRST_FRAGMENT,
kSduFragmentSize);
ASSERT_TRUE(view.IntrinsicSizeInBytes().Ok());
EXPECT_EQ(static_cast<size_t>(view.IntrinsicSizeInBytes().Read()),
view.hdr_size().Read() + kSduFragmentSize + 4);
InitializeIsoPacket(view,
emboss::TsFlag::TIMESTAMP_NOT_PRESENT,
emboss::IsoDataPbFlag::INTERMEDIATE_FRAGMENT,
kSduFragmentSize);
ASSERT_TRUE(view.IntrinsicSizeInBytes().Ok());
EXPECT_EQ(static_cast<size_t>(view.IntrinsicSizeInBytes().Read()),
view.hdr_size().Read() + kSduFragmentSize);
InitializeIsoPacket(view,
emboss::TsFlag::TIMESTAMP_NOT_PRESENT,
emboss::IsoDataPbFlag::COMPLETE_SDU,
kSduFragmentSize);
ASSERT_TRUE(view.IntrinsicSizeInBytes().Ok());
EXPECT_EQ(static_cast<size_t>(view.IntrinsicSizeInBytes().Read()),
view.hdr_size().Read() + kSduFragmentSize + 4);
InitializeIsoPacket(view,
emboss::TsFlag::TIMESTAMP_NOT_PRESENT,
emboss::IsoDataPbFlag::LAST_FRAGMENT,
kSduFragmentSize);
ASSERT_TRUE(view.IntrinsicSizeInBytes().Ok());
EXPECT_EQ(static_cast<size_t>(view.IntrinsicSizeInBytes().Read()),
view.hdr_size().Read() + kSduFragmentSize);
InitializeIsoPacket(view,
emboss::TsFlag::TIMESTAMP_PRESENT,
emboss::IsoDataPbFlag::FIRST_FRAGMENT,
kSduFragmentSize);
ASSERT_TRUE(view.IntrinsicSizeInBytes().Ok());
EXPECT_EQ(static_cast<size_t>(view.IntrinsicSizeInBytes().Read()),
view.hdr_size().Read() + kSduFragmentSize + 8);
InitializeIsoPacket(view,
emboss::TsFlag::TIMESTAMP_PRESENT,
emboss::IsoDataPbFlag::INTERMEDIATE_FRAGMENT,
kSduFragmentSize);
ASSERT_TRUE(view.IntrinsicSizeInBytes().Ok());
EXPECT_EQ(static_cast<size_t>(view.IntrinsicSizeInBytes().Read()),
view.hdr_size().Read() + kSduFragmentSize + 4);
InitializeIsoPacket(view,
emboss::TsFlag::TIMESTAMP_PRESENT,
emboss::IsoDataPbFlag::COMPLETE_SDU,
kSduFragmentSize);
ASSERT_TRUE(view.IntrinsicSizeInBytes().Ok());
EXPECT_EQ(static_cast<size_t>(view.IntrinsicSizeInBytes().Read()),
view.hdr_size().Read() + kSduFragmentSize + 8);
InitializeIsoPacket(view,
emboss::TsFlag::TIMESTAMP_PRESENT,
emboss::IsoDataPbFlag::LAST_FRAGMENT,
kSduFragmentSize);
ASSERT_TRUE(view.IntrinsicSizeInBytes().Ok());
EXPECT_EQ(static_cast<size_t>(view.IntrinsicSizeInBytes().Read()),
view.hdr_size().Read() + kSduFragmentSize + 4);
}
// Test and demonstrate various ways of reading opcodes.
TEST(EmbossTest, ReadOpcodesFromCommandHeader) {
// First two bytes will be used as opcode.
std::array<uint8_t, 4> buffer = {0x00, 0x00, 0x02, 0x03};
auto view = emboss::MakeTestCommandPacketView(&buffer);
EXPECT_TRUE(view.IsComplete());
auto header = view.header();
EXPECT_EQ(header.opcode().Read(), emboss::OpCode::UNSPECIFIED);
EXPECT_EQ(header.opcode_bits().BackingStorage().ReadUInt(), 0x0000);
EXPECT_EQ(header.opcode_bits().ogf().Read(), 0x00);
EXPECT_EQ(header.opcode_bits().ocf().Read(), 0x00);
// LINK_KEY_REQUEST_REPLY is OGF 0x01 and OCF 0x0B.
header.opcode().Write(emboss::OpCode::LINK_KEY_REQUEST_REPLY);
EXPECT_EQ(header.opcode().Read(), emboss::OpCode::LINK_KEY_REQUEST_REPLY);
EXPECT_EQ(header.opcode_bits().BackingStorage().ReadUInt(), 0x040B);
EXPECT_EQ(header.opcode_bits().ogf().Read(), 0x01);
EXPECT_EQ(header.opcode_bits().ocf().Read(), 0x0B);
}
// Test and demonstrate various ways of writing opcodes.
TEST(EmbossTest, WriteOpcodesFromCommandHeader) {
std::array<uint8_t, 4> buffer = {};
buffer.fill(0xFF);
auto view = emboss::MakeTestCommandPacketView(&buffer);
EXPECT_TRUE(view.IsComplete());
auto header = view.header();
header.opcode().Write(emboss::OpCode::UNSPECIFIED);
EXPECT_EQ(header.opcode_bits().BackingStorage().ReadUInt(), 0x0000);
header.opcode_bits().ocf().Write(0x0B);
EXPECT_EQ(header.opcode_bits().BackingStorage().ReadUInt(), 0x000B);
header.opcode_bits().ogf().Write(0x01);
EXPECT_EQ(header.opcode_bits().BackingStorage().ReadUInt(), 0x040B);
// LINK_KEY_REQUEST_REPLY is OGF 0x01 and OCF 0x0B.
EXPECT_EQ(header.opcode().Read(), emboss::OpCode::LINK_KEY_REQUEST_REPLY);
}
// Test and demonstrate using to_underlying with OpCodes enums
TEST(EmbossTest, OPCodeEnumsWithToUnderlying) {
EXPECT_EQ(0x0000, cpp23::to_underlying(emboss::OpCode::UNSPECIFIED));
}
TEST(EmbossTest, ReadAndWriteOpcodesInCommandResponseHeader) {
// First two bytes will be used as opcode.
std::array<uint8_t,
emboss::ReadBufferSizeCommandCompleteEventView::SizeInBytes()>
buffer;
std::iota(buffer.begin(), buffer.end(), 100);
auto view = emboss::MakeReadBufferSizeCommandCompleteEventView(&buffer);
EXPECT_TRUE(view.IsComplete());
auto header = view.command_complete();
header.command_opcode_uint().Write(0x0000);
EXPECT_EQ(header.command_opcode().Read(), emboss::OpCode::UNSPECIFIED);
EXPECT_EQ(header.command_opcode_bits().BackingStorage().ReadUInt(), 0x0000);
EXPECT_EQ(header.command_opcode_bits().ogf().Read(), 0x00);
EXPECT_EQ(header.command_opcode_bits().ocf().Read(), 0x00);
// LINK_KEY_REQUEST_REPLY is OGF 0x01 and OCF 0x0B.
header.command_opcode().Write(emboss::OpCode::LINK_KEY_REQUEST_REPLY);
EXPECT_EQ(header.command_opcode().Read(),
emboss::OpCode::LINK_KEY_REQUEST_REPLY);
EXPECT_EQ(header.command_opcode_bits().BackingStorage().ReadUInt(), 0x040B);
EXPECT_EQ(header.command_opcode_bits().ogf().Read(), 0x01);
EXPECT_EQ(header.command_opcode_bits().ocf().Read(), 0x0B);
}
TEST(EmbossTest, ReadAndWriteEventCodesInEventHeader) {
std::array<uint8_t, emboss::EventHeaderWriter::SizeInBytes()> buffer;
std::iota(buffer.begin(), buffer.end(), 100);
auto header = emboss::MakeEventHeaderView(&buffer);
EXPECT_TRUE(header.IsComplete());
header.event_code_uint().Write(
cpp23::to_underlying(emboss::EventCode::NUMBER_OF_COMPLETED_PACKETS));
EXPECT_EQ(header.event_code().Read(),
emboss::EventCode::NUMBER_OF_COMPLETED_PACKETS);
EXPECT_EQ(
header.event_code_uint().Read(),
cpp23::to_underlying(emboss::EventCode::NUMBER_OF_COMPLETED_PACKETS));
EXPECT_EQ(
header.event_code_uint().Read(),
cpp23::to_underlying(emboss::EventCode::NUMBER_OF_COMPLETED_PACKETS));
header.event_code().Write(emboss::EventCode::CONNECTION_REQUEST);
EXPECT_EQ(header.event_code_uint().Read(),
cpp23::to_underlying(emboss::EventCode::CONNECTION_REQUEST));
}
TEST(EmbossTest, ReadCommandPayloadLength) {
std::array<uint8_t, 8> hci_buffer = {
0x4c, 0xfc, 0x05, 0x73, 0x86, 0x30, 0x00, 0x00};
emboss::CommandHeaderView command = emboss::MakeCommandHeaderView(
hci_buffer.data(), emboss::CommandHeaderView::SizeInBytes());
EXPECT_TRUE(command.IsComplete());
EXPECT_EQ(command.parameter_total_size().Read(), 5);
}
TEST(EmbossTest, ReadEventPayloadLength) {
std::array<uint8_t, 8> hci_buffer = {0x0e, 0x04, 0x01, 0x2e, 0xfc, 0x00};
emboss::EventHeaderView event = emboss::MakeEventHeaderView(
hci_buffer.data(), emboss::EventHeaderView::SizeInBytes());
EXPECT_TRUE(event.IsComplete());
EXPECT_EQ(event.parameter_total_size().Read(), 4);
}
TEST(EmbossTest, ReadAclPayloadLength) {
std::array<uint8_t, 16> hci_buffer = {0x0c,
0x00,
0x0c,
0x00,
0x08,
0x00,
0x01,
0x00,
0x06,
0x06,
0x04,
0x00,
0x5b,
0x00,
0x41,
0x00};
emboss::AclDataFrameHeaderView acl = emboss::MakeAclDataFrameHeaderView(
hci_buffer.data(), emboss::AclDataFrameHeaderView::SizeInBytes());
EXPECT_TRUE(acl.IsComplete());
EXPECT_EQ(acl.data_total_length().Read(), 12);
}
TEST(EmbossTest, ReadScoPayloadLength) {
std::array<uint8_t, 9> hci_buffer = {
0x02, 0x00, 0x06, 0xFF, 0xD3, 0x4A, 0x1B, 0x2C, 0x3D};
emboss::ScoDataHeaderView sco = emboss::ScoDataHeaderView(
hci_buffer.data(), emboss::ScoDataHeaderView::SizeInBytes());
EXPECT_TRUE(sco.IsComplete());
EXPECT_EQ(sco.data_total_length().Read(), 6);
}
TEST(EmbossTest, WriteSniffMode) {
std::array<uint8_t, emboss::SniffModeCommandWriter::SizeInBytes()> buffer{};
emboss::SniffModeCommandWriter writer =
emboss::MakeSniffModeCommandView(&buffer);
writer.header().opcode().Write(emboss::OpCode::SNIFF_MODE);
writer.header().parameter_total_size().Write(
emboss::SniffModeCommandWriter::SizeInBytes() -
emboss::CommandHeaderWriter::SizeInBytes());
writer.connection_handle().Write(0x0004);
writer.sniff_max_interval().Write(0x0330);
writer.sniff_min_interval().Write(0x0190);
writer.sniff_attempt().Write(0x0004);
writer.sniff_timeout().Write(0x0001);
std::array<uint8_t, emboss::SniffModeCommandView::SizeInBytes()> expected{
// Opcode (LSB, MSB)
0x03,
0x08,
// Parameter Total Size
0x0A,
// Connection Handle (LSB, MSB)
0x04,
0x00,
// Sniff Max Interval (LSB, MSB)
0x30,
0x03,
// Sniff Min Interval (LSB, MSB)
0x90,
0x01,
// Sniff Attempt (LSB, MSB)
0x04,
0x00,
// Sniff Timeout (LSB, MSB)
0x01,
0x00};
EXPECT_EQ(buffer, expected);
}
TEST(EmbossTest, ReadSniffMode) {
std::array<uint8_t, emboss::SniffModeCommandView::SizeInBytes()> buffer{
// Opcode (LSB, MSB)
0x03,
0x08,
// Parameter Total Size
0x0A,
// Connection Handle (LSB, MSB)
0x04,
0x00,
// Sniff Max Interval (LSB, MSB)
0x30,
0x03,
// Sniff Min Interval (LSB, MSB)
0x90,
0x01,
// Sniff Attempt (LSB, MSB)
0x04,
0x00,
// Sniff Timeout (LSB, MSB)
0x01,
0x00};
emboss::SniffModeCommandView view = emboss::MakeSniffModeCommandView(&buffer);
EXPECT_EQ(view.header().opcode().Read(), emboss::OpCode::SNIFF_MODE);
EXPECT_TRUE(view.header().IsComplete());
EXPECT_EQ(view.connection_handle().Read(), 0x0004);
EXPECT_EQ(view.sniff_max_interval().Read(), 0x0330);
EXPECT_EQ(view.sniff_min_interval().Read(), 0x0190);
EXPECT_EQ(view.sniff_attempt().Read(), 0x0004);
EXPECT_EQ(view.sniff_timeout().Read(), 0x0001);
}
TEST(EmbossTest, ReadRfcomm) {
std::array<uint8_t,
emboss::RfcommFrame::MinSizeInBytes() + /*credits*/ 1 +
/*payload*/ 3>
buffer_with_credits = {// Address
0x19,
// UIH Poll/Final
0xFF,
// Information Length
0x07,
// Credits
0x0A,
// Payload/Information
0xAB,
0xCD,
0xEF,
// FCS
0x49};
emboss::RfcommFrameView rfcomm =
emboss::MakeRfcommFrameView(&buffer_with_credits);
EXPECT_TRUE(rfcomm.Ok());
EXPECT_EQ(rfcomm.credits().Read(), 10);
EXPECT_EQ(rfcomm.information()[0].Read(), 0xAB);
EXPECT_EQ(rfcomm.information()[1].Read(), 0xCD);
EXPECT_EQ(rfcomm.information()[2].Read(), 0xEF);
EXPECT_EQ(rfcomm.fcs().Read(), 0x49);
std::array<uint8_t,
emboss::RfcommFrame::MinSizeInBytes() +
/*payload*/ 3>
buffer_without_credits = {// Address
0x19,
// UIH
0xEF,
// Information Length
0x07,
// Payload/Information
0xAB,
0xCD,
0xEF,
// FCS
0x55};
rfcomm = emboss::MakeRfcommFrameView(&buffer_without_credits);
EXPECT_TRUE(rfcomm.Ok());
EXPECT_FALSE(rfcomm.has_credits().ValueOrDefault());
EXPECT_EQ(rfcomm.information()[0].Read(), 0xAB);
EXPECT_EQ(rfcomm.information()[1].Read(), 0xCD);
EXPECT_EQ(rfcomm.information()[2].Read(), 0xEF);
EXPECT_EQ(rfcomm.fcs().Read(), 0x55);
}
TEST(EmbossTest, ReadRfcommExtended) {
constexpr size_t kMaxShortLength = 0x7f;
std::array<uint8_t,
emboss::RfcommFrame::MinSizeInBytes() + /*length_extended*/ 1 +
/*credits*/ 1 +
/*payload*/ (kMaxShortLength + 1)>
buffer_extended_length_with_credits = {
// Address
0x19,
// UIH Poll/Final
0xFF,
// Information Length
0x00,
0x01,
// Credits
0x0A,
// Payload/Information
0xAB,
0xCD,
0xEF,
};
// FCS
buffer_extended_length_with_credits
[buffer_extended_length_with_credits.size() - 1] = 0x49;
emboss::RfcommFrameView rfcomm =
emboss::MakeRfcommFrameView(&buffer_extended_length_with_credits);
EXPECT_TRUE(rfcomm.Ok());
EXPECT_TRUE(rfcomm.has_credits().ValueOrDefault());
EXPECT_TRUE(rfcomm.has_length_extended().ValueOrDefault());
EXPECT_EQ(rfcomm.information_length().Read(), 128);
EXPECT_EQ(rfcomm.information()[0].Read(), 0xAB);
EXPECT_EQ(rfcomm.information()[1].Read(), 0xCD);
EXPECT_EQ(rfcomm.information()[2].Read(), 0xEF);
EXPECT_EQ(rfcomm.fcs().Read(), 0x49);
}
TEST(EmbossTest, WriteRfcomm) {
const std::array<uint8_t, 3> expected_payload = {0xAB, 0xCD, 0xEF};
constexpr size_t kFrameSize = emboss::RfcommFrame::MinSizeInBytes() +
/*credits*/ 1 + expected_payload.size();
std::array<uint8_t, kFrameSize> buffer{};
emboss::RfcommFrameWriter rfcomm = emboss::MakeRfcommFrameView(&buffer);
rfcomm.extended_address().Write(true);
rfcomm.command_response().Write(false);
rfcomm.direction().Write(false);
rfcomm.channel().Write(3);
rfcomm.control().Write(
emboss::RfcommFrameType::
UNNUMBERED_INFORMATION_WITH_HEADER_CHECK_AND_POLL_FINAL);
rfcomm.length_extended_flag().Write(emboss::RfcommLengthExtended::NORMAL);
rfcomm.length().Write(expected_payload.size());
EXPECT_TRUE(rfcomm.has_credits().ValueOrDefault());
rfcomm.credits().Write(10);
std::memcpy(rfcomm.information().BackingStorage().data(),
expected_payload.data(),
expected_payload.size());
rfcomm.fcs().Write(0x49);
std::array<uint8_t, kFrameSize> expected{// Address
0x19,
// UIH Poll/Final
0xFF,
// Information Length
0x07,
// Credits
0x0A,
// Payload/Information
0xAB,
0xCD,
0xEF,
// FCS
0x49};
EXPECT_EQ(buffer, expected);
}
TEST(EmbossTest, WriteRfcommExtended) {
const std::array<uint8_t, 128> expected_payload = {0xAB, 0xCD, 0xEF};
constexpr size_t kFrameSize = emboss::RfcommFrame::MinSizeInBytes() +
/* length_extended */ 1 +
/*credits*/ 1 + expected_payload.size();
std::array<uint8_t, kFrameSize> buffer{};
emboss::RfcommFrameWriter rfcomm = emboss::MakeRfcommFrameView(&buffer);
rfcomm.extended_address().Write(true);
rfcomm.command_response().Write(false);
rfcomm.direction().Write(false);
rfcomm.channel().Write(3);
rfcomm.control().Write(
emboss::RfcommFrameType::
UNNUMBERED_INFORMATION_WITH_HEADER_CHECK_AND_POLL_FINAL);
rfcomm.length_extended_flag().Write(emboss::RfcommLengthExtended::EXTENDED);
rfcomm.length_extended().Write(expected_payload.size());
EXPECT_TRUE(rfcomm.has_credits().ValueOrDefault());
rfcomm.credits().Write(10);
std::memcpy(rfcomm.information().BackingStorage().data(),
expected_payload.data(),
expected_payload.size());
rfcomm.fcs().Write(0x49);
std::array<uint8_t, kFrameSize> expected{
// Address
0x19,
// UIH Poll/Final
0xFF,
// Information Length
0x00,
0x01,
// Credits
0x0A,
// Payload/Information
0xAB,
0xCD,
0xEF,
};
// FCS
expected[expected.size() - 1] = 0x49;
EXPECT_EQ(expected[2], buffer[2]);
EXPECT_EQ(expected[3], buffer[3]);
EXPECT_EQ(buffer, expected);
}
TEST(EmbossTest, WriteSniffOffloadParametersZeroFields) {
std::array<uint8_t,
vendor::android_hci::WriteSniffOffloadParametersCommandWriter::
SizeInBytes()>
buffer{};
vendor::android_hci::WriteSniffOffloadParametersCommandWriter writer =
vendor::android_hci::MakeWriteSniffOffloadParametersCommandView(&buffer);
writer.header().opcode().Write(emboss::OpCode::UNSPECIFIED);
writer.header().parameter_total_size().Write(
vendor::android_hci::WriteSniffOffloadParametersCommandView::
SizeInBytes() -
emboss::CommandHeaderView::SizeInBytes());
writer.connection_handle().Write(0x0004);
writer.max_interval().Write(0x0000);
writer.min_interval().Write(0x0000);
writer.attempts().Write(0x0000);
writer.sniff_timeout().Write(0x0000);
writer.link_inactivity_timeout().Write(0x0000);
writer.max_latency().Write(0x0000);
writer.min_remote_timeout().Write(0x0000);
writer.min_local_timeout().Write(0x0000);
EXPECT_TRUE(writer.Ok());
EXPECT_TRUE(writer.IsComplete());
}
TEST(EmbossTest, ReadWriteMaxSlotsChange) {
std::array<uint8_t, emboss::MaxSlotsChangeEventView::SizeInBytes()> buffer{};
auto writer = emboss::MakeMaxSlotsChangeEventView(&buffer);
writer.header().event_code().Write(emboss::EventCode::MAX_SLOTS_CHANGE);
writer.header().parameter_total_size().Write(
emboss::MaxSlotsChangeEventView::SizeInBytes() -
emboss::EventHeaderWriter::SizeInBytes());
writer.connection_handle().Write(0x0123);
writer.lmp_max_slots().Write(5);
EXPECT_TRUE(writer.IsComplete());
auto reader = emboss::MakeMaxSlotsChangeEventView(&buffer);
EXPECT_TRUE(reader.Ok());
EXPECT_EQ(reader.header().event_code().Read(),
emboss::EventCode::MAX_SLOTS_CHANGE);
EXPECT_EQ(reader.connection_handle().Read(), 0x0123);
EXPECT_EQ(reader.lmp_max_slots().Read(), 5);
}
TEST(EmbossTest, ReadWriteConnectionPacketTypeChanged) {
std::array<uint8_t,
emboss::ConnectionPacketTypeChangedEventView::SizeInBytes()>
buffer{};
auto writer = emboss::MakeConnectionPacketTypeChangedEventView(&buffer);
writer.header().event_code().Write(
emboss::EventCode::CONNECTION_PACKET_TYPE_CHANGED);
writer.header().parameter_total_size().Write(
emboss::ConnectionPacketTypeChangedEventView::SizeInBytes() -
emboss::EventHeaderWriter::SizeInBytes());
writer.status().Write(emboss::StatusCode::SUCCESS);
writer.connection_handle().Write(0x0123);
writer.packet_type().BackingStorage().WriteUInt(0xcc18);
EXPECT_TRUE(writer.IsComplete());
auto reader = emboss::MakeConnectionPacketTypeChangedEventView(&buffer);
EXPECT_TRUE(reader.Ok());
EXPECT_EQ(reader.header().event_code().Read(),
emboss::EventCode::CONNECTION_PACKET_TYPE_CHANGED);
EXPECT_EQ(reader.status().Read(), emboss::StatusCode::SUCCESS);
EXPECT_EQ(reader.connection_handle().Read(), 0x0123);
EXPECT_EQ(reader.packet_type().BackingStorage().ReadUInt(), 0xcc18);
}
TEST(EmbossTest, AvrcpVolumeControlPacket) {
// 1. SetAbsoluteVolume Command
std::array<uint8_t, 14> volume_cmd = {
0x00, // Transaction Label (0) | Packet Type (0, Single) | C/R (0,
// Command)
0x11, // Profile ID (0x110E, AVRCP)
0x0E,
0x00, // AV/C Header: ctype (0, CONTROL)
0x48, // Subunit type (9, Panel) & Subunit ID (0)
0x00, // Opcode (0x00, Vendor Dependent)
0x00, // Company ID (0x001958, Bluetooth SIG)
0x19,
0x58,
0x50, // PDU ID (0x50, SetAbsoluteVolume)
0x00, // Packet Type (AVRCP fragmentation, usually 0)
0x00, // Parameter Length (MSB)
0x01, // Parameter Length (LSB)
0x00 // Volume (0%)
};
auto view1 = emboss::MakeAvrcpVolumeControlPacketView(&volume_cmd);
ASSERT_TRUE(view1.Ok());
EXPECT_TRUE(view1.IsComplete());
EXPECT_EQ(view1.avctp_header().packet_type().Read(),
emboss::AvctpPacketType::SINGLE_PACKET);
EXPECT_FALSE(view1.avctp_header().cr().Read());
EXPECT_FALSE(view1.avctp_header().ipid().Read());
EXPECT_EQ(view1.avctp_header().transaction_label().Read(), 0u);
EXPECT_EQ(view1.profile_id().Read(), 0x110E);
EXPECT_EQ(view1.avc_header().ctype().Read(), emboss::AvcCtype::CONTROL);
EXPECT_EQ(view1.avc_header().subunit_address().subunit_type().Read(), 9);
EXPECT_EQ(view1.avc_header().subunit_address().subunit_id().Read(), 0);
EXPECT_EQ(view1.avc_header().opcode().Read(), 0x00);
EXPECT_EQ(view1.avrcp_header().company_id().Read(), 0x001958u);
EXPECT_EQ(view1.avrcp_header().pdu_id().Read(), 0x50);
EXPECT_EQ(view1.packet_type().Read(), 0);
EXPECT_EQ(view1.parameter_length().Read(), 1);
ASSERT_TRUE(view1.absolute_volume().Ok());
EXPECT_EQ(view1.absolute_volume().Read(), 0);
EXPECT_FALSE(view1.event_id().Ok());
EXPECT_FALSE(view1.changed_volume().Ok());
// 2. SetAbsoluteVolume Response (ACCEPTED)
std::array<uint8_t, 14> volume_resp = {
0x22, // Transaction Label (2) | Packet Type (0, Single) | C/R (1,
// Response)
0x11, // Profile ID (0x110E, AVRCP)
0x0E,
0x09, // AV/C Header: response (0x09, ACCEPTED)
0x48, // Subunit type (9, Panel) & Subunit ID (0)
0x00, // Opcode (0x00, Vendor Dependent)
0x00, // Company ID (0x001958, Bluetooth SIG)
0x19,
0x58,
0x50, // PDU ID (0x50, SetAbsoluteVolume)
0x00, // Packet Type
0x00, // Parameter Length (MSB)
0x01, // Parameter Length (LSB)
0x3f // Volume (50%)
};
auto view_resp = emboss::MakeAvrcpVolumeControlPacketView(&volume_resp);
ASSERT_TRUE(view_resp.Ok());
EXPECT_TRUE(view_resp.IsComplete());
EXPECT_EQ(view_resp.avctp_header().packet_type().Read(),
emboss::AvctpPacketType::SINGLE_PACKET);
EXPECT_TRUE(view_resp.avctp_header().cr().Read());
EXPECT_FALSE(view_resp.avctp_header().ipid().Read());
EXPECT_EQ(view_resp.avctp_header().transaction_label().Read(), 2u);
EXPECT_EQ(view_resp.profile_id().Read(), 0x110E);
EXPECT_EQ(view_resp.avc_header().response().Read(),
emboss::AvcResponseCode::ACCEPTED);
EXPECT_EQ(view_resp.avrcp_header().pdu_id().Read(), 0x50);
EXPECT_EQ(view_resp.parameter_length().Read(), 1);
ASSERT_TRUE(view_resp.absolute_volume().Ok());
EXPECT_EQ(view_resp.absolute_volume().Read(), 0x3f);
EXPECT_FALSE(view_resp.event_id().Ok());
// 3. RegisterNotification Command
std::array<uint8_t, 18> volume_notify_cmd = {
0x30, // Transaction Label (3) | Packet Type (0, Single) | C/R (0,
// Command)
0x11, // Profile ID (0x110E, AVRCP)
0x0E,
0x00, // AV/C Header: ctype (0, CONTROL)
0x48, // Subunit type (9, Panel) & Subunit ID (0)
0x00, // Opcode (0x00, Vendor Dependent)
0x00, // Company ID (0x001958, Bluetooth SIG)
0x19,
0x58,
0x31, // PDU ID (0x31, RegisterNotification)
0x00, // Packet Type
0x00, // Parameter Length (MSB)
0x05, // Parameter Length (LSB)
0x0d, // Event ID (0x0d, VolumeChanged)
0x00, // Playback Interval (byte 1)
0x00, // Playback Interval (byte 2)
0x00, // Playback Interval (byte 3)
0x00, // Playback Interval (byte 4)
};
auto view2 = emboss::MakeAvrcpVolumeControlPacketView(&volume_notify_cmd);
ASSERT_TRUE(view2.Ok());
EXPECT_TRUE(view2.IsComplete());
EXPECT_EQ(view2.avctp_header().packet_type().Read(),
emboss::AvctpPacketType::SINGLE_PACKET);
EXPECT_FALSE(view2.avctp_header().cr().Read());
EXPECT_EQ(view2.avctp_header().transaction_label().Read(), 3u);
EXPECT_EQ(view2.profile_id().Read(), 0x110E);
EXPECT_EQ(view2.avc_header().opcode().Read(), 0x00);
EXPECT_EQ(view2.avrcp_header().company_id().Read(), 0x001958u);
EXPECT_EQ(view2.avrcp_header().pdu_id().Read(), 0x31);
EXPECT_EQ(view2.packet_type().Read(), 0);
EXPECT_EQ(view2.parameter_length().Read(), 5);
ASSERT_TRUE(view2.event_id().Ok());
EXPECT_EQ(view2.event_id().Read(), 0x0D);
ASSERT_TRUE(view2.playback_interval().Ok());
EXPECT_EQ(view2.playback_interval().Read(), 0u);
EXPECT_FALSE(view2.changed_volume().Ok());
EXPECT_FALSE(view2.absolute_volume().Ok());
// 4. RegisterNotification Interim Response
std::array<uint8_t, 15> volume_notify_interim_resp = {
0x32, // Transaction Label (3) | Packet Type (0, Single) | C/R (1,
// Response)
0x11, // Profile ID (0x110E, AVRCP)
0x0E,
0x0f, // AV/C Header: response (0x0f, INTERIM)
0x48, // Subunit type (9, Panel) & Subunit ID (0)
0x00, // Opcode (0x00, Vendor Dependent)
0x00, // Company ID (0x001958, Bluetooth SIG)
0x19,
0x58,
0x31, // PDU ID (0x31, RegisterNotification)
0x00, // Packet Type
0x00, // Parameter Length (MSB)
0x02, // Parameter Length (LSB)
0x0d, // Event ID (0x0d, VolumeChanged)
0x40, // Volume (0x40, 50%)
};
auto view_interim =
emboss::MakeAvrcpVolumeControlPacketView(&volume_notify_interim_resp);
ASSERT_TRUE(view_interim.Ok());
EXPECT_TRUE(view_interim.IsComplete());
EXPECT_EQ(view_interim.avctp_header().packet_type().Read(),
emboss::AvctpPacketType::SINGLE_PACKET);
EXPECT_TRUE(view_interim.avctp_header().cr().Read());
EXPECT_EQ(view_interim.avctp_header().transaction_label().Read(), 3u);
EXPECT_EQ(view_interim.profile_id().Read(), 0x110E);
EXPECT_EQ(view_interim.avc_header().response().Read(),
emboss::AvcResponseCode::INTERIM);
EXPECT_EQ(view_interim.avrcp_header().pdu_id().Read(), 0x31);
EXPECT_EQ(view_interim.packet_type().Read(), 0);
EXPECT_EQ(view_interim.parameter_length().Read(), 2);
ASSERT_TRUE(view_interim.event_id().Ok());
EXPECT_EQ(view_interim.event_id().Read(), 0x0D);
ASSERT_TRUE(view_interim.changed_volume().Ok());
EXPECT_EQ(view_interim.changed_volume().Read(), 0x40);
EXPECT_FALSE(view_interim.playback_interval().Ok());
// 5. RegisterNotification Changed Response
std::array<uint8_t, 15> volume_notify_changed_resp = {
0x32, // Transaction Label (3) | Packet Type (0, Single) | C/R (1,
// Response)
0x11, // Profile ID (0x110E, AVRCP)
0x0E,
0x0d, // AV/C Header: response (0x0d, CHANGED)
0x48, // Subunit type (9, Panel) & Subunit ID (0)
0x00, // Opcode (0x00, Vendor Dependent)
0x00, // Company ID (0x001958, Bluetooth SIG)
0x19,
0x58,
0x31, // PDU ID (0x31, RegisterNotification)
0x00, // Packet Type
0x00, // Parameter Length (MSB)
0x02, // Parameter Length (LSB)
0x0d, // Event ID (0x0d, VolumeChanged)
0x48, // Volume (0x48, 56.5%)
};
auto view_changed =
emboss::MakeAvrcpVolumeControlPacketView(&volume_notify_changed_resp);
ASSERT_TRUE(view_changed.Ok());
EXPECT_TRUE(view_changed.IsComplete());
EXPECT_EQ(view_changed.avctp_header().packet_type().Read(),
emboss::AvctpPacketType::SINGLE_PACKET);
EXPECT_TRUE(view_changed.avctp_header().cr().Read());
EXPECT_FALSE(view_changed.avctp_header().ipid().Read());
EXPECT_EQ(view_changed.profile_id().Read(), 0x110E);
EXPECT_EQ(view_changed.avc_header().response().Read(),
emboss::AvcResponseCode::CHANGED);
EXPECT_EQ(view_changed.avrcp_header().pdu_id().Read(), 0x31);
EXPECT_EQ(view_changed.packet_type().Read(), 0);
EXPECT_EQ(view_changed.parameter_length().Read(), 2);
ASSERT_TRUE(view_changed.event_id().Ok());
EXPECT_EQ(view_changed.event_id().Read(), 0x0D);
ASSERT_TRUE(view_changed.changed_volume().Ok());
EXPECT_EQ(view_changed.changed_volume().Read(), 0x48);
EXPECT_FALSE(view_changed.playback_interval().Ok());
EXPECT_FALSE(view_changed.absolute_volume().Ok());
// 6. Insufficient buffer size test
std::array<uint8_t, 10> small_buffer = {
0x00, 0x11, 0x0E, 0x00, 0x48, 0x00, 0x00, 0x19, 0x58, 0x31};
auto view4 = emboss::MakeAvrcpVolumeControlPacketView(&small_buffer);
EXPECT_FALSE(view4.Ok());
}
} // namespace
} // namespace pw::bluetooth