faceauth_tests: revamp test framework

With AuthToken push, test framework was quite outdated and also
getting quite ugly. This is a major refactoring and introduces
different layers of tests that I have been procrastinating to
write so far.

Bug: 133278842
Test: bazel
Change-Id: Ieb79e356aae9243767a80ccd1fff84e8ce6b0ef5
Signed-off-by: Ji Soo Shin <jisshin@google.com>
diff --git a/src/faceauth_tests.cc b/src/faceauth_tests.cc
index cd41d98..5aff16e 100644
--- a/src/faceauth_tests.cc
+++ b/src/faceauth_tests.cc
@@ -16,36 +16,28 @@
 
 namespace {
 
+const uint8_t EMBEDDING_VECTOR_NULL = 0x0;
+const uint8_t EMBEDDING_VECTOR_1 = 0x11;
+const uint8_t EMBEDDING_VECTOR_2 = 0xAA;
+
 class FaceAuthTest: public testing::Test {
+ public:
+  const uint32_t PROFILE_1 = 1;
+  const uint32_t PROFILE_2 = 2;
+  const uint32_t PROFILE_3 = 3;
+  const uint32_t PROFILE_4 = 4;
+  const uint32_t PROFILE_5 = 5;
+  const uint32_t PROFILE_6 = 6;
+  static unique_ptr<nos::NuggetClientInterface> client;
+  static unique_ptr<test_harness::TestHarness> uart_printer;
+
  protected:
   void SetUp() override;
 
   static void SetUpTestCase();
   static void TearDownTestCase();
-
-  static unique_ptr<nos::NuggetClientInterface> client;
-  static unique_ptr<test_harness::TestHarness> uart_printer;
-
-  static const fa_result_t RunTask(const fa_task_t task,
-                                   const fa_embedding_t* embed = NULL,
-                                   const fa_token_t* token = NULL);
-  static void Run(const fa_result_t expected, const fa_task_t task,
-                  const fa_embedding_t* embed = NULL,
-                  const fa_token_t* token = NULL);
-
-  static void LockProfileTest(uint32_t profile1);
-  static void UnlockProfileTest(uint32_t profile1);
-  static bool IsProfileLocked(uint32_t profile1);
-  static void UnockProfileTest(uint32_t profile1);
-  static void FullMatchMismatchTest(uint32_t profile1, uint32_t profile2,
-                                    uint32_t slot1, uint32_t slot2);
-  static fa_token_t MakeToken(uint32_t profile_id);
-
-  static vector<uint64_t> user_ids;
 };
 
-vector<uint64_t> FaceAuthTest::user_ids;
-
 unique_ptr<nos::NuggetClientInterface> FaceAuthTest::client;
 unique_ptr<test_harness::TestHarness> FaceAuthTest::uart_printer;
 
@@ -83,504 +75,1100 @@
   return (uint8_t)(crc >> 8);
 }
 
-static fa_task_t MakeTask(uint64_t session_id, uint32_t profile_id,
-                          uint32_t cmd, uint32_t input_data1 = 0,
-                          uint32_t input_data2 = 0,
-                          uint32_t version = FACEAUTH_MIN_ABH_VERSION) {
-  fa_task_t task;
-  task.version = version;
-  task.session_id = session_id;
-  task.profile_id = profile_id;
-  task.cmd = cmd;
-  task.input.data.first = input_data1;
-  task.input.data.second = input_data2;
-  task.crc = CalcCrc8(reinterpret_cast<const uint8_t*>(&task),
-                      offsetof(struct fa_task_t, crc));
-  return task;
-}
-
-static fa_embedding_t* MakeEmbedding(uint32_t base, uint32_t version = 1) {
-  static fa_embedding_t embed;
-  memset(&embed, base, sizeof(fa_embedding_t));
-  embed.version = version;
-  embed.valid = 0;
-  embed.crc = CalcCrc8(reinterpret_cast<const uint8_t*>(&embed),
-                       offsetof(struct fa_embedding_t, crc));
-  return &embed;
-}
-
-static fa_result_t MakeResult(uint64_t session_id, int32_t error,
-                              uint32_t output_data1 = 0,
-                              uint32_t output_data2 = 0,
-                              uint32_t output_data3 = 0) {
-  fa_result_t result;
-  memset(&result, 0, sizeof(fa_result_t));
-  result.version = 1;
-  result.session_id = session_id;
-  result.error = error;
-  result.output.data.first = output_data1;
-  result.output.data.second = output_data2;
-  result.output.data.third = output_data3;
-  result.complete = 1;
-  result.crc = CalcCrc8(reinterpret_cast<const uint8_t*>(&result),
-                        offsetof(struct fa_result_t, crc));
-  return result;
-}
-
-fa_token_t FaceAuthTest::MakeToken(uint32_t profile_id) {
-  fa_token_t token;
-  token.user_id = user_ids[profile_id];
-  return token;
-}
-
-vector<uint8_t> Task2Buffer(const fa_task_t task, const fa_embedding_t* embed,
-                            const fa_token_t* token) {
-  vector<uint8_t> buffer;
-  for (size_t i = 0; i < sizeof(fa_task_t); ++i) {
-    buffer.push_back(*(reinterpret_cast<const uint8_t*>(&task) + i));
-  }
-  for (size_t i = 0; i < sizeof(fa_embedding_t); ++i) {
-    if (embed)
-      buffer.push_back(*(reinterpret_cast<const uint8_t*>(embed) + i));
-    else
-      buffer.push_back(0);
+class Task {
+ public:
+  Task(uint32_t profile_id, uint32_t cmd,
+       uint32_t version = FACEAUTH_MIN_ABH_VERSION) {
+    memset(&task, 0, sizeof(task));
+    task.version = version;
+    task.session_id = 0xFACEBEEFBEEFFACEull;
+    task.profile_id = profile_id;
+    task.cmd = cmd;
   }
 
-  if (token) {
-    for (size_t i = 0; i < sizeof(fa_token_t); ++i) {
-      buffer.push_back(*(reinterpret_cast<const uint8_t*>(token) + i));
+  Task& SetFirst(uint64_t first) {
+    task.input.data.first = first;
+    return *this;
+  }
+
+  Task& SetChallenge(uint64_t challenge) {
+    memcpy(task.input.challenge, &challenge, sizeof(challenge));
+    return *this;
+  }
+
+  Task& Finalize() {
+    task.crc = CalcCrc8(reinterpret_cast<const uint8_t*>(&task),
+                        offsetof(struct fa_task_t, crc));
+    return *this;
+  }
+
+  vector<uint8_t> ToBuffer() {
+    vector<uint8_t> buffer;
+    for (size_t i = 0; i < sizeof(fa_task_t); ++i) {
+      buffer.push_back(*(reinterpret_cast<const uint8_t*>(&task) + i));
     }
+    return buffer;
   }
 
-  return buffer;
+ private:
+  fa_task_t task;
+};
+
+class Embedding {
+ public:
+  Embedding() { memset(&embed, 0, sizeof(embed)); }
+  Embedding(uint8_t base, uint32_t version = 1) {
+    memset(&embed, base, sizeof(embed));
+    embed.version = version;
+    embed.valid = 0;
+  }
+
+  void Finalize() {
+    embed.crc = CalcCrc8(reinterpret_cast<const uint8_t*>(&embed),
+                         offsetof(struct fa_embedding_t, crc));
+  }
+
+  vector<uint8_t> ToBuffer() {
+    vector<uint8_t> buffer;
+    for (size_t i = 0; i < sizeof(fa_embedding_t); ++i) {
+      buffer.push_back(*(reinterpret_cast<const uint8_t*>(&embed) + i));
+    }
+    return buffer;
+  }
+
+ private:
+  fa_embedding_t embed;
+};
+
+class Token {
+ public:
+  Token() { memset(&token, 0, sizeof(token)); }
+
+  Token(fa_token_t token) : token(token) {}
+
+  Token(uint64_t challenge, uint64_t user_id, uint64_t auth_id) {
+    memset(&token, 0, sizeof(token));
+    token.challenge = challenge;
+    token.user_id = user_id;
+    token.authenticator_id = auth_id;
+  }
+
+  vector<uint8_t> ToBuffer() {
+    vector<uint8_t> buffer;
+    for (size_t i = 0; i < sizeof(fa_token_t); ++i) {
+      buffer.push_back(*(reinterpret_cast<const uint8_t*>(&token) + i));
+    }
+    return buffer;
+  }
+
+  fa_token_t GetRaw() { return token; }
+
+ private:
+  fa_token_t token;
+};
+
+class Result {
+ public:
+  Result() { memset(&result, 0, sizeof(result)); }
+
+  Result(fa_result_t result) : result(result) {}
+
+  Result(int32_t error) {
+    memset(&result, 0, sizeof(result));
+    SetError(error);
+  }
+
+  fa_result_t GetRaw() {
+    Finalize();
+    return result;
+  }
+
+  Result& SetError(int32_t error) {
+    result.error = error;
+    return *this;
+  }
+
+  Result& SetChallenge(uint64_t challenge) {
+    memcpy(result.output.challenge, &challenge, sizeof(challenge));
+    return *this;
+  }
+
+  Result& SetSecond(uint32_t second) {
+    result.output.data.second = second;
+    return *this;
+  }
+
+  Result& SetThird(uint32_t third) {
+    result.output.data.third = third;
+    return *this;
+  }
+
+  uint64_t GetChallenge() {
+    uint64_t challenge;
+    memcpy(&challenge, result.output.challenge, sizeof(challenge));
+    return challenge;
+  }
+
+  uint32_t GetFirst() { return result.output.data.first; }
+  uint32_t GetSecond() { return result.output.data.second; }
+
+  Result& Finalize() {
+    result.version = 1;
+    result.session_id = 0xFACEBEEFBEEFFACEull;
+    result.complete = 1;
+    result.crc = CalcCrc8(reinterpret_cast<const uint8_t*>(&result),
+                          offsetof(struct fa_result_t, crc));
+    return *this;
+  }
+
+ protected:
+  fa_result_t result;
+};
+
+static void EXPECT_REQ(Result r1, Result r2) {
+  fa_result_t observed = r1.GetRaw();
+  fa_result_t expected = r2.GetRaw();
+
+  EXPECT_EQ(observed.version, expected.version);
+  EXPECT_EQ(observed.session_id, expected.session_id);
+  EXPECT_EQ(observed.error, expected.error);
+  EXPECT_EQ(observed.output.data.first, expected.output.data.first);
+  EXPECT_EQ(observed.output.data.second, expected.output.data.second);
+  EXPECT_EQ(observed.output.data.third, expected.output.data.third);
+  EXPECT_EQ(observed.complete, expected.complete);
+  EXPECT_EQ(observed.crc, expected.crc);
 }
 
-static const fa_result_t Buffer2Result(const vector<uint8_t>& buffer)
-{
-  const fa_result_t result = *(reinterpret_cast<const fa_result_t*>(
-                               buffer.data()));
-  return result;
+class ResetLockoutResult : public Result {
+ public:
+  ResetLockoutResult(Result result) : Result(result) {}
+
+  ResetLockoutResult(int32_t error) : Result(error) {}
+
+  ResetLockoutResult& SetLockoutEvent(uint32_t lockout_event) {
+    result.output.data.third = lockout_event;
+    return *this;
+  }
+};
+
+class MigrateResult : public Result {
+ public:
+  MigrateResult(Result result) : Result(result) {}
+
+  MigrateResult(int32_t error) : Result(error) {}
+
+  MigrateResult& SetEmbeddingNum(uint32_t num) {
+    result.output.data.second = num;
+    return *this;
+  }
+
+  MigrateResult& SetMatch(bool m) {
+    result.output.data.third = m;
+    return *this;
+  }
+};
+
+class AuthenticateResult : public Result {
+ public:
+  AuthenticateResult(Result result, Token token)
+      : Result(result), token(token) {}
+
+  AuthenticateResult(int32_t error) : Result(error) {}
+
+  AuthenticateResult& SetMatch(bool m) {
+    result.output.data.first = m;
+    return *this;
+  }
+
+  AuthenticateResult& SetLockoutEvent(uint32_t lockout_event) {
+    result.output.data.third = lockout_event;
+    return *this;
+  }
+
+  Token GetToken() { return token; }
+
+ protected:
+  Token token;
+};
+
+static void EXPECT_AEQ(AuthenticateResult r1, AuthenticateResult r2) {
+  EXPECT_REQ(r1, r2);
+
+  fa_token_t observed = r1.GetToken().GetRaw();
+  fa_token_t expected = r2.GetToken().GetRaw();
+
+  EXPECT_EQ(observed.challenge, expected.challenge);
+  EXPECT_EQ(observed.user_id, expected.user_id);
+  EXPECT_EQ(observed.authenticator_id, expected.authenticator_id);
 }
 
-static void EXPECT_RESULT_EQ(const fa_result_t& r1, const fa_result_t& r2)
-{
-  EXPECT_EQ(r1.version, r2.version);
-  EXPECT_EQ(r1.session_id, r2.session_id);
-  EXPECT_EQ(r1.error, r2.error);
-  EXPECT_EQ(r1.output.data.first, r2.output.data.first);
-  EXPECT_EQ(r1.output.data.second, r2.output.data.second);
-  EXPECT_EQ(r1.output.data.third, r2.output.data.third);
-  EXPECT_EQ(r1.complete, r2.complete);
-  EXPECT_EQ(r1.crc, r2.crc);
-}
+class Transaction {
+ public:
+  Transaction(Task task, Embedding embed, Token token)
+      : input_task(task), input_embed(embed), input_token(token) {}
 
-const fa_result_t FaceAuthTest::RunTask(const fa_task_t task,
-                                        const fa_embedding_t* embed,
-                                        const fa_token_t* token) {
-  vector<uint8_t> buffer_rx;
-  buffer_rx.resize(512);
+  Transaction& Finalize() {
+    input_task.Finalize();
+    input_embed.Finalize();
+    return *this;
+  }
 
-  vector<uint8_t> buffer_tx = Task2Buffer(task, embed, token);
-  FaceAuthTest::client->CallApp(APP_ID_FACEAUTH_TEST, 1, buffer_tx, &buffer_rx);
+  Transaction& Run() {
+    vector<uint8_t> task = input_task.ToBuffer();
+    vector<uint8_t> embed = input_embed.ToBuffer();
+    vector<uint8_t> token = input_token.ToBuffer();
 
-  return Buffer2Result(buffer_rx);
-}
+    vector<uint8_t> buffer_rx;
+    buffer_rx.resize(512);
 
-void FaceAuthTest::Run(const fa_result_t expected, const fa_task_t task,
-                       const fa_embedding_t* embed, const fa_token_t* token) {
-  EXPECT_RESULT_EQ(expected, RunTask(task, embed, token));
-}
+    vector<uint8_t> buffer_tx;
+    buffer_tx.insert(buffer_tx.end(), task.begin(), task.end());
+    buffer_tx.insert(buffer_tx.end(), embed.begin(), embed.end());
+    buffer_tx.insert(buffer_tx.end(), token.begin(), token.end());
+
+    FaceAuthTest::client->CallApp(APP_ID_FACEAUTH_TEST, 1, buffer_tx,
+                                  &buffer_rx);
+
+    struct fa_output_t {
+      fa_result_t result;
+      fa_token_t token;
+    } __attribute__((packed));
+
+    fa_output_t output =
+        *(reinterpret_cast<struct fa_output_t*>(buffer_rx.data()));
+
+    output_result = output.result;
+    output_token = output.token;
+
+    return *this;
+  }
+
+  Result GetResult() { return Result(output_result); }
+  Token GetToken() { return Token(output_token); }
+
+ private:
+  Task input_task;
+  Embedding input_embed;
+  Token input_token;
+  fa_result_t output_result;
+  fa_token_t output_token;
+};
+
+class Device {
+ public:
+  static Result Erase(uint32_t profile_id) {
+    return Transaction(Task(profile_id, FACEAUTH_CMD_ERASE), Embedding(),
+                       Token())
+        .Finalize()
+        .Run()
+        .GetResult();
+  }
+
+  static Result GenerateChallenge() {
+    Result ret =
+        Transaction(Task(0, FACEAUTH_CMD_GET_CHALLENGE), Embedding(), Token())
+            .Finalize()
+            .Run()
+            .GetResult();
+    memcpy(&challenge, ret.GetRaw().output.challenge, sizeof(challenge));
+    return ret;
+  }
+
+  static Result RevokeChallenge() {
+    return Transaction(Task(0, FACEAUTH_CMD_REVOKE_CHALLENGE), Embedding(),
+                       Token())
+        .Finalize()
+        .Run()
+        .GetResult();
+  }
+
+  static uint64_t GetChallenge() { return challenge; }
+
+ private:
+  static uint64_t challenge;
+};
+
+uint64_t Device::challenge;
 
 void FaceAuthTest::SetUp() {
-  user_ids.clear();
-  for (int i = 0; i <= MAX_NUM_PROFILES; ++i) {
-    user_ids.push_back(rand());
-  }
-
-  fa_task_t task;
-  fa_embedding_t embed;
-  fa_token_t token;
-  memset(&task, 0, sizeof(task));
-  memset(&embed, 0, sizeof(embed));
-  memset(&token, 0, sizeof(token));
-  RunTask(task, &embed, &token);
-
-  for (int profiles = 1; profiles <= MAX_NUM_PROFILES; ++profiles) {
-    Run(MakeResult(0x0, FACEAUTH_SUCCESS),
-        MakeTask(0x0, profiles, FACEAUTH_CMD_ERASE));
-  }
+  for (int i = 0; i < MAX_NUM_PROFILES; ++i)
+    EXPECT_REQ(Device::Erase(i + 1), Result(FACEAUTH_SUCCESS));
 }
 
-TEST_F(FaceAuthTest, SimpleMatchMismatchTest) {
-  uint64_t session_id = 0xFACE000011110000ull;
-  session_id++;
-
-  Run(MakeResult(session_id, FACEAUTH_SUCCESS, FACEAUTH_NOMATCH),
-      MakeTask(session_id, 0x1, FACEAUTH_CMD_COMP), MakeEmbedding(0x11));
-  session_id++;
-  Run(MakeResult(session_id, FACEAUTH_SUCCESS, 0, 0, 0x1),
-      MakeTask(session_id, 0x1, FACEAUTH_CMD_ENROLL), MakeEmbedding(0x11));
-  session_id++;
-  Run(MakeResult(session_id, FACEAUTH_SUCCESS, FACEAUTH_MATCH),
-      MakeTask(session_id, 0x1, FACEAUTH_CMD_COMP), MakeEmbedding(0x11));
-  session_id++;
-  Run(MakeResult(session_id, FACEAUTH_SUCCESS),
-      MakeTask(session_id, 0x1, FACEAUTH_CMD_ERASE));
-  session_id++;
-  Run(MakeResult(session_id, FACEAUTH_SUCCESS, FACEAUTH_NOMATCH),
-      MakeTask(session_id, 0x1, FACEAUTH_CMD_COMP), MakeEmbedding(0x11));
+TEST_F(FaceAuthTest, OldFirmwareVersionShouldError) {
+  EXPECT_REQ(Transaction(Task(PROFILE_1, FACEAUTH_CMD_ERASE,
+                              FACEAUTH_MIN_ABH_VERSION - 0x100),
+                         Embedding(), Token())
+                 .Finalize()
+                 .Run()
+                 .GetResult(),
+             Result(FACEAUTH_ERR_VERSION));
 }
 
-void FaceAuthTest::FullMatchMismatchTest(uint32_t profile1, uint32_t profile2,
-                                         uint32_t slot1, uint32_t slot2) {
-  uint64_t session_id = 0xFACE000022220000ull;
-  for (uint32_t i = 0; i < 20; ++i) {
-    session_id++;
-    Run(MakeResult(session_id, FACEAUTH_SUCCESS, 0, 0, profile1),
-        MakeTask(session_id, profile1, FACEAUTH_CMD_ENROLL),
-        MakeEmbedding((i == slot1) ? 0x11 : 0x0));
-
-    session_id++;
-    Run(MakeResult(session_id, FACEAUTH_SUCCESS, 0, 0, profile2),
-        MakeTask(session_id, profile2, FACEAUTH_CMD_ENROLL),
-        MakeEmbedding((i == slot2) ? 0xAA : 0x0));
-  }
-
-  session_id++;
-  Run(MakeResult(session_id, FACEAUTH_SUCCESS, FACEAUTH_MATCH),
-      MakeTask(session_id, profile1, FACEAUTH_CMD_COMP), MakeEmbedding(0x11));
-  session_id++;
-  Run(MakeResult(session_id, FACEAUTH_SUCCESS, FACEAUTH_NOMATCH),
-      MakeTask(session_id, profile1, FACEAUTH_CMD_COMP), MakeEmbedding(0xAA));
-  session_id++;
-  Run(MakeResult(session_id, FACEAUTH_SUCCESS, FACEAUTH_NOMATCH),
-      MakeTask(session_id, profile2, FACEAUTH_CMD_COMP), MakeEmbedding(0x11));
-  session_id++;
-  Run(MakeResult(session_id, FACEAUTH_SUCCESS, FACEAUTH_MATCH),
-      MakeTask(session_id, profile2, FACEAUTH_CMD_COMP), MakeEmbedding(0xAA));
+TEST_F(FaceAuthTest, NewFirmwareVersionShouldNotError) {
+  EXPECT_REQ(Transaction(Task(PROFILE_1, FACEAUTH_CMD_ERASE,
+                              FACEAUTH_MIN_ABH_VERSION + 0x100),
+                         Embedding(), Token())
+                 .Finalize()
+                 .Run()
+                 .GetResult(),
+             Result(FACEAUTH_SUCCESS));
 }
 
-TEST_F(FaceAuthTest, SFSFullTest) {
-  uint64_t session_id = 0xFACE000033330000ull;
-  for (int i = 0; i < 20; ++i) {
-    session_id++;
-    Run(MakeResult(session_id, FACEAUTH_SUCCESS, 0, 0, 0x1),
-        MakeTask(session_id, 0x1, FACEAUTH_CMD_ENROLL), MakeEmbedding(0x0));
-  }
-
-  session_id++;
-  Run(MakeResult(session_id, FACEAUTH_ERR_SFS_FULL, 0, 0, 0x1),
-      MakeTask(session_id, 0x1, FACEAUTH_CMD_ENROLL), MakeEmbedding(0x0));
+TEST_F(FaceAuthTest, TaskCRCErrorShouldBeDetected) {
+  EXPECT_REQ(
+      Transaction(Task(PROFILE_1, FACEAUTH_CMD_ERASE), Embedding(), Token())
+          .Run()
+          .GetResult(),
+      Result(FACEAUTH_ERR_CRC));
 }
 
-void FaceAuthTest::LockProfileTest(uint32_t profile1) {
-  uint64_t session_id = 0xFACE000044440000ull;
-
-  for (int i = 0; i < 4; ++i) {
-    session_id++;
-    Run(MakeResult(session_id, FACEAUTH_SUCCESS, FACEAUTH_NOMATCH),
-        MakeTask(session_id, profile1, FACEAUTH_CMD_COMP), MakeEmbedding(0x0));
-  }
-
-  session_id++;
-  Run(MakeResult(session_id, FACEAUTH_SUCCESS, FACEAUTH_NOMATCH, 0,
-                 FACEAUTH_LOCKOUT_ENFORCED),
-      MakeTask(session_id, profile1, FACEAUTH_CMD_COMP), MakeEmbedding(0x0));
-
-  session_id++;
-  Run(MakeResult(session_id, FACEAUTH_ERR_THROTTLE, FACEAUTH_NOMATCH),
-      MakeTask(session_id, profile1, FACEAUTH_CMD_COMP), MakeEmbedding(0x0));
+TEST_F(FaceAuthTest, ZeroChallengeShouldError) {
+  EXPECT_REQ(Transaction(Task(PROFILE_1, FACEAUTH_CMD_ENROLL),
+                         Embedding(EMBEDDING_VECTOR_1, 1), Token())
+                 .Finalize()
+                 .Run()
+                 .GetResult(),
+             Result(FACEAUTH_ERR_INVALID_CHALLENGE));
+  EXPECT_REQ(Transaction(Task(PROFILE_1, FACEAUTH_CMD_SET_FEATURE),
+                         Embedding(EMBEDDING_VECTOR_1, 1), Token())
+                 .Finalize()
+                 .Run()
+                 .GetResult(),
+             Result(FACEAUTH_ERR_INVALID_CHALLENGE));
+  EXPECT_REQ(Transaction(Task(PROFILE_1, FACEAUTH_CMD_CLR_FEATURE),
+                         Embedding(EMBEDDING_VECTOR_1, 1), Token())
+                 .Finalize()
+                 .Run()
+                 .GetResult(),
+             Result(FACEAUTH_ERR_INVALID_CHALLENGE));
+  EXPECT_REQ(Transaction(Task(PROFILE_1, FACEAUTH_CMD_RESET_LOCKOUT),
+                         Embedding(EMBEDDING_VECTOR_1, 1), Token())
+                 .Finalize()
+                 .Run()
+                 .GetResult(),
+             Result(FACEAUTH_ERR_INVALID_CHALLENGE));
 }
 
-bool FaceAuthTest::IsProfileLocked(uint32_t profile1) {
-  uint64_t session_id = 0xFACE000066660000ull;
+TEST_F(FaceAuthTest, InvalidChallengeShouldError) {
+  Result generate_result = Device::GenerateChallenge();
+  EXPECT_REQ(generate_result,
+             Result(FACEAUTH_SUCCESS).SetChallenge(Device::GetChallenge()));
 
-  const fa_result_t observed =
-      RunTask(MakeTask(session_id, profile1, FACEAUTH_CMD_GET_USER_INFO));
-  const fa_result_t expected =
-      MakeResult(session_id, FACEAUTH_SUCCESS, 0, observed.output.data.second);
-  EXPECT_RESULT_EQ(expected, observed);
-  return observed.output.data.second;
+  EXPECT_REQ(Transaction(Task(PROFILE_1, FACEAUTH_CMD_ENROLL),
+                         Embedding(EMBEDDING_VECTOR_1, 1), Token(rand(), 0, 0))
+                 .Finalize()
+                 .Run()
+                 .GetResult(),
+             Result(FACEAUTH_ERR_INVALID_CHALLENGE));
+  EXPECT_REQ(Transaction(Task(PROFILE_1, FACEAUTH_CMD_SET_FEATURE),
+                         Embedding(EMBEDDING_VECTOR_1, 1), Token(rand(), 0, 0))
+                 .Finalize()
+                 .Run()
+                 .GetResult(),
+             Result(FACEAUTH_ERR_INVALID_CHALLENGE));
+  EXPECT_REQ(Transaction(Task(PROFILE_1, FACEAUTH_CMD_CLR_FEATURE),
+                         Embedding(EMBEDDING_VECTOR_1, 1), Token(rand(), 0, 0))
+                 .Finalize()
+                 .Run()
+                 .GetResult(),
+             Result(FACEAUTH_ERR_INVALID_CHALLENGE));
+  EXPECT_REQ(Transaction(Task(PROFILE_1, FACEAUTH_CMD_RESET_LOCKOUT),
+                         Embedding(EMBEDDING_VECTOR_1, 1), Token(rand(), 0, 0))
+                 .Finalize()
+                 .Run()
+                 .GetResult(),
+             Result(FACEAUTH_ERR_INVALID_CHALLENGE));
 }
 
-void FaceAuthTest::UnlockProfileTest(uint32_t profile1) {
-  uint64_t session_id = 0xFACE000077770000ull;
-  session_id++;
-  Run(MakeResult(session_id, FACEAUTH_SUCCESS, 0, 0, FACEAUTH_LOCKOUT_REMOVED),
-      MakeTask(session_id, profile1, FACEAUTH_CMD_RESET_LOCKOUT));
+TEST_F(FaceAuthTest, EmbeddingCRCErrorShouldBeDetected) {
+  EXPECT_REQ(Transaction(Task(PROFILE_1, FACEAUTH_CMD_COMP).Finalize(),
+                         Embedding(EMBEDDING_VECTOR_1, 1), Token())
+                 .Run()
+                 .GetResult(),
+             Result(FACEAUTH_ERR_CRC));
+  EXPECT_REQ(Transaction(Task(PROFILE_1, FACEAUTH_CMD_MIGRATE).Finalize(),
+                         Embedding(EMBEDDING_VECTOR_1, 1), Token())
+                 .Run()
+                 .GetResult(),
+             Result(FACEAUTH_ERR_CRC));
+
+  Result generate_result = Device::GenerateChallenge();
+  EXPECT_REQ(generate_result,
+             Result(FACEAUTH_SUCCESS).SetChallenge(Device::GetChallenge()));
+  EXPECT_REQ(Transaction(Task(PROFILE_1, FACEAUTH_CMD_ENROLL).Finalize(),
+                         Embedding(EMBEDDING_VECTOR_1, 1),
+                         Token(Device::GetChallenge(), 0, 0))
+                 .Run()
+                 .GetResult(),
+             Result(FACEAUTH_ERR_CRC));
 }
 
-TEST_F(FaceAuthTest, ExhaustiveLockoutTest) {
-  EXPECT_EQ(IsProfileLocked(1), false);
-  EXPECT_EQ(IsProfileLocked(4), false);
-  EXPECT_EQ(IsProfileLocked(5), false);
-  EXPECT_EQ(IsProfileLocked(6), false);
+TEST_F(FaceAuthTest, InvalidCommandShouldError) {
+  Task task(PROFILE_1, 0);
+  Embedding embed;
+  Token token;
 
-  LockProfileTest(1);
-  LockProfileTest(5);
-  LockProfileTest(6);
-
-  EXPECT_EQ(IsProfileLocked(1), true);
-  EXPECT_EQ(IsProfileLocked(4), false);
-  EXPECT_EQ(IsProfileLocked(5), true);
-  EXPECT_EQ(IsProfileLocked(6), true);
-
-  UnlockProfileTest(1);
-  UnlockProfileTest(6);
-
-  EXPECT_EQ(IsProfileLocked(1), false);
-  EXPECT_EQ(IsProfileLocked(4), false);
-  EXPECT_EQ(IsProfileLocked(5), true);
-  EXPECT_EQ(IsProfileLocked(6), false);
+  EXPECT_REQ(Transaction(task, embed, token).Finalize().Run().GetResult(),
+             Result(FACEAUTH_ERR_INVALID_ARGS));
 }
 
 TEST_F(FaceAuthTest, ValidProfileIDTest) {
-  uint64_t session_id = 0xFACE000088880000ull;
-  session_id++;
-  Run(MakeResult(session_id, FACEAUTH_ERR_INVALID_ARGS),
-      MakeTask(session_id, 0, FACEAUTH_CMD_ENROLL), MakeEmbedding(0x0));
+  Embedding embed;
+  Token token;
+
+  EXPECT_REQ(Transaction(Task(0, FACEAUTH_CMD_GET_USER_INFO), embed, token)
+                 .Finalize()
+                 .Run()
+                 .GetResult(),
+             Result(FACEAUTH_ERR_INVALID_ARGS));
 
   for (int i = 1; i <= MAX_NUM_PROFILES; ++i) {
-    session_id++;
-    Run(MakeResult(session_id, FACEAUTH_SUCCESS, 0, 0, i),
-        MakeTask(session_id, i, FACEAUTH_CMD_ENROLL), MakeEmbedding(0x0));
+    EXPECT_REQ(Transaction(Task(i, FACEAUTH_CMD_GET_USER_INFO), embed, token)
+                   .Finalize()
+                   .Run()
+                   .GetResult(),
+               Result(FACEAUTH_SUCCESS));
   }
 
-  session_id++;
-  Run(MakeResult(session_id, FACEAUTH_ERR_INVALID_ARGS),
-      MakeTask(session_id, MAX_NUM_PROFILES + 1, FACEAUTH_CMD_ENROLL));
+  EXPECT_REQ(Transaction(Task(MAX_NUM_PROFILES + 1, FACEAUTH_CMD_GET_USER_INFO),
+                         embed, token)
+                 .Finalize()
+                 .Run()
+                 .GetResult(),
+             Result(FACEAUTH_ERR_INVALID_ARGS));
 }
 
-TEST_F(FaceAuthTest, InvalidCommandTest) {
-  uint64_t session_id = 0xFACE000099990000ull;
-  session_id++;
-  Run(MakeResult(session_id, FACEAUTH_ERR_INVALID_ARGS),
-      MakeTask(session_id, 0x1, 0x0));
-}
+class User {
+ public:
+  User(uint8_t embed_base) : embed_base(embed_base) {
+    user_id = rand();
+    user_id <<= 32;
+    user_id += rand();
+  }
 
-TEST_F(FaceAuthTest, SimpleFeatureTest) {
-  uint64_t session_id = 0xFACE0000AAAA0000ull;
-  uint32_t index = 0;
-  uint32_t feature_msk[MAX_NUM_PROFILES] = {0};
+  User& SetEmbeddingVersion(uint8_t version) {
+    embed_version = version;
+    return *this;
+  }
 
-  for (int k = 0; k < 5; ++k) {
-    for (int i = 1; i <= MAX_NUM_PROFILES; ++i) {
-      session_id++;
-      Run(MakeResult(session_id, FACEAUTH_SUCCESS, feature_msk[i - 1]),
-          MakeTask(session_id, i, FACEAUTH_CMD_GET_USER_INFO));
+  User& SetEmbeddingBase(uint8_t base) {
+    embed_base = base;
+    return *this;
+  }
+
+  User& SetUserID(uint64_t sid) {
+    user_id = sid;
+    return *this;
+  }
+
+  Result GetProfileInfo() {
+    return Transaction(Task(0, FACEAUTH_CMD_GET_PROFILE_INFO), Embedding(),
+                       Token(0, user_id, 0))
+        .Finalize()
+        .Run()
+        .GetResult();
+  }
+
+  Result GetUserInfo(uint32_t profile_id) {
+    return Transaction(Task(profile_id, FACEAUTH_CMD_GET_USER_INFO),
+                       Embedding(), Token())
+        .Finalize()
+        .Run()
+        .GetResult();
+  }
+
+  Result Enroll(uint32_t profile_id) {
+    Result generate_result = Device::GenerateChallenge();
+    EXPECT_REQ(generate_result,
+               Result(FACEAUTH_SUCCESS).SetChallenge(Device::GetChallenge()));
+
+    Result ret = Transaction(Task(profile_id, FACEAUTH_CMD_ENROLL),
+                             Embedding(embed_base, embed_version),
+                             Token(Device::GetChallenge(), user_id, 0))
+                     .Finalize()
+                     .Run()
+                     .GetResult();
+
+    uint64_t auth_id_temp = ret.GetChallenge();
+    memcpy(&auth_id, &auth_id_temp, sizeof(auth_id));
+    EXPECT_REQ(Device::RevokeChallenge(), Result(FACEAUTH_SUCCESS));
+
+    return ret;
+  }
+
+  MigrateResult Migrate(uint32_t profile_id, bool should_migrate) {
+    return Transaction(
+               Task(profile_id, FACEAUTH_CMD_MIGRATE).SetFirst(should_migrate),
+               Embedding(embed_base, embed_version), Token(0, user_id, 0))
+        .Finalize()
+        .Run()
+        .GetResult();
+  }
+
+  AuthenticateResult Authenticate(uint32_t profile_id) {
+    operation_id = rand();
+    operation_id <<= 32;
+    operation_id += rand();
+
+    Transaction t =
+        Transaction(
+            Task(profile_id, FACEAUTH_CMD_COMP).SetChallenge(operation_id),
+            Embedding(embed_base, embed_version), Token())
+            .Finalize()
+            .Run();
+
+    return AuthenticateResult(t.GetResult(), t.GetToken());
+  }
+
+  ResetLockoutResult ResetLockout(uint32_t profile_id) {
+    Result generate_result = Device::GenerateChallenge();
+    EXPECT_REQ(generate_result,
+               Result(FACEAUTH_SUCCESS).SetChallenge(Device::GetChallenge()));
+
+    Result ret =
+        Transaction(Task(profile_id, FACEAUTH_CMD_RESET_LOCKOUT), Embedding(),
+                    Token(Device::GetChallenge(), user_id, 0))
+            .Finalize()
+            .Run()
+            .GetResult();
+
+    EXPECT_REQ(Device::RevokeChallenge(), Result(FACEAUTH_SUCCESS));
+
+    return ret;
+  }
+
+  bool IsProfileLocked(uint32_t profile_id) {
+    return (GetUserInfo(profile_id).GetSecond() > 0);
+  }
+
+  void LockProfile(uint32_t profile_id) {
+    uint8_t original_embed_base = embed_base;
+    embed_base = EMBEDDING_VECTOR_NULL;
+    /* Fail Authentication 4 times */
+    for (int i = 0; i < 4; ++i) {
+      EXPECT_REQ(Authenticate(profile_id),
+                 AuthenticateResult(FACEAUTH_SUCCESS).SetMatch(false));
     }
 
-    for (int i = 1; i <= MAX_NUM_PROFILES; ++i) {
-      session_id++;
-      Run(MakeResult(session_id, FACEAUTH_SUCCESS),
-          MakeTask(session_id, i, FACEAUTH_CMD_SET_FEATURE, (1 << index)));
-      feature_msk[i - 1] |= (1 << index);
+    /* Fifth Authentication failure should trigger lockout event */
+    EXPECT_REQ(Authenticate(profile_id),
+               AuthenticateResult(FACEAUTH_SUCCESS)
+                   .SetMatch(false)
+                   .SetLockoutEvent(FACEAUTH_LOCKOUT_ENFORCED));
+
+    embed_base = original_embed_base;
+  }
+
+  uint32_t GetFeature(uint32_t profile_id) {
+    return GetUserInfo(profile_id).GetFirst();
+  }
+
+  Result SetFeature(uint32_t profile_id, uint32_t feature) {
+    Result generate_result = Device::GenerateChallenge();
+    EXPECT_REQ(generate_result,
+               Result(FACEAUTH_SUCCESS).SetChallenge(Device::GetChallenge()));
+
+    Result ret =
+        Transaction(
+            Task(profile_id, FACEAUTH_CMD_SET_FEATURE).SetFirst(feature),
+            Embedding(), Token(Device::GetChallenge(), user_id, 0))
+            .Finalize()
+            .Run()
+            .GetResult();
+
+    EXPECT_REQ(Device::RevokeChallenge(), Result(FACEAUTH_SUCCESS));
+
+    return ret;
+  }
+
+  Result ClrFeature(uint32_t profile_id, uint32_t feature) {
+    Result generate_result = Device::GenerateChallenge();
+    EXPECT_REQ(generate_result,
+               Result(FACEAUTH_SUCCESS).SetChallenge(Device::GetChallenge()));
+
+    Result ret =
+        Transaction(
+            Task(profile_id, FACEAUTH_CMD_CLR_FEATURE).SetFirst(feature),
+            Embedding(), Token(Device::GetChallenge(), user_id, 0))
+            .Finalize()
+            .Run()
+            .GetResult();
+
+    EXPECT_REQ(Device::RevokeChallenge(), Result(FACEAUTH_SUCCESS));
+
+    return ret;
+  }
+
+  uint64_t GetOperationID() { return operation_id; }
+  uint64_t GetAuthID() { return auth_id; }
+  uint64_t GetUserID() { return user_id; }
+
+ private:
+  uint64_t operation_id;
+  uint64_t auth_id = 0;
+  uint64_t user_id;
+  uint8_t embed_base;
+  uint8_t embed_version;
+};
+
+class SuccessfulAuthenticateResult : public AuthenticateResult {
+ public:
+  SuccessfulAuthenticateResult(User u) : AuthenticateResult(FACEAUTH_SUCCESS) {
+    token = Token(u.GetOperationID(), u.GetUserID(), u.GetAuthID());
+    SetMatch(true);
+  }
+};
+
+TEST_F(FaceAuthTest, GetProfileInfoTest) {
+  vector<User> users;
+  for (int i = 0; i < MAX_NUM_PROFILES; ++i) {
+    users.push_back(User(EMBEDDING_VECTOR_1));
+    users[i].SetEmbeddingVersion(1);
+  }
+
+  users[0].SetUserID(0x1122334455667788);
+  users[1].SetUserID(0x1122334455667788);
+  users[3].SetUserID(0x1122334455667788);
+
+  for (int i = 0; i < MAX_NUM_PROFILES; ++i) users[i].Enroll(i + 1);
+
+  union {
+    uint8_t map[8] = {0};
+    uint64_t info;
+  };
+
+  for (int i = 0; i < MAX_NUM_PROFILES; ++i) {
+    for (int j = 0; j < MAX_NUM_PROFILES; ++j) {
+      map[i] |= ((users[i].GetUserID() == users[j].GetUserID()) << j);
+    }
+  }
+
+  for (int i = 0; i < MAX_NUM_PROFILES; ++i) {
+    map[MAX_NUM_PROFILES] = map[i];
+    EXPECT_REQ(users[i].GetProfileInfo(),
+               Result(FACEAUTH_SUCCESS).SetChallenge(info));
+    EXPECT_REQ(users[i].GetUserInfo(i + 1),
+               Result(FACEAUTH_SUCCESS).SetThird(map[i]));
+  }
+}
+
+TEST_F(FaceAuthTest, EnrollShouldOnlyAcceptSameUser) {
+  User user1(EMBEDDING_VECTOR_1);
+  user1.SetEmbeddingVersion(1);
+
+  User user2(EMBEDDING_VECTOR_NULL);
+  user2.SetEmbeddingVersion(1);
+
+  /* User 1 Enroll to Profile 1 */
+  Result enroll_result = user1.Enroll(PROFILE_1);
+  EXPECT_REQ(enroll_result,
+             Result(FACEAUTH_SUCCESS).SetChallenge(user1.GetAuthID()));
+
+  /* User 2 Enroll to Profile 1 should fail */
+  enroll_result = user2.Enroll(PROFILE_1);
+  EXPECT_REQ(enroll_result, Result(FACEAUTH_ERR_INVALID_USER_ID));
+
+  /* User 1 Enroll to Profile 1 should be successful */
+  enroll_result = user1.Enroll(PROFILE_1);
+  EXPECT_REQ(enroll_result,
+             Result(FACEAUTH_SUCCESS).SetChallenge(user1.GetAuthID()));
+}
+
+TEST_F(FaceAuthTest, EnrollAfterWipeShouldBeSuccessful) {
+  User user1(EMBEDDING_VECTOR_1);
+  user1.SetEmbeddingVersion(1);
+
+  User user2(EMBEDDING_VECTOR_1);
+  user2.SetEmbeddingVersion(1);
+
+  /* Enroll to Profile 1 */
+  Result enroll_result = user1.Enroll(PROFILE_1);
+  EXPECT_REQ(enroll_result,
+             Result(FACEAUTH_SUCCESS).SetChallenge(user1.GetAuthID()));
+
+  /* Profile 1 Authentication should now pass */
+  AuthenticateResult auth_result = user1.Authenticate(PROFILE_1);
+  EXPECT_AEQ(auth_result, SuccessfulAuthenticateResult(user1));
+
+  ASSERT_TRUE(nugget_tools::WipeUserData(client.get()));
+
+  /* Enroll to Profile 1 */
+  enroll_result = user2.Enroll(PROFILE_1);
+  EXPECT_REQ(enroll_result,
+             Result(FACEAUTH_SUCCESS).SetChallenge(user2.GetAuthID()));
+
+  /* Profile 1 Authentication should now pass */
+  auth_result = user2.Authenticate(PROFILE_1);
+  EXPECT_AEQ(auth_result, SuccessfulAuthenticateResult(user2));
+}
+
+TEST_F(FaceAuthTest, ValidUserIDCheck) {
+  User user1(EMBEDDING_VECTOR_1);
+  user1.SetEmbeddingVersion(1);
+
+  User user2(EMBEDDING_VECTOR_1);
+  user2.SetEmbeddingVersion(1);
+
+  /* User 1 Enroll to Profile 1 */
+  Result enroll_result = user1.Enroll(PROFILE_1);
+  EXPECT_REQ(enroll_result,
+             Result(FACEAUTH_SUCCESS).SetChallenge(user1.GetAuthID()));
+
+  /* Reset Lockout User ID Check */
+  EXPECT_REQ(user1.ResetLockout(PROFILE_1), Result(FACEAUTH_SUCCESS));
+  EXPECT_REQ(user2.ResetLockout(PROFILE_1),
+             Result(FACEAUTH_ERR_INVALID_USER_ID));
+  /* Set Feature User ID Check */
+  EXPECT_REQ(user1.SetFeature(PROFILE_1, 0), Result(FACEAUTH_SUCCESS));
+  EXPECT_REQ(user2.SetFeature(PROFILE_1, 0),
+             Result(FACEAUTH_ERR_INVALID_USER_ID));
+  /* Clear Feature User ID Check */
+  EXPECT_REQ(user1.ClrFeature(PROFILE_1, 0), Result(FACEAUTH_SUCCESS));
+  EXPECT_REQ(user2.ClrFeature(PROFILE_1, 0),
+             Result(FACEAUTH_ERR_INVALID_USER_ID));
+}
+
+TEST_F(FaceAuthTest, EnrollMismatchVersionShouldFail) {
+  User user1(EMBEDDING_VECTOR_1);
+  user1.SetEmbeddingVersion(1);
+
+  /* Enroll using version 1 to profile 1 */
+  Result enroll_result = user1.Enroll(PROFILE_1);
+  EXPECT_REQ(enroll_result,
+             Result(FACEAUTH_SUCCESS).SetChallenge(user1.GetAuthID()));
+
+  /* Enroll using version 2 to the same profile slot should fail */
+  EXPECT_REQ(user1.SetEmbeddingVersion(2).Enroll(PROFILE_1),
+             Result(FACEAUTH_ERR_EMBEDDING_VERSION));
+}
+
+TEST_F(FaceAuthTest, SFSFullTest) {
+  User user1(EMBEDDING_VECTOR_1);
+  user1.SetEmbeddingVersion(1);
+
+  /* Enroll to Profile1 20 times */
+  for (int i = 0; i < 20; ++i) {
+    Result enroll_result = user1.Enroll(PROFILE_1);
+    EXPECT_REQ(enroll_result,
+               Result(FACEAUTH_SUCCESS).SetChallenge(user1.GetAuthID()));
+  }
+
+  /* Enrolling one more time should fail */
+  EXPECT_REQ(user1.Enroll(PROFILE_1), Result(FACEAUTH_ERR_SFS_FULL));
+}
+
+TEST_F(FaceAuthTest, SimpleMatchMismatchTest) {
+  User user1(EMBEDDING_VECTOR_1);
+  user1.SetEmbeddingVersion(1);
+
+  /* Profile 1 is empty: authentication will fail */
+  EXPECT_REQ(user1.Authenticate(PROFILE_1),
+             AuthenticateResult(FACEAUTH_SUCCESS).SetMatch(false));
+
+  /* Enroll to Profile 1 */
+  Result enroll_result = user1.Enroll(PROFILE_1);
+  EXPECT_REQ(enroll_result,
+             Result(FACEAUTH_SUCCESS).SetChallenge(user1.GetAuthID()));
+
+  /* Profile 1 Authentication should now pass */
+  AuthenticateResult auth_result = user1.Authenticate(PROFILE_1);
+  EXPECT_AEQ(auth_result, SuccessfulAuthenticateResult(user1));
+
+  /* Erase Profile 1*/
+  EXPECT_REQ(Device::Erase(PROFILE_1), Result(FACEAUTH_SUCCESS));
+
+  /* Profile 1 is now empty again: authentication should fail */
+  EXPECT_REQ(user1.Authenticate(PROFILE_1),
+             AuthenticateResult(FACEAUTH_SUCCESS).SetMatch(false));
+}
+
+TEST_F(FaceAuthTest, EmbeddingMismatchVersionComparisonShouldError) {
+  User user1(EMBEDDING_VECTOR_1);
+  user1.SetEmbeddingVersion(1);
+
+  /* Enroll to Profile 1 */
+  Result enroll_result = user1.Enroll(PROFILE_1);
+  EXPECT_REQ(enroll_result,
+             Result(FACEAUTH_SUCCESS).SetChallenge(user1.GetAuthID()));
+
+  /* Upgrade Embedding Version for User 1 */
+  user1.SetEmbeddingVersion(2);
+
+  /* Authenticate should now return recalibration error */
+  EXPECT_REQ(user1.Authenticate(PROFILE_1), Result(FACEAUTH_ERR_RECALIBRATE));
+}
+
+TEST_F(FaceAuthTest, MigrateShouldPreventDowngrade) {
+  User user1(EMBEDDING_VECTOR_1);
+  user1.SetEmbeddingVersion(5);
+
+  /* User1: enroll to profile 1 */
+  Result enroll_result = user1.Enroll(PROFILE_1);
+  EXPECT_REQ(enroll_result,
+             Result(FACEAUTH_SUCCESS).SetChallenge(user1.GetAuthID()));
+
+  /* User1: authenticate successfully to profile 1 */
+  AuthenticateResult auth_result = user1.Authenticate(PROFILE_1);
+  EXPECT_AEQ(auth_result, SuccessfulAuthenticateResult(user1));
+
+  /* User1: downgrade and attempt to migrate to profile 3 */
+  EXPECT_REQ(user1.SetEmbeddingVersion(4).Migrate(PROFILE_3, 0),
+             Result(FACEAUTH_ERR_EMBEDDING_DOWNGRADE));
+}
+
+TEST_F(FaceAuthTest, MigrateShouldPreventMismatchVersion) {
+  User user1(EMBEDDING_VECTOR_1);
+  user1.SetEmbeddingVersion(5);
+
+  /* User1: enroll to profile 1 */
+  Result enroll_result = user1.Enroll(PROFILE_1);
+  EXPECT_REQ(enroll_result,
+             Result(FACEAUTH_SUCCESS).SetChallenge(user1.GetAuthID()));
+
+  /* User1: authenticate successfully to profile 1 */
+  AuthenticateResult auth_result = user1.Authenticate(PROFILE_1);
+  EXPECT_AEQ(auth_result, SuccessfulAuthenticateResult(user1));
+
+  /* User1: upgrade and migrate to profile 3 */
+  EXPECT_REQ(user1.SetEmbeddingVersion(6).Migrate(PROFILE_3, 1),
+             MigrateResult(FACEAUTH_SUCCESS).SetEmbeddingNum(1).SetMatch(true));
+  EXPECT_REQ(user1.SetEmbeddingVersion(7).Migrate(PROFILE_3, 0),
+             Result(FACEAUTH_ERR_EMBEDDING_VERSION));
+}
+
+TEST_F(FaceAuthTest, MigrateShouldCopyUserInfo) {
+  User user1(EMBEDDING_VECTOR_1);
+  user1.SetEmbeddingVersion(5);
+
+  /* User1: enroll to profile 1 */
+  Result enroll_result = user1.Enroll(PROFILE_1);
+  EXPECT_REQ(enroll_result,
+             Result(FACEAUTH_SUCCESS).SetChallenge(user1.GetAuthID()));
+
+  /* User1: set feature */
+  EXPECT_REQ(user1.SetFeature(PROFILE_1, 0xFACEDEAD), Result(FACEAUTH_SUCCESS));
+
+  /* User1: authenticate successfully to profile 1 */
+  AuthenticateResult auth_result = user1.Authenticate(PROFILE_1);
+  EXPECT_AEQ(auth_result, SuccessfulAuthenticateResult(user1));
+
+  /* User1: migrate to profile 3 */
+  EXPECT_REQ(user1.SetEmbeddingVersion(8).Migrate(PROFILE_3, 1),
+             MigrateResult(FACEAUTH_SUCCESS).SetEmbeddingNum(1).SetMatch(true));
+
+  EXPECT_EQ(0xFACEDEAD, user1.GetFeature(PROFILE_3));
+
+  /* User1: authenticate successfully using profile 3 */
+  auth_result = user1.Authenticate(PROFILE_3);
+  EXPECT_AEQ(auth_result, SuccessfulAuthenticateResult(user1));
+}
+
+TEST_F(FaceAuthTest, SimpleAuthenticateMigrateFlow) {
+  User user1(EMBEDDING_VECTOR_1);
+  user1.SetEmbeddingVersion(1);
+
+  User user2(EMBEDDING_VECTOR_1);
+  user2.SetEmbeddingVersion(1);
+
+  /* User1: enroll to profile 1 */
+  Result enroll_result = user1.Enroll(PROFILE_1);
+  EXPECT_REQ(enroll_result,
+             Result(FACEAUTH_SUCCESS).SetChallenge(user1.GetAuthID()));
+
+  /* User2: enroll to profile 2 */
+  enroll_result = user2.Enroll(PROFILE_2);
+  EXPECT_REQ(enroll_result,
+             Result(FACEAUTH_SUCCESS).SetChallenge(user2.GetAuthID()));
+
+  /* User1: authenticate successfully to profile 1 */
+  AuthenticateResult auth_result = user1.Authenticate(PROFILE_1);
+  EXPECT_AEQ(auth_result, SuccessfulAuthenticateResult(user1));
+
+  /* User1: migrate to profile 3 */
+  EXPECT_REQ(user1.Migrate(PROFILE_3, 0), MigrateResult(FACEAUTH_SUCCESS));
+
+  /* User2: authenticate successfully to profile 2 */
+  auth_result = user2.Authenticate(PROFILE_2);
+  EXPECT_AEQ(auth_result, SuccessfulAuthenticateResult(user2));
+
+  /* User2: migrate to profile 3 should fail */
+  EXPECT_REQ(user2.Migrate(PROFILE_3, 0),
+             MigrateResult(FACEAUTH_ERR_INVALID_USER_ID));
+
+  /* User2: migrate to profile 4 */
+  EXPECT_REQ(user2.Migrate(PROFILE_4, 1),
+             MigrateResult(FACEAUTH_SUCCESS).SetEmbeddingNum(1).SetMatch(true));
+  EXPECT_REQ(user2.Migrate(PROFILE_4, 1),
+             MigrateResult(FACEAUTH_SUCCESS).SetEmbeddingNum(2).SetMatch(true));
+}
+
+TEST_F(FaceAuthTest, FiveAuthenticationFailureSequenceTest) {
+  User user1(EMBEDDING_VECTOR_1);
+  user1.SetEmbeddingVersion(1);
+
+  User user2(EMBEDDING_VECTOR_NULL);
+  user2.SetEmbeddingVersion(1);
+
+  /* Enroll to Profile 1 */
+  Result enroll_result = user1.Enroll(PROFILE_1);
+  EXPECT_REQ(enroll_result,
+             Result(FACEAUTH_SUCCESS).SetChallenge(user1.GetAuthID()));
+
+  /* Fail Authentication 4 times */
+  for (int i = 0; i < 4; ++i) {
+    EXPECT_REQ(user2.Authenticate(PROFILE_1),
+               AuthenticateResult(FACEAUTH_SUCCESS).SetMatch(false));
+  }
+
+  /* Fifth Authentication failure should trigger lockout event */
+  EXPECT_REQ(user2.Authenticate(PROFILE_1),
+             AuthenticateResult(FACEAUTH_SUCCESS)
+                 .SetMatch(false)
+                 .SetLockoutEvent(FACEAUTH_LOCKOUT_ENFORCED));
+
+  /* User will be throttled for 30 seconds */
+  Result result = user2.GetUserInfo(PROFILE_1);
+  EXPECT_GE(result.GetSecond(), 25);
+  EXPECT_REQ(
+      result,
+      Result(FACEAUTH_SUCCESS).SetSecond(result.GetSecond()).SetThird(1));
+
+  /* Following Authentication attempt will be throttled */
+  EXPECT_REQ(user2.Authenticate(PROFILE_1),
+             AuthenticateResult(FACEAUTH_ERR_THROTTLE));
+
+  /* Reset Lockout should trigger lockout event */
+  EXPECT_REQ(user1.ResetLockout(PROFILE_1),
+             ResetLockoutResult(FACEAUTH_SUCCESS)
+                 .SetLockoutEvent(FACEAUTH_LOCKOUT_REMOVED));
+}
+
+TEST_F(FaceAuthTest, ExhaustiveLockoutTest) {
+  vector<User> users;
+
+  for (int i = 0; i < MAX_NUM_PROFILES; ++i) {
+    users.push_back(User(EMBEDDING_VECTOR_NULL).SetEmbeddingVersion(1));
+    Result enroll_result = users[i].Enroll(i + 1);
+    EXPECT_REQ(enroll_result,
+               Result(FACEAUTH_SUCCESS).SetChallenge(users[i].GetAuthID()));
+    EXPECT_EQ(users[i].IsProfileLocked(i + 1), false);
+  }
+
+  bool lock_test_vectors[6] = {true, false, false, false, true, true};
+  for (int i = 0; i < MAX_NUM_PROFILES; ++i) {
+    if (lock_test_vectors[i]) {
+      users[i].LockProfile(i + 1);
+    }
+  }
+
+  for (int i = 0; i < MAX_NUM_PROFILES; ++i) {
+    EXPECT_EQ(users[i].IsProfileLocked(i + 1), lock_test_vectors[i]);
+  }
+
+  bool unlock_test_vectors[6] = {true, false, false, false, true, false};
+  for (int i = 0; i < MAX_NUM_PROFILES; ++i) {
+    if (unlock_test_vectors[i]) {
+      users[i].ResetLockout(i + 1);
+    }
+  }
+
+  for (int i = 0; i < MAX_NUM_PROFILES; ++i) {
+    EXPECT_EQ(users[i].IsProfileLocked(i + 1),
+              lock_test_vectors[i] && !unlock_test_vectors[i]);
+  }
+}
+
+TEST_F(FaceAuthTest, ExhaustiveFeatureTest) {
+  vector<User> users;
+
+  uint32_t feature_msk[MAX_NUM_PROFILES] = {0};
+  for (int i = 0; i < MAX_NUM_PROFILES; ++i) {
+    users.push_back(User(EMBEDDING_VECTOR_NULL).SetEmbeddingVersion(1));
+    Result enroll_result = users[i].Enroll(i + 1);
+    EXPECT_REQ(enroll_result,
+               Result(FACEAUTH_SUCCESS).SetChallenge(users[i].GetAuthID()));
+  }
+
+  uint32_t index = 0;
+  for (int k = 0; k < 5; ++k) {
+    for (int i = 0; i < MAX_NUM_PROFILES; ++i)
+      EXPECT_EQ(users[i].GetFeature(i + 1), feature_msk[i]);
+
+    for (int i = 0; i < MAX_NUM_PROFILES; ++i) {
+      EXPECT_REQ(users[i].SetFeature(i + 1, 1 << index),
+                 Result(FACEAUTH_SUCCESS));
+      feature_msk[i] |= (1 << index);
       index++;
     }
   }
 
   index = 0;
-
   for (int k = 0; k < 5; ++k) {
-    for (int i = 1; i <= MAX_NUM_PROFILES; ++i) {
-      session_id++;
-      Run(MakeResult(session_id, FACEAUTH_SUCCESS, feature_msk[i - 1]),
-          MakeTask(session_id, i, FACEAUTH_CMD_GET_USER_INFO));
-    }
+    for (int i = 0; i < MAX_NUM_PROFILES; ++i)
+      EXPECT_EQ(users[i].GetFeature(i + 1), feature_msk[i]);
 
-    for (int i = 1; i <= MAX_NUM_PROFILES; ++i) {
-      session_id++;
-      Run(MakeResult(session_id, FACEAUTH_SUCCESS),
-          MakeTask(session_id, i, FACEAUTH_CMD_CLR_FEATURE, (1 << index)));
-      feature_msk[i - 1] &= ~(1 << index);
+    for (int i = 0; i < MAX_NUM_PROFILES; ++i) {
+      EXPECT_REQ(users[i].ClrFeature(i + 1, 1 << index),
+                 Result(FACEAUTH_SUCCESS));
+      feature_msk[i] &= ~(1 << index);
       index++;
     }
   }
 }
 
-TEST_F(FaceAuthTest, EmbeddingVersionTest) {
-  uint64_t session_id = 0xFACE0000BBBB0000ull;
-  session_id++;
-  Run(MakeResult(session_id, FACEAUTH_SUCCESS, 0, 0, 1),
-      MakeTask(session_id, 0x1, FACEAUTH_CMD_ENROLL), MakeEmbedding(0x11));
-  session_id++;
-  Run(MakeResult(session_id, FACEAUTH_SUCCESS, FACEAUTH_MATCH),
-      MakeTask(session_id, 0x1, FACEAUTH_CMD_COMP), MakeEmbedding(0x11));
-  session_id++;
-  Run(MakeResult(session_id, FACEAUTH_ERR_RECALIBRATE, FACEAUTH_NOMATCH),
-      MakeTask(session_id, 0x1, FACEAUTH_CMD_COMP), MakeEmbedding(0x11, 0x2));
-}
+static void FullMatchMismatchTest(uint32_t profile_1, uint32_t profile_2,
+                                  uint32_t slot_1, uint32_t slot_2) {
+  User user1(EMBEDDING_VECTOR_1);
+  user1.SetEmbeddingVersion(1);
 
-TEST_F(FaceAuthTest, FirmwareVersionTest) {
-  uint64_t session_id = 0xFACE0000CCCC0000ull;
-  session_id++;
-  Run(MakeResult(session_id, FACEAUTH_SUCCESS, 0, 0, 1),
-      MakeTask(session_id, 0x1, FACEAUTH_CMD_ENROLL), MakeEmbedding(0x11));
-  session_id++;
-  Run(MakeResult(session_id, FACEAUTH_ERR_VERSION, FACEAUTH_NOMATCH),
-      MakeTask(session_id, 0x1, FACEAUTH_CMD_COMP, 0, 0, 0x1),
-      MakeEmbedding(0x11));
-  session_id++;
-  Run(MakeResult(session_id, FACEAUTH_ERR_VERSION, FACEAUTH_NOMATCH),
-      MakeTask(session_id, 0x1, FACEAUTH_CMD_COMP, 0, 0,
-               FACEAUTH_MIN_ABH_VERSION - 0x100),
-      MakeEmbedding(0x11));
-  session_id++;
-  Run(MakeResult(session_id, FACEAUTH_SUCCESS, FACEAUTH_MATCH),
-      MakeTask(session_id, 0x1, FACEAUTH_CMD_COMP, 0, 0,
-               FACEAUTH_MIN_ABH_VERSION),
-      MakeEmbedding(0x11));
-  session_id++;
-  Run(MakeResult(session_id, FACEAUTH_SUCCESS, FACEAUTH_MATCH),
-      MakeTask(session_id, 0x1, FACEAUTH_CMD_COMP, 0, 0,
-               FACEAUTH_MIN_ABH_VERSION + 0x100),
-      MakeEmbedding(0x11));
-}
+  User user2(EMBEDDING_VECTOR_NULL);
+  user2.SetEmbeddingVersion(1);
 
-TEST_F(FaceAuthTest, GetProfileInfoTest) {
-  uint64_t session_id = 0xFACE0000CCCC0000ull;
-  user_ids[1] = user_ids[4];
-  user_ids[2] = user_ids[4];
+  for (uint32_t i = 0; i < 20; ++i) {
+    Result result;
+    if (i == slot_1)
+      result = user1.SetEmbeddingBase(EMBEDDING_VECTOR_1).Enroll(profile_1);
+    else
+      result = user1.SetEmbeddingBase(EMBEDDING_VECTOR_NULL).Enroll(profile_1);
+    EXPECT_REQ(result,
+               Result(FACEAUTH_SUCCESS).SetChallenge(user1.GetAuthID()));
 
-  for (int i = 1; i <= MAX_NUM_PROFILES; ++i) {
-    session_id++;
-    fa_token_t token = MakeToken(i);
-    Run(MakeResult(session_id, FACEAUTH_SUCCESS, 0, 0, i),
-        MakeTask(session_id, i, FACEAUTH_CMD_ENROLL), MakeEmbedding(0x11),
-        &token);
+    if (i == slot_2)
+      result = user2.SetEmbeddingBase(EMBEDDING_VECTOR_2).Enroll(profile_2);
+    else
+      result = user2.SetEmbeddingBase(EMBEDDING_VECTOR_NULL).Enroll(profile_2);
+    EXPECT_REQ(result,
+               Result(FACEAUTH_SUCCESS).SetChallenge(user2.GetAuthID()));
   }
 
-  union {
-    uint8_t map[8];
-    struct {
-      uint32_t first;
-      uint32_t second;
-    };
-  } expected = {0};
+  user1.SetEmbeddingBase(EMBEDDING_VECTOR_1);
+  user2.SetEmbeddingBase(EMBEDDING_VECTOR_2);
 
-  for (int i = 0; i < MAX_NUM_PROFILES; ++i) {
-    for (int j = 0; j < MAX_NUM_PROFILES; ++j) {
-      expected.map[i] |= ((user_ids[i + 1] == user_ids[j + 1]) << j);
-    }
-  }
-
-  for (int i = 1; i <= MAX_NUM_PROFILES; ++i) {
-    session_id++;
-    fa_token_t token = MakeToken(i);
-    expected.map[MAX_NUM_PROFILES] = expected.map[i - 1];
-    Run(MakeResult(session_id, FACEAUTH_SUCCESS, expected.first,
-                   expected.second),
-        MakeTask(session_id, 1, FACEAUTH_CMD_GET_PROFILE_INFO),
-        MakeEmbedding(0x11), &token);
-  }
-
-  for (int i = 1; i <= MAX_NUM_PROFILES; ++i) {
-    session_id++;
-    Run(MakeResult(session_id, FACEAUTH_SUCCESS, 0, 0, expected.map[i - 1]),
-        MakeTask(session_id, i, FACEAUTH_CMD_GET_USER_INFO),
-        MakeEmbedding(0x11));
-  }
-}
-
-TEST_F(FaceAuthTest, EnrollShouldOnlyAcceptSameUser) {
-  uint64_t session_id = 0xFACE0000DDDD0000ull;
-  session_id++;
-
-  session_id++;
-  fa_token_t token = MakeToken(1);
-  Run(MakeResult(session_id, FACEAUTH_SUCCESS, 0, 0, 0x1),
-      MakeTask(session_id, 0x1, FACEAUTH_CMD_ENROLL), MakeEmbedding(0x11),
-      &token);
-  session_id++;
-  token = MakeToken(2);
-  Run(MakeResult(session_id, FACEAUTH_ERR_INVALID_TOKEN),
-      MakeTask(session_id, 0x1, FACEAUTH_CMD_ENROLL), MakeEmbedding(0x11),
-      &token);
-  session_id++;
-  token = MakeToken(1);
-  Run(MakeResult(session_id, FACEAUTH_SUCCESS, 0, 0, 0x1),
-      MakeTask(session_id, 0x1, FACEAUTH_CMD_ENROLL), MakeEmbedding(0x11),
-      &token);
-}
-
-TEST_F(FaceAuthTest, MigrateShouldOnlyAcceptSameUser) {
-  uint64_t session_id = 0xFACE0000DDDD0000ull;
-  session_id++;
-
-  session_id++;
-  fa_token_t token = MakeToken(1);
-  Run(MakeResult(session_id, FACEAUTH_SUCCESS, 0x1),
-      MakeTask(session_id, 0x1, FACEAUTH_CMD_MIGRATE), MakeEmbedding(0x11),
-      &token);
-  session_id++;
-  token = MakeToken(2);
-  Run(MakeResult(session_id, FACEAUTH_ERR_INVALID_TOKEN),
-      MakeTask(session_id, 0x1, FACEAUTH_CMD_MIGRATE), MakeEmbedding(0x11),
-      &token);
-  session_id++;
-  token = MakeToken(1);
-  Run(MakeResult(session_id, FACEAUTH_SUCCESS, 0x1),
-      MakeTask(session_id, 0x1, FACEAUTH_CMD_MIGRATE), MakeEmbedding(0x11),
-      &token);
-}
-
-TEST_F(FaceAuthTest, SimpleAuthenticateMigrateFlow) {
-  uint64_t session_id = 0xFACE0000EEEE0000ull;
-  session_id++;
-
-  session_id++;
-  fa_token_t token = MakeToken(1);
-  Run(MakeResult(session_id, FACEAUTH_SUCCESS, 0, 0, 0x1),
-      MakeTask(session_id, 0x1, FACEAUTH_CMD_ENROLL), MakeEmbedding(0x11),
-      &token);
-  session_id++;
-  token = MakeToken(2);
-  Run(MakeResult(session_id, FACEAUTH_SUCCESS, 0, 0, 0x2),
-      MakeTask(session_id, 0x2, FACEAUTH_CMD_ENROLL), MakeEmbedding(0x11),
-      &token);
-
-  session_id++;
-  Run(MakeResult(session_id, FACEAUTH_SUCCESS, FACEAUTH_MATCH),
-      MakeTask(session_id, 0x1, FACEAUTH_CMD_COMP), MakeEmbedding(0x11));
-  session_id++;
-  Run(MakeResult(session_id, FACEAUTH_SUCCESS, 3, 0, FACEAUTH_NOMATCH),
-      MakeTask(session_id, 0x3, FACEAUTH_CMD_MIGRATE), MakeEmbedding(0x11));
-
-  session_id++;
-  Run(MakeResult(session_id, FACEAUTH_SUCCESS, FACEAUTH_MATCH),
-      MakeTask(session_id, 0x2, FACEAUTH_CMD_COMP), MakeEmbedding(0x11));
-  session_id++;
-  Run(MakeResult(session_id, FACEAUTH_ERR_INVALID_TOKEN),
-      MakeTask(session_id, 0x3, FACEAUTH_CMD_MIGRATE), MakeEmbedding(0x11));
-  session_id++;
-  Run(MakeResult(session_id, FACEAUTH_SUCCESS, 4, 1, FACEAUTH_MATCH),
-      MakeTask(session_id, 0x4, FACEAUTH_CMD_MIGRATE, 1), MakeEmbedding(0x11));
-  session_id++;
-  Run(MakeResult(session_id, FACEAUTH_SUCCESS, 4, 2, FACEAUTH_MATCH),
-      MakeTask(session_id, 0x4, FACEAUTH_CMD_MIGRATE, 1), MakeEmbedding(0x11));
-
-  session_id++;
-  Run(MakeResult(session_id, FACEAUTH_SUCCESS, FACEAUTH_NOMATCH),
-      MakeTask(session_id, 0x3, FACEAUTH_CMD_COMP), MakeEmbedding(0x11));
-  session_id++;
-  Run(MakeResult(session_id, FACEAUTH_SUCCESS, FACEAUTH_MATCH),
-      MakeTask(session_id, 0x4, FACEAUTH_CMD_COMP), MakeEmbedding(0x11));
+  AuthenticateResult auth_result = user1.Authenticate(profile_1);
+  EXPECT_AEQ(auth_result, SuccessfulAuthenticateResult(user1));
+  auth_result = user2.Authenticate(profile_1);
+  EXPECT_REQ(auth_result, AuthenticateResult(FACEAUTH_SUCCESS).SetMatch(false));
+  auth_result = user1.Authenticate(profile_2);
+  EXPECT_REQ(auth_result, AuthenticateResult(FACEAUTH_SUCCESS).SetMatch(false));
+  auth_result = user2.Authenticate(profile_2);
+  EXPECT_AEQ(auth_result, SuccessfulAuthenticateResult(user2));
 }
 
 TEST_F(FaceAuthTest, ExhaustiveMatchMismatchTest) {
-  FullMatchMismatchTest(1, 6, 0, 19);
-  FullMatchMismatchTest(2, 5, 1, 18);
-  FullMatchMismatchTest(3, 4, 2, 17);
+  FullMatchMismatchTest(PROFILE_1, PROFILE_6, 0, 19);
+  FullMatchMismatchTest(PROFILE_2, PROFILE_5, 1, 18);
+  FullMatchMismatchTest(PROFILE_3, PROFILE_4, 2, 17);
   SetUp();
-  FullMatchMismatchTest(2, 4, 3, 16);
-  FullMatchMismatchTest(1, 5, 4, 15);
-  FullMatchMismatchTest(3, 6, 5, 14);
+  FullMatchMismatchTest(PROFILE_2, PROFILE_4, 3, 16);
+  FullMatchMismatchTest(PROFILE_1, PROFILE_5, 4, 15);
+  FullMatchMismatchTest(PROFILE_3, PROFILE_6, 5, 14);
   SetUp();
-  FullMatchMismatchTest(3, 5, 6, 13);
-  FullMatchMismatchTest(1, 4, 7, 12);
-  FullMatchMismatchTest(2, 6, 8, 11);
+  FullMatchMismatchTest(PROFILE_3, PROFILE_5, 6, 13);
+  FullMatchMismatchTest(PROFILE_1, PROFILE_4, 7, 12);
+  FullMatchMismatchTest(PROFILE_2, PROFILE_6, 8, 11);
   SetUp();
-  FullMatchMismatchTest(3, 6, 9, 10);
+  FullMatchMismatchTest(PROFILE_3, PROFILE_6, 9, 10);
 }
 }