Loading tools/rootcanal/model/hci/h4_data_channel_packetizer.cc +1 −1 Original line number Diff line number Diff line Loading @@ -39,7 +39,7 @@ H4DataChannelPacketizer::H4DataChannelPacketizer( PacketReadCallback sco_cb, PacketReadCallback iso_cb, ClientDisconnectCallback disconnect_cb) : uart_socket_(socket), h4_parser_(command_cb, event_cb, acl_cb, sco_cb, iso_cb), h4_parser_(command_cb, event_cb, acl_cb, sco_cb, iso_cb, true), disconnect_cb_(std::move(disconnect_cb)) {} size_t H4DataChannelPacketizer::Send(uint8_t type, const uint8_t* data, Loading tools/rootcanal/model/hci/h4_parser.cc +45 −9 Original line number Diff line number Diff line Loading @@ -16,8 +16,8 @@ #include "model/hci/h4_parser.h" // for H4Parser, PacketType, H4Pars... #include <stddef.h> // for size_t #include <array> #include <cstddef> // for size_t #include <cstdint> // for uint8_t, int32_t #include <functional> // for function #include <utility> // for move Loading Loading @@ -60,12 +60,13 @@ size_t H4Parser::HciGetPacketLengthForType(PacketType type, H4Parser::H4Parser(PacketReadCallback command_cb, PacketReadCallback event_cb, PacketReadCallback acl_cb, PacketReadCallback sco_cb, PacketReadCallback iso_cb) PacketReadCallback iso_cb, bool enable_recovery_state) : command_cb_(std::move(command_cb)), event_cb_(std::move(event_cb)), acl_cb_(std::move(acl_cb)), sco_cb_(std::move(sco_cb)), iso_cb_(std::move(iso_cb)) {} iso_cb_(std::move(iso_cb)), enable_recovery_state_(enable_recovery_state) {} void H4Parser::OnPacketReady() { switch (hci_packet_type_) { Loading Loading @@ -95,6 +96,7 @@ void H4Parser::OnPacketReady() { size_t H4Parser::BytesRequested() { switch (state_) { case HCI_TYPE: case HCI_RECOVERY: return 1; case HCI_PREAMBLE: case HCI_PAYLOAD: Loading @@ -102,7 +104,7 @@ size_t H4Parser::BytesRequested() { } } bool H4Parser::Consume(uint8_t* buffer, int32_t bytes_read) { bool H4Parser::Consume(const uint8_t* buffer, int32_t bytes_read) { size_t bytes_to_read = BytesRequested(); if (bytes_read <= 0) { LOG_INFO("remote disconnected, or unhandled error?"); Loading @@ -129,6 +131,31 @@ bool H4Parser::Consume(uint8_t* buffer, int32_t bytes_read) { packet_type_ = *buffer; packet_.clear(); break; case HCI_RECOVERY: { // Skip all received bytes until the HCI Reset command is received. // The parser can end up in a bad state when the host is restarted. const std::array<uint8_t, 4> reset_command{0x01, 0x03, 0x0c, 0x00}; size_t offset = packet_.size(); LOG_WARN("Received byte in recovery state : 0x%x", static_cast<unsigned>(*buffer)); packet_.push_back(*buffer); // Last byte does not match expected byte in the sequence. // Drop all the bytes and start over. if (packet_[offset] != reset_command[offset]) { packet_.clear(); // The mismatched byte can also be the first of the correct sequence. if (*buffer == reset_command[0]) { packet_.push_back(*buffer); } } if (packet_.size() == reset_command.size()) { LOG_INFO("Received HCI Reset command, exiting recovery state"); bytes_wanted_ = 0; } break; } case HCI_PREAMBLE: case HCI_PAYLOAD: packet_.insert(packet_.end(), buffer, buffer + bytes_read); Loading @@ -144,13 +171,21 @@ bool H4Parser::Consume(uint8_t* buffer, int32_t bytes_read) { hci_packet_type_ != PacketType::COMMAND && hci_packet_type_ != PacketType::EVENT && hci_packet_type_ != PacketType::ISO) { LOG_ALWAYS_FATAL("Unimplemented packet type %hhd", packet_type_); } if (!enable_recovery_state_) { LOG_ALWAYS_FATAL("Received invalid packet type 0x%x", static_cast<unsigned>(packet_type_)); } LOG_ERROR("Received invalid packet type 0x%x, entering recovery state", static_cast<unsigned>(packet_type_)); state_ = HCI_RECOVERY; hci_packet_type_ = PacketType::COMMAND; bytes_wanted_ = 1; } else { state_ = HCI_PREAMBLE; bytes_wanted_ = preamble_size[static_cast<size_t>(hci_packet_type_)]; } break; case HCI_PREAMBLE: if (bytes_wanted_ == 0) { size_t payload_size = HciGetPacketLengthForType(hci_packet_type_, packet_.data()); Loading @@ -163,6 +198,7 @@ bool H4Parser::Consume(uint8_t* buffer, int32_t bytes_read) { } } break; case HCI_RECOVERY: case HCI_PAYLOAD: if (bytes_wanted_ == 0) { OnPacketReady(); Loading tools/rootcanal/model/hci/h4_parser.h +24 −7 Original line number Diff line number Diff line Loading @@ -45,14 +45,14 @@ using ClientDisconnectCallback = std::function<void()>; // The parser keeps internal state and is not thread safe. class H4Parser { public: enum State { HCI_TYPE, HCI_PREAMBLE, HCI_PAYLOAD }; enum State { HCI_TYPE, HCI_PREAMBLE, HCI_PAYLOAD, HCI_RECOVERY }; H4Parser(PacketReadCallback command_cb, PacketReadCallback event_cb, PacketReadCallback acl_cb, PacketReadCallback sco_cb, PacketReadCallback iso_cb); PacketReadCallback iso_cb, bool enable_recovery_state = false); // Consumes the given number of bytes, returns true on success. bool Consume(uint8_t* buffer, int32_t bytes); bool Consume(const uint8_t* buffer, int32_t bytes); // The maximum number of bytes the parser can consume in the current state. size_t BytesRequested(); Loading @@ -62,13 +62,15 @@ class H4Parser { State CurrentState() { return state_; }; void EnableRecovery() { enable_recovery_state_ = true; } void DisableRecovery() { enable_recovery_state_ = false; } private: void OnPacketReady(); // 2 bytes for opcode, 1 byte for parameter length (Volume 2, Part E, 5.4.1) static constexpr size_t COMMAND_PREAMBLE_SIZE = 3; static constexpr size_t COMMAND_LENGTH_OFFSET = 2; // 2 bytes for handle, 2 bytes for data length (Volume 2, Part E, 5.4.2) static constexpr size_t ACL_PREAMBLE_SIZE = 4; static constexpr size_t ACL_LENGTH_OFFSET = 2; Loading Loading @@ -101,13 +103,28 @@ class H4Parser { uint8_t packet_type_{}; std::vector<uint8_t> packet_; size_t bytes_wanted_{0}; bool enable_recovery_state_{false}; }; inline std::ostream& operator<<(std::ostream& os, H4Parser::State const& state_) { os << (state_ == H4Parser::State::HCI_TYPE ? "HCI_TYPE" : state_ == H4Parser::State::HCI_PREAMBLE ? "HCI_PREAMBLE" : "HCI_PAYLOAD"); switch (state_) { case H4Parser::State::HCI_TYPE: os << "HCI_TYPE"; break; case H4Parser::State::HCI_PREAMBLE: os << "HCI_PREAMBLE"; break; case H4Parser::State::HCI_PAYLOAD: os << "HCI_PAYLOAD"; break; case H4Parser::State::HCI_RECOVERY: os << "HCI_RECOVERY"; break; default: os << "unknown state " << static_cast<int>(state_); break; } return os; } Loading tools/rootcanal/test/h4_parser_unittest.cc +44 −1 Original line number Diff line number Diff line Loading @@ -18,6 +18,8 @@ #include <gtest/gtest.h> #include <array> namespace rootcanal { using PacketData = std::vector<uint8_t>; Loading Loading @@ -57,6 +59,7 @@ class H4ParserTest : public ::testing::Test { type_ = PacketType::ISO; PacketReadCallback(p); }, true, }; PacketData packet_; PacketType type_; Loading Loading @@ -109,9 +112,10 @@ TEST_F(H4ParserTest, TooMuchIsDeath) { } TEST_F(H4ParserTest, WrongTypeIsDeath) { parser_.DisableRecovery(); PacketData bad_bit({0xfd}); ASSERT_DEATH(parser_.Consume(bad_bit.data(), bad_bit.size()), "Unimplemented packet type.*"); "Received invalid packet type.*"); } TEST_F(H4ParserTest, CallsTheRightCallbacks) { Loading Loading @@ -139,4 +143,43 @@ TEST_F(H4ParserTest, CallsTheRightCallbacks) { } } TEST_F(H4ParserTest, Recovery) { // Validate that the recovery state is exited only after receiving the // HCI Reset command. parser_.EnableRecovery(); // Enter recovery state after receiving an invalid packet type. uint8_t invalid_packet_type = 0xfd; ASSERT_TRUE(parser_.Consume(&invalid_packet_type, 1)); ASSERT_EQ(parser_.CurrentState(), H4Parser::State::HCI_RECOVERY); const std::array<uint8_t, 4> reset_command{0x01, 0x03, 0x0c, 0x00}; // Send prefixes of the HCI Reset command, restarting over from the start. for (size_t n = 1; n < 4; n++) { for (size_t i = 0; i < n; i++) { ASSERT_TRUE(parser_.Consume(&reset_command[i], 1)); ASSERT_EQ(parser_.CurrentState(), H4Parser::State::HCI_RECOVERY); } } // Finally send the full HCI Reset command. for (size_t i = 0; i < 4; i++) { ASSERT_EQ(parser_.CurrentState(), H4Parser::State::HCI_RECOVERY); ASSERT_TRUE(parser_.Consume(&reset_command[i], 1)); } // Validate that the HCI recovery state is exited, // and the HCI Reset command correctly received on the command callback. ASSERT_EQ(parser_.CurrentState(), H4Parser::State::HCI_TYPE); ASSERT_LT(0, (int)packet_.size()); // Validate that the HCI Reset command was correctly received. ASSERT_EQ(type_, PacketType::COMMAND); ASSERT_EQ(packet_.size(), reset_command.size()); for (size_t i = 0; i < packet_.size(); i++) { ASSERT_EQ(packet_[i], reset_command[i]); } } } // namespace rootcanal Loading
tools/rootcanal/model/hci/h4_data_channel_packetizer.cc +1 −1 Original line number Diff line number Diff line Loading @@ -39,7 +39,7 @@ H4DataChannelPacketizer::H4DataChannelPacketizer( PacketReadCallback sco_cb, PacketReadCallback iso_cb, ClientDisconnectCallback disconnect_cb) : uart_socket_(socket), h4_parser_(command_cb, event_cb, acl_cb, sco_cb, iso_cb), h4_parser_(command_cb, event_cb, acl_cb, sco_cb, iso_cb, true), disconnect_cb_(std::move(disconnect_cb)) {} size_t H4DataChannelPacketizer::Send(uint8_t type, const uint8_t* data, Loading
tools/rootcanal/model/hci/h4_parser.cc +45 −9 Original line number Diff line number Diff line Loading @@ -16,8 +16,8 @@ #include "model/hci/h4_parser.h" // for H4Parser, PacketType, H4Pars... #include <stddef.h> // for size_t #include <array> #include <cstddef> // for size_t #include <cstdint> // for uint8_t, int32_t #include <functional> // for function #include <utility> // for move Loading Loading @@ -60,12 +60,13 @@ size_t H4Parser::HciGetPacketLengthForType(PacketType type, H4Parser::H4Parser(PacketReadCallback command_cb, PacketReadCallback event_cb, PacketReadCallback acl_cb, PacketReadCallback sco_cb, PacketReadCallback iso_cb) PacketReadCallback iso_cb, bool enable_recovery_state) : command_cb_(std::move(command_cb)), event_cb_(std::move(event_cb)), acl_cb_(std::move(acl_cb)), sco_cb_(std::move(sco_cb)), iso_cb_(std::move(iso_cb)) {} iso_cb_(std::move(iso_cb)), enable_recovery_state_(enable_recovery_state) {} void H4Parser::OnPacketReady() { switch (hci_packet_type_) { Loading Loading @@ -95,6 +96,7 @@ void H4Parser::OnPacketReady() { size_t H4Parser::BytesRequested() { switch (state_) { case HCI_TYPE: case HCI_RECOVERY: return 1; case HCI_PREAMBLE: case HCI_PAYLOAD: Loading @@ -102,7 +104,7 @@ size_t H4Parser::BytesRequested() { } } bool H4Parser::Consume(uint8_t* buffer, int32_t bytes_read) { bool H4Parser::Consume(const uint8_t* buffer, int32_t bytes_read) { size_t bytes_to_read = BytesRequested(); if (bytes_read <= 0) { LOG_INFO("remote disconnected, or unhandled error?"); Loading @@ -129,6 +131,31 @@ bool H4Parser::Consume(uint8_t* buffer, int32_t bytes_read) { packet_type_ = *buffer; packet_.clear(); break; case HCI_RECOVERY: { // Skip all received bytes until the HCI Reset command is received. // The parser can end up in a bad state when the host is restarted. const std::array<uint8_t, 4> reset_command{0x01, 0x03, 0x0c, 0x00}; size_t offset = packet_.size(); LOG_WARN("Received byte in recovery state : 0x%x", static_cast<unsigned>(*buffer)); packet_.push_back(*buffer); // Last byte does not match expected byte in the sequence. // Drop all the bytes and start over. if (packet_[offset] != reset_command[offset]) { packet_.clear(); // The mismatched byte can also be the first of the correct sequence. if (*buffer == reset_command[0]) { packet_.push_back(*buffer); } } if (packet_.size() == reset_command.size()) { LOG_INFO("Received HCI Reset command, exiting recovery state"); bytes_wanted_ = 0; } break; } case HCI_PREAMBLE: case HCI_PAYLOAD: packet_.insert(packet_.end(), buffer, buffer + bytes_read); Loading @@ -144,13 +171,21 @@ bool H4Parser::Consume(uint8_t* buffer, int32_t bytes_read) { hci_packet_type_ != PacketType::COMMAND && hci_packet_type_ != PacketType::EVENT && hci_packet_type_ != PacketType::ISO) { LOG_ALWAYS_FATAL("Unimplemented packet type %hhd", packet_type_); } if (!enable_recovery_state_) { LOG_ALWAYS_FATAL("Received invalid packet type 0x%x", static_cast<unsigned>(packet_type_)); } LOG_ERROR("Received invalid packet type 0x%x, entering recovery state", static_cast<unsigned>(packet_type_)); state_ = HCI_RECOVERY; hci_packet_type_ = PacketType::COMMAND; bytes_wanted_ = 1; } else { state_ = HCI_PREAMBLE; bytes_wanted_ = preamble_size[static_cast<size_t>(hci_packet_type_)]; } break; case HCI_PREAMBLE: if (bytes_wanted_ == 0) { size_t payload_size = HciGetPacketLengthForType(hci_packet_type_, packet_.data()); Loading @@ -163,6 +198,7 @@ bool H4Parser::Consume(uint8_t* buffer, int32_t bytes_read) { } } break; case HCI_RECOVERY: case HCI_PAYLOAD: if (bytes_wanted_ == 0) { OnPacketReady(); Loading
tools/rootcanal/model/hci/h4_parser.h +24 −7 Original line number Diff line number Diff line Loading @@ -45,14 +45,14 @@ using ClientDisconnectCallback = std::function<void()>; // The parser keeps internal state and is not thread safe. class H4Parser { public: enum State { HCI_TYPE, HCI_PREAMBLE, HCI_PAYLOAD }; enum State { HCI_TYPE, HCI_PREAMBLE, HCI_PAYLOAD, HCI_RECOVERY }; H4Parser(PacketReadCallback command_cb, PacketReadCallback event_cb, PacketReadCallback acl_cb, PacketReadCallback sco_cb, PacketReadCallback iso_cb); PacketReadCallback iso_cb, bool enable_recovery_state = false); // Consumes the given number of bytes, returns true on success. bool Consume(uint8_t* buffer, int32_t bytes); bool Consume(const uint8_t* buffer, int32_t bytes); // The maximum number of bytes the parser can consume in the current state. size_t BytesRequested(); Loading @@ -62,13 +62,15 @@ class H4Parser { State CurrentState() { return state_; }; void EnableRecovery() { enable_recovery_state_ = true; } void DisableRecovery() { enable_recovery_state_ = false; } private: void OnPacketReady(); // 2 bytes for opcode, 1 byte for parameter length (Volume 2, Part E, 5.4.1) static constexpr size_t COMMAND_PREAMBLE_SIZE = 3; static constexpr size_t COMMAND_LENGTH_OFFSET = 2; // 2 bytes for handle, 2 bytes for data length (Volume 2, Part E, 5.4.2) static constexpr size_t ACL_PREAMBLE_SIZE = 4; static constexpr size_t ACL_LENGTH_OFFSET = 2; Loading Loading @@ -101,13 +103,28 @@ class H4Parser { uint8_t packet_type_{}; std::vector<uint8_t> packet_; size_t bytes_wanted_{0}; bool enable_recovery_state_{false}; }; inline std::ostream& operator<<(std::ostream& os, H4Parser::State const& state_) { os << (state_ == H4Parser::State::HCI_TYPE ? "HCI_TYPE" : state_ == H4Parser::State::HCI_PREAMBLE ? "HCI_PREAMBLE" : "HCI_PAYLOAD"); switch (state_) { case H4Parser::State::HCI_TYPE: os << "HCI_TYPE"; break; case H4Parser::State::HCI_PREAMBLE: os << "HCI_PREAMBLE"; break; case H4Parser::State::HCI_PAYLOAD: os << "HCI_PAYLOAD"; break; case H4Parser::State::HCI_RECOVERY: os << "HCI_RECOVERY"; break; default: os << "unknown state " << static_cast<int>(state_); break; } return os; } Loading
tools/rootcanal/test/h4_parser_unittest.cc +44 −1 Original line number Diff line number Diff line Loading @@ -18,6 +18,8 @@ #include <gtest/gtest.h> #include <array> namespace rootcanal { using PacketData = std::vector<uint8_t>; Loading Loading @@ -57,6 +59,7 @@ class H4ParserTest : public ::testing::Test { type_ = PacketType::ISO; PacketReadCallback(p); }, true, }; PacketData packet_; PacketType type_; Loading Loading @@ -109,9 +112,10 @@ TEST_F(H4ParserTest, TooMuchIsDeath) { } TEST_F(H4ParserTest, WrongTypeIsDeath) { parser_.DisableRecovery(); PacketData bad_bit({0xfd}); ASSERT_DEATH(parser_.Consume(bad_bit.data(), bad_bit.size()), "Unimplemented packet type.*"); "Received invalid packet type.*"); } TEST_F(H4ParserTest, CallsTheRightCallbacks) { Loading Loading @@ -139,4 +143,43 @@ TEST_F(H4ParserTest, CallsTheRightCallbacks) { } } TEST_F(H4ParserTest, Recovery) { // Validate that the recovery state is exited only after receiving the // HCI Reset command. parser_.EnableRecovery(); // Enter recovery state after receiving an invalid packet type. uint8_t invalid_packet_type = 0xfd; ASSERT_TRUE(parser_.Consume(&invalid_packet_type, 1)); ASSERT_EQ(parser_.CurrentState(), H4Parser::State::HCI_RECOVERY); const std::array<uint8_t, 4> reset_command{0x01, 0x03, 0x0c, 0x00}; // Send prefixes of the HCI Reset command, restarting over from the start. for (size_t n = 1; n < 4; n++) { for (size_t i = 0; i < n; i++) { ASSERT_TRUE(parser_.Consume(&reset_command[i], 1)); ASSERT_EQ(parser_.CurrentState(), H4Parser::State::HCI_RECOVERY); } } // Finally send the full HCI Reset command. for (size_t i = 0; i < 4; i++) { ASSERT_EQ(parser_.CurrentState(), H4Parser::State::HCI_RECOVERY); ASSERT_TRUE(parser_.Consume(&reset_command[i], 1)); } // Validate that the HCI recovery state is exited, // and the HCI Reset command correctly received on the command callback. ASSERT_EQ(parser_.CurrentState(), H4Parser::State::HCI_TYPE); ASSERT_LT(0, (int)packet_.size()); // Validate that the HCI Reset command was correctly received. ASSERT_EQ(type_, PacketType::COMMAND); ASSERT_EQ(packet_.size(), reset_command.size()); for (size_t i = 0; i < packet_.size(); i++) { ASSERT_EQ(packet_[i], reset_command[i]); } } } // namespace rootcanal