| // Copyright 2018 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. |
| |
| #include <lib/fxl/logging.h> |
| |
| #include "garnet/drivers/bluetooth/lib/common/byte_buffer.h" |
| #include "garnet/drivers/bluetooth/lib/common/slab_allocator.h" |
| #include "garnet/drivers/bluetooth/lib/rfcomm/frames.h" |
| #include "garnet/drivers/bluetooth/lib/rfcomm/mux_commands.h" |
| #include "garnet/drivers/bluetooth/lib/rfcomm/rfcomm.h" |
| #include "gtest/gtest.h" |
| |
| namespace btlib { |
| namespace rfcomm { |
| namespace { |
| |
| // Contruction of "empty" RFCOMM frame: |
| // Please see GSM 5.2.1 and RFCOMM 5.1. |
| // Our frame will have the following characteristics: |
| // - Sent from the RFCOMM initiator |
| // - SABM frame |
| // - Sent to DLCI 0x02 |
| // - Command frame |
| // - P/F bit = 1 |
| constexpr Role kEmptyFrameRole = Role::kInitiator; |
| constexpr CommandResponse kEmptyFrameCR = CommandResponse::kCommand; |
| constexpr FrameType kEmptyFrameType = FrameType::kSetAsynchronousBalancedMode; |
| constexpr DLCI kEmptyFrameDLCI = 0x02; |
| constexpr bool kEmptyFramePF = true; |
| constexpr bool kEmptyFrameCreditBasedFlow = false; |
| const auto kEmptyFrame = common::CreateStaticByteBuffer( |
| // Address octet: |
| // E/A bit is always 1. C/R bit is 1 in the case of a command being sent |
| // from the initiator role. DLCI is 0x02. Thus: 1 (E/A) ++ 1 (C/R) ++ 010000 |
| // (DLCI) = 11010000 |
| 0b00001011, |
| // Control octet: |
| // SABM is 1111p100, P/F bit (p) is 1 --> 11111100 |
| 0b00111111, |
| // Length octet: |
| // Length is 0; E/A bit is thus 1 --> 10000000 |
| 0b00000001, |
| // FCS octet: |
| // Please see GSM 5.2.1.6, GSM Annex B, and RFCOMM 5.1.1. |
| 0b01011001); |
| |
| // Contruction of "helloworld" RFCOMM frame: |
| // - Sent from the RFCOMM responder |
| // - UIH frame |
| // - Sent to DLCI 0x23 |
| // - Command frame |
| // - P/F bit = 0 |
| constexpr Role kHelloFrameRole = Role::kResponder; |
| constexpr CommandResponse kHelloFrameCR = CommandResponse::kCommand; |
| constexpr FrameType kHelloFrameType = FrameType::kUnnumberedInfoHeaderCheck; |
| constexpr DLCI kHelloFrameDLCI = 0x23; |
| constexpr bool kHelloFramePF = false; |
| constexpr bool kHelloFrameCreditBasedFlow = false; |
| const auto kHelloFrameInformation = common::CreateStaticByteBuffer( |
| 'h', 'e', 'l', 'l', 'o', 'w', 'o', 'r', 'l', 'd'); |
| const auto kHelloFrame = common::CreateStaticByteBuffer( |
| // Address octet: |
| // E/A bit is always 1. C/R bit is 0 in the case of a command being sent |
| // from the responder role. DLCI is 0x23. Thus: 1 (E/A) ++ 0 (C/R) ++ 110001 |
| // (DLCI) = 10110001 |
| 0b10001101, |
| // Control octet: |
| // UIH is 1111p111, P/F bit (p) is 0 --> 11110111 |
| 0b11101111, |
| // Length octet: |
| // Length is 10; E/A bit is thus 1 --> 10101000 |
| 0b00010101, |
| // Information |
| 'h', 'e', 'l', 'l', 'o', 'w', 'o', 'r', 'l', 'd', |
| // FCS octet: |
| // Please see GSM 5.2.1.6, GSM Annex B, and RFCOMM 5.1.1. |
| 0b10011101); |
| |
| // Contruction of "hellofuchsia" RFCOMM frame: |
| // - Sent from the RFCOMM responder |
| // - UIH frame |
| // - Sent to DLCI 0x23 |
| // - Command frame |
| // - P/F bit = 1 (credit-based flow on) |
| constexpr Role kFuchsiaFrameRole = Role::kResponder; |
| constexpr CommandResponse kFuchsiaFrameCR = CommandResponse::kCommand; |
| constexpr FrameType kFuchsiaFrameType = FrameType::kUnnumberedInfoHeaderCheck; |
| constexpr DLCI kFuchsiaFrameDLCI = 0x23; |
| constexpr bool kFuchsiaFramePF = true; |
| constexpr bool kFuchsiaFrameCreditBasedFlow = true; |
| constexpr uint8_t kFuchsiaFrameCredits = 5; |
| const auto kFuchsiaFrameInformation = common::CreateStaticByteBuffer( |
| 'h', 'e', 'l', 'l', 'o', 'f', 'u', 'c', 'h', 's', 'i', 'a'); |
| const auto kFuchsiaFrame = common::CreateStaticByteBuffer( |
| // Address octet: |
| // E/A bit is always 1. C/R bit is 0 in the case of a command being sent |
| // from the responder role. DLCI is 0x23. Thus: 1 (E/A) ++ 0 (C/R) ++ 110001 |
| // (DLCI) = 10110001 |
| 0b10001101, |
| // Control octet: |
| // UIH is 1111p111, P/F bit (p) is 1 --> 11111111 |
| 0b11111111, |
| // Length octet: |
| // Length is 12; E/A bit is thus 1 --> 10011000 |
| 0b00011001, |
| // Credit octet: |
| // Credits = 5 |
| 0b00000101, |
| // Information |
| 'h', 'e', 'l', 'l', 'o', 'f', 'u', 'c', 'h', 's', 'i', 'a', |
| // FCS octet: |
| // Please see GSM 5.2.1.6, GSM Annex B, and RFCOMM 5.1.1. |
| 0b10000001); |
| |
| constexpr Role kTestCommandFrameRole = Role::kInitiator; |
| constexpr CommandResponse kTestCommandFrameCR = CommandResponse::kCommand; |
| constexpr FrameType kTestCommandFrameType = |
| FrameType::kUnnumberedInfoHeaderCheck; |
| constexpr bool kTestCommandCreditBasedFlow = true; |
| constexpr uint8_t kTestCommandFrameCredits = 63; |
| constexpr MuxCommandType kTestCommandFrameMuxCommandType = |
| MuxCommandType::kTestCommand; |
| const auto kTestCommandFrameMuxCommandPattern = |
| common::CreateStaticByteBuffer(0, 1, 2, 3); |
| constexpr CommandResponse kTestCommandFrameMuxCommandCR = |
| CommandResponse::kCommand; |
| const auto kTestCommandFrame = common::CreateStaticByteBuffer( |
| // Address: E/A = 1, C/R is 1 for a command from the initiator, DLCI = 0. |
| 0b00000011, |
| // Control: UIH is 1111p11, P/F is 1 due to presence of a credits field. |
| 0b11111111, |
| // Length: E/A = 1, length = 6 |
| 0b00001101, |
| // Credits |
| kTestCommandFrameCredits, |
| // Mux command type field octet: E/A = 1, C/R = 1, Test Command type |
| 0b00100011, |
| // Mux command length field: E/A = 1, length = 4 |
| 0b00001001, |
| // Mux command test pattern |
| 0, 1, 2, 3, |
| // FCS |
| 0b01101100); |
| |
| // Contruction of pre-multiplexer-startup SABM frame: |
| // - Role is unset |
| // - Sent to DLCI 0 |
| const auto kPreMuxSABMFrame = common::CreateStaticByteBuffer( |
| // Address octet: |
| // E/A bit is always 1. Our implementation sets C/R bit to 1 for |
| // pre-mux-startup SABM frames. DLCI is 0. |
| 0b00000011, |
| // Control octet: |
| // P/F bit (p) is 1 for SABM. |
| 0b00111111, |
| // Length octet: |
| // Length is 0. |
| 0b00000001, |
| // FCS octet: |
| // Please see GSM 5.2.1.6, GSM Annex B, and RFCOMM 5.1.1. |
| 0b00011100); |
| |
| // Contruction of pre-multiplexer-startup SABM frame: |
| // - Role is unset |
| // - Sent to DLCI 0 |
| const auto kPreMuxUAFrame = common::CreateStaticByteBuffer( |
| // Address octet: |
| // E/A bit is always 1. Our implementation sets C/R bit to 1 for |
| // pre-mux-startup UA frames. DLCI is 0. |
| 0b00000011, |
| // Control octet: |
| // P/F bit (Final bit) is 1 for UA. |
| 0b01110011, |
| // Length octet: |
| // Length is 0. |
| 0b00000001, |
| // FCS octet: |
| // Please see GSM 5.2.1.6, GSM Annex B, and RFCOMM 5.1.1. |
| 0b11010111); |
| |
| // Contruction of pre-multiplexer-startup DM frame: |
| // - Role is unset |
| // - Sent to DLCI 0 |
| const auto kPreMuxDMFrame = common::CreateStaticByteBuffer( |
| // Address octet: |
| // E/A bit is always 1. Our implementation sets C/R bit to 1 for |
| // pre-mux-startup frames. DLCI is 0. |
| 0b00000011, |
| // Control octet: |
| // P/F bit (Final bit) is 1 for DM. |
| 0b00011111, |
| // Length octet: |
| // Length is 0. |
| 0b00000001, |
| // FCS octet: |
| // Please see GSM 5.2.1.6, GSM Annex B, and RFCOMM 5.1.1. |
| 0b00110110); |
| |
| const auto kInvalidLengthFrame = common::CreateStaticByteBuffer(0, 1, 2); |
| |
| // Same as the hellofuchsia frame, but information field is too short. |
| const auto kInvalidLengthFrame2 = common::CreateStaticByteBuffer( |
| 0b10001101, 0b11111111, 0b00011001, 0b00000101, 'h', 'e', 'l', 'l', 'o'); |
| |
| // Same as the hellofuchsia frame, but with an invalid FCS. |
| const auto kInvalidFCSFrame = common::CreateStaticByteBuffer( |
| 0b10001101, 0b11111111, 0b00011001, 0b00000101, 'h', 'e', 'l', 'l', 'o', |
| 'f', 'u', 'c', 'h', 's', 'i', 'a', 0b10000001 + 1); |
| |
| // Same as the hellofuchsia frame, but with an invalid DLCI (1) |
| const auto kInvalidDLCIFrame = common::CreateStaticByteBuffer( |
| 0b00000101, 0b11111111, 0b00011001, 0b00000101, 'h', 'e', 'l', 'l', 'o', |
| 'f', 'u', 'c', 'h', 's', 'i', 'a', 0b11000011); |
| |
| // Same as the hellofuchsia frame, but with an invalid DLCI (62) |
| const auto kInvalidDLCIFrame2 = common::CreateStaticByteBuffer( |
| 0b11111001, 0b11111111, 0b00011001, 0b00000101, 'h', 'e', 'l', 'l', 'o', |
| 'f', 'u', 'c', 'h', 's', 'i', 'a', 0b10011111); |
| |
| using RFCOMM_FrameTest = ::testing::Test; |
| |
| TEST_F(RFCOMM_FrameTest, WriteFrame) { |
| SetAsynchronousBalancedModeCommand frame(kEmptyFrameRole, kEmptyFrameDLCI); |
| common::DynamicByteBuffer buffer(frame.written_size()); |
| frame.Write(buffer.mutable_view()); |
| EXPECT_EQ(4ul, frame.written_size()); |
| EXPECT_EQ(kEmptyFrame, buffer); |
| } |
| |
| TEST_F(RFCOMM_FrameTest, WriteFrameWithData) { |
| auto information = common::NewSlabBuffer(kHelloFrameInformation.size()); |
| kHelloFrameInformation.Copy(information.get()); |
| UserDataFrame frame(kHelloFrameRole, kHelloFrameCreditBasedFlow, |
| kHelloFrameDLCI, std::move(information)); |
| common::DynamicByteBuffer buffer(frame.written_size()); |
| frame.Write(buffer.mutable_view()); |
| EXPECT_EQ(14ul, frame.written_size()); |
| EXPECT_EQ(kHelloFrame, buffer); |
| } |
| |
| TEST_F(RFCOMM_FrameTest, WriteFrameWithDataAndCredits) { |
| auto information = common::NewSlabBuffer(kFuchsiaFrameInformation.size()); |
| kFuchsiaFrameInformation.Copy(information.get()); |
| UserDataFrame frame(kFuchsiaFrameRole, kFuchsiaFrameCreditBasedFlow, |
| kFuchsiaFrameDLCI, std::move(information)); |
| frame.set_credits(kFuchsiaFrameCredits); |
| common::DynamicByteBuffer buffer(frame.written_size()); |
| frame.Write(buffer.mutable_view()); |
| EXPECT_EQ(17ul, frame.written_size()); |
| EXPECT_EQ(kFuchsiaFrame, buffer); |
| } |
| |
| TEST_F(RFCOMM_FrameTest, WriteFrameWithMuxCommandAndCredits) { |
| auto mux_command = std::make_unique<TestCommand>( |
| kTestCommandFrameMuxCommandCR, kTestCommandFrameMuxCommandPattern); |
| MuxCommandFrame frame(kTestCommandFrameRole, kTestCommandCreditBasedFlow, |
| std::move(mux_command)); |
| frame.set_credits(kTestCommandFrameCredits); |
| EXPECT_EQ(kTestCommandFrame.size(), frame.written_size()); |
| common::DynamicByteBuffer buffer(frame.written_size()); |
| frame.Write(buffer.mutable_view()); |
| EXPECT_EQ(kTestCommandFrame, buffer); |
| } |
| |
| TEST_F(RFCOMM_FrameTest, WritePreMuxStartupSABM) { |
| SetAsynchronousBalancedModeCommand frame(Role::kUnassigned, kMuxControlDLCI); |
| common::DynamicByteBuffer buffer(frame.written_size()); |
| frame.Write(buffer.mutable_view()); |
| EXPECT_EQ(kPreMuxSABMFrame, buffer); |
| } |
| |
| TEST_F(RFCOMM_FrameTest, WritePreMuxStartupUA) { |
| UnnumberedAcknowledgementResponse frame(Role::kUnassigned, kMuxControlDLCI); |
| common::DynamicByteBuffer buffer(frame.written_size()); |
| frame.Write(buffer.mutable_view()); |
| EXPECT_EQ(kPreMuxUAFrame, buffer); |
| } |
| TEST_F(RFCOMM_FrameTest, WritePreMuxStartupDM) { |
| DisconnectedModeResponse frame(Role::kUnassigned, kMuxControlDLCI); |
| common::DynamicByteBuffer buffer(frame.written_size()); |
| frame.Write(buffer.mutable_view()); |
| EXPECT_EQ(kPreMuxDMFrame, buffer); |
| } |
| |
| TEST_F(RFCOMM_FrameTest, ReadFrame) { |
| auto frame = |
| Frame::Parse(kEmptyFrameCreditBasedFlow, kEmptyFrameRole, kEmptyFrame); |
| EXPECT_EQ(kEmptyFrameCR, frame->command_response()); |
| EXPECT_EQ(kEmptyFrameDLCI, frame->dlci()); |
| EXPECT_EQ((uint8_t)kEmptyFrameType, frame->control()); |
| EXPECT_EQ(kEmptyFramePF, frame->poll_final()); |
| EXPECT_EQ(0, frame->length()); |
| } |
| |
| TEST_F(RFCOMM_FrameTest, ReadFrameWithData) { |
| auto frame = |
| Frame::Parse(kHelloFrameCreditBasedFlow, kHelloFrameRole, kHelloFrame); |
| EXPECT_EQ((uint8_t)kHelloFrameType, frame->control()); |
| auto user_data_frame = Frame::DowncastFrame<UserDataFrame>(std::move(frame)); |
| EXPECT_EQ(nullptr, frame); |
| EXPECT_EQ(kHelloFrameCR, user_data_frame->command_response()); |
| EXPECT_EQ(kHelloFrameDLCI, user_data_frame->dlci()); |
| EXPECT_EQ(kHelloFramePF, user_data_frame->poll_final()); |
| EXPECT_EQ(kHelloFrameInformation.size(), user_data_frame->length()); |
| EXPECT_EQ(0, user_data_frame->credits()); |
| EXPECT_EQ(kHelloFrameInformation, *user_data_frame->TakeInformation()); |
| EXPECT_EQ(nullptr, user_data_frame->TakeInformation()); |
| } |
| |
| TEST_F(RFCOMM_FrameTest, ReadFrameWithDataAndCredits) { |
| auto frame = Frame::Parse(kFuchsiaFrameCreditBasedFlow, kFuchsiaFrameRole, |
| kFuchsiaFrame); |
| EXPECT_EQ((uint8_t)kFuchsiaFrameType, frame->control()); |
| auto user_data_frame = Frame::DowncastFrame<UserDataFrame>(std::move(frame)); |
| EXPECT_EQ(nullptr, frame); |
| EXPECT_EQ(kFuchsiaFrameCR, user_data_frame->command_response()); |
| EXPECT_EQ(kFuchsiaFrameDLCI, user_data_frame->dlci()); |
| EXPECT_EQ(kFuchsiaFramePF, user_data_frame->poll_final()); |
| EXPECT_EQ(kFuchsiaFrameInformation.size(), user_data_frame->length()); |
| EXPECT_EQ(kFuchsiaFrameCredits, user_data_frame->credits()); |
| EXPECT_EQ(kFuchsiaFrameInformation, *user_data_frame->TakeInformation()); |
| EXPECT_EQ(nullptr, user_data_frame->TakeInformation()); |
| } |
| |
| TEST_F(RFCOMM_FrameTest, ReadFrameWithMuxCommandAndCredits) { |
| auto frame = Frame::Parse(kTestCommandCreditBasedFlow, kTestCommandFrameRole, |
| kTestCommandFrame); |
| EXPECT_EQ(kTestCommandFrameCR, frame->command_response()); |
| EXPECT_EQ(kTestCommandFrameType, static_cast<FrameType>(frame->control())); |
| EXPECT_EQ(kMuxControlDLCI, frame->dlci()); |
| auto mux_command_frame = |
| Frame::DowncastFrame<MuxCommandFrame>(std::move(frame)); |
| EXPECT_EQ(nullptr, frame); |
| auto mux_command = mux_command_frame->TakeMuxCommand(); |
| EXPECT_EQ(kTestCommandFrameMuxCommandCR, mux_command->command_response()); |
| EXPECT_EQ(kTestCommandFrameMuxCommandType, mux_command->command_type()); |
| EXPECT_EQ(nullptr, mux_command_frame->TakeMuxCommand()); |
| auto test_command = std::unique_ptr<TestCommand>( |
| static_cast<TestCommand*>(mux_command.release())); |
| EXPECT_EQ(kTestCommandFrameMuxCommandPattern, test_command->test_pattern()); |
| } |
| |
| TEST_F(RFCOMM_FrameTest, ReadFramesPreMuxStartup) { |
| auto frame = Frame::Parse(true, Role::kUnassigned, kPreMuxSABMFrame); |
| EXPECT_TRUE(frame); |
| EXPECT_EQ(CommandResponse::kCommand, frame->command_response()); |
| |
| frame = Frame::Parse(true, Role::kUnassigned, kPreMuxUAFrame); |
| EXPECT_TRUE(frame); |
| EXPECT_EQ(CommandResponse::kResponse, frame->command_response()); |
| |
| frame = Frame::Parse(true, Role::kUnassigned, kPreMuxDMFrame); |
| EXPECT_TRUE(frame); |
| EXPECT_EQ(CommandResponse::kResponse, frame->command_response()); |
| |
| EXPECT_FALSE(Frame::Parse(true, Role::kUnassigned, kHelloFrame)); |
| EXPECT_FALSE(Frame::Parse(true, Role::kUnassigned, kFuchsiaFrame)); |
| EXPECT_FALSE(Frame::Parse(true, Role::kUnassigned, kTestCommandFrame)); |
| } |
| |
| TEST_F(RFCOMM_FrameTest, ReadInvalidFrame_TooShort) { |
| auto frame = Frame::Parse(true, Role::kInitiator, kInvalidLengthFrame); |
| EXPECT_EQ(nullptr, frame); |
| } |
| |
| TEST_F(RFCOMM_FrameTest, ReadInvalidFrame_EndsUnexpectedly) { |
| auto frame = Frame::Parse(true, Role::kInitiator, kInvalidLengthFrame2); |
| EXPECT_EQ(nullptr, frame); |
| } |
| |
| TEST_F(RFCOMM_FrameTest, ReadInvalidFrame_InvalidFCS) { |
| auto frame = Frame::Parse(true, Role::kInitiator, kInvalidFCSFrame); |
| EXPECT_EQ(nullptr, frame); |
| } |
| |
| TEST_F(RFCOMM_FrameTest, ReadInvalidFrame_InvalidDLCI) { |
| EXPECT_EQ(nullptr, Frame::Parse(true, Role::kInitiator, kInvalidDLCIFrame)); |
| EXPECT_EQ(nullptr, Frame::Parse(true, Role::kInitiator, kInvalidDLCIFrame2)); |
| } |
| |
| } // namespace |
| } // namespace rfcomm |
| } // namespace btlib |