// 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 #include "lib/stdcompat/utility.h" #include "pw_unit_test/framework.h" // IWYU pragma: keep // clang-format off // All emboss headers are listed (even if they don't have explicit tests) to // ensure they are compiled. #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 // clang-format on namespace pw::bluetooth { namespace { // Examples are used in docs.rst. TEST(EmbossExamples, MakeView) { // DOCSTAG: [pw_bluetooth-examples-make_view] std::array 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 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 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(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(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(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(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(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(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(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(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 buffer = {0x00, 0x00, 0x02, 0x03}; auto view = emboss::MakeTestCommandPacketView(&buffer); EXPECT_TRUE(view.IsComplete()); auto header = view.header(); EXPECT_EQ(header.opcode_enum().Read(), emboss::OpCode::UNSPECIFIED); EXPECT_EQ(header.opcode().BackingStorage().ReadUInt(), 0x0000); EXPECT_EQ(header.opcode_bits().ogf().Read(), 0x00); EXPECT_EQ(header.opcode_bits().ocf().Read(), 0x00); // TODO: https://pwbug.dev/338068316 - Delete these opcode type // OpCodeBits cases once opcode has type OpCode. EXPECT_EQ(header.opcode().ogf().Read(), 0x00); EXPECT_EQ(header.opcode().ocf().Read(), 0x00); // LINK_KEY_REQUEST_REPLY is OGF 0x01 and OCF 0x0B. header.opcode_enum().Write(emboss::OpCode::LINK_KEY_REQUEST_REPLY); EXPECT_EQ(header.opcode_enum().Read(), emboss::OpCode::LINK_KEY_REQUEST_REPLY); EXPECT_EQ(header.opcode().BackingStorage().ReadUInt(), 0x040B); EXPECT_EQ(header.opcode_bits().ogf().Read(), 0x01); EXPECT_EQ(header.opcode_bits().ocf().Read(), 0x0B); // TODO: https://pwbug.dev/338068316 - Delete these opcode type // OpCodeBits cases once opcode has type OpCode. EXPECT_EQ(header.opcode().ogf().Read(), 0x01); EXPECT_EQ(header.opcode().ocf().Read(), 0x0B); } // Test and demonstrate various ways of writing opcodes. TEST(EmbossTest, WriteOpcodesFromCommandHeader) { std::array buffer = {}; buffer.fill(0xFF); auto view = emboss::MakeTestCommandPacketView(&buffer); EXPECT_TRUE(view.IsComplete()); auto header = view.header(); header.opcode_enum().Write(emboss::OpCode::UNSPECIFIED); EXPECT_EQ(header.opcode().BackingStorage().ReadUInt(), 0x0000); header.opcode().ocf().Write(0x0B); EXPECT_EQ(header.opcode().BackingStorage().ReadUInt(), 0x000B); header.opcode().ogf().Write(0x01); EXPECT_EQ(header.opcode().BackingStorage().ReadUInt(), 0x040B); // LINK_KEY_REQUEST_REPLY is OGF 0x01 and OCF 0x0B. EXPECT_EQ(header.opcode_enum().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 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().BackingStorage().WriteUInt(0x0000); EXPECT_EQ(header.command_opcode_enum().Read(), emboss::OpCode::UNSPECIFIED); EXPECT_EQ(header.command_opcode().BackingStorage().ReadUInt(), 0x0000); EXPECT_EQ(header.command_opcode_bits().ogf().Read(), 0x00); EXPECT_EQ(header.command_opcode_bits().ocf().Read(), 0x00); // TODO: https://pwbug.dev/338068316 - Delete these command_opcode type // OpCodeBits cases once command_opcode has type OpCode. EXPECT_EQ(header.command_opcode().ogf().Read(), 0x00); EXPECT_EQ(header.command_opcode().ocf().Read(), 0x00); // LINK_KEY_REQUEST_REPLY is OGF 0x01 and OCF 0x0B. header.command_opcode_enum().Write(emboss::OpCode::LINK_KEY_REQUEST_REPLY); EXPECT_EQ(header.command_opcode_enum().Read(), emboss::OpCode::LINK_KEY_REQUEST_REPLY); EXPECT_EQ(header.command_opcode().BackingStorage().ReadUInt(), 0x040B); EXPECT_EQ(header.command_opcode_bits().ogf().Read(), 0x01); EXPECT_EQ(header.command_opcode_bits().ocf().Read(), 0x0B); // TODO: https://pwbug.dev/338068316 - Delete these command_opcode type // OpCodeBits cases once command_opcode has type OpCode. EXPECT_EQ(header.command_opcode().ogf().Read(), 0x01); EXPECT_EQ(header.command_opcode().ocf().Read(), 0x0B); } TEST(EmbossTest, ReadAndWriteEventCodesInEventHeader) { std::array 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_enum().Read(), emboss::EventCode::NUMBER_OF_COMPLETED_PACKETS); EXPECT_EQ( header.event_code_uint().Read(), cpp23::to_underlying(emboss::EventCode::NUMBER_OF_COMPLETED_PACKETS)); // TODO: https://pwbug.dev/338068316 - Delete these event_code type // UInt cases once event_code has type EventCode. EXPECT_EQ( header.event_code().Read(), cpp23::to_underlying(emboss::EventCode::NUMBER_OF_COMPLETED_PACKETS)); header.event_code().Write( cpp23::to_underlying(emboss::EventCode::CONNECTION_REQUEST)); EXPECT_EQ(header.event_code_uint().Read(), cpp23::to_underlying(emboss::EventCode::CONNECTION_REQUEST)); } TEST(EmbossTest, ReadCommandPayloadLength) { std::array 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 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 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 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 buffer{}; emboss::SniffModeCommandWriter writer = emboss::MakeSniffModeCommandView(&buffer); writer.header().opcode_enum().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 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 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_enum().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 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 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 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 expected_payload = {0xAB, 0xCD, 0xEF}; constexpr size_t kFrameSize = emboss::RfcommFrame::MinSizeInBytes() + /*credits*/ 1 + expected_payload.size(); std::array buffer{}; emboss::RfcommFrameWriter rfcomm = emboss::MakeRfcommFrameView(&buffer); rfcomm.extended_address().Write(true); rfcomm.command_response_direction().Write( emboss::RfcommCommandResponseAndDirection::COMMAND_FROM_RESPONDER); 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 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 expected_payload = {0xAB, 0xCD, 0xEF}; constexpr size_t kFrameSize = emboss::RfcommFrame::MinSizeInBytes() + /* length_extended */ 1 + /*credits*/ 1 + expected_payload.size(); std::array buffer{}; emboss::RfcommFrameWriter rfcomm = emboss::MakeRfcommFrameView(&buffer); rfcomm.extended_address().Write(true); rfcomm.command_response_direction().Write( emboss::RfcommCommandResponseAndDirection::COMMAND_FROM_RESPONDER); 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 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); } } // namespace } // namespace pw::bluetooth