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

Commit 541bfe9c authored by Myles Watson's avatar Myles Watson Committed by Gerrit Code Review
Browse files

Merge "RootCanal H4: Add recovery state"

parents e654dd9d ef82b746
Loading
Loading
Loading
Loading
+1 −1
Original line number Diff line number Diff line
@@ -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,
+45 −9
Original line number Diff line number Diff line
@@ -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
@@ -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_) {
@@ -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:
@@ -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?");
@@ -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);
@@ -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());
@@ -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();
+24 −7
Original line number Diff line number Diff line
@@ -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();
@@ -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;
@@ -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;
}

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

#include <gtest/gtest.h>

#include <array>

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

@@ -57,6 +59,7 @@ class H4ParserTest : public ::testing::Test {
        type_ = PacketType::ISO;
        PacketReadCallback(p);
      },
      true,
  };
  PacketData packet_;
  PacketType type_;
@@ -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) {
@@ -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