Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit e751a198 authored by Henri Chataing's avatar Henri Chataing Committed by Cherrypicker Worker
Browse files

RootCanal H4: Add recovery state

The recovery state is entered when an invalid Idc
is received. In recovery state, all bytes are dropped
until a valid HCI Reset command is received.
The reasoning is that this issue occurs when the emulator
is rebooted, leaving bytes in the VirtIO console.
The next valid command should be HCI Reset command then.

Test: atest rootcanal_test_host
Bug: 261261502
Change-Id: I6e5482d9b2642fa789e6ed415ce3833663097bff
(cherry picked from commit ef82b746)
Merged-In: I6e5482d9b2642fa789e6ed415ce3833663097bff
parent ee690a73
Loading
Loading
Loading
Loading
+1 −1
Original line number Original line Diff line number Diff line
@@ -39,7 +39,7 @@ H4DataChannelPacketizer::H4DataChannelPacketizer(
    PacketReadCallback sco_cb, PacketReadCallback iso_cb,
    PacketReadCallback sco_cb, PacketReadCallback iso_cb,
    ClientDisconnectCallback disconnect_cb)
    ClientDisconnectCallback disconnect_cb)
    : uart_socket_(socket),
    : 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)) {}
      disconnect_cb_(std::move(disconnect_cb)) {}


size_t H4DataChannelPacketizer::Send(uint8_t type, const uint8_t* data,
size_t H4DataChannelPacketizer::Send(uint8_t type, const uint8_t* data,
+45 −9
Original line number Original line Diff line number Diff line
@@ -16,8 +16,8 @@


#include "model/hci/h4_parser.h"  // for H4Parser, PacketType, H4Pars...
#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 <cstdint>     // for uint8_t, int32_t
#include <functional>  // for function
#include <functional>  // for function
#include <utility>     // for move
#include <utility>     // for move
@@ -60,12 +60,13 @@ size_t H4Parser::HciGetPacketLengthForType(PacketType type,


H4Parser::H4Parser(PacketReadCallback command_cb, PacketReadCallback event_cb,
H4Parser::H4Parser(PacketReadCallback command_cb, PacketReadCallback event_cb,
                   PacketReadCallback acl_cb, PacketReadCallback sco_cb,
                   PacketReadCallback acl_cb, PacketReadCallback sco_cb,
                   PacketReadCallback iso_cb)
                   PacketReadCallback iso_cb, bool enable_recovery_state)
    : command_cb_(std::move(command_cb)),
    : command_cb_(std::move(command_cb)),
      event_cb_(std::move(event_cb)),
      event_cb_(std::move(event_cb)),
      acl_cb_(std::move(acl_cb)),
      acl_cb_(std::move(acl_cb)),
      sco_cb_(std::move(sco_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() {
void H4Parser::OnPacketReady() {
  switch (hci_packet_type_) {
  switch (hci_packet_type_) {
@@ -95,6 +96,7 @@ void H4Parser::OnPacketReady() {
size_t H4Parser::BytesRequested() {
size_t H4Parser::BytesRequested() {
  switch (state_) {
  switch (state_) {
    case HCI_TYPE:
    case HCI_TYPE:
    case HCI_RECOVERY:
      return 1;
      return 1;
    case HCI_PREAMBLE:
    case HCI_PREAMBLE:
    case HCI_PAYLOAD:
    case HCI_PAYLOAD:
@@ -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();
  size_t bytes_to_read = BytesRequested();
  if (bytes_read <= 0) {
  if (bytes_read <= 0) {
    LOG_INFO("remote disconnected, or unhandled error?");
    LOG_INFO("remote disconnected, or unhandled error?");
@@ -128,6 +130,31 @@ bool H4Parser::Consume(uint8_t* buffer, int32_t bytes_read) {
      packet_type_ = *buffer;
      packet_type_ = *buffer;
      packet_.clear();
      packet_.clear();
      break;
      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_PREAMBLE:
    case HCI_PAYLOAD:
    case HCI_PAYLOAD:
      packet_.insert(packet_.end(), buffer, buffer + bytes_read);
      packet_.insert(packet_.end(), buffer, buffer + bytes_read);
@@ -143,13 +170,21 @@ bool H4Parser::Consume(uint8_t* buffer, int32_t bytes_read) {
          hci_packet_type_ != PacketType::COMMAND &&
          hci_packet_type_ != PacketType::COMMAND &&
          hci_packet_type_ != PacketType::EVENT &&
          hci_packet_type_ != PacketType::EVENT &&
          hci_packet_type_ != PacketType::ISO) {
          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;
        state_ = HCI_PREAMBLE;
        bytes_wanted_ = preamble_size[static_cast<size_t>(hci_packet_type_)];
        bytes_wanted_ = preamble_size[static_cast<size_t>(hci_packet_type_)];
      }
      break;
      break;
    case HCI_PREAMBLE:
    case HCI_PREAMBLE:

      if (bytes_wanted_ == 0) {
      if (bytes_wanted_ == 0) {
        size_t payload_size =
        size_t payload_size =
            HciGetPacketLengthForType(hci_packet_type_, packet_.data());
            HciGetPacketLengthForType(hci_packet_type_, packet_.data());
@@ -162,6 +197,7 @@ bool H4Parser::Consume(uint8_t* buffer, int32_t bytes_read) {
        }
        }
      }
      }
      break;
      break;
    case HCI_RECOVERY:
    case HCI_PAYLOAD:
    case HCI_PAYLOAD:
      if (bytes_wanted_ == 0) {
      if (bytes_wanted_ == 0) {
        OnPacketReady();
        OnPacketReady();
+24 −7
Original line number Original line Diff line number Diff line
@@ -45,14 +45,14 @@ using ClientDisconnectCallback = std::function<void()>;
// The parser keeps internal state and is not thread safe.
// The parser keeps internal state and is not thread safe.
class H4Parser {
class H4Parser {
 public:
 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,
  H4Parser(PacketReadCallback command_cb, PacketReadCallback event_cb,
           PacketReadCallback acl_cb, PacketReadCallback sco_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.
  // 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.
  // The maximum number of bytes the parser can consume in the current state.
  size_t BytesRequested();
  size_t BytesRequested();
@@ -62,13 +62,15 @@ class H4Parser {


  State CurrentState() { return state_; };
  State CurrentState() { return state_; };


  void EnableRecovery() { enable_recovery_state_ = true; }
  void DisableRecovery() { enable_recovery_state_ = false; }

 private:
 private:
  void OnPacketReady();
  void OnPacketReady();


  // 2 bytes for opcode, 1 byte for parameter length (Volume 2, Part E, 5.4.1)
  // 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_PREAMBLE_SIZE = 3;
  static constexpr size_t COMMAND_LENGTH_OFFSET = 2;
  static constexpr size_t COMMAND_LENGTH_OFFSET = 2;

  // 2 bytes for handle, 2 bytes for data length (Volume 2, Part E, 5.4.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_PREAMBLE_SIZE = 4;
  static constexpr size_t ACL_LENGTH_OFFSET = 2;
  static constexpr size_t ACL_LENGTH_OFFSET = 2;
@@ -101,13 +103,28 @@ class H4Parser {
  uint8_t packet_type_{};
  uint8_t packet_type_{};
  std::vector<uint8_t> packet_;
  std::vector<uint8_t> packet_;
  size_t bytes_wanted_{0};
  size_t bytes_wanted_{0};
  bool enable_recovery_state_{false};
};
};


inline std::ostream& operator<<(std::ostream& os,
inline std::ostream& operator<<(std::ostream& os,
                                H4Parser::State const& state_) {
                                H4Parser::State const& state_) {
  os << (state_ == H4Parser::State::HCI_TYPE       ? "HCI_TYPE"
  switch (state_) {
         : state_ == H4Parser::State::HCI_PREAMBLE ? "HCI_PREAMBLE"
    case H4Parser::State::HCI_TYPE:
                                                   : "HCI_PAYLOAD");
      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;
  return os;
}
}


+44 −1
Original line number Original line Diff line number Diff line
@@ -18,6 +18,8 @@


#include <gtest/gtest.h>
#include <gtest/gtest.h>


#include <array>

namespace rootcanal {
namespace rootcanal {
using PacketData = std::vector<uint8_t>;
using PacketData = std::vector<uint8_t>;


@@ -57,6 +59,7 @@ class H4ParserTest : public ::testing::Test {
        type_ = PacketType::ISO;
        type_ = PacketType::ISO;
        PacketReadCallback(p);
        PacketReadCallback(p);
      },
      },
      true,
  };
  };
  PacketData packet_;
  PacketData packet_;
  PacketType type_;
  PacketType type_;
@@ -109,9 +112,10 @@ TEST_F(H4ParserTest, TooMuchIsDeath) {
}
}


TEST_F(H4ParserTest, WrongTypeIsDeath) {
TEST_F(H4ParserTest, WrongTypeIsDeath) {
  parser_.DisableRecovery();
  PacketData bad_bit({0xfd});
  PacketData bad_bit({0xfd});
  ASSERT_DEATH(parser_.Consume(bad_bit.data(), bad_bit.size()),
  ASSERT_DEATH(parser_.Consume(bad_bit.data(), bad_bit.size()),
               "Unimplemented packet type.*");
               "Received invalid packet type.*");
}
}


TEST_F(H4ParserTest, CallsTheRightCallbacks) {
TEST_F(H4ParserTest, CallsTheRightCallbacks) {
@@ -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
}  // namespace rootcanal