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_