Add AesCmacParameters C++ classes.

PiperOrigin-RevId: 463096153
diff --git a/cc/mac/BUILD.bazel b/cc/mac/BUILD.bazel
index 5f6f3d8..b681672 100644
--- a/cc/mac/BUILD.bazel
+++ b/cc/mac/BUILD.bazel
@@ -151,6 +151,32 @@
     ],
 )
 
+cc_library(
+    name = "failing_mac",
+    testonly = 1,
+    srcs = ["failing_mac.cc"],
+    hdrs = ["failing_mac.h"],
+    include_prefix = "tink/mac",
+    visibility = ["//visibility:public"],
+    deps = [
+        "//:mac",
+        "@com_google_absl//absl/strings",
+    ],
+)
+
+cc_library(
+    name = "aes_cmac_parameters",
+    srcs = ["aes_cmac_parameters.cc"],
+    hdrs = ["aes_cmac_parameters.h"],
+    include_prefix = "tink/mac",
+    deps = [
+        ":mac_parameters",
+        "//:crypto_format",
+        "//util:status",
+        "//util:statusor",
+    ],
+)
+
 # tests
 
 cc_test(
@@ -276,19 +302,6 @@
     ],
 )
 
-cc_library(
-    name = "failing_mac",
-    testonly = 1,
-    srcs = ["failing_mac.cc"],
-    hdrs = ["failing_mac.h"],
-    include_prefix = "tink/mac",
-    visibility = ["//visibility:public"],
-    deps = [
-        "//:mac",
-        "@com_google_absl//absl/strings",
-    ],
-)
-
 cc_test(
     name = "failing_mac_test",
     srcs = ["failing_mac_test.cc"],
@@ -299,3 +312,15 @@
         "@com_google_googletest//:gtest_main",
     ],
 )
+
+cc_test(
+    name = "aes_cmac_parameters_test",
+    size = "small",
+    srcs = ["aes_cmac_parameters_test.cc"],
+    deps = [
+        ":aes_cmac_parameters",
+        "//util:statusor",
+        "//util:test_matchers",
+        "@com_google_googletest//:gtest_main",
+    ],
+)
diff --git a/cc/mac/CMakeLists.txt b/cc/mac/CMakeLists.txt
index f0808da..ab763f1 100644
--- a/cc/mac/CMakeLists.txt
+++ b/cc/mac/CMakeLists.txt
@@ -143,6 +143,28 @@
     tink::util::statusor
 )
 
+tink_cc_library(
+  NAME failing_mac
+  SRCS
+    failing_mac.cc
+    failing_mac.h
+  DEPS
+    absl::strings
+    tink::core::mac
+)
+
+tink_cc_library(
+  NAME aes_cmac_parameters
+  SRCS
+    aes_cmac_parameters.cc
+    aes_cmac_parameters.h
+  DEPS
+    tink::mac::mac_parameters
+    tink::core::crypto_format
+    tink::util::status
+    tink::util::statusor
+)
+
 # tests
 
 tink_cc_test(
@@ -261,16 +283,6 @@
     tink::proto::hmac_cc_proto
 )
 
-tink_cc_library(
-  NAME failing_mac
-  SRCS
-    failing_mac.cc
-    failing_mac.h
-  DEPS
-    absl::strings
-    tink::core::mac
-)
-
 tink_cc_test(
   NAME failing_mac_test
   SRCS
@@ -281,3 +293,14 @@
     absl::status
     tink::util::test_matchers
 )
+
+tink_cc_test(
+  NAME aes_cmac_parameters_test
+  SRCS
+    aes_cmac_parameters_test.cc
+  DEPS
+    tink::mac::aes_cmac_parameters
+    gmock
+    tink::util::statusor
+    tink::util::test_matchers
+)
diff --git a/cc/mac/aes_cmac_parameters.cc b/cc/mac/aes_cmac_parameters.cc
new file mode 100644
index 0000000..c4d8429
--- /dev/null
+++ b/cc/mac/aes_cmac_parameters.cc
@@ -0,0 +1,84 @@
+// Copyright 2022 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
+//
+//      http://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 "tink/mac/aes_cmac_parameters.h"
+
+#include <cstdlib>
+#include <iostream>
+#include <memory>
+#include <ostream>
+#include <set>
+
+#include "tink/crypto_format.h"
+#include "tink/util/status.h"
+#include "tink/util/statusor.h"
+
+namespace crypto {
+namespace tink {
+
+util::StatusOr<AesCmacParameters> AesCmacParameters::Create(
+    int cryptographic_tag_size_in_bytes, Variant variant) {
+  if (cryptographic_tag_size_in_bytes < 10) {
+    return util::Status(
+        absl::StatusCode::kInvalidArgument,
+        absl::StrCat("Tag size should be at least 10 bytes, got ",
+                     cryptographic_tag_size_in_bytes, " bytes."));
+  }
+  if (cryptographic_tag_size_in_bytes > 16) {
+    return util::Status(
+        absl::StatusCode::kInvalidArgument,
+        absl::StrCat("Tag size should not exceed 16 bytes, got ",
+                     cryptographic_tag_size_in_bytes, " bytes."));
+  }
+  static const std::set<Variant>* supported_variants =
+      new std::set<Variant>({Variant::kTink, Variant::kCrunchy,
+                             Variant::kLegacy, Variant::kNoPrefix});
+  if (supported_variants->find(variant) == supported_variants->end()) {
+    return util::Status(
+        absl::StatusCode::kInvalidArgument,
+        "Cannot create AES-CMAC parameters with unknown variant.");
+  }
+  return AesCmacParameters(cryptographic_tag_size_in_bytes, variant);
+}
+
+int AesCmacParameters::TotalTagSizeInBytes() const {
+  switch (variant_) {
+    case Variant::kTink:
+    case Variant::kCrunchy:
+    case Variant::kLegacy:
+      return CryptographicTagSizeInBytes() + CryptoFormat::kNonRawPrefixSize;
+    case Variant::kNoPrefix:
+      return CryptographicTagSizeInBytes();
+    default:
+      // Parameters objects with unknown variants should never be created.
+      std::cerr << "AES-CMAC parameters has an unknown variant." << std::endl;
+      std::exit(1);
+  }
+}
+
+bool AesCmacParameters::operator==(const Parameters& other) const {
+  const AesCmacParameters* that =
+      dynamic_cast<const AesCmacParameters*>(&other);
+  if (that == nullptr) {
+    return false;
+  }
+  return cryptographic_tag_size_in_bytes_ ==
+             that->cryptographic_tag_size_in_bytes_ &&
+         variant_ == that->variant_;
+}
+
+}  // namespace tink
+}  // namespace crypto
diff --git a/cc/mac/aes_cmac_parameters.h b/cc/mac/aes_cmac_parameters.h
new file mode 100644
index 0000000..a4a09a7
--- /dev/null
+++ b/cc/mac/aes_cmac_parameters.h
@@ -0,0 +1,93 @@
+// Copyright 2022 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
+//
+//      http://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.
+//
+////////////////////////////////////////////////////////////////////////////////
+
+#ifndef TINK_MAC_AES_CMAC_PARAMETERS_H_
+#define TINK_MAC_AES_CMAC_PARAMETERS_H_
+
+#include <memory>
+
+#include "tink/mac/mac_parameters.h"
+#include "tink/util/statusor.h"
+
+namespace crypto {
+namespace tink {
+
+// Describes the parameters of an `AesCmacKey`.
+class AesCmacParameters : public MacParameters {
+ public:
+  // Describes the details of a MAC computation.
+  //
+  // The usual AES-CMAC key is used for variant `NO_PREFIX`. Other variants
+  // slightly change how the MAC is computed, or add a prefix to every
+  // computation depending on the key id.
+  enum class Variant : int {
+    // Prepends '0x01<big endian key id>' to tag.
+    kTink = 1,
+    // Prepends '0x00<big endian key id>' to tag.
+    kCrunchy = 2,
+    // Appends a 0-byte to input message BEFORE computing the tag, then
+    // prepends '0x00<big endian key id>' to tag.
+    kLegacy = 3,
+    // Does not prepend any prefix (i.e., keys must have no ID requirement).
+    kNoPrefix = 4,
+    // Added to guard from failures that may be caused by future expansions.
+    kDoNotUseInsteadUseDefaultWhenWritingSwitchStatements = 20,
+  };
+
+  // Copyable and movable.
+  AesCmacParameters(const AesCmacParameters& other) = default;
+  AesCmacParameters& operator=(const AesCmacParameters& other) = default;
+  AesCmacParameters(AesCmacParameters&& other) = default;
+  AesCmacParameters& operator=(AesCmacParameters&& other) = default;
+
+  // Creates a new AES-CMAC parameters object. Returns an error status if
+  // `cryptographic_tag_size_in_bytes` falls outside [10,...,16].  Otherwise,
+  // returns the parameters object.
+  static util::StatusOr<AesCmacParameters> Create(
+      int cryptographic_tag_size_in_bytes, Variant variant);
+
+  Variant GetVariant() const { return variant_; }
+
+  // Returns the size of the tag, which is computed cryptographically from the
+  // message. Note that this may differ from the total size of the tag, as for
+  // some keys, Tink prefixes the tag with a key dependent output prefix.
+  int CryptographicTagSizeInBytes() const {
+    return cryptographic_tag_size_in_bytes_;
+  }
+
+  // Returns the size of the cryptographic tag plus the size of the prefix with
+  // which this key prefixes every cryptographic tag.
+  int TotalTagSizeInBytes() const;
+
+  bool HasIdRequirement() const override {
+    return variant_ != Variant::kNoPrefix;
+  }
+
+  bool operator==(const Parameters& other) const override;
+
+ private:
+  AesCmacParameters(int cryptographic_tag_size_in_bytes, Variant variant)
+      : cryptographic_tag_size_in_bytes_(cryptographic_tag_size_in_bytes),
+        variant_(variant) {}
+
+  int cryptographic_tag_size_in_bytes_;
+  Variant variant_;
+};
+
+}  // namespace tink
+}  // namespace crypto
+
+#endif  // TINK_MAC_AES_CMAC_PARAMETERS_H_
diff --git a/cc/mac/aes_cmac_parameters_test.cc b/cc/mac/aes_cmac_parameters_test.cc
new file mode 100644
index 0000000..db1f2c3
--- /dev/null
+++ b/cc/mac/aes_cmac_parameters_test.cc
@@ -0,0 +1,203 @@
+// Copyright 2022 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
+//
+//      http://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 "tink/mac/aes_cmac_parameters.h"
+
+#include <memory>
+#include <tuple>
+
+#include "gmock/gmock.h"
+#include "gtest/gtest.h"
+#include "tink/util/statusor.h"
+#include "tink/util/test_matchers.h"
+
+namespace crypto {
+namespace tink {
+namespace {
+
+using ::crypto::tink::test::IsOk;
+using ::crypto::tink::test::StatusIs;
+using ::testing::Combine;
+using ::testing::Eq;
+using ::testing::Range;
+using ::testing::TestWithParam;
+using ::testing::Values;
+
+struct CreateTestCase {
+  AesCmacParameters::Variant variant;
+  int cryptographic_tag_size_in_bytes;
+  int total_tag_size_in_bytes;
+  bool has_id_requirement;
+};
+
+class AesCmacParametersCreateTest : public TestWithParam<CreateTestCase> {};
+
+INSTANTIATE_TEST_SUITE_P(
+    AesCmacParametersCreateTestSuite, AesCmacParametersCreateTest,
+    Values(CreateTestCase{AesCmacParameters::Variant::kTink, 10, 15, true},
+           CreateTestCase{AesCmacParameters::Variant::kCrunchy, 12, 17, true},
+           CreateTestCase{AesCmacParameters::Variant::kLegacy, 14, 19, true},
+           CreateTestCase{AesCmacParameters::Variant::kNoPrefix, 16, 16,
+                          false}));
+
+TEST_P(AesCmacParametersCreateTest, Create) {
+  CreateTestCase test_case = GetParam();
+
+  util::StatusOr<AesCmacParameters> parameters = AesCmacParameters::Create(
+      test_case.cryptographic_tag_size_in_bytes, test_case.variant);
+  ASSERT_THAT(parameters, IsOk());
+
+  EXPECT_THAT(parameters->GetVariant(), Eq(test_case.variant));
+  EXPECT_THAT(parameters->CryptographicTagSizeInBytes(),
+              Eq(test_case.cryptographic_tag_size_in_bytes));
+  EXPECT_THAT(parameters->TotalTagSizeInBytes(),
+              Eq(test_case.total_tag_size_in_bytes));
+  EXPECT_THAT(parameters->HasIdRequirement(), Eq(test_case.has_id_requirement));
+}
+
+TEST(AesCmacParametersTest, CreateWithInvalidVariantFails) {
+  EXPECT_THAT(AesCmacParameters::Create(
+                  /*cryptographic_tag_size_in_bytes=*/12,
+                  AesCmacParameters::Variant::
+                      kDoNotUseInsteadUseDefaultWhenWritingSwitchStatements)
+                  .status(),
+              StatusIs(absl::StatusCode::kInvalidArgument));
+}
+
+TEST(AesCmacParametersTest, CreateWithInvalidTagSizeFails) {
+  // Too small.
+  EXPECT_THAT(AesCmacParameters::Create(/*cryptographic_tag_size_in_bytes=*/7,
+                                        AesCmacParameters::Variant::kNoPrefix)
+                  .status(),
+              StatusIs(absl::StatusCode::kInvalidArgument));
+  EXPECT_THAT(AesCmacParameters::Create(/*cryptographic_tag_size_in_bytes=*/8,
+                                        AesCmacParameters::Variant::kNoPrefix)
+                  .status(),
+              StatusIs(absl::StatusCode::kInvalidArgument));
+  EXPECT_THAT(AesCmacParameters::Create(/*cryptographic_tag_size_in_bytes=*/9,
+                                        AesCmacParameters::Variant::kNoPrefix)
+                  .status(),
+              StatusIs(absl::StatusCode::kInvalidArgument));
+  // Too big;
+  EXPECT_THAT(AesCmacParameters::Create(/*cryptographic_tag_size_in_bytes=*/17,
+                                        AesCmacParameters::Variant::kNoPrefix)
+                  .status(),
+              StatusIs(absl::StatusCode::kInvalidArgument));
+  EXPECT_THAT(AesCmacParameters::Create(/*cryptographic_tag_size_in_bytes=*/18,
+                                        AesCmacParameters::Variant::kNoPrefix)
+                  .status(),
+              StatusIs(absl::StatusCode::kInvalidArgument));
+  EXPECT_THAT(AesCmacParameters::Create(/*cryptographic_tag_size_in_bytes=*/19,
+                                        AesCmacParameters::Variant::kNoPrefix)
+                  .status(),
+              StatusIs(absl::StatusCode::kInvalidArgument));
+}
+
+TEST(AesCmacParametersTest, CopyConstructor) {
+  util::StatusOr<AesCmacParameters> parameters = AesCmacParameters::Create(
+      /*cryptographic_tag_size_in_bytes=*/12,
+      AesCmacParameters::Variant::kTink);
+  ASSERT_THAT(parameters, IsOk());
+
+  AesCmacParameters copy(*parameters);
+  EXPECT_THAT(copy.GetVariant(), Eq(parameters->GetVariant()));
+  EXPECT_THAT(copy.CryptographicTagSizeInBytes(),
+              Eq(parameters->CryptographicTagSizeInBytes()));
+  EXPECT_THAT(copy.TotalTagSizeInBytes(),
+              Eq(parameters->TotalTagSizeInBytes()));
+  EXPECT_THAT(copy.HasIdRequirement(), Eq(parameters->HasIdRequirement()));
+}
+
+TEST(AesCmacParametersTest, CopyAssignment) {
+  util::StatusOr<AesCmacParameters> parameters = AesCmacParameters::Create(
+      /*cryptographic_tag_size_in_bytes=*/12,
+      AesCmacParameters::Variant::kTink);
+  ASSERT_THAT(parameters, IsOk());
+
+  AesCmacParameters copy = *parameters;
+  EXPECT_THAT(copy.GetVariant(), Eq(parameters->GetVariant()));
+  EXPECT_THAT(copy.CryptographicTagSizeInBytes(),
+              Eq(parameters->CryptographicTagSizeInBytes()));
+  EXPECT_THAT(copy.TotalTagSizeInBytes(),
+              Eq(parameters->TotalTagSizeInBytes()));
+  EXPECT_THAT(copy.HasIdRequirement(), Eq(parameters->HasIdRequirement()));
+}
+
+class AesCmacParametersVariantTest
+    : public TestWithParam<std::tuple<AesCmacParameters::Variant, int>> {};
+
+INSTANTIATE_TEST_SUITE_P(AesCmacParametersVariantTestSuite,
+                         AesCmacParametersVariantTest,
+                         Combine(Values(AesCmacParameters::Variant::kTink,
+                                        AesCmacParameters::Variant::kCrunchy,
+                                        AesCmacParameters::Variant::kLegacy,
+                                        AesCmacParameters::Variant::kNoPrefix),
+                                 Range(10, 16)));
+
+TEST_P(AesCmacParametersVariantTest, ParametersEquals) {
+  AesCmacParameters::Variant variant;
+  int cryptographic_tag_size;
+  std::tie(variant, cryptographic_tag_size) = GetParam();
+
+  util::StatusOr<AesCmacParameters> parameters =
+      AesCmacParameters::Create(cryptographic_tag_size, variant);
+  ASSERT_THAT(parameters, IsOk());
+
+  util::StatusOr<AesCmacParameters> other_parameters =
+      AesCmacParameters::Create(cryptographic_tag_size, variant);
+  ASSERT_THAT(other_parameters, IsOk());
+
+  EXPECT_TRUE(*parameters == *other_parameters);
+  EXPECT_TRUE(*other_parameters == *parameters);
+  EXPECT_FALSE(*parameters != *other_parameters);
+  EXPECT_FALSE(*other_parameters != *parameters);
+}
+
+TEST(AesCmacParametersTest, TagSizeNotEqual) {
+  util::StatusOr<AesCmacParameters> parameters = AesCmacParameters::Create(
+      /*cryptographic_tag_size_in_bytes=*/10,
+      AesCmacParameters::Variant::kNoPrefix);
+  ASSERT_THAT(parameters, IsOk());
+
+  util::StatusOr<AesCmacParameters> other_parameters =
+      AesCmacParameters::Create(
+          /*cryptographic_tag_size_in_bytes=*/11,
+          AesCmacParameters::Variant::kNoPrefix);
+  ASSERT_THAT(other_parameters, IsOk());
+
+  EXPECT_TRUE(*parameters != *other_parameters);
+  EXPECT_FALSE(*parameters == *other_parameters);
+}
+
+TEST(AesCmacParametersTest, VariantNotEqual) {
+  util::StatusOr<AesCmacParameters> parameters = AesCmacParameters::Create(
+      /*cryptographic_tag_size_in_bytes=*/10,
+      AesCmacParameters::Variant::kNoPrefix);
+  ASSERT_THAT(parameters, IsOk());
+
+  util::StatusOr<AesCmacParameters> other_parameters =
+      AesCmacParameters::Create(
+          /*cryptographic_tag_size_in_bytes=*/10,
+          AesCmacParameters::Variant::kTink);
+  ASSERT_THAT(other_parameters, IsOk());
+
+  EXPECT_TRUE(*parameters != *other_parameters);
+  EXPECT_FALSE(*parameters == *other_parameters);
+}
+
+}  // namespace
+}  // namespace tink
+}  // namespace crypto