Add hex file parsing functionality to file_util.

This is to be used to parse the file containing the secret API key.

Also add tests for all of file_util.

Bug: 36537

Change-Id: Iba1fd2e2903363aa9973a0c1e1dcfd1b1442f0da
diff --git a/src/lib/util/BUILD.gn b/src/lib/util/BUILD.gn
index 102599a..33776a0 100644
--- a/src/lib/util/BUILD.gn
+++ b/src/lib/util/BUILD.gn
@@ -2,6 +2,7 @@
 # Use of this source code is governed by a BSD-style license that can be
 # found in the LICENSE file.
 
+import("//build/test.gni")
 import("//third_party/protobuf/proto_library.gni")
 
 source_set("clock") {
@@ -121,6 +122,34 @@
     "$cobalt_root/src:logging",
     "$cobalt_root/src/lib/crypto_util",
     "$cobalt_root/third_party/statusor",
+    "//third_party/abseil-cpp/absl/strings",
+  ]
+}
+
+test("file_util_test") {
+  testonly = true
+  sources = [
+    "file_util_test.cc",
+  ]
+  configs += [ "$cobalt_root:cobalt_config" ]
+  deps = [
+    ":file_util",
+    ":file_util_test_files",
+    "$cobalt_root/src:logging",
+    "//third_party/abseil-cpp/absl/strings",
+    "//third_party/googletest:gtest",
+  ]
+}
+
+copy("file_util_test_files") {
+  sources = [
+    "test_data/deadbeef",
+    "test_data/empty",
+    "test_data/hex_too_short",
+    "test_data/hex_wrong_char",
+  ]
+  outputs = [
+    "$root_out_dir/tests/cpp/file_util_test_files/{{source_file_part}}",
   ]
 }
 
@@ -238,6 +267,7 @@
     ":consistent_proto_store_test",
     ":datetime_util_test",
     ":encrypted_message_util_test",
+    ":file_util_test",
     ":protected_fields_test",
     ":sleeper_test",
   ]
diff --git a/src/lib/util/file_util.cc b/src/lib/util/file_util.cc
index 329ab4f..27b1a47 100644
--- a/src/lib/util/file_util.cc
+++ b/src/lib/util/file_util.cc
@@ -10,6 +10,8 @@
 #include "src/lib/util/status.h"
 #include "src/lib/util/status_codes.h"
 #include "src/logging.h"
+#include "third_party/abseil-cpp/absl/strings/ascii.h"
+#include "third_party/abseil-cpp/absl/strings/escaping.h"
 
 namespace cobalt::util {
 
@@ -65,4 +67,32 @@
   return read_file_result;
 }
 
+statusor::StatusOr<std::string> ReadHexFile(const std::string& file_path) {
+  auto read_file_result = ReadNonEmptyTextFile(file_path);
+  if (!read_file_result.ok()) {
+    return read_file_result;
+  }
+
+  auto hex = absl::StripAsciiWhitespace(read_file_result.ValueOrDie());
+  if (hex.size() % 2 == 1) {
+    return Status(FAILED_PRECONDITION, "Hex file has invalid size: " + file_path);
+  }
+
+  if (std::find_if_not(hex.begin(), hex.end(), absl::ascii_isxdigit) != hex.end()) {
+    return Status(FAILED_PRECONDITION, "Hex file has non-hex characters: " + file_path);
+  }
+
+  return absl::HexStringToBytes(hex);
+}
+
+std::string ReadHexFileOrDefault(const std::string& file_path, const std::string& default_string) {
+  auto read_hex_file_result = ReadHexFile(file_path);
+  if (!read_hex_file_result.ok()) {
+    VLOG(1) << "Failed to read hex file: " << read_hex_file_result.status().error_message();
+    return default_string;
+  }
+
+  return read_hex_file_result.ValueOrDie();
+}
+
 }  // namespace cobalt::util
diff --git a/src/lib/util/file_util.h b/src/lib/util/file_util.h
index 2da7b1e..9748494 100644
--- a/src/lib/util/file_util.h
+++ b/src/lib/util/file_util.h
@@ -15,6 +15,10 @@
 
 statusor::StatusOr<std::string> ReadNonEmptyTextFile(const std::string& file_path);
 
+statusor::StatusOr<std::string> ReadHexFile(const std::string& file_path);
+
+std::string ReadHexFileOrDefault(const std::string& file_path, const std::string& default_string);
+
 }  // namespace util
 }  // namespace cobalt
 #endif  // COBALT_SRC_LIB_UTIL_FILE_UTIL_H_
diff --git a/src/lib/util/file_util_test.cc b/src/lib/util/file_util_test.cc
new file mode 100644
index 0000000..f588c0d
--- /dev/null
+++ b/src/lib/util/file_util_test.cc
@@ -0,0 +1,137 @@
+// Copyright 2019 The Fuchsia 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 "src/lib/util/file_util.h"
+
+#include "glog/logging.h"
+#include "third_party/abseil-cpp/absl/strings/str_cat.h"
+#include "third_party/googletest/googletest/include/gtest/gtest.h"
+
+namespace {
+
+// Directory in which the test files can be found.
+const char* kTestFilesDir;
+
+// Test file names.
+const char* kDeadbeefFile = "deadbeef";
+const char* kEmptyFile = "empty";
+const char* kNonExistentFile = "non_existent";
+const char* kHexTooShortFile = "hex_too_short";
+const char* kHexWrongCharFile = "hex_wrong_char";
+
+const char* kDeadbeef = "\xde\xad\xbe\xef";
+
+TEST(ReadTextFile, ReadFile) {
+  std::string path = absl::StrCat(kTestFilesDir, kDeadbeefFile);
+  auto read_result = cobalt::util::ReadTextFile(path);
+  ASSERT_TRUE(read_result.ok());
+  EXPECT_EQ(read_result.ValueOrDie(), "deadbeef\n");
+}
+
+TEST(ReadTextFile, ReadEmptyFile) {
+  std::string path = absl::StrCat(kTestFilesDir, kEmptyFile);
+  auto read_result = cobalt::util::ReadTextFile(path);
+  ASSERT_TRUE(read_result.ok());
+  EXPECT_EQ(read_result.ValueOrDie(), "");
+}
+
+TEST(ReadTextFile, ReadNonExistentFile) {
+  std::string path = absl::StrCat(kTestFilesDir, kNonExistentFile);
+  auto read_result = cobalt::util::ReadTextFile(path);
+  ASSERT_FALSE(read_result.ok());
+}
+
+TEST(ReadNonEmptyTextFile, ReadFile) {
+  std::string path = absl::StrCat(kTestFilesDir, kDeadbeefFile);
+  auto read_result = cobalt::util::ReadNonEmptyTextFile(path);
+  ASSERT_TRUE(read_result.ok());
+  EXPECT_EQ(read_result.ValueOrDie(), "deadbeef\n");
+}
+
+TEST(ReadNonEmptyTextFile, ReadEmptyFile) {
+  std::string path = absl::StrCat(kTestFilesDir, kEmptyFile);
+  auto read_result = cobalt::util::ReadNonEmptyTextFile(path);
+  EXPECT_FALSE(read_result.ok());
+}
+
+TEST(ReadNonEmtpyTextFile, ReadNonExistentFile) {
+  std::string path = absl::StrCat(kTestFilesDir, kNonExistentFile);
+  auto read_result = cobalt::util::ReadNonEmptyTextFile(path);
+  EXPECT_FALSE(read_result.ok());
+}
+
+TEST(ReadHexFile, ReadHexFile) {
+  std::string path = absl::StrCat(kTestFilesDir, kDeadbeefFile);
+  auto read_result = cobalt::util::ReadHexFile(path);
+  ASSERT_TRUE(read_result.ok());
+  EXPECT_EQ(read_result.ValueOrDie(), kDeadbeef);
+}
+
+TEST(ReadHexFile, ReadEmptyFile) {
+  std::string path = absl::StrCat(kTestFilesDir, kEmptyFile);
+  auto read_result = cobalt::util::ReadHexFile(path);
+  EXPECT_FALSE(read_result.ok());
+}
+
+TEST(ReadHexFile, ReadNonExistentFile) {
+  std::string path = absl::StrCat(kTestFilesDir, kNonExistentFile);
+  auto read_result = cobalt::util::ReadHexFile(path);
+  EXPECT_FALSE(read_result.ok());
+}
+
+TEST(ReadHexFile, ReadHexTooShortFile) {
+  std::string path = absl::StrCat(kTestFilesDir, kHexTooShortFile);
+  auto read_result = cobalt::util::ReadHexFile(path);
+  EXPECT_FALSE(read_result.ok());
+}
+
+TEST(ReadHexFile, ReadHexWrongCharFile) {
+  std::string path = absl::StrCat(kTestFilesDir, kHexWrongCharFile);
+  auto read_result = cobalt::util::ReadHexFile(path);
+  EXPECT_FALSE(read_result.ok());
+}
+
+TEST(ReadHexFileOrDefault, ReadHexFile) {
+  const std::string kDefault = "default";
+  std::string path = absl::StrCat(kTestFilesDir, kDeadbeefFile);
+  EXPECT_EQ(cobalt::util::ReadHexFileOrDefault(path, kDefault), kDeadbeef);
+}
+
+TEST(ReadHexFileOrDefault, ReadEmptyFile) {
+  const std::string kDefault = "default";
+  std::string path = absl::StrCat(kTestFilesDir, kEmptyFile);
+  EXPECT_EQ(cobalt::util::ReadHexFileOrDefault(path, kDefault), kDefault);
+}
+
+TEST(ReadHexFileOrDefault, ReadNonExistentFile) {
+  const std::string kDefault = "default";
+  std::string path = absl::StrCat(kTestFilesDir, kNonExistentFile);
+  EXPECT_EQ(cobalt::util::ReadHexFileOrDefault(path, kDefault), kDefault);
+}
+
+TEST(ReadHexFileOrDefault, ReadHexTooShortFile) {
+  const std::string kDefault = "default";
+  std::string path = absl::StrCat(kTestFilesDir, kHexTooShortFile);
+  EXPECT_EQ(cobalt::util::ReadHexFileOrDefault(path, kDefault), kDefault);
+}
+
+TEST(ReadHexFileOrDefault, ReadHexWrongCharFile) {
+  const std::string kDefault = "default";
+  std::string path = absl::StrCat(kTestFilesDir, kHexWrongCharFile);
+  EXPECT_EQ(cobalt::util::ReadHexFileOrDefault(path, kDefault), kDefault);
+}
+
+}  // namespace
+
+int main(int argc, char* argv[]) {
+  // Compute the path where keys are stored in the output directory.
+  std::string path(argv[0]);
+  auto slash = path.find_last_of('/');
+  CHECK(slash != std::string::npos);
+  path = path.substr(0, slash) + "/file_util_test_files/";
+  kTestFilesDir = path.c_str();
+
+  ::testing::InitGoogleTest(&argc, argv);
+  return RUN_ALL_TESTS();
+}
diff --git a/src/lib/util/test_data/deadbeef b/src/lib/util/test_data/deadbeef
new file mode 100644
index 0000000..5ca31d5
--- /dev/null
+++ b/src/lib/util/test_data/deadbeef
@@ -0,0 +1 @@
+deadbeef
diff --git a/src/lib/util/test_data/empty b/src/lib/util/test_data/empty
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/src/lib/util/test_data/empty
diff --git a/src/lib/util/test_data/hex_file.hex b/src/lib/util/test_data/hex_file.hex
new file mode 100644
index 0000000..5ca31d5
--- /dev/null
+++ b/src/lib/util/test_data/hex_file.hex
@@ -0,0 +1 @@
+deadbeef
diff --git a/src/lib/util/test_data/hex_too_short b/src/lib/util/test_data/hex_too_short
new file mode 100644
index 0000000..aa8eac6
--- /dev/null
+++ b/src/lib/util/test_data/hex_too_short
@@ -0,0 +1 @@
+dea
diff --git a/src/lib/util/test_data/hex_wrong_char b/src/lib/util/test_data/hex_wrong_char
new file mode 100644
index 0000000..b4b74dd
--- /dev/null
+++ b/src/lib/util/test_data/hex_wrong_char
@@ -0,0 +1 @@
+deadbeeg