biod: Add FpInfoCommand class

The FpInfoCommand provides a higher-level abstraction for working with
the EC_CMD_FP_INFO EC command.

BUG=b:144956297, b:76037094
TEST=FEATURES="test" emerge-hatch biod
     cros_deploy <IP> biod
     Enroll finger, lock/unlock, delete finger

Change-Id: I17d4c5aa8c333d2dfd87580433d66611ce6b706d
Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/platform2/+/2268335
Reviewed-by: Yicheng Li <yichengli@chromium.org>
Tested-by: Yicheng Li <yichengli@chromium.org>
Tested-by: Tom Hughes <tomhughes@chromium.org>
Commit-Queue: Tom Hughes <tomhughes@chromium.org>
Auto-Submit: Tom Hughes <tomhughes@chromium.org>
diff --git a/biod/BUILD.gn b/biod/BUILD.gn
index 89b5e61..b74d126 100644
--- a/biod/BUILD.gn
+++ b/biod/BUILD.gn
@@ -66,6 +66,7 @@
     "ec_command_factory.cc",
     "fp_context_command.cc",
     "fp_context_command_factory.cc",
+    "fp_info_command.cc",
     "fp_mode.cc",
     "power_button_filter.cc",
     "power_manager_client.cc",
@@ -126,6 +127,7 @@
       "ec_command_test.cc",
       "fp_context_command_factory_test.cc",
       "fp_context_command_test.cc",
+      "fp_info_command_test.cc",
       "fp_mode_test.cc",
       "power_button_filter_test.cc",
       "power_manager_client_test.cc",
diff --git a/biod/cros_fp_device_test.cc b/biod/cros_fp_device_test.cc
index f9cccce..2c38a84 100644
--- a/biod/cros_fp_device_test.cc
+++ b/biod/cros_fp_device_test.cc
@@ -29,6 +29,12 @@
     EXPECT_CALL(*cmd, Run).WillOnce(testing::Return(true));
     return cmd;
   }
+
+  std::unique_ptr<biod::FpInfoCommand> FpInfoCommand() override {
+    // Should never be called for this test.
+    EXPECT_TRUE(false);
+    return nullptr;
+  }
 };
 
 class CrosFpDevice_ResetContext : public testing::Test {
diff --git a/biod/ec_command.h b/biod/ec_command.h
index 8a5fba2..cbd8a21 100644
--- a/biod/ec_command.h
+++ b/biod/ec_command.h
@@ -125,7 +125,7 @@
     return false;
   }
 
-  I* Resp() { return &data_.resp; }
+  virtual I* Resp() { return &data_.resp; }
   uint32_t RespSize() const { return data_.cmd.insize; }
   O* Req() { return &data_.req; }
   uint32_t Result() const { return data_.cmd.result; }
diff --git a/biod/ec_command_factory.cc b/biod/ec_command_factory.cc
index 888b118..259cdfc 100644
--- a/biod/ec_command_factory.cc
+++ b/biod/ec_command_factory.cc
@@ -3,6 +3,7 @@
 // found in the LICENSE file.
 
 #include "biod/ec_command_factory.h"
+#include "biod/fp_info_command.h"
 
 namespace biod {
 
@@ -11,4 +12,8 @@
   return FpContextCommandFactory::Create(cros_fp, user_id);
 }
 
+std::unique_ptr<FpInfoCommand> EcCommandFactory::FpInfoCommand() {
+  return std::make_unique<biod::FpInfoCommand>();
+}
+
 }  // namespace biod
diff --git a/biod/ec_command_factory.h b/biod/ec_command_factory.h
index e3d34f2..048b9c6 100644
--- a/biod/ec_command_factory.h
+++ b/biod/ec_command_factory.h
@@ -10,15 +10,23 @@
 
 #include "biod/cros_fp_device_interface.h"
 #include "biod/fp_context_command_factory.h"
+#include "biod/fp_info_command.h"
 
 namespace biod {
 
 class EcCommandFactoryInterface {
  public:
   virtual ~EcCommandFactoryInterface() = default;
+
   virtual std::unique_ptr<EcCommandInterface> FpContextCommand(
       CrosFpDeviceInterface* cros_fp, const std::string& user_id) = 0;
-  // TODO(https://crbug.com/1011010): Add factory methods for all of the EC
+
+  virtual std::unique_ptr<biod::FpInfoCommand> FpInfoCommand() = 0;
+  static_assert(std::is_base_of<EcCommandInterface, biod::FpInfoCommand>::value,
+                "All commands created by this class should derive from "
+                "EcCommandInterface");
+
+  // TODO(b/144956297): Add factory methods for all of the EC
   // commands we use so that we can easily mock them for testing.
 };
 
@@ -32,6 +40,8 @@
 
   std::unique_ptr<EcCommandInterface> FpContextCommand(
       CrosFpDeviceInterface* cros_fp, const std::string& user_id) override;
+
+  std::unique_ptr<biod::FpInfoCommand> FpInfoCommand() override;
 };
 
 }  // namespace biod
diff --git a/biod/fp_info_command.cc b/biod/fp_info_command.cc
new file mode 100644
index 0000000..1833809
--- /dev/null
+++ b/biod/fp_info_command.cc
@@ -0,0 +1,100 @@
+// Copyright 2020 The Chromium OS 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 "biod/fp_info_command.h"
+
+namespace biod {
+
+/**
+ * @return non-owning pointer which can be nullptr if command hasn't been run.
+ */
+SensorId* FpInfoCommand::sensor_id() {
+  if (!Resp()) {
+    return nullptr;
+  }
+  if (!sensor_id_) {
+    sensor_id_ =
+        std::make_unique<SensorId>(Resp()->vendor_id, Resp()->product_id,
+                                   Resp()->model_id, Resp()->version);
+  }
+  return sensor_id_.get();
+}
+
+/**
+ * @return non-owning pointer which can be nullptr if command hasn't been run.
+ */
+SensorImage* FpInfoCommand::sensor_image() {
+  if (!Resp()) {
+    return nullptr;
+  }
+  if (!sensor_image_) {
+    sensor_image_ = std::make_unique<SensorImage>(
+        Resp()->width, Resp()->height, Resp()->frame_size, Resp()->pixel_format,
+        Resp()->bpp);
+  }
+  return sensor_image_.get();
+}
+
+/**
+ * @return non-owning pointer which can be nullptr if command hasn't been run.
+ */
+TemplateInfo* FpInfoCommand::template_info() {
+  if (!Resp()) {
+    return nullptr;
+  }
+  if (!template_info_) {
+    template_info_ = std::make_unique<TemplateInfo>(
+        Resp()->template_version, Resp()->template_size, Resp()->template_max,
+        Resp()->template_valid, Resp()->template_dirty);
+  }
+  return template_info_.get();
+}
+
+/**
+ * @return number of dead pixels or kDeadPixelsUnknown
+ */
+int FpInfoCommand::NumDeadPixels() {
+  if (!Resp()) {
+    return kDeadPixelsUnknown;
+  }
+  uint16_t num_dead_pixels = Resp()->errors;
+  if (num_dead_pixels == FP_ERROR_DEAD_PIXELS_UNKNOWN) {
+    return kDeadPixelsUnknown;
+  }
+  return num_dead_pixels;
+}
+
+/**
+ * @return FpSensorErrors
+ */
+FpSensorErrors FpInfoCommand::GetFpSensorErrors() {
+  FpSensorErrors ret = FpSensorErrors::kNone;
+
+  if (!Resp()) {
+    return ret;
+  }
+
+  auto errors = Resp()->errors;
+
+  if (errors & FP_ERROR_NO_IRQ) {
+    ret |= FpSensorErrors::kNoIrq;
+  }
+  if (errors & FP_ERROR_BAD_HWID) {
+    ret |= FpSensorErrors::kBadHardwareID;
+  }
+  if (errors & FP_ERROR_INIT_FAIL) {
+    ret |= FpSensorErrors::kInitializationFailure;
+  }
+  if (errors & FP_ERROR_SPI_COMM) {
+    ret |= FpSensorErrors::kSpiCommunication;
+  }
+  if ((FP_ERROR_DEAD_PIXELS(errors) != FP_ERROR_DEAD_PIXELS_UNKNOWN) &&
+      (FP_ERROR_DEAD_PIXELS(errors) != 0)) {
+    ret |= FpSensorErrors::kDeadPixels;
+  }
+
+  return ret;
+}
+
+}  // namespace biod
diff --git a/biod/fp_info_command.h b/biod/fp_info_command.h
new file mode 100644
index 0000000..029d0b9
--- /dev/null
+++ b/biod/fp_info_command.h
@@ -0,0 +1,40 @@
+// Copyright 2020 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef BIOD_FP_INFO_COMMAND_H_
+#define BIOD_FP_INFO_COMMAND_H_
+
+#include <memory>
+
+#include "biod/ec_command.h"
+#include "biod/ec_command_async.h"
+#include "biod/fp_sensor_errors.h"
+#include "biod/sensor_id.h"
+#include "biod/sensor_image.h"
+#include "biod/template_info.h"
+
+namespace biod {
+
+class FpInfoCommand : public EcCommand<EmptyParam, struct ec_response_fp_info> {
+ public:
+  static const int kDeadPixelsUnknown = -1;
+
+  FpInfoCommand() : EcCommand(EC_CMD_FP_INFO, kVersionOne) {}
+  ~FpInfoCommand() override = default;
+
+  SensorId* sensor_id();
+  SensorImage* sensor_image();
+  TemplateInfo* template_info();
+  int NumDeadPixels();
+  FpSensorErrors GetFpSensorErrors();
+
+ private:
+  std::unique_ptr<SensorId> sensor_id_;
+  std::unique_ptr<SensorImage> sensor_image_;
+  std::unique_ptr<TemplateInfo> template_info_;
+};
+
+}  // namespace biod
+
+#endif  // BIOD_FP_INFO_COMMAND_H_
diff --git a/biod/fp_info_command_test.cc b/biod/fp_info_command_test.cc
new file mode 100644
index 0000000..ded3b91
--- /dev/null
+++ b/biod/fp_info_command_test.cc
@@ -0,0 +1,250 @@
+// Copyright 2020 The Chromium OS 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 <gmock/gmock.h>
+#include <gtest/gtest.h>
+#include <bitset>
+
+#include "biod/ec_command.h"
+#include "biod/fp_info_command.h"
+
+namespace biod {
+namespace {
+
+using ::testing::Return;
+
+TEST(FpInfoCommand, FpInfoCommand) {
+  auto cmd = std::make_unique<FpInfoCommand>();
+  EXPECT_TRUE(cmd);
+  EXPECT_EQ(cmd->Version(), 1);
+  EXPECT_EQ(cmd->Command(), EC_CMD_FP_INFO);
+}
+
+/**
+ * Tests FpInfoCommand's "errors()" method.
+ */
+class FpInfoCommandErrorsTest : public testing::Test {
+ public:
+  class MockFpInfoCommand : public FpInfoCommand {
+   public:
+    MOCK_METHOD(ec_response_fp_info*, Resp, (), (override));
+  };
+  MockFpInfoCommand mock_fp_info_command_;
+};
+
+TEST_F(FpInfoCommandErrorsTest, Errors_None) {
+  EXPECT_CALL(mock_fp_info_command_, Resp).WillOnce(Return(nullptr));
+
+  EXPECT_EQ(mock_fp_info_command_.GetFpSensorErrors(), FpSensorErrors::kNone);
+}
+
+TEST_F(FpInfoCommandErrorsTest, Errors_NoIrq) {
+  struct ec_response_fp_info resp = {.errors = FP_ERROR_NO_IRQ |
+                                               FP_ERROR_DEAD_PIXELS_UNKNOWN};
+
+  EXPECT_CALL(mock_fp_info_command_, Resp).WillRepeatedly(Return(&resp));
+
+  EXPECT_EQ(mock_fp_info_command_.GetFpSensorErrors(), FpSensorErrors::kNoIrq);
+}
+
+TEST_F(FpInfoCommandErrorsTest, Errors_SpiCommunication) {
+  struct ec_response_fp_info resp = {.errors = FP_ERROR_SPI_COMM |
+                                               FP_ERROR_DEAD_PIXELS_UNKNOWN};
+
+  EXPECT_CALL(mock_fp_info_command_, Resp).WillRepeatedly(Return(&resp));
+
+  EXPECT_EQ(mock_fp_info_command_.GetFpSensorErrors(),
+            FpSensorErrors::kSpiCommunication);
+}
+
+TEST_F(FpInfoCommandErrorsTest, Errors_BadHardwareID) {
+  struct ec_response_fp_info resp = {.errors = FP_ERROR_BAD_HWID |
+                                               FP_ERROR_DEAD_PIXELS_UNKNOWN};
+
+  EXPECT_CALL(mock_fp_info_command_, Resp).WillRepeatedly(Return(&resp));
+
+  EXPECT_EQ(mock_fp_info_command_.GetFpSensorErrors(),
+            FpSensorErrors::kBadHardwareID);
+}
+
+TEST_F(FpInfoCommandErrorsTest, Errors_InitializationFailure) {
+  struct ec_response_fp_info resp = {.errors = FP_ERROR_INIT_FAIL |
+                                               FP_ERROR_DEAD_PIXELS_UNKNOWN};
+
+  EXPECT_CALL(mock_fp_info_command_, Resp).WillRepeatedly(Return(&resp));
+
+  EXPECT_EQ(mock_fp_info_command_.GetFpSensorErrors(),
+            FpSensorErrors::kInitializationFailure);
+}
+
+TEST_F(FpInfoCommandErrorsTest, Errors_DeadPixels_0) {
+  struct ec_response_fp_info resp = {.errors = FP_ERROR_DEAD_PIXELS(0)};
+
+  EXPECT_CALL(mock_fp_info_command_, Resp).WillRepeatedly(Return(&resp));
+
+  EXPECT_EQ(mock_fp_info_command_.GetFpSensorErrors(), FpSensorErrors::kNone);
+}
+
+TEST_F(FpInfoCommandErrorsTest, Errors_DeadPixels_1) {
+  struct ec_response_fp_info resp = {.errors = FP_ERROR_DEAD_PIXELS(1)};
+
+  EXPECT_CALL(mock_fp_info_command_, Resp).WillRepeatedly(Return(&resp));
+
+  EXPECT_EQ(mock_fp_info_command_.GetFpSensorErrors(),
+            FpSensorErrors::kDeadPixels);
+}
+
+TEST_F(FpInfoCommandErrorsTest, Errors_Multiple) {
+  struct ec_response_fp_info resp = {.errors = FP_ERROR_DEAD_PIXELS(1) |
+                                               FP_ERROR_BAD_HWID};
+
+  EXPECT_CALL(mock_fp_info_command_, Resp).WillRepeatedly(Return(&resp));
+
+  EXPECT_EQ(mock_fp_info_command_.GetFpSensorErrors(),
+            FpSensorErrors::kDeadPixels | FpSensorErrors::kBadHardwareID);
+}
+
+/**
+ * Tests FpInfoCommand's "NumDeadPixels()" method.
+ */
+class FpInfoCommandNumDeadPixelsTest : public testing::Test {
+ public:
+  class MockFpInfoCommand : public FpInfoCommand {
+   public:
+    MOCK_METHOD(ec_response_fp_info*, Resp, (), (override));
+  };
+  MockFpInfoCommand mock_fp_info_command_;
+};
+
+TEST_F(FpInfoCommandNumDeadPixelsTest, NoResponse) {
+  EXPECT_CALL(mock_fp_info_command_, Resp).WillRepeatedly(Return(nullptr));
+
+  const auto expected = FpInfoCommand::kDeadPixelsUnknown;
+  EXPECT_EQ(mock_fp_info_command_.NumDeadPixels(), expected);
+}
+
+TEST_F(FpInfoCommandNumDeadPixelsTest, DeadPixelsUnknown) {
+  struct ec_response_fp_info resp = {.errors = FP_ERROR_DEAD_PIXELS_UNKNOWN};
+
+  EXPECT_CALL(mock_fp_info_command_, Resp).WillRepeatedly(Return(&resp));
+
+  const auto expected = FpInfoCommand::kDeadPixelsUnknown;
+  EXPECT_EQ(mock_fp_info_command_.NumDeadPixels(), expected);
+}
+
+TEST_F(FpInfoCommandNumDeadPixelsTest, ZeroDeadPixels) {
+  struct ec_response_fp_info resp = {.errors = FP_ERROR_DEAD_PIXELS(0)};
+
+  EXPECT_CALL(mock_fp_info_command_, Resp).WillRepeatedly(Return(&resp));
+
+  EXPECT_EQ(mock_fp_info_command_.NumDeadPixels(), 0);
+}
+
+TEST_F(FpInfoCommandNumDeadPixelsTest, OneDeadPixel) {
+  struct ec_response_fp_info resp = {.errors = FP_ERROR_DEAD_PIXELS(1)};
+
+  EXPECT_CALL(mock_fp_info_command_, Resp).WillRepeatedly(Return(&resp));
+
+  EXPECT_EQ(mock_fp_info_command_.NumDeadPixels(), 1);
+}
+
+/**
+ * Tests FpInfoCommand's "sensor_id" method.
+ */
+class FpInfoCommandSensorIdTest : public testing::Test {
+ public:
+  class MockFpInfoCommand : public FpInfoCommand {
+   public:
+    MOCK_METHOD(ec_response_fp_info*, Resp, (), (override));
+  };
+  MockFpInfoCommand mock_fp_info_command;
+};
+
+TEST_F(FpInfoCommandSensorIdTest, NullResponse) {
+  EXPECT_CALL(mock_fp_info_command, Resp).WillRepeatedly(Return(nullptr));
+
+  EXPECT_EQ(mock_fp_info_command.sensor_id(), nullptr);
+}
+
+TEST_F(FpInfoCommandSensorIdTest, ValidSensorId) {
+  struct ec_response_fp_info resp = {
+      .vendor_id = 1, .product_id = 2, .model_id = 3, .version = 4};
+  EXPECT_CALL(mock_fp_info_command, Resp).WillRepeatedly(Return(&resp));
+
+  EXPECT_NE(mock_fp_info_command.sensor_id(), nullptr);
+  EXPECT_EQ(mock_fp_info_command.sensor_id()->vendor_id, 1);
+  EXPECT_EQ(mock_fp_info_command.sensor_id()->product_id, 2);
+  EXPECT_EQ(mock_fp_info_command.sensor_id()->model_id, 3);
+  EXPECT_EQ(mock_fp_info_command.sensor_id()->version, 4);
+}
+
+/**
+ * Tests FpInfoCommand's "sensor_image" method.
+ */
+class FpInfoCommandSensorImageTest : public testing::Test {
+ public:
+  class MockFpInfoCommand : public FpInfoCommand {
+   public:
+    MOCK_METHOD(ec_response_fp_info*, Resp, (), (override));
+  };
+  MockFpInfoCommand mock_fp_info_command;
+};
+
+TEST_F(FpInfoCommandSensorImageTest, NullResponse) {
+  EXPECT_CALL(mock_fp_info_command, Resp).WillRepeatedly(Return(nullptr));
+
+  EXPECT_EQ(mock_fp_info_command.sensor_image(), nullptr);
+}
+
+TEST_F(FpInfoCommandSensorImageTest, ValidSensorImage) {
+  struct ec_response_fp_info resp = {
+      .frame_size = 1, .pixel_format = 2, .width = 3, .height = 4, .bpp = 5};
+  EXPECT_CALL(mock_fp_info_command, Resp).WillRepeatedly(Return(&resp));
+
+  EXPECT_NE(mock_fp_info_command.sensor_image(), nullptr);
+  EXPECT_EQ(mock_fp_info_command.sensor_image()->frame_size, 1);
+  EXPECT_EQ(mock_fp_info_command.sensor_image()->pixel_format, 2);
+  EXPECT_EQ(mock_fp_info_command.sensor_image()->width, 3);
+  EXPECT_EQ(mock_fp_info_command.sensor_image()->height, 4);
+  EXPECT_EQ(mock_fp_info_command.sensor_image()->bpp, 5);
+}
+
+/**
+ * Tests FpInfoCommand's "template_info" method.
+ */
+class FpInfoCommandTemplateInfoTest : public testing::Test {
+ public:
+  class MockFpInfoCommand : public FpInfoCommand {
+   public:
+    MOCK_METHOD(ec_response_fp_info*, Resp, (), (override));
+  };
+  MockFpInfoCommand mock_fp_info_command;
+};
+
+TEST_F(FpInfoCommandTemplateInfoTest, NullResponse) {
+  EXPECT_CALL(mock_fp_info_command, Resp).WillRepeatedly(Return(nullptr));
+
+  EXPECT_EQ(mock_fp_info_command.sensor_image(), nullptr);
+}
+
+TEST_F(FpInfoCommandTemplateInfoTest, ValidTemplateInfo) {
+  struct ec_response_fp_info resp = {.template_size = 1024,
+                                     .template_max = 4,
+                                     .template_valid = 3,
+                                     .template_dirty = 1 << 3,
+                                     .template_version = 1};
+
+  EXPECT_CALL(mock_fp_info_command, Resp).WillRepeatedly(Return(&resp));
+
+  EXPECT_NE(mock_fp_info_command.template_info(), nullptr);
+  EXPECT_EQ(mock_fp_info_command.template_info()->size, 1024);
+  EXPECT_EQ(mock_fp_info_command.template_info()->max_templates, 4);
+  EXPECT_EQ(mock_fp_info_command.template_info()->num_valid, 3);
+  EXPECT_EQ(mock_fp_info_command.template_info()->dirty,
+            std::bitset<32>(1 << 3));
+  EXPECT_EQ(mock_fp_info_command.template_info()->version, 1);
+}
+
+}  // namespace
+}  // namespace biod
diff --git a/biod/fp_sensor_errors.h b/biod/fp_sensor_errors.h
new file mode 100644
index 0000000..441dfe6
--- /dev/null
+++ b/biod/fp_sensor_errors.h
@@ -0,0 +1,24 @@
+// Copyright 2020 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef BIOD_FP_SENSOR_ERRORS_H_
+#define BIOD_FP_SENSOR_ERRORS_H_
+
+#include <brillo/enum_flags.h>
+
+namespace biod {
+
+enum class FpSensorErrors {
+  kNone = 0,
+  kNoIrq = 1u << 0u,
+  kSpiCommunication = 1u << 1u,
+  kBadHardwareID = 1u << 2u,
+  kInitializationFailure = 1u << 3u,
+  kDeadPixels = 1u << 4u,
+};
+DECLARE_FLAGS_ENUM(FpSensorErrors);
+
+}  // namespace biod
+
+#endif  // BIOD_FP_SENSOR_ERRORS_H_
diff --git a/biod/sensor_id.h b/biod/sensor_id.h
new file mode 100644
index 0000000..469e95d
--- /dev/null
+++ b/biod/sensor_id.h
@@ -0,0 +1,26 @@
+// Copyright 2020 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef BIOD_SENSOR_ID_H_
+#define BIOD_SENSOR_ID_H_
+
+#include <cstdint>
+
+struct SensorId {
+  SensorId(uint32_t vendor_id,
+           uint32_t product_id,
+           uint32_t model_id,
+           uint32_t version)
+      : vendor_id(vendor_id),
+        product_id(product_id),
+        model_id(model_id),
+        version(version) {}
+
+  uint32_t vendor_id = 0;
+  uint32_t product_id = 0;
+  uint32_t model_id = 0;
+  uint32_t version = 0;
+};
+
+#endif  // BIOD_SENSOR_ID_H_
diff --git a/biod/sensor_image.h b/biod/sensor_image.h
new file mode 100644
index 0000000..0794d00
--- /dev/null
+++ b/biod/sensor_image.h
@@ -0,0 +1,29 @@
+// Copyright 2020 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef BIOD_SENSOR_IMAGE_H_
+#define BIOD_SENSOR_IMAGE_H_
+
+#include <cstdint>
+
+struct SensorImage {
+  SensorImage(int width,
+              int height,
+              uint32_t frame_size,
+              uint32_t pixel_format,
+              uint16_t bpp)
+      : width(width),
+        height(height),
+        frame_size(frame_size),
+        pixel_format(pixel_format),
+        bpp(bpp) {}
+
+  int width = 0;
+  int height = 0;
+  uint32_t frame_size = 0;
+  uint32_t pixel_format = 0;
+  uint16_t bpp = 0;
+};
+
+#endif  // BIOD_SENSOR_IMAGE_H_
diff --git a/biod/template_info.h b/biod/template_info.h
new file mode 100644
index 0000000..03c5562
--- /dev/null
+++ b/biod/template_info.h
@@ -0,0 +1,30 @@
+// Copyright 2020 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef BIOD_TEMPLATE_INFO_H_
+#define BIOD_TEMPLATE_INFO_H_
+
+#include <bitset>
+#include <cstdint>
+
+struct TemplateInfo {
+  TemplateInfo(uint32_t version,
+               uint32_t size,
+               uint16_t max_templates,
+               uint16_t num_valid,
+               uint32_t dirty)
+      : version(version),
+        size(size),
+        max_templates(max_templates),
+        num_valid(num_valid),
+        dirty(dirty) {}
+
+  uint32_t version = 0;       /**< version of the template format */
+  uint32_t size = 0;          /**< max template size in bytes */
+  uint16_t max_templates = 0; /**< maximum number of fingers/templates */
+  uint16_t num_valid = 0;     /**< number of valid fingers/templates */
+  std::bitset<32> dirty;      /**< bitmap of templates with MCU side changes */
+};
+
+#endif  // BIOD_TEMPLATE_INFO_H_