diff --git a/p4_pdpi/BUILD.bazel b/p4_pdpi/BUILD.bazel index 1d2c025..cd3c217 100644 --- a/p4_pdpi/BUILD.bazel +++ b/p4_pdpi/BUILD.bazel @@ -548,6 +548,48 @@ cc_library( deps = ["@abseil-cpp//absl/strings:str_format"], ) +proto_library( + name = "action_profile_mode_proto", + srcs = ["action_profile_mode.proto"], + deps = ["@p4runtime//proto/p4/config/v1:p4info_proto"], +) + +cc_proto_library( + name = "action_profile_mode_cc_proto", + deps = [":action_profile_mode_proto"], +) + +cc_library( + name = "action_profile_modes", + srcs = [ + "action_profile_modes.cc", + ], + hdrs = [ + "action_profile_modes.h", + ], + deps = [ + ":action_profile_mode_cc_proto", + ":annotation_parser", + "@abseil-cpp//absl/status:statusor", + "@abseil-cpp//absl/strings", + "@p4runtime//proto/p4/config/v1:p4info_cc_proto", + "@p4runtime//proto/p4/config/v1:p4types_cc_proto", + "@protobuf", + ], +) + +cc_test( + name = "action_profile_modes_test", + srcs = ["action_profile_modes_test.cc"], + deps = [ + ":action_profile_mode_cc_proto", + ":action_profile_modes", + "@googletest//:gtest_main", + "@p4runtime//proto/p4/config/v1:p4info_cc_proto", + "@p4runtime//proto/p4/config/v1:p4types_cc_proto", + ], +) + cc_library( name = "annotation_parser", srcs = [ diff --git a/p4_pdpi/action_profile_mode.proto b/p4_pdpi/action_profile_mode.proto new file mode 100644 index 0000000..555a19e --- /dev/null +++ b/p4_pdpi/action_profile_mode.proto @@ -0,0 +1,52 @@ +// 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 +// +// 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. + +syntax = "proto3"; + +package pdpi; + +import "p4/config/v1/p4info.proto"; + +// Supported or required ActionProfile group programming mode. +message ActionProfileMode { + enum ActionSelectionMode { + UNDEFINED_ACTION_SELECTION_MODE = 0; + HASH = 1; + RANDOM = 2; + } + ActionSelectionMode action_selection_mode = 1; + + // Size semantics specified by P4Runtime ActionProfile. + oneof size_semantics { + p4.config.v1.ActionProfile.SumOfWeights sum_of_weights = 2; + p4.config.v1.ActionProfile.SumOfMembers sum_of_members = 3; + } + + // Defines how groups of particular modes use resources. + message ResourceUsageMultipliers { + // Multiplies the members used by a group of this type by this number. + // Affects the guarantee given by ActionProfile `size`. + // copybara:strip_begin + // On Broadcom switches that support it, Native WCMP member resources are + // multiplied by 4, while Legacy ECMP resources are multiplied by 1. + // copybara:strip_end + // If the field is not present, it is treated as 1. + optional int32 member_usage_multiplier = 1; + } + ResourceUsageMultipliers resource_usage_multipliers = 4; +} + +message ActionProfileModes { + repeated ActionProfileMode action_profile_modes = 1; +} diff --git a/p4_pdpi/action_profile_modes.cc b/p4_pdpi/action_profile_modes.cc new file mode 100644 index 0000000..65c3b14 --- /dev/null +++ b/p4_pdpi/action_profile_modes.cc @@ -0,0 +1,230 @@ +// 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 +// +// 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 "p4_pdpi/action_profile_modes.h" + +#include +#include +#include +#include + +#include "absl/status/statusor.h" +#include "absl/strings/ascii.h" +#include "absl/strings/match.h" +#include "absl/strings/numbers.h" +#include "absl/strings/str_replace.h" +#include "absl/strings/string_view.h" +#include "google/protobuf/util/message_differencer.h" +#include "p4/config/v1/p4info.pb.h" +#include "p4/config/v1/p4types.pb.h" +#include "p4_pdpi/action_profile_mode.pb.h" +#include "p4_pdpi/annotation_parser.h" + +namespace pdpi { +namespace { + +std::string CleanToken(absl::string_view s) { + s = absl::StripAsciiWhitespace(s); + while (s.size() >= 2 && ((s.front() == '"' && s.back() == '"') || + (s.front() == '{' && s.back() == '}') || + (s.front() == '\'' && s.back() == '\''))) { + s = s.substr(1, s.size() - 2); + s = absl::StripAsciiWhitespace(s); + } + return std::string(s); +} + +void ParseSizeSemantics(absl::string_view raw_val, ActionProfileMode& mode) { + std::string cleaned = CleanToken(raw_val); + if (cleaned.empty()) return; + + absl::StatusOr> parts = + pdpi::annotation::ParseAsArgList( + absl::StrReplaceAll(cleaned, {{":", ","}})); + if (!parts.ok() || parts->empty()) { + parts = std::vector{cleaned}; + } + + for (size_t i = 0; i < parts->size(); ++i) { + std::string part = std::string(absl::StripAsciiWhitespace((*parts)[i])); + size_t eq_pos = part.find('='); + if (eq_pos != std::string::npos) { + std::string key = CleanToken(part.substr(0, eq_pos)); + std::string val = CleanToken(part.substr(eq_pos + 1)); + if (key == "semantics" || key == "size_semantics") { + std::string upper_val = absl::AsciiStrToUpper(val); + if (upper_val == "SUM_OF_WEIGHTS") { + mode.mutable_sum_of_weights(); + } else if (upper_val == "SUM_OF_MEMBERS") { + mode.mutable_sum_of_members(); + } + } else if (key == "max_weight" || key == "max_member_weight") { + int64_t w; + if (absl::SimpleAtoi(val, &w)) { + if (!mode.has_sum_of_weights() && !mode.has_sum_of_members()) { + mode.mutable_sum_of_members(); + } + if (mode.has_sum_of_members()) { + mode.mutable_sum_of_members()->set_max_member_weight(w); + } + } + } else if (key == "member_multiplier" || + key == "member_usage_multiplier") { + int64_t mult; + if (absl::SimpleAtoi(val, &mult)) { + mode.mutable_resource_usage_multipliers() + ->set_member_usage_multiplier(mult); + } + } + } else { + std::string token = CleanToken(part); + std::string upper_token = absl::AsciiStrToUpper(token); + if (upper_token == "SUM_OF_WEIGHTS") { + mode.mutable_sum_of_weights(); + } else if (upper_token == "SUM_OF_MEMBERS") { + mode.mutable_sum_of_members(); + } else if (i > 0) { + int64_t val; + if (absl::SimpleAtoi(token, &val)) { + if (mode.has_sum_of_members()) { + if (val > 100) { + mode.mutable_sum_of_members()->set_max_member_weight(val); + } else { + mode.mutable_resource_usage_multipliers() + ->set_member_usage_multiplier(val); + } + } + } + } + } + } +} + +ActionProfileMode ParseActionProfileModeFromKvList( + const p4::config::v1::KeyValuePairList& kv_list) { + ActionProfileMode mode; + for (const auto& kv : kv_list.kv_pairs()) { + if (kv.key() == "action_selection_mode") { + std::string val = + absl::AsciiStrToUpper(CleanToken(kv.value().string_value())); + if (val == "HASH") { + mode.set_action_selection_mode(ActionProfileMode::HASH); + } else if (val == "RANDOM") { + mode.set_action_selection_mode(ActionProfileMode::RANDOM); + } + } else if (kv.key() == "size_semantics") { + ParseSizeSemantics(kv.value().string_value(), mode); + } else if (kv.key() == "sum_of_weights") { + mode.mutable_sum_of_weights(); + } else if (kv.key() == "sum_of_members") { + mode.mutable_sum_of_members(); + } else if (kv.key() == "member_multiplier" || + kv.key() == "member_usage_multiplier") { + if (kv.value().has_int64_value()) { + mode.mutable_resource_usage_multipliers()->set_member_usage_multiplier( + kv.value().int64_value()); + } + } else if (kv.key() == "max_member_weight" || kv.key() == "max_weight") { + if (kv.value().has_int64_value()) { + if (!mode.has_sum_of_weights() && !mode.has_sum_of_members()) { + mode.mutable_sum_of_members(); + } + if (mode.has_sum_of_members()) { + mode.mutable_sum_of_members()->set_max_member_weight( + kv.value().int64_value()); + } + } + } + } + return mode; +} + +std::vector ParseActionProfileModesFromExpressionList( + const p4::config::v1::ExpressionList& expression_list) { + std::vector modes; + const auto& exprs = expression_list.expressions(); + + ActionProfileMode mode; + for (size_t i = 0; i < exprs.size(); i += 3) { + mode.Clear(); + + std::string action_selection_mode = + absl::AsciiStrToUpper(CleanToken(exprs[i].string_value())); + if (action_selection_mode == "HASH") { + mode.set_action_selection_mode(ActionProfileMode::HASH); + } else if (action_selection_mode == "RANDOM") { + mode.set_action_selection_mode(ActionProfileMode::RANDOM); + } + + if (i + 1 < exprs.size()) { + ParseSizeSemantics(exprs[i + 1].string_value(), mode); + } + + if (i + 2 < exprs.size()) { + int64_t val; + if (absl::SimpleAtoi(CleanToken(exprs[i + 2].string_value()), &val)) { + if (mode.has_sum_of_members()) { + mode.mutable_sum_of_members()->set_max_member_weight(val); + } else if (mode.has_sum_of_weights()) { + mode.mutable_resource_usage_multipliers() + ->set_member_usage_multiplier(val); + } + } + } + + modes.push_back(mode); + } + + return modes; +} + +} // namespace + +absl::StatusOr> +ParseRequiredModesFromActionProfile( + const p4::config::v1::ActionProfile& action_profile) { + std::vector modes; + + for (const auto& sa : action_profile.preamble().structured_annotations()) { + if (!absl::StartsWith(sa.name(), "required_mode") && + !absl::StartsWith(sa.name(), "required_modes")) { + continue; + } + if (sa.has_kv_pair_list()) { + modes.push_back(ParseActionProfileModeFromKvList(sa.kv_pair_list())); + } else if (sa.has_expression_list()) { + std::vector parsed_modes = + ParseActionProfileModesFromExpressionList(sa.expression_list()); + modes.insert(modes.end(), parsed_modes.begin(), parsed_modes.end()); + } + } + + std::vector deduplicated_modes; + for (const auto& mode : modes) { + bool exists = false; + for (const auto& existing : deduplicated_modes) { + if (google::protobuf::util::MessageDifferencer::Equals(mode, existing)) { + exists = true; + break; + } + } + if (!exists) { + deduplicated_modes.push_back(mode); + } + } + + return deduplicated_modes; +} + +} // namespace pdpi diff --git a/p4_pdpi/action_profile_modes.h b/p4_pdpi/action_profile_modes.h new file mode 100644 index 0000000..73cbc22 --- /dev/null +++ b/p4_pdpi/action_profile_modes.h @@ -0,0 +1,35 @@ +// 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 +// +// 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 P4_INFRA_P4_PDPI_ACTION_PROFILE_MODES_H_ +#define P4_INFRA_P4_PDPI_ACTION_PROFILE_MODES_H_ + +#include + +#include "absl/status/statusor.h" +#include "p4/config/v1/p4info.pb.h" +#include "p4_pdpi/action_profile_mode.pb.h" + +namespace pdpi { + +// Parses required ActionProfile modes from `@required_mode(...)`/ +// `@required_modes(...)` structured annotations in the ActionProfile's +// preamble. +absl::StatusOr> +ParseRequiredModesFromActionProfile( + const p4::config::v1::ActionProfile& action_profile); + +} // namespace pdpi + +#endif // P4_INFRA_P4_PDPI_ACTION_PROFILE_MODES_H_ diff --git a/p4_pdpi/action_profile_modes_test.cc b/p4_pdpi/action_profile_modes_test.cc new file mode 100644 index 0000000..95aa45a --- /dev/null +++ b/p4_pdpi/action_profile_modes_test.cc @@ -0,0 +1,261 @@ +// 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 +// +// 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 "p4_pdpi/action_profile_modes.h" + +#include +#include + +#include "gmock/gmock.h" +#include "gtest/gtest.h" +#include "p4/config/v1/p4info.pb.h" +#include "p4/config/v1/p4types.pb.h" +#include "p4_pdpi/action_profile_mode.pb.h" + +namespace pdpi { +namespace { + +TEST(ParseRequiredModesFromActionProfileTest, + ParsesStructuredAnnotationKeyValuePairList) { + p4::config::v1::ActionProfile ap; + auto* sa = ap.mutable_preamble()->add_structured_annotations(); + sa->set_name("required_mode"); + auto* kv_list = sa->mutable_kv_pair_list(); + + auto* kv1 = kv_list->add_kv_pairs(); + kv1->set_key("action_selection_mode"); + kv1->mutable_value()->set_string_value("HASH"); + + auto* kv2 = kv_list->add_kv_pairs(); + kv2->set_key("size_semantics"); + kv2->mutable_value()->set_string_value("sum_of_weights"); + + auto* kv3 = kv_list->add_kv_pairs(); + kv3->set_key("member_multiplier"); + kv3->mutable_value()->set_int64_value(1); + + auto* kv4 = kv_list->add_kv_pairs(); + kv4->set_key("max_member_weight"); + kv4->mutable_value()->set_int64_value(0); + + ASSERT_OK_AND_ASSIGN(std::vector modes, + ParseRequiredModesFromActionProfile(ap)); + + ASSERT_EQ(modes.size(), 1); + EXPECT_EQ(modes[0].action_selection_mode(), ActionProfileMode::HASH); + EXPECT_TRUE(modes[0].has_sum_of_weights()); + EXPECT_EQ(modes[0].resource_usage_multipliers().member_usage_multiplier(), 1); +} + +TEST(ParseRequiredModesFromActionProfileTest, + ParsesStructuredAnnotationExpressionList) { + p4::config::v1::ActionProfile ap; + auto* sa = ap.mutable_preamble()->add_structured_annotations(); + sa->set_name("required_mode"); + auto* expr_list = sa->mutable_expression_list(); + + expr_list->add_expressions()->set_string_value("HASH"); + expr_list->add_expressions()->set_string_value("sum_of_members: 1"); + expr_list->add_expressions()->set_string_value("4095"); + + ASSERT_OK_AND_ASSIGN(std::vector modes, + ParseRequiredModesFromActionProfile(ap)); + + ASSERT_EQ(modes.size(), 1); + EXPECT_EQ(modes[0].action_selection_mode(), ActionProfileMode::HASH); + EXPECT_TRUE(modes[0].has_sum_of_members()); + EXPECT_EQ(modes[0].resource_usage_multipliers().member_usage_multiplier(), 1); + EXPECT_EQ(modes[0].sum_of_members().max_member_weight(), 4095); +} + +TEST(ParseRequiredModesFromActionProfileTest, + ParsesMultipleSeparateRequiredModeAnnotations) { + p4::config::v1::ActionProfile ap; + + auto* sa1 = ap.mutable_preamble()->add_structured_annotations(); + sa1->set_name("required_mode"); + auto* kv_list1 = sa1->mutable_kv_pair_list(); + auto* kv1_1 = kv_list1->add_kv_pairs(); + kv1_1->set_key("action_selection_mode"); + kv1_1->mutable_value()->set_string_value("HASH"); + auto* kv1_2 = kv_list1->add_kv_pairs(); + kv1_2->set_key("size_semantics"); + kv1_2->mutable_value()->set_string_value("sum_of_weights"); + + auto* sa2 = ap.mutable_preamble()->add_structured_annotations(); + sa2->set_name("required_modes"); + auto* kv_list2 = sa2->mutable_kv_pair_list(); + auto* kv2_1 = kv_list2->add_kv_pairs(); + kv2_1->set_key("action_selection_mode"); + kv2_1->mutable_value()->set_string_value("RANDOM"); + auto* kv2_2 = kv_list2->add_kv_pairs(); + kv2_2->set_key("size_semantics"); + kv2_2->mutable_value()->set_string_value("sum_of_members"); + + ASSERT_OK_AND_ASSIGN(std::vector modes, + ParseRequiredModesFromActionProfile(ap)); + + ASSERT_EQ(modes.size(), 2); + EXPECT_EQ(modes[0].action_selection_mode(), ActionProfileMode::HASH); + EXPECT_TRUE(modes[0].has_sum_of_weights()); + EXPECT_EQ(modes[1].action_selection_mode(), ActionProfileMode::RANDOM); + EXPECT_TRUE(modes[1].has_sum_of_members()); +} + +TEST(ParseRequiredModesFromActionProfileTest, + DeduplicatesStructuredAnnotations) { + p4::config::v1::ActionProfile ap; + + auto add_structured_mode = [&](const std::string& mode_str, + const std::string& size_str, int mult, + int weight) { + auto* sa = ap.mutable_preamble()->add_structured_annotations(); + sa->set_name("required_mode"); + auto* kv_list = sa->mutable_kv_pair_list(); + + auto* kv1 = kv_list->add_kv_pairs(); + kv1->set_key("action_selection_mode"); + kv1->mutable_value()->set_string_value(mode_str); + + auto* kv2 = kv_list->add_kv_pairs(); + kv2->set_key("size_semantics"); + kv2->mutable_value()->set_string_value(size_str); + + auto* kv3 = kv_list->add_kv_pairs(); + kv3->set_key("member_multiplier"); + kv3->mutable_value()->set_int64_value(mult); + + auto* kv4 = kv_list->add_kv_pairs(); + kv4->set_key("max_member_weight"); + kv4->mutable_value()->set_int64_value(weight); + }; + + add_structured_mode("HASH", "sum_of_weights", 1, 0); + add_structured_mode("HASH", "sum_of_weights", 1, 0); + + ASSERT_OK_AND_ASSIGN(std::vector modes, + ParseRequiredModesFromActionProfile(ap)); + + EXPECT_EQ(modes.size(), 1); +} + +TEST(ParseRequiredModesFromActionProfileTest, + ParsesNumberedRequiredModeAnnotations) { + p4::config::v1::ActionProfile ap; + auto* sa1 = ap.mutable_preamble()->add_structured_annotations(); + sa1->set_name("required_mode_1"); + auto* kv1 = sa1->mutable_kv_pair_list()->add_kv_pairs(); + kv1->set_key("action_selection_mode"); + kv1->mutable_value()->set_string_value("HASH"); + + auto* sa2 = ap.mutable_preamble()->add_structured_annotations(); + sa2->set_name("required_mode_2"); + auto* kv2 = sa2->mutable_kv_pair_list()->add_kv_pairs(); + kv2->set_key("action_selection_mode"); + kv2->mutable_value()->set_string_value("RANDOM"); + + ASSERT_OK_AND_ASSIGN(std::vector modes, + ParseRequiredModesFromActionProfile(ap)); + + ASSERT_EQ(modes.size(), 2); + EXPECT_EQ(modes[0].action_selection_mode(), ActionProfileMode::HASH); + EXPECT_EQ(modes[1].action_selection_mode(), ActionProfileMode::RANDOM); +} + +TEST(ParseRequiredModesFromActionProfileTest, + ParsesMultipleModesFromSingleExpressionList) { + p4::config::v1::ActionProfile ap; + auto* sa = ap.mutable_preamble()->add_structured_annotations(); + sa->set_name("required_modes"); + auto* expr_list = sa->mutable_expression_list(); + + expr_list->add_expressions()->set_string_value("HASH"); + expr_list->add_expressions()->set_string_value("sum_of_weights"); + expr_list->add_expressions()->set_string_value("0"); + + expr_list->add_expressions()->set_string_value("RANDOM"); + expr_list->add_expressions()->set_string_value("sum_of_members: 4"); + expr_list->add_expressions()->set_string_value("4095"); + + ASSERT_OK_AND_ASSIGN(std::vector modes, + ParseRequiredModesFromActionProfile(ap)); + + ASSERT_EQ(modes.size(), 2); + EXPECT_EQ(modes[0].action_selection_mode(), ActionProfileMode::HASH); + EXPECT_TRUE(modes[0].has_sum_of_weights()); + EXPECT_EQ(modes[1].action_selection_mode(), ActionProfileMode::RANDOM); + EXPECT_TRUE(modes[1].has_sum_of_members()); + EXPECT_EQ(modes[1].resource_usage_multipliers().member_usage_multiplier(), 4); +} + +TEST(ParseRequiredModesFromActionProfileTest, + ParsesSymmetricSumOfWeightsAndKeyValuePairs) { + p4::config::v1::ActionProfile ap; + auto* sa = ap.mutable_preamble()->add_structured_annotations(); + sa->set_name("required_modes"); + auto* kv_list = sa->mutable_kv_pair_list(); + + auto* kv1 = kv_list->add_kv_pairs(); + kv1->set_key("action_selection_mode"); + kv1->mutable_value()->set_string_value("HASH"); + + auto* kv2 = kv_list->add_kv_pairs(); + kv2->set_key("size_semantics"); + kv2->mutable_value()->set_string_value( + "{semantics=\"sum_of_members\", max_weight=4095}"); + + auto* kv3 = kv_list->add_kv_pairs(); + kv3->set_key("member_usage_multiplier"); + kv3->mutable_value()->set_int64_value(4); + + ASSERT_OK_AND_ASSIGN(std::vector modes, + ParseRequiredModesFromActionProfile(ap)); + + ASSERT_EQ(modes.size(), 1); + EXPECT_EQ(modes[0].action_selection_mode(), ActionProfileMode::HASH); + EXPECT_TRUE(modes[0].has_sum_of_members()); + EXPECT_EQ(modes[0].sum_of_members().max_member_weight(), 4095); + EXPECT_EQ(modes[0].resource_usage_multipliers().member_usage_multiplier(), 4); +} + +TEST(ParseRequiredModesFromActionProfileTest, + ParsesSymmetricSumOfWeightsBracedSyntax) { + p4::config::v1::ActionProfile ap; + auto* sa = ap.mutable_preamble()->add_structured_annotations(); + sa->set_name("required_modes"); + auto* kv_list = sa->mutable_kv_pair_list(); + + auto* kv1 = kv_list->add_kv_pairs(); + kv1->set_key("action_selection_mode"); + kv1->mutable_value()->set_string_value("HASH"); + + auto* kv2 = kv_list->add_kv_pairs(); + kv2->set_key("size_semantics"); + kv2->mutable_value()->set_string_value("{\"sum_of_weights\"}"); + + auto* kv3 = kv_list->add_kv_pairs(); + kv3->set_key("member_usage_multiplier"); + kv3->mutable_value()->set_int64_value(1); + + ASSERT_OK_AND_ASSIGN(std::vector modes, + ParseRequiredModesFromActionProfile(ap)); + + ASSERT_EQ(modes.size(), 1); + EXPECT_EQ(modes[0].action_selection_mode(), ActionProfileMode::HASH); + EXPECT_TRUE(modes[0].has_sum_of_weights()); + EXPECT_EQ(modes[0].resource_usage_multipliers().member_usage_multiplier(), 1); +} + +} // namespace +} // namespace pdpi diff --git a/p4_pdpi/p4info_union_lib.cc b/p4_pdpi/p4info_union_lib.cc index 814070d..03ee322 100644 --- a/p4_pdpi/p4info_union_lib.cc +++ b/p4_pdpi/p4info_union_lib.cc @@ -16,6 +16,7 @@ #include #include +#include #include #include "absl/status/status.h" @@ -176,9 +177,18 @@ absl::Status SetUnionFirstRepeatedFieldIntoSecond(const T& fields, for (const auto& field : fields) { bool field_exists = false; for (const auto& unioned_field : unioned_fields) { - if (field == unioned_field) { - field_exists = true; - break; + if constexpr (std::is_base_of_v>) { + if (google::protobuf::util::MessageDifferencer::Equals(field, + unioned_field)) { + field_exists = true; + break; + } + } else { + if (field == unioned_field) { + field_exists = true; + break; + } } } if (!field_exists) { @@ -268,9 +278,8 @@ absl::Status UnionFirstPkgInfoIntoSecond( } // Unions the annotations as though they were sets. Ignores the -// annotation_locations (since we don't currently need them) and asserts that -// there are no structured_annotations (returning an UnimplementedError -// otherwise). Checks that everything else is equal. +// annotation_locations (since we don't currently need them). +// Checks that everything else is equal. // Requires: GetId(preamble) == GetId(unioned_preamble) absl::Status UnionFirstPreambleIntoSecondAssertingIdenticalId( const p4::config::v1::Preamble& preamble, @@ -279,22 +288,10 @@ absl::Status UnionFirstPreambleIntoSecondAssertingIdenticalId( RETURN_IF_ERROR(SetUnionFirstRepeatedFieldIntoSecond( preamble.annotations(), *unioned_preamble.mutable_annotations())); + RETURN_IF_ERROR(SetUnionFirstRepeatedFieldIntoSecond( + preamble.structured_annotations(), + *unioned_preamble.mutable_structured_annotations())); // `annotation_locations` are ignored. - // `structured_annotations` are asserted to be empty. - if (!preamble.structured_annotations().empty() || - !unioned_preamble.structured_annotations().empty()) { - unioned_preamble.mutable_structured_annotations()->MergeFrom( - preamble.structured_annotations()); - return absl::UnimplementedError(absl::Substitute( - "$0 failed since `structured_annotations` was not empty " - "for a field with id '$1'. `structured_annotations` = '$2'", - __func__, preamble.id(), - absl::StrJoin(unioned_preamble.structured_annotations(), ",", - [](std::string* out, - const p4::config::v1::StructuredAnnotation& element) { - return absl::StrAppend(out, element.DebugString()); - }))); - } return absl::OkStatus(); } @@ -394,6 +391,9 @@ absl::Status UnionFirstFieldIntoSecondAssertingIdenticalId( RETURN_IF_ERROR( AssertIdsAreEqualForUnioning(action_profile, unioned_action_profile)); + RETURN_IF_ERROR(UnionFirstPreambleIntoSecondAssertingIdenticalId( + action_profile.preamble(), *unioned_action_profile.mutable_preamble())); + // Selector size semantic relationships: // NOT_SET == kSumOfWeights (P4RT Spec makes these equivalent) // kSumOfWeights is generally less permissive than kSumOfMembers (in terms of @@ -416,10 +416,10 @@ absl::Status UnionFirstFieldIntoSecondAssertingIdenticalId( action_profile.sum_of_members().max_member_weight())); } - if (auto diff_result = DiffMessages( - action_profile, unioned_action_profile, - /*ignored_fields=*/ - {"size", "max_group_size", "sum_of_weights", "sum_of_members"}); + if (auto diff_result = DiffMessages(action_profile, unioned_action_profile, + /*ignored_fields=*/ + {"preamble", "size", "max_group_size", + "sum_of_weights", "sum_of_members"}); diff_result.has_value()) { return absl::InvalidArgumentError(absl::Substitute( "action profiles with identical id '$0' were incompatible. "