| // Copyright 2026 Google LLC |
| // |
| // Licensed under the Apache License, Version 2.0 (the "License"); |
| // you may not use this file except in compliance with the License. |
| // You may obtain a copy of the License at |
| // |
| // https://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License. |
| // |
| |
| #include "bindings/input_hasher.h" |
| |
| #include <memory> |
| #include <string> |
| #include <utility> |
| |
| #include "absl/status/status.h" |
| #include "absl/status/status_matchers.h" |
| #include "absl/status/statusor.h" |
| #include "absl/strings/escaping.h" |
| #include "absl/strings/string_view.h" |
| #include "crypto/algorithms.h" |
| #include "crypto/default/hasher.h" |
| #include "crypto/hash.h" |
| #include "gmock/gmock.h" |
| #include "gtest/gtest.h" |
| #include "riegeli/bytes/string_reader.h" |
| |
| namespace credentio { |
| namespace { |
| |
| using ::absl_testing::IsOk; |
| using ::absl_testing::StatusIs; |
| using ::testing::Eq; |
| using ::testing::HasSubstr; |
| using ::testing::Return; |
| |
| std::string Sha256Digest(absl::string_view data) { |
| absl::StatusOr<std::unique_ptr<Hasher>> hasher = CreateHasher("sha256"); |
| if (!hasher.ok()) return ""; |
| (*hasher)->Update(data); |
| return (*hasher)->Digest(); |
| } |
| |
| TEST(InputHasherTest, CreateWithValidAlgString) { |
| EXPECT_THAT(InputHasher::Create("sha256"), IsOk()); |
| EXPECT_THAT(InputHasher::Create("sha384"), IsOk()); |
| EXPECT_THAT(InputHasher::Create("sha512"), IsOk()); |
| } |
| |
| TEST(InputHasherTest, CreateWithInvalidAlgString) { |
| EXPECT_THAT(InputHasher::Create("invalid"), |
| StatusIs(absl::StatusCode::kInvalidArgument)); |
| } |
| |
| TEST(InputHasherTest, CreateWithValidAlgEnum) { |
| EXPECT_THAT(InputHasher::Create(HashAlgorithm::kSha256), IsOk()); |
| EXPECT_THAT(InputHasher::Create(HashAlgorithm::kSha384), IsOk()); |
| EXPECT_THAT(InputHasher::Create(HashAlgorithm::kSha512), IsOk()); |
| } |
| |
| class MockHasher : public Hasher { |
| public: |
| MOCK_METHOD(void, Update, (absl::string_view data), (override)); |
| MOCK_METHOD(std::string, Digest, (), (override)); |
| }; |
| |
| TEST(InputHasherTest, CreateWithCustomHasher) { |
| auto mock_hasher = std::make_unique<MockHasher>(); |
| EXPECT_CALL(*mock_hasher, Update("test")); |
| EXPECT_CALL(*mock_hasher, Digest()).WillOnce(Return("mock_digest")); |
| |
| auto input_hasher = InputHasher::Create(std::move(mock_hasher)); |
| ASSERT_THAT(input_hasher, IsOk()); |
| (*input_hasher)->Update("test"); |
| EXPECT_THAT((*input_hasher)->Digest(), Eq("mock_digest")); |
| } |
| |
| TEST(InputHasherTest, UpdateStringView) { |
| auto hasher = InputHasher::Create("sha256"); |
| ASSERT_THAT(hasher, IsOk()); |
| (*hasher)->Update("hello"); |
| (*hasher)->Update(" "); |
| (*hasher)->Update("world"); |
| |
| EXPECT_THAT(absl::BytesToHexString((*hasher)->Digest()), |
| Eq(absl::BytesToHexString(Sha256Digest("hello world")))); |
| } |
| |
| TEST(InputHasherTest, UpdateRiegeliEmpty) { |
| auto hasher = InputHasher::Create("sha256"); |
| ASSERT_THAT(hasher, IsOk()); |
| std::string data = ""; |
| riegeli::StringReader reader(data); |
| EXPECT_THAT((*hasher)->Update(reader, 0, 0), IsOk()); |
| |
| EXPECT_THAT(absl::BytesToHexString((*hasher)->Digest()), |
| Eq(absl::BytesToHexString(Sha256Digest(data)))); |
| } |
| |
| TEST(InputHasherTest, UpdateRiegeliSmall) { |
| auto hasher = InputHasher::Create("sha256"); |
| ASSERT_THAT(hasher, IsOk()); |
| std::string data = "small data"; |
| riegeli::StringReader reader(data); |
| EXPECT_THAT((*hasher)->Update(reader, 0, data.length()), IsOk()); |
| |
| EXPECT_THAT(absl::BytesToHexString((*hasher)->Digest()), |
| Eq(absl::BytesToHexString(Sha256Digest(data)))); |
| } |
| |
| TEST(InputHasherTest, UpdateRiegeliLarge) { |
| auto hasher = InputHasher::Create("sha256"); |
| ASSERT_THAT(hasher, IsOk()); |
| std::string data(20000, 'a'); // Larger than default chunk size |
| riegeli::StringReader reader(data); |
| EXPECT_THAT((*hasher)->Update(reader, 0, data.length()), IsOk()); |
| |
| EXPECT_THAT(absl::BytesToHexString((*hasher)->Digest()), |
| Eq(absl::BytesToHexString(Sha256Digest(data)))); |
| } |
| |
| TEST(InputHasherTest, UpdateRiegeliWithOffset) { |
| auto hasher = InputHasher::Create("sha256"); |
| ASSERT_THAT(hasher, IsOk()); |
| std::string data = "prefix_and_data"; |
| riegeli::StringReader reader(data); |
| EXPECT_THAT((*hasher)->Update(reader, 7, 3), IsOk()); // "and" |
| |
| EXPECT_THAT(absl::BytesToHexString((*hasher)->Digest()), |
| Eq(absl::BytesToHexString(Sha256Digest("and")))); |
| } |
| |
| TEST(InputHasherTest, UpdateRiegeliMultiple) { |
| auto hasher = InputHasher::Create("sha256"); |
| ASSERT_THAT(hasher, IsOk()); |
| std::string data1 = "first part"; |
| std::string data2 = " second part"; |
| |
| riegeli::StringReader reader1(data1); |
| riegeli::StringReader reader2(data2); |
| |
| EXPECT_THAT((*hasher)->Update(reader1, 0, data1.length()), IsOk()); |
| (*hasher)->Update(" middle "); |
| EXPECT_THAT((*hasher)->Update(reader2, 0, data2.length()), IsOk()); |
| |
| EXPECT_THAT(absl::BytesToHexString((*hasher)->Digest()), |
| Eq(absl::BytesToHexString( |
| Sha256Digest("first part middle second part")))); |
| } |
| |
| TEST(InputHasherTest, UpdateRiegeliClosedReaderFails) { |
| auto hasher = InputHasher::Create("sha256"); |
| ASSERT_THAT(hasher, IsOk()); |
| riegeli::StringReader reader("test"); |
| reader.Close(); |
| |
| EXPECT_THAT((*hasher)->Update(reader, 0, 4), |
| StatusIs(absl::StatusCode::kFailedPrecondition, |
| HasSubstr("Object closed"))); |
| } |
| |
| TEST(InputHasherTest, UpdateRiegeliOffsetOutOfRange) { |
| auto hasher = InputHasher::Create("sha256"); |
| ASSERT_THAT(hasher, IsOk()); |
| riegeli::StringReader reader("test"); |
| |
| EXPECT_THAT( |
| (*hasher)->Update(reader, 10, 4), |
| StatusIs(absl::StatusCode::kInternal, HasSubstr("Failed to seek to 10"))); |
| } |
| |
| TEST(InputHasherTest, UpdateRiegeliLengthExceedsSourceFails) { |
| auto hasher = InputHasher::Create("sha256"); |
| ASSERT_THAT(hasher, IsOk()); |
| riegeli::StringReader reader("test"); |
| |
| EXPECT_THAT((*hasher)->Update(reader, 0, 100), |
| StatusIs(absl::StatusCode::kDataLoss, |
| HasSubstr("Failed to read from source"))); |
| } |
| |
| } // namespace |
| } // namespace credentio |