From ca8c020f0711a97c168d4ad4a9d4d98869ef8b9f Mon Sep 17 00:00:00 2001 From: divyagayathri-hcl <159437886+divyagayathri-hcl@users.noreply.github.com> Date: Wed, 11 Dec 2024 02:38:13 +0000 Subject: [PATCH 01/13] [Thinkit] Increase stat check timeout to 25 seconds, Clear P4 entries before pushing gNMI config & Increase test tolerance and wait time. (#814) Co-authored-by: smolkaj Co-authored-by: kishanps --- tests/qos/cpu_qos_test.cc | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/tests/qos/cpu_qos_test.cc b/tests/qos/cpu_qos_test.cc index 2011e722..b1b42d3b 100644 --- a/tests/qos/cpu_qos_test.cc +++ b/tests/qos/cpu_qos_test.cc @@ -94,13 +94,13 @@ constexpr int kFrameCheckSequenceSize = 4; // gNMI have to be incremented after a packet hits a queue. // Empirically, for PINS, queue counters currently seem to get updated every // 10 seconds. -constexpr absl::Duration kMaxQueueCounterUpdateTime = absl::Seconds(20); +constexpr absl::Duration kMaxQueueCounterUpdateTime = absl::Seconds(25); // After pushing gNMI config to a switch, the tests sleep for this duration // assuming that the gNMI config will have been fully applied afterwards. // TODO: Instead of hard-coding this time, tests should dynamically // poll the state of the switch to ensure config has been applied. -constexpr absl::Duration kTimeToWaitForGnmiConfigToApply = absl::Seconds(15); +constexpr absl::Duration kTimeToWaitForGnmiConfigToApply = absl::Seconds(30); struct QueueInfo { std::string gnmi_queue_name; // Openconfig queue name. @@ -1040,7 +1040,7 @@ TEST_P(CpuQosTestWithoutIxia, TrafficToLoopackIpGetsMappedToCorrectQueues) { // Level of tolerance for packet rate verification. // This could be parameterized in future if this is platform // dependent. -constexpr float kTolerancePercent = 4.0; +constexpr float kTolerancePercent = 10.0; // Ixia configurations: // 1. Frames sent per second by Ixia. @@ -1116,6 +1116,11 @@ TEST_P(CpuQosTestWithIxia, TestCPUQueueAssignmentAndQueueRateLimit) { thinkit::Switch &sut = generic_testbed->Sut(); + // Set up P4Runtime session. + ASSERT_OK_AND_ASSIGN(std::unique_ptr sut_p4_session, + pdpi::P4RuntimeSession::CreateWithP4InfoAndClearTables( + generic_testbed->Sut(), GetParam().p4info)); + // Push GNMI config. ASSERT_OK(pins_test::PushGnmiConfig(sut, GetParam().gnmi_config)); @@ -1165,6 +1170,11 @@ TEST_P(CpuQosTestWithIxia, TestPuntFlowRateLimitAndCounters) { thinkit::Switch &sut = generic_testbed->Sut(); + // Set up P4Runtime session. + ASSERT_OK_AND_ASSIGN(std::unique_ptr sut_p4_session, + pdpi::P4RuntimeSession::CreateWithP4InfoAndClearTables( + generic_testbed->Sut(), GetParam().p4info)); + // Push GNMI config. ASSERT_OK(pins_test::PushGnmiConfig(sut, GetParam().gnmi_config)); @@ -1263,11 +1273,6 @@ TEST_P(CpuQosTestWithIxia, TestPuntFlowRateLimitAndCounters) { ASSERT_OK(pins_test::ixia::SetDestIPv4(traffic_ref, dest_ip.ToString(), *generic_testbed)); - // Set up P4Runtime session. - ASSERT_OK_AND_ASSIGN(std::unique_ptr sut_p4_session, - pdpi::P4RuntimeSession::CreateWithP4InfoAndClearTables( - generic_testbed->Sut(), GetParam().p4info)); - // Listen for punted packets from the SUT. PacketReceiveInfo packet_receive_info; { From 48cb06a9237fd4582b604ec8c75723cd6d1916ef Mon Sep 17 00:00:00 2001 From: VSuryaprasad-hcl <159443973+VSuryaprasad-HCL@users.noreply.github.com> Date: Wed, 11 Dec 2024 02:39:34 +0000 Subject: [PATCH 02/13] [P4_Symbolic] Remove assumption on argument order matching parameter definition order. Match parameter to argument based on name. k (#816) Co-authored-by: kishanps Co-authored-by: smolkaj --- p4_symbolic/symbolic/BUILD.bazel | 1 + p4_symbolic/symbolic/action.cc | 35 ++-- tests/forwarding/ouroboros_test.cc | 312 +++++++++++++++++++++++++++++ 3 files changed, 333 insertions(+), 15 deletions(-) create mode 100644 tests/forwarding/ouroboros_test.cc diff --git a/p4_symbolic/symbolic/BUILD.bazel b/p4_symbolic/symbolic/BUILD.bazel index c729f154..606102cb 100644 --- a/p4_symbolic/symbolic/BUILD.bazel +++ b/p4_symbolic/symbolic/BUILD.bazel @@ -44,6 +44,7 @@ cc_library( "values.h", ], deps = [ + "//gutil:collections", "//gutil:status", "//p4_pdpi:ir_cc_proto", "//p4_pdpi/internal:ordered_map", diff --git a/p4_symbolic/symbolic/action.cc b/p4_symbolic/symbolic/action.cc index e5b55b4f..f0625a86 100644 --- a/p4_symbolic/symbolic/action.cc +++ b/p4_symbolic/symbolic/action.cc @@ -17,7 +17,9 @@ #include "absl/status/status.h" #include "absl/strings/str_cat.h" #include "absl/strings/str_format.h" +#include "absl/strings/string_view.h" #include "glog/logging.h" +#include "gutil/collections.h" #include "p4_symbolic/symbolic/operators.h" #include "p4_symbolic/symbolic/symbolic.h" #include "p4_symbolic/z3_util.h" @@ -320,27 +322,30 @@ absl::Status EvaluateAction(const ir::Action &action, VLOG(1) << "evaluating action '" << context.action_name << "'"; // Add action parameters to scope. - const auto ¶meters = action.action_definition().params_by_id(); + const auto ¶meters = action.action_definition().params_by_name(); if (static_cast(parameters.size()) != args.size()) { return absl::InvalidArgumentError( absl::StrCat("Action ", action.action_definition().preamble().name(), " called with incompatible number of parameters")); } - // Find each parameter value in argument by parameter's name. - for (size_t i = 1; i <= parameters.size(); i++) { - // parameter id is the same as its index + 1. - const pdpi::IrActionDefinition::IrActionParamDefinition ¶meter = - parameters.at(i); - const std::string ¶meter_name = parameter.param().name(); - const std::string ¶meter_type_name = - parameter.param().type_name().name(); - const int bitwidth = parameter.param().bitwidth(); - ASSIGN_OR_RETURN(z3::expr parameter_value, - values::FormatP4RTValue( - Z3Context(), /*field_name=*/"", parameter_type_name, - args.at(i - 1).value(), bitwidth, translator)); - context.scope.insert({parameter_name, parameter_value}); + // Find each parameter value in arguments by argument name. We should not rely + // on argument order matching param definition order, because the P4 runtime + // spec does not enforce this assumption in implementations, and furthermore + // the spec explicitly states that read entries do not have to preserve the + // order of repeated fields in written entries. + for (const auto &arg : args) { + absl::string_view arg_name = arg.name(); + ASSIGN_OR_RETURN(const pdpi::IrActionDefinition::IrActionParamDefinition + *param_definition, + gutil::FindPtrOrStatus(parameters, arg_name)); + ASSIGN_OR_RETURN( + z3::expr parameter_value, + values::FormatP4RTValue( + Z3Context(), /*field_name=*/"", + param_definition->param().type_name().name(), arg.value(), + param_definition->param().bitwidth(), translator)); + context.scope.insert({param_definition->param().name(), parameter_value}); } // Iterate over the body in order, and evaluate each statement. diff --git a/tests/forwarding/ouroboros_test.cc b/tests/forwarding/ouroboros_test.cc new file mode 100644 index 00000000..24a7cb00 --- /dev/null +++ b/tests/forwarding/ouroboros_test.cc @@ -0,0 +1,312 @@ +// Copyright 2024 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 "tests/forwarding/ouroboros_test.h" + +#include +#include +#include +#include +#include + +#include "absl/algorithm/container.h" +#include "absl/container/btree_map.h" +#include "absl/container/btree_set.h" +#include "absl/container/flat_hash_map.h" +#include "absl/random/random.h" +#include "absl/status/status.h" +#include "absl/status/statusor.h" +#include "absl/strings/escaping.h" +#include "absl/strings/str_cat.h" +#include "absl/strings/str_format.h" +#include "absl/strings/str_join.h" +#include "absl/strings/string_view.h" +#include "absl/strings/substitute.h" +#include "absl/time/clock.h" +#include "absl/time/time.h" +#include "absl/types/span.h" +#include "dvaas/dataplane_validation.h" +#include "glog/logging.h" +#include "gmock/gmock.h" +#include "google/protobuf/descriptor.h" +#include "gtest/gtest.h" +#include "gutil/proto.h" +#include "gutil/status.h" +#include "gutil/status_matchers.h" +#include "lib/gnmi/gnmi_helper.h" +#include "lib/gnmi/openconfig.pb.h" +#include "lib/p4rt/p4rt_port.h" +#include "p4/config/v1/p4info.pb.h" +#include "p4/v1/p4runtime.pb.h" +#include "p4_fuzzer/annotation_util.h" +#include "p4_fuzzer/fuzz_util.h" +#include "p4_fuzzer/fuzzer.pb.h" +#include "p4_fuzzer/fuzzer_config.h" +#include "p4_fuzzer/switch_state.h" +#include "p4_pdpi/ir.h" +#include "p4_pdpi/ir.pb.h" +#include "p4_pdpi/p4_runtime_session.h" +#include "p4_pdpi/p4_runtime_session_extras.h" +#include "p4_pdpi/packetlib/packetlib.h" +#include "p4_pdpi/packetlib/packetlib.pb.h" +#include "p4_pdpi/pd.h" +#include "p4_pdpi/string_encodings/decimal_string.h" +#include "proto/gnmi/gnmi.grpc.pb.h" +#include "sai_p4/instantiations/google/sai_pd.pb.h" +#include "tests/forwarding/test_vector.h" +#include "tests/forwarding/test_vector.pb.h" +#include "tests/forwarding/util.h" +#include "tests/lib/switch_test_setup_helpers.h" +#include "thinkit/mirror_testbed.h" +#include "thinkit/test_environment.h" + +namespace pins_test { +namespace { + +using ::dvaas::Switch; +using ::p4_fuzzer::FuzzerConfig; + +// -- Auxiliary functions ------------------------------------------------------ + +std::string CreateHeader(absl::string_view title) { + return absl::StrCat(std::string(80, '#'), "\n### ", title, "\n", + std::string(80, '#'), "\n"); +} + +// Reads table entries on `sut` and outputs them into an artifact given by +// `artifact_name`. +absl::Status OutputTableEntriesToArtifact(Switch& sut, + thinkit::TestEnvironment& environment, + absl::string_view artifact_name, + int iteration) { + RETURN_IF_ERROR(environment.AppendToTestArtifact( + artifact_name, + CreateHeader(absl::StrCat("Entries after iteration ", iteration)))); + // Read sorted entries back (for determinism) and store in an artifact. + ASSIGN_OR_RETURN(pdpi::IrTableEntries entries, + pdpi::ReadIrTableEntriesSorted(*sut.p4rt)); + return environment.AppendToTestArtifact(artifact_name, entries.DebugString()); +} + +// Augments the given FuzzerConfig to fit the `sut` and Ouroboros Test by +// replacing the IrP4Info and available ports with those read from the switch +// and setting mutation probability to 0. +absl::Status AugmentFuzzerConfig(Switch& sut, FuzzerConfig& fuzzer_config) { + ASSIGN_OR_RETURN(p4::v1::GetForwardingPipelineConfigResponse response, + pdpi::GetForwardingPipelineConfig(sut.p4rt.get())); + ASSIGN_OR_RETURN(pdpi::IrP4Info ir_info, + pdpi::CreateIrP4Info(response.config().p4info())); + + fuzzer_config.info = ir_info; + ASSIGN_OR_RETURN(fuzzer_config.ports, + pins_test::GetMatchingP4rtPortIds( + *sut.gnmi, pins_test::IsEnabledEthernetInterface)); + fuzzer_config.mutate_update_probability = 0.0; + // Our validator, BMv2, does not support empty action profile groups. + fuzzer_config.no_empty_action_profile_groups = true; + return absl::OkStatus(); +} + +// Creates connections to the SUT and Control switch and configures them with a +// `gnmi_config` and `p4info` (if given). Mirrors the SUTs interfaces on the +// control switch and waits for them to be Up. +// Returns a configured (SUT, Control Switch) pair. +absl::StatusOr> ConfigureMirrorTestbed( + thinkit::MirrorTestbed& testbed, std::optional gnmi_config, + std::optional p4info) { + // Configure both switches and set up gNMI and P4Runtime sessions to them. + Switch sut; + Switch control_switch; + ASSIGN_OR_RETURN(sut.gnmi, testbed.Sut().CreateGnmiStub()); + ASSIGN_OR_RETURN(control_switch.gnmi, + testbed.ControlSwitch().CreateGnmiStub()); + // TODO: Update to use whole ForwardingPipelineConfig when the + // function supports that. + ASSIGN_OR_RETURN( + std::tie(sut.p4rt, control_switch.p4rt), + ConfigureSwitchPairAndReturnP4RuntimeSessionPair( + testbed.Sut(), testbed.ControlSwitch(), gnmi_config, p4info)); + + // Mirror testbed ports. + RETURN_IF_ERROR(MirrorSutP4rtPortIdConfigToControlSwitch(testbed)); + + // Ensure that all enabled ports are up. + RETURN_IF_ERROR(WaitForEnabledInterfacesToBeUp(testbed.Sut())).SetPrepend() + << "expected enabled interfaces on SUT to be up: "; + RETURN_IF_ERROR(WaitForEnabledInterfacesToBeUp(testbed.ControlSwitch())) + .SetPrepend() + << "expected enabled interfaces on control switch to be up: "; + + return std::make_pair(std::move(sut), std::move(control_switch)); +} + +// -- Main functions ----------------------------------------------------------- + +// Generates updates to switch state using the P4-Fuzzer and sends them to the +// switch. +absl::Status FuzzSwitchState(absl::BitGen& gen, Switch& sut, + thinkit::TestEnvironment& environment, + int iteration, const FuzzerConfig& fuzzer_config, + int min_num_updates, + p4_fuzzer::SwitchState& state) { + int num_updates = 0; + int num_fuzzing_cycles = 0; + while (num_updates < min_num_updates) { + num_fuzzing_cycles++; + p4_fuzzer::AnnotatedWriteRequest annotated_request = + p4_fuzzer::FuzzWriteRequest(&gen, fuzzer_config, state); + p4::v1::WriteRequest request = + p4_fuzzer::RemoveAnnotations(annotated_request); + num_updates += request.updates_size(); + + // Send to switch. + ASSIGN_OR_RETURN(pdpi::IrWriteRpcStatus response, + pdpi::SendPiUpdatesAndReturnPerUpdateStatus( + *sut.p4rt, request.updates())); + + RETURN_IF_ERROR(p4_fuzzer::OutputInterleavedRequestAndResponseToArtifact( + environment, /*artifact_name=*/"ouroboros_requests_and_responses.txt", + /*identifying_prefix=*/ + absl::StrCat("Iteration ", iteration, ".", num_fuzzing_cycles), + annotated_request, response)); + + // Update the switch state to reflect any accepted updates. + for (int i = 0; i < request.updates_size(); ++i) { + if (response.rpc_response().statuses(i).code() == google::rpc::OK) { + RETURN_IF_ERROR(state.ApplyUpdate(request.updates(i))); + } + } + } + + constexpr absl::string_view kSwitchStateArtifactName = + "ouroboros_switch_state_after_iterations.txt"; + RETURN_IF_ERROR(environment.AppendToTestArtifact( + kSwitchStateArtifactName, + CreateHeader(absl::StrCat("SwitchState after iteration ", iteration)))); + RETURN_IF_ERROR(environment.AppendToTestArtifact(kSwitchStateArtifactName, + state.SwitchStateSummary())); + RETURN_IF_ERROR( + environment.AppendToTestArtifact(kSwitchStateArtifactName, "\n\n")); + return absl::OkStatus(); +} + +TEST_P( + OuroborosTest, + SwitchUnderTestConformsToP4ModelUnderSyntheticStreamOfEntriesAndPackets) { + thinkit::MirrorTestbed& testbed = + GetParam().mirror_testbed->GetMirrorTestbed(); + thinkit::TestEnvironment& environment = testbed.Environment(); + + // Get the start time to determine when to stop the test. + const absl::Time deadline = absl::Now() + GetParam().target_test_time; + + // Store the original control switch gNMI interface config before changing + // it. + // WARNING: This may fail if a gNMI config has not been pushed. + ASSERT_OK_AND_ASSIGN( + std::unique_ptr control_gnmi_stub, + testbed.ControlSwitch().CreateGnmiStub()); + ASSERT_OK_AND_ASSIGN( + const pins_test::openconfig::Interfaces original_control_interfaces, + pins_test::GetInterfacesAsProto(*control_gnmi_stub, + gnmi::GetRequest::CONFIG)); + + Switch sut, control_switch; + ASSERT_OK_AND_ASSIGN(std::tie(sut, control_switch), + ConfigureMirrorTestbed(testbed, GetParam().gnmi_config, + GetParam().config.p4info())); + + ASSERT_OK_AND_ASSIGN(auto ir_p4info, + pdpi::CreateIrP4Info(GetParam().config.p4info())); + ASSERT_OK(environment.AppendToTestArtifact("sut_initial_ir_p4info.txt", + ir_p4info.DebugString())); + + // Set up SUT with initial entries. + ASSERT_OK(pdpi::InstallIrTableEntries(*sut.p4rt, + GetParam().initial_sut_table_entries)); + + FuzzerConfig fuzzer_config = GetParam().fuzzer_config; + ASSERT_OK(AugmentFuzzerConfig(sut, fuzzer_config)); + p4_fuzzer::SwitchState fuzzer_switch_state(fuzzer_config.info); + + absl::BitGen gen; + + LOG(INFO) << "Running Ouroboros for up to " << GetParam().target_test_time + << " or " << GetParam().max_iterations << " iterations."; + + // We keep track of the last iteration time to run the test for as close to + // `target_test_time` while ensuring we don't go above it. We expect that the + // next iteration will take no more than 2x the time of the last iteration. + absl::Duration last_iteration_time = absl::ZeroDuration(); + int iteration = 0; + while (iteration < GetParam().max_iterations && + deadline - absl::Now() > 2 * last_iteration_time) { + iteration++; + SCOPED_TRACE(absl::StrCat("Iteration: ", iteration)); + absl::Time iteration_start_time = absl::Now(); + + ASSERT_OK(FuzzSwitchState(gen, sut, environment, iteration, fuzzer_config, + GetParam().min_num_updates_per_loop, + fuzzer_switch_state)); + + ASSERT_OK(OutputTableEntriesToArtifact( + sut, environment, /*artifact_name=*/"ouroboros_table_entries.txt", + iteration)); + + ASSERT_OK_AND_ASSIGN( + dvaas::ValidationResult validation_result_unused, + GetParam().validator->ValidateDataplane( + sut, control_switch, /*params=*/ + dvaas::DataplaneValidationParams{ + .ignored_fields_for_validation = + GetParam().ignored_fields_for_validation, + .ignored_metadata_for_validation = + GetParam().ignored_packetin_metadata_for_validation, + .artifact_prefix = "ouroboros", + .get_artifact_header = + [=]() { + return CreateHeader( + absl::StrCat("Iteration ", iteration)); + }, + .max_packets_to_send_per_second = + GetParam().max_packets_to_send_per_second, + })); + // Mark that the validation result is currently unused. + (void)validation_result_unused; + + last_iteration_time = absl::Now() - iteration_start_time; + } + + dvaas::PacketStatistics statistics = + GetParam().validator->GetCurrentPacketStatistics(); + + LOG(INFO) << "Ran for " << iteration << " iterations."; + LOG(INFO) << "The last iteration took " << last_iteration_time << "."; + LOG(INFO) << "Total number of packets sent: " + << statistics.total_packets_injected; + LOG(INFO) << "Total number of packets forwarded: " + << statistics.total_packets_forwarded; + LOG(INFO) << "Total number of packets punted: " + << statistics.total_packets_punted; + LOG(INFO) << "Final switch forwarding state is:\n" + << fuzzer_switch_state.SwitchStateSummary(); + + // Restore the original control switch gNMI interface config's P4RT IDs. + ASSERT_OK(pins_test::SetInterfaceP4rtIds(*control_switch.gnmi, + original_control_interfaces)); +} + +} // namespace + +} // namespace pins_test From bf0191a37193ece86e89e9d441f76644cfd6f720 Mon Sep 17 00:00:00 2001 From: VSuryaprasad-hcl <159443973+VSuryaprasad-HCL@users.noreply.github.com> Date: Wed, 11 Dec 2024 02:40:17 +0000 Subject: [PATCH 03/13] [P4_Symbolic] Adding p4_symbolic/ir/expected/fall_through_transition.txt [Evaluate parsers symbolically.] (#817) Co-authored-by: kishanps --- .../ir/expected/fall_through_transition.txt | 404 ++++++++++++++++++ 1 file changed, 404 insertions(+) create mode 100644 p4_symbolic/ir/expected/fall_through_transition.txt diff --git a/p4_symbolic/ir/expected/fall_through_transition.txt b/p4_symbolic/ir/expected/fall_through_transition.txt new file mode 100644 index 00000000..1240f4cf --- /dev/null +++ b/p4_symbolic/ir/expected/fall_through_transition.txt @@ -0,0 +1,404 @@ +headers { + key: "ethernet" + value { + name: "ethernet_t" + id: 2 + fields { + key: "dst_addr" + value { + name: "dst_addr" + bitwidth: 48 + } + } + fields { + key: "ether_type" + value { + name: "ether_type" + bitwidth: 16 + } + } + fields { + key: "src_addr" + value { + name: "src_addr" + bitwidth: 48 + } + } + } +} +headers { + key: "ipv4" + value { + name: "ipv4_t" + id: 3 + fields { + key: "do_not_fragment" + value { + name: "do_not_fragment" + bitwidth: 1 + } + } + fields { + key: "dscp" + value { + name: "dscp" + bitwidth: 6 + } + } + fields { + key: "dst_addr" + value { + name: "dst_addr" + bitwidth: 32 + } + } + fields { + key: "ecn" + value { + name: "ecn" + bitwidth: 2 + } + } + fields { + key: "frag_offset" + value { + name: "frag_offset" + bitwidth: 13 + } + } + fields { + key: "header_checksum" + value { + name: "header_checksum" + bitwidth: 16 + } + } + fields { + key: "identification" + value { + name: "identification" + bitwidth: 16 + } + } + fields { + key: "ihl" + value { + name: "ihl" + bitwidth: 4 + } + } + fields { + key: "more_fragments" + value { + name: "more_fragments" + bitwidth: 1 + } + } + fields { + key: "protocol" + value { + name: "protocol" + bitwidth: 8 + } + } + fields { + key: "reserved" + value { + name: "reserved" + bitwidth: 1 + } + } + fields { + key: "src_addr" + value { + name: "src_addr" + bitwidth: 32 + } + } + fields { + key: "total_len" + value { + name: "total_len" + bitwidth: 16 + } + } + fields { + key: "ttl" + value { + name: "ttl" + bitwidth: 8 + } + } + fields { + key: "version" + value { + name: "version" + bitwidth: 4 + } + } + } +} +headers { + key: "scalars" + value { + name: "scalars_0" + } +} +headers { + key: "standard_metadata" + value { + name: "standard_metadata" + id: 1 + fields { + key: "_padding" + value { + name: "_padding" + bitwidth: 3 + } + } + fields { + key: "checksum_error" + value { + name: "checksum_error" + bitwidth: 1 + } + } + fields { + key: "deq_qdepth" + value { + name: "deq_qdepth" + bitwidth: 19 + } + } + fields { + key: "deq_timedelta" + value { + name: "deq_timedelta" + bitwidth: 32 + } + } + fields { + key: "egress_global_timestamp" + value { + name: "egress_global_timestamp" + bitwidth: 48 + } + } + fields { + key: "egress_port" + value { + name: "egress_port" + bitwidth: 9 + } + } + fields { + key: "egress_rid" + value { + name: "egress_rid" + bitwidth: 16 + } + } + fields { + key: "egress_spec" + value { + name: "egress_spec" + bitwidth: 9 + } + } + fields { + key: "enq_qdepth" + value { + name: "enq_qdepth" + bitwidth: 19 + } + } + fields { + key: "enq_timestamp" + value { + name: "enq_timestamp" + bitwidth: 32 + } + } + fields { + key: "ingress_global_timestamp" + value { + name: "ingress_global_timestamp" + bitwidth: 48 + } + } + fields { + key: "ingress_port" + value { + name: "ingress_port" + bitwidth: 9 + } + } + fields { + key: "instance_type" + value { + name: "instance_type" + bitwidth: 32 + } + } + fields { + key: "mcast_grp" + value { + name: "mcast_grp" + bitwidth: 16 + } + } + fields { + key: "packet_length" + value { + name: "packet_length" + bitwidth: 32 + } + } + fields { + key: "parser_error" + value { + name: "parser_error" + bitwidth: 32 + } + } + fields { + key: "priority" + value { + name: "priority" + bitwidth: 3 + } + } + } +} +pipeline { + key: "egress" + value { + name: "egress" + initial_control: "__END_OF_PIPELINE__" + } +} +pipeline { + key: "ingress" + value { + name: "ingress" + initial_control: "__END_OF_PIPELINE__" + } +} +parsers { + key: "parser" + value { + name: "parser" + initial_state: "start" + parse_states { + key: "parse_ipv4" + value { + name: "parse_ipv4" + parser_ops { + extract { + header { + header_name: "ipv4" + } + } + } + transitions { + default_transition { + next_state: "__END_OF_PARSER__" + } + } + optimized_symbolic_execution_info { + merge_point: "__END_OF_PARSER__" + continue_to_merge_point: true + } + } + } + parse_states { + key: "start" + value { + name: "start" + parser_ops { + extract { + header { + header_name: "ethernet" + } + } + } + transition_key_fields { + field { + header_name: "ethernet" + field_name: "ether_type" + } + } + transitions { + hex_string_transition { + value { + value: "0x0800" + } + mask { + } + next_state: "parse_ipv4" + } + } + optimized_symbolic_execution_info { + merge_point: "parse_ipv4" + continue_to_merge_point: true + } + } + } + } +} +errors { + key: "HeaderTooShort" + value { + name: "HeaderTooShort" + value: 4 + } +} +errors { + key: "NoError" + value { + name: "NoError" + } +} +errors { + key: "NoMatch" + value { + name: "NoMatch" + value: 2 + } +} +errors { + key: "PacketTooShort" + value { + name: "PacketTooShort" + value: 1 + } +} +errors { + key: "ParserInvalidArgument" + value { + name: "ParserInvalidArgument" + value: 6 + } +} +errors { + key: "ParserTimeout" + value { + name: "ParserTimeout" + value: 5 + } +} +errors { + key: "StackOutOfBounds" + value { + name: "StackOutOfBounds" + value: 3 + } +} +deparsers { + key: "deparser" + value { + name: "deparser" + header_order: "ethernet" + } +} + From 3a28fe771f401ff378b669cd7ebfd4179b640df5 Mon Sep 17 00:00:00 2001 From: bibhuprasad-hcl <161687009+bibhuprasad-hcl@users.noreply.github.com> Date: Wed, 11 Dec 2024 02:41:18 +0000 Subject: [PATCH 04/13] gNMI port interface tests. Fix BERT test to allow running with Sandcastle control switches. (#818) Co-authored-by: Srikishen Pondicherry Shanmugam --- tests/gnoi/bert_tests.cc | 40 +++++++++++++++------------ tests/thinkit_gnmi_interface_tests.cc | 2 +- 2 files changed, 23 insertions(+), 19 deletions(-) diff --git a/tests/gnoi/bert_tests.cc b/tests/gnoi/bert_tests.cc index 5014534c..d7308d96 100644 --- a/tests/gnoi/bert_tests.cc +++ b/tests/gnoi/bert_tests.cc @@ -144,7 +144,10 @@ void SendStartBertRequestSuccessfullyOnControlSwitch( // Verify response. ASSERT_THAT(response.per_port_responses(), SizeIs(request.per_port_requests_size())); - EXPECT_EQ(response.bert_operation_id(), request.bert_operation_id()); + + // TODO: Sandcastle does not populate bert_request_id in + // response, remove check for now. + // EXPECT_EQ(response.bert_operation_id(), request.bert_operation_id()); for (int idx = 0; idx < response.per_port_responses_size(); ++idx) { auto per_port_response = response.per_port_responses(idx); @@ -294,10 +297,12 @@ void VerifyBertResultForSuccess( EXPECT_TRUE(bert_result.peer_lock_established()); EXPECT_FALSE(bert_result.peer_lock_lost()); // Check the timestamps to verify if time taken for BERT is between test - // duration and (test duration + 60 seconds). + // duration and (test duration + 60 seconds). Allow duration to be slightly + // less: Sandcastle BERT duration is sometimes just under 180s, by less than + // 1 millisecond. EXPECT_GE(bert_result.last_bert_get_result_timestamp() - bert_result.last_bert_start_timestamp(), - absl::ToInt64Microseconds(kTestDuration)); + absl::ToInt64Microseconds(kTestDuration - absl::Milliseconds(1))); EXPECT_LE(bert_result.last_bert_get_result_timestamp() - bert_result.last_bert_start_timestamp(), absl::ToInt64Microseconds(kTestDuration + absl::Seconds(60))); @@ -433,8 +438,8 @@ void CheckRunningBertAndForceAdminDown( void GetAndVerifyBertResultsWithAdminDownInterfaces( const gnoi::diag::StartBERTRequest& bert_request, const gnoi::diag::GetBERTResultResponse& result_response, - const std::vector& sut_admin_down_interfaces, - const std::vector& control_switch_admin_down_interfaces) { + const std::vector& admin_down_interfaces, + const std::vector& admin_down_on_peer_interfaces) { ASSERT_THAT(result_response.per_port_responses(), SizeIs(bert_request.per_port_requests_size())); for (unsigned idx = 0; idx < result_response.per_port_responses_size(); @@ -446,15 +451,11 @@ void GetAndVerifyBertResultsWithAdminDownInterfaces( result_response.per_port_responses(idx).interface())); LOG(INFO) << "Verifying result for interface: " << interface_name; // Check if interface is part of list where admin state was disabled. - if (IsInterfaceInList(interface_name, sut_admin_down_interfaces) || - IsInterfaceInList(interface_name, - control_switch_admin_down_interfaces)) { + if (IsInterfaceInList(interface_name, admin_down_interfaces) || + IsInterfaceInList(interface_name, admin_down_on_peer_interfaces)) { // Verify BERT failure. EXPECT_EQ(result_response.per_port_responses(idx).status(), gnoi::diag::BERT_STATUS_PEER_LOCK_LOST); - EXPECT_TRUE( - result_response.per_port_responses(idx).peer_lock_established()); - EXPECT_TRUE(result_response.per_port_responses(idx).peer_lock_lost()); continue; } // If it is normal BERT running port, verify normal SUCCESS result. @@ -1070,19 +1071,23 @@ TEST_P(BertTest, RunBertOnMaximumAllowedPorts) { std::back_inserter(sut_interfaces_for_admin_down), num_interfaces_to_disable, std::mt19937(absl::GetFlag(FLAGS_idx_seed))); + // Get control switch interfaces connected to the admin down SUT interfaces. + ASSERT_OK_AND_ASSIGN( + std::vector control_switch_interfaces_peer_admin_down, + GetPeerInterfacesForSutInterfaces(sut_interfaces_for_admin_down)); // Select SUT interfaces whose peer interfaces on control switch will be admin // disabled in the range // [sut_test_interfaces_/2..sut_test_interfaces_.size()). - std::vector sut_interfaces_for_peer_select; + std::vector sut_interfaces_peer_admin_down; std::sample(sut_test_interfaces_.begin() + sut_test_interfaces_.size() / 2, sut_test_interfaces_.end(), - std::back_inserter(sut_interfaces_for_peer_select), + std::back_inserter(sut_interfaces_peer_admin_down), num_interfaces_to_disable, std::mt19937(absl::GetFlag(FLAGS_idx_seed))); // Get control switch interfaces for admin disable. ASSERT_OK_AND_ASSIGN( std::vector control_switch_interfaces_for_admin_down, - GetPeerInterfacesForSutInterfaces(sut_interfaces_for_peer_select)); + GetPeerInterfacesForSutInterfaces(sut_interfaces_peer_admin_down)); LOG(INFO) << "Starting BERT on " << sut_test_interfaces_.size() << " {SUT, control_device} links: "; @@ -1149,7 +1154,7 @@ TEST_P(BertTest, RunBertOnMaximumAllowedPorts) { &bert_response_sut)); ASSERT_NO_FATAL_FAILURE(GetAndVerifyBertResultsWithAdminDownInterfaces( bert_request_sut, bert_response_sut, sut_interfaces_for_admin_down, - control_switch_interfaces_for_admin_down)); + sut_interfaces_peer_admin_down)); // Get the BERT result from control switch and verify it. LOG(INFO) << "Verify BERT results on control switch interfaces."; ASSERT_OK_AND_ASSIGN( @@ -1158,7 +1163,8 @@ TEST_P(BertTest, RunBertOnMaximumAllowedPorts) { GetBertResultRequestFromStartRequest(bert_request_control_switch))); ASSERT_NO_FATAL_FAILURE(GetAndVerifyBertResultsWithAdminDownInterfaces( bert_request_control_switch, bert_response_control_switch, - sut_interfaces_for_admin_down, control_switch_interfaces_for_admin_down)); + control_switch_interfaces_for_admin_down, + control_switch_interfaces_peer_admin_down)); // Enable admin state on SUT and control switch interfaces. ASSERT_NO_FATAL_FAILURE(SetAdminStateOnInterfaceList( @@ -1336,8 +1342,6 @@ TEST_P(BertTest, StopBertSucceeds) { SizeIs(bert_request_control_switch.per_port_requests_size())); EXPECT_EQ(response.per_port_responses(0).status(), gnoi::diag::BERT_STATUS_PEER_LOCK_LOST); - EXPECT_TRUE(response.per_port_responses(0).peer_lock_established()); - EXPECT_TRUE(response.per_port_responses(0).peer_lock_lost()); } ASSERT_OK( diff --git a/tests/thinkit_gnmi_interface_tests.cc b/tests/thinkit_gnmi_interface_tests.cc index 0b330d11..c8e34e0b 100644 --- a/tests/thinkit_gnmi_interface_tests.cc +++ b/tests/thinkit_gnmi_interface_tests.cc @@ -152,7 +152,7 @@ void BreakoutDuringPortInUse(thinkit::Switch& sut, EXPECT_THAT(status.error_message(), HasSubstr(absl::StrCat( "SET failed: YangToDb_port_breakout_subtree_xfmr: port ", - port_info.port_name, " is in use"))); + in_use_port, " is in use"))); // Get expected port information for new breakout mode. ASSERT_OK_AND_ASSIGN( From 14e8f54e7a391538e44187c6e3f3cb31acc5560e Mon Sep 17 00:00:00 2001 From: bibhuprasad-hcl <161687009+bibhuprasad-hcl@users.noreply.github.com> Date: Wed, 11 Dec 2024 02:42:10 +0000 Subject: [PATCH 05/13] [Thinkit] Skip bert test if there are no sut interfaces to test (#819) Co-authored-by: Srikishen Pondicherry Shanmugam --- tests/gnoi/bert_tests.cc | 35 +++++++++++++++++++++-------------- tests/gnoi/bert_tests.h | 10 +++++----- 2 files changed, 26 insertions(+), 19 deletions(-) diff --git a/tests/gnoi/bert_tests.cc b/tests/gnoi/bert_tests.cc index d7308d96..48aaf4f6 100644 --- a/tests/gnoi/bert_tests.cc +++ b/tests/gnoi/bert_tests.cc @@ -537,8 +537,9 @@ bool IsListPartOfInterfaceList(const std::vector& list, // Test StartBERT with invalid request parameters. TEST_P(BertTest, StartBertFailsIfRequestParametersInvalid) { - ASSERT_NO_FATAL_FAILURE( - InitializeTestEnvironment("c1dcb1cc-4806-45cc-8f8a-676beafde103")); + if (sut_interfaces_.empty()) { + GTEST_SKIP() << "No SUT interfaces to test"; + } thinkit::Switch& sut = generic_testbed_->Sut(); ASSERT_OK( pins_test::PortsUp(sut, absl::Span(sut_interfaces_))); @@ -638,8 +639,9 @@ TEST_P(BertTest, StartBertFailsIfRequestParametersInvalid) { // 2) If StopBERT RPC is requested on a port that is not running BERT, RPC // should fail. TEST_P(BertTest, StopBertfailsIfRequestParametersInvalid) { - ASSERT_NO_FATAL_FAILURE( - InitializeTestEnvironment("224db9cf-c709-486d-a0d3-6ab64c1a1e1f")); + if (sut_interfaces_.empty()) { + GTEST_SKIP() << "No SUT interfaces to test"; + } thinkit::Switch& sut = generic_testbed_->Sut(); ASSERT_OK( pins_test::PortsUp(sut, absl::Span(sut_interfaces_))); @@ -708,8 +710,9 @@ TEST_P(BertTest, StopBertfailsIfRequestParametersInvalid) { // 2) If GetBERTResult RPC is requested on a port that never ran BERT before, // RPC should fail. TEST_P(BertTest, GetBertResultFailsIfRequestParametersInvalid) { - ASSERT_NO_FATAL_FAILURE( - InitializeTestEnvironment("4f837d7a-ab44-4694-9ca9-399d576757f4")); + if (sut_interfaces_.empty()) { + GTEST_SKIP() << "No SUT interfaces to test"; + } thinkit::Switch& sut = generic_testbed_->Sut(); ASSERT_OK( pins_test::PortsUp(sut, absl::Span(sut_interfaces_))); @@ -776,8 +779,9 @@ TEST_P(BertTest, GetBertResultFailsIfRequestParametersInvalid) { // Test StartBERT fails if peer port is not running BERT. TEST_P(BertTest, StartBertfailsIfPeerPortNotRunningBert) { - ASSERT_NO_FATAL_FAILURE( - InitializeTestEnvironment("37e48922-0616-4d16-8fd3-975897491956")); + if (sut_interfaces_.empty()) { + GTEST_SKIP() << "No SUT interfaces to test"; + } thinkit::Switch& sut = generic_testbed_->Sut(); ASSERT_OK( pins_test::PortsUp(sut, absl::Span(sut_interfaces_))); @@ -858,8 +862,9 @@ TEST_P(BertTest, StartBertfailsIfPeerPortNotRunningBert) { // 3) Operation id that was used earlier to start the BERT test will fail to // start BERT if used again. TEST_P(BertTest, StartBertSucceeds) { - ASSERT_NO_FATAL_FAILURE( - InitializeTestEnvironment("b31a796a-d078-4d45-b785-f09ec598e05a")); + if (sut_interfaces_.empty()) { + GTEST_SKIP() << "No SUT interfaces to test"; + } thinkit::Switch& sut = generic_testbed_->Sut(); ASSERT_OK( pins_test::PortsUp(sut, absl::Span(sut_interfaces_))); @@ -1040,8 +1045,9 @@ TEST_P(BertTest, StartBertSucceeds) { // This helps us verify a mix of operation during BERT - unexpected software or // hardware errors. TEST_P(BertTest, RunBertOnMaximumAllowedPorts) { - ASSERT_NO_FATAL_FAILURE( - InitializeTestEnvironment("ce526e97-2a62-4044-9dce-8fc74b232e4b")); + if (sut_interfaces_.empty()) { + GTEST_SKIP() << "No SUT interfaces to test"; + } thinkit::Switch& sut = generic_testbed_->Sut(); ASSERT_OK( pins_test::PortsUp(sut, absl::Span(sut_interfaces_))); @@ -1184,8 +1190,9 @@ TEST_P(BertTest, RunBertOnMaximumAllowedPorts) { // stop on SUT and this will cause BERT failure on control switch as control // switch side port will lose lock with its peer port on SUT side. TEST_P(BertTest, StopBertSucceeds) { - ASSERT_NO_FATAL_FAILURE( - InitializeTestEnvironment("be7b6653-51b9-4231-a438-d9589bbcb677")); + if (sut_interfaces_.empty()) { + GTEST_SKIP() << "No SUT interfaces to test"; + } thinkit::Switch& sut = generic_testbed_->Sut(); ASSERT_OK( pins_test::PortsUp(sut, absl::Span(sut_interfaces_))); diff --git a/tests/gnoi/bert_tests.h b/tests/gnoi/bert_tests.h index 9ad3157a..f231a93b 100644 --- a/tests/gnoi/bert_tests.h +++ b/tests/gnoi/bert_tests.h @@ -46,6 +46,11 @@ class BertTest : public thinkit::GenericTestbedFixture<> { absl::flat_hash_map interface_info = generic_testbed_->GetSutInterfaceInfo(); + ASSERT_OK_AND_ASSIGN(sut_gnmi_stub_, + generic_testbed_->Sut().CreateGnmiStub()); + ASSERT_OK_AND_ASSIGN(sut_diag_stub_, + generic_testbed_->Sut().CreateGnoiDiagStub()); + for (const auto &[interface, info] : interface_info) { if ((info.interface_modes.contains(thinkit::CONTROL_INTERFACE)) == thinkit::CONTROL_INTERFACE) { sut_interfaces_.push_back(interface); @@ -53,11 +58,6 @@ class BertTest : public thinkit::GenericTestbedFixture<> { sut_to_peer_interface_mapping_[interface] = info.peer_interface_name; } } - - ASSERT_OK_AND_ASSIGN(sut_gnmi_stub_, - generic_testbed_->Sut().CreateGnmiStub()); - ASSERT_OK_AND_ASSIGN(sut_diag_stub_, - generic_testbed_->Sut().CreateGnoiDiagStub()); } absl::StatusOr> GetPeerInterfacesForSutInterfaces( From 03982a16350e5dc31e5ad1176d2c7f7e77ebb757 Mon Sep 17 00:00:00 2001 From: bibhuprasad-hcl <161687009+bibhuprasad-hcl@users.noreply.github.com> Date: Wed, 11 Dec 2024 02:43:04 +0000 Subject: [PATCH 06/13] [Thinkit] Take p4_info as a test paramter in GenericTextbedFixture. Wait for port ID convergence (#820) Co-authored-by: Srikishen Pondicherry Shanmugam --- tests/gnmi/ethcounter_ixia_test.cc | 40 +++++++++++++++++------------- 1 file changed, 23 insertions(+), 17 deletions(-) diff --git a/tests/gnmi/ethcounter_ixia_test.cc b/tests/gnmi/ethcounter_ixia_test.cc index 788b048d..f1280a1c 100644 --- a/tests/gnmi/ethcounter_ixia_test.cc +++ b/tests/gnmi/ethcounter_ixia_test.cc @@ -73,7 +73,8 @@ constexpr uint32_t kMtu = 1514; // // The rules will punt all matching packets to the CPU. // -absl::Status TrapToCPU(thinkit::Switch &sut) { +absl::Status TrapToCPU(thinkit::Switch &sut, + const p4::config::v1::P4Info &p4info) { auto acl_entry = gutil::ParseProtoOrDie(absl::Substitute( R"pb( acl_ingress_table_entry { @@ -94,18 +95,13 @@ absl::Status TrapToCPU(thinkit::Switch &sut) { std::unique_ptr p4_session, pdpi::P4RuntimeSession::Create(std::move(p4_stub), sut.DeviceId())); - LOG(INFO) << "GetP4Info"; - p4::config::v1::P4Info p4info = - sai::GetP4Info(sai::Instantiation::kMiddleblock); - LOG(INFO) << "CreateIrP4Info"; ASSIGN_OR_RETURN(auto ir_p4info, pdpi::CreateIrP4Info(p4info)); LOG(INFO) << "SetForwardingPipelineConfig"; RETURN_IF_ERROR(pdpi::SetMetadataAndSetForwardingPipelineConfig( p4_session.get(), - p4::v1::SetForwardingPipelineConfigRequest::RECONCILE_AND_COMMIT, - sai::GetP4Info(sai::Instantiation::kMiddleblock))) + p4::v1::SetForwardingPipelineConfigRequest::RECONCILE_AND_COMMIT, p4info)) << "SetForwardingPipelineConfig: Failed to push P4Info: "; LOG(INFO) << "ClearTableEntries"; @@ -138,7 +134,8 @@ absl::Status TrapToCPU(thinkit::Switch &sut) { // use IPv4. // absl::Status ForwardToEgress(uint32_t in_port, uint32_t out_port, bool is_ipv6, - thinkit::Switch &sut) { + thinkit::Switch &sut, + const p4::config::v1::P4Info &p4info) { constexpr absl::string_view kVrfId = "vrf-80"; constexpr absl::string_view kRifOutId = "router-interface-1"; constexpr absl::string_view kRifInId = "router-interface-2"; @@ -236,18 +233,13 @@ absl::Status ForwardToEgress(uint32_t in_port, uint32_t out_port, bool is_ipv6, std::unique_ptr p4_session, pdpi::P4RuntimeSession::Create(std::move(p4_stub), sut.DeviceId())); - LOG(INFO) << "GetP4Info"; - p4::config::v1::P4Info p4info = - sai::GetP4Info(sai::Instantiation::kMiddleblock); - LOG(INFO) << "CreateIrP4Info"; ASSIGN_OR_RETURN(auto ir_p4info, pdpi::CreateIrP4Info(p4info)); LOG(INFO) << "SetForwardingPipelineConfig"; RETURN_IF_ERROR(pdpi::SetMetadataAndSetForwardingPipelineConfig( p4_session.get(), - p4::v1::SetForwardingPipelineConfigRequest::RECONCILE_AND_COMMIT, - sai::GetP4Info(sai::Instantiation::kMiddleblock))) + p4::v1::SetForwardingPipelineConfigRequest::RECONCILE_AND_COMMIT, p4info)) << "SetForwardingPipelineConfig: Failed to push P4Info: "; LOG(INFO) << "ClearTableEntries"; @@ -854,9 +846,14 @@ TEST_P(ExampleIxiaTestFixture, TestIPv4Pkts) { // Set the egress port to loopback mode EXPECT_OK(SetLoopback(true, sut_out_interface, gnmi_stub.get())); + ASSERT_OK(pins_test::WaitForGnmiPortIdConvergence( + generic_testbed->Sut(), GetParam().gnmi_config, + /*timeout=*/absl::Minutes(3))); + // Set up the switch to forward inbound IPv4 packets to the egress port LOG(INFO) << "\n\n----- TestIPv4Pkts: ForwardToEgress -----\n"; - EXPECT_OK(ForwardToEgress(in_id, out_id, false, generic_testbed->Sut())); + EXPECT_OK( + ForwardToEgress(in_id, out_id, false, generic_testbed->Sut(), GetParam().p4_info)); LOG(INFO) << "\n\n----- ForwardToEgress Done -----\n"; // Read some initial counters via GNMI from the SUT @@ -1128,6 +1125,10 @@ TEST_P(ExampleIxiaTestFixture, TestOutDiscards) { auto out_status_speed = CheckPortSpeed(sut_out_interface, gnmi_stub.get()); EXPECT_THAT(out_status_speed, IsOkAndHolds(kSpeed40GB)); + ASSERT_OK(pins_test::WaitForGnmiPortIdConvergence( + generic_testbed->Sut(), GetParam().gnmi_config, + /*timeout=*/absl::Minutes(3))); + // Set up the switch to forward inbound packets to the egress port LOG(INFO) << "\n\n----- ForwardToEgress: TestOutDiscards -----\n"; EXPECT_OK(ForwardToEgress(in_id, out_id, false, generic_testbed->Sut())); @@ -1404,8 +1405,13 @@ TEST_P(ExampleIxiaTestFixture, TestIPv6Pkts) { // Set the egress port to loopback mode EXPECT_OK(SetLoopback(true, sut_out_interface, gnmi_stub.get())); + ASSERT_OK(pins_test::WaitForGnmiPortIdConvergence( + generic_testbed->Sut(), GetParam().gnmi_config, + /*timeout=*/absl::Minutes(3))); + // Set up the switch to forward inbound packets to the egress port - EXPECT_OK(ForwardToEgress(in_id, out_id, true, generic_testbed->Sut())); + EXPECT_OK( + ForwardToEgress(in_id, out_id, true, generic_testbed->Sut(), GetParam().p4_info)); // Read some initial counters via GNMI from the SUT ASSERT_OK_AND_ASSIGN(auto initial_in_counters, @@ -1638,7 +1644,7 @@ TEST_P(ExampleIxiaTestFixture, TestCPUOutDiscards) { // We're doing this to overwhelm the egress port so at least some of the // packets we'll send from the CPU get discarded LOG(INFO) << "\n\n----- TestCPUOutDiscards: TrapToCPU -----\n"; - EXPECT_OK(TrapToCPU(generic_testbed->Sut())); + EXPECT_OK(TrapToCPU(generic_testbed->Sut(), GetParam().p4_info)); LOG(INFO) << "\n\n----- TrapToCPU Done -----\n"; // Read some initial counters via GNMI from the SUT From 0773462dce4c12e38a894b324cdad3ce88d9b504 Mon Sep 17 00:00:00 2001 From: bibhuprasad-hcl <161687009+bibhuprasad-hcl@users.noreply.github.com> Date: Wed, 11 Dec 2024 02:44:33 +0000 Subject: [PATCH 07/13] [Thinkit] Add l3 admit entry for setting up L3 flows. Remove pushing config as part of test. (#821) Co-authored-by: Srikishen Pondicherry Shanmugam --- tests/gnmi/ethcounter_ixia_test.cc | 42 ++++++++++++++++-------------- 1 file changed, 22 insertions(+), 20 deletions(-) diff --git a/tests/gnmi/ethcounter_ixia_test.cc b/tests/gnmi/ethcounter_ixia_test.cc index f1280a1c..d9790ac1 100644 --- a/tests/gnmi/ethcounter_ixia_test.cc +++ b/tests/gnmi/ethcounter_ixia_test.cc @@ -134,11 +134,11 @@ absl::Status TrapToCPU(thinkit::Switch &sut, // use IPv4. // absl::Status ForwardToEgress(uint32_t in_port, uint32_t out_port, bool is_ipv6, + const absl::string_view dest_mac, thinkit::Switch &sut, const p4::config::v1::P4Info &p4info) { constexpr absl::string_view kVrfId = "vrf-80"; constexpr absl::string_view kRifOutId = "router-interface-1"; - constexpr absl::string_view kRifInId = "router-interface-2"; constexpr absl::string_view kNhopId = "nexthop-1"; constexpr absl::string_view kNborIdv4 = "1.1.1.2"; constexpr absl::string_view kNborIdv6 = "fe80::002:02ff:fe02:0202"; @@ -165,17 +165,6 @@ absl::Status ForwardToEgress(uint32_t in_port, uint32_t out_port, bool is_ipv6, )pb", kRifOutId, out_port)); - auto rif_in_entry = gutil::ParseProtoOrDie(absl::Substitute( - R"pb( - router_interface_table_entry { - match { router_interface_id: "$0" } - action { - set_port_and_src_mac { port: "$1" src_mac: "88:55:44:33:22:11" } - } - } - )pb", - kRifInId, in_port)); - auto nbor_entry = gutil::ParseProtoOrDie(absl::Substitute( R"pb( neighbor_table_entry { @@ -224,6 +213,17 @@ absl::Status ForwardToEgress(uint32_t in_port, uint32_t out_port, bool is_ipv6, )pb", kVrfId)); + auto l3_admit_entry = + gutil::ParseProtoOrDie(absl::Substitute( + R"pb( + l3_admit_table_entry { + match { dst_mac { value: "$0" mask: "FF:FF:FF:FF:FF:FF" } } + action { admit_to_l3 {} } + priority: 1 + } + )pb", + dest_mac)); + LOG(INFO) << "p4_stub"; ASSIGN_OR_RETURN(std::unique_ptr p4_stub, sut.CreateP4RuntimeStub()); @@ -252,8 +252,8 @@ absl::Status ForwardToEgress(uint32_t in_port, uint32_t out_port, bool is_ipv6, LOG(INFO) << "for loop"; std::vector pi_entries; for (const auto &pd_entry : - {vrf_entry, rif_out_entry, rif_in_entry, nbor_entry, nhop_entry, - is_ipv6 ? ipv6_entry : ipv4_entry, acl_entry}) { + {vrf_entry, rif_out_entry, nbor_entry, nhop_entry, + is_ipv6 ? ipv6_entry : ipv4_entry, acl_entry, l3_admit_entry}) { LOG(INFO) << "loop"; ASSIGN_OR_RETURN( p4::v1::TableEntry pi_entry, @@ -852,8 +852,9 @@ TEST_P(ExampleIxiaTestFixture, TestIPv4Pkts) { // Set up the switch to forward inbound IPv4 packets to the egress port LOG(INFO) << "\n\n----- TestIPv4Pkts: ForwardToEgress -----\n"; - EXPECT_OK( - ForwardToEgress(in_id, out_id, false, generic_testbed->Sut(), GetParam().p4_info)); + constexpr absl::string_view kDestMac = "02:02:02:02:02:02"; + EXPECT_OK(ForwardToEgress(in_id, out_id, false, kDestMac, + generic_testbed->Sut(), GetParam().p4_info)); LOG(INFO) << "\n\n----- ForwardToEgress Done -----\n"; // Read some initial counters via GNMI from the SUT @@ -901,7 +902,7 @@ TEST_P(ExampleIxiaTestFixture, TestIPv4Pkts) { ASSERT_OK(ixia::SetFrameCount(tref, 90000000, *generic_testbed)); // Set the destination MAC address - ASSERT_OK(ixia::SetDestMac(tref, "02:02:02:02:02:02", *generic_testbed)); + ASSERT_OK(ixia::SetDestMac(tref, kDestMac, *generic_testbed)); // Set the source MAC address ASSERT_OK(ixia::SetSrcMac(tref, "00:01:02:03:04:05", *generic_testbed)); @@ -1410,8 +1411,9 @@ TEST_P(ExampleIxiaTestFixture, TestIPv6Pkts) { /*timeout=*/absl::Minutes(3))); // Set up the switch to forward inbound packets to the egress port - EXPECT_OK( - ForwardToEgress(in_id, out_id, true, generic_testbed->Sut(), GetParam().p4_info)); + constexpr absl::string_view kDestMac = "02:02:02:02:02:02"; + EXPECT_OK(ForwardToEgress(in_id, out_id, true, kDestMac, + generic_testbed->Sut(), GetParam().p4_info)); // Read some initial counters via GNMI from the SUT ASSERT_OK_AND_ASSIGN(auto initial_in_counters, @@ -1457,7 +1459,7 @@ TEST_P(ExampleIxiaTestFixture, TestIPv6Pkts) { // Set the source and destination MAC addresses ASSERT_OK(ixia::SetSrcMac(tref, "00:01:02:03:04:05", *generic_testbed)); - ASSERT_OK(ixia::SetDestMac(tref, "02:02:02:02:02:02", *generic_testbed)); + ASSERT_OK(ixia::SetDestMac(tref, kDestMac, *generic_testbed)); // Add an IPv6 header ASSERT_OK(ixia::AppendIPv6(tref, *generic_testbed)); From 6f5714b5481e441325a0574eeaa1848daa6857d0 Mon Sep 17 00:00:00 2001 From: VSuryaprasad-hcl <159443973+VSuryaprasad-HCL@users.noreply.github.com> Date: Wed, 11 Dec 2024 02:45:33 +0000 Subject: [PATCH 08/13] [P4_Symbolic] Adding p4_symbolic/testdata/parser/fall_through_transition.p4 [Evaluate parsers symbolically.] (#822) Co-authored-by: kishanps --- p4_symbolic/ir/BUILD.bazel | 7 ++ .../ir/expected/fall_through_transition.txt | 7 -- .../parser/fall_through_transition.p4 | 68 +++++++++++++++++++ 3 files changed, 75 insertions(+), 7 deletions(-) create mode 100644 p4_symbolic/testdata/parser/fall_through_transition.p4 diff --git a/p4_symbolic/ir/BUILD.bazel b/p4_symbolic/ir/BUILD.bazel index 8ae8aa81..ca493ddd 100644 --- a/p4_symbolic/ir/BUILD.bazel +++ b/p4_symbolic/ir/BUILD.bazel @@ -243,3 +243,10 @@ ir_parsing_test( p4_deps = ["//p4_symbolic/testdata:common/headers.p4"], p4_program = "//p4_symbolic/testdata:parser/hex_string_transition.p4", ) + +ir_parsing_test( + name = "fall_through_transition_test", + golden_file = "expected/fall_through_transition.txt", + p4_deps = ["//p4_symbolic/testdata:common/headers.p4"], + p4_program = "//p4_symbolic/testdata:parser/fall_through_transition.p4", +) diff --git a/p4_symbolic/ir/expected/fall_through_transition.txt b/p4_symbolic/ir/expected/fall_through_transition.txt index 1240f4cf..08808a10 100644 --- a/p4_symbolic/ir/expected/fall_through_transition.txt +++ b/p4_symbolic/ir/expected/fall_through_transition.txt @@ -394,11 +394,4 @@ errors { value: 3 } } -deparsers { - key: "deparser" - value { - name: "deparser" - header_order: "ethernet" - } -} diff --git a/p4_symbolic/testdata/parser/fall_through_transition.p4 b/p4_symbolic/testdata/parser/fall_through_transition.p4 new file mode 100644 index 00000000..e6f79f92 --- /dev/null +++ b/p4_symbolic/testdata/parser/fall_through_transition.p4 @@ -0,0 +1,68 @@ +#include +#include "../common/headers.p4" + +struct local_metadata_t { + /* empty */ +} + +struct headers_t { + ethernet_t ethernet; + ipv4_t ipv4; +} + +parser packet_parser(packet_in packet, out headers_t headers, + inout local_metadata_t local_metadata, + inout standard_metadata_t standard_metadata) { + state start { + transition parse_ethernet; + } + + state parse_ethernet { + packet.extract(headers.ethernet); + transition select(headers.ethernet.ether_type) { + ETHERTYPE_IPV4: parse_ipv4; + } + } + + state parse_ipv4 { + packet.extract(headers.ipv4); + transition accept; + } +} + +control empty_verify_checksum(inout headers_t headers, + inout local_metadata_t local_metadata) { + apply {} +} // control empty_verify_checksum + +control ingress(inout headers_t headers, + inout local_metadata_t local_metadata, + inout standard_metadata_t standard_metadata) { + apply {} +} // control ingress + +control egress(inout headers_t headers, + inout local_metadata_t local_metadata, + inout standard_metadata_t standard_metadata) { + apply {} +} // control egress + +control empty_compute_checksum(inout headers_t headers, + inout local_metadata_t local_metadata) { + apply {} +} // control empty_compute_checksum + +control packet_deparser(packet_out packet, in headers_t headers) { + apply { + packet.emit(headers.ethernet); + } +} // control packet_deparser + +V1Switch( + packet_parser(), + empty_verify_checksum(), + ingress(), + egress(), + empty_compute_checksum(), + packet_deparser() +) main; From f89f776bd1e7962b67ac2c4f5fa840637c8794d5 Mon Sep 17 00:00:00 2001 From: VSuryaprasad-hcl <159443973+VSuryaprasad-HCL@users.noreply.github.com> Date: Wed, 11 Dec 2024 02:46:30 +0000 Subject: [PATCH 09/13] [P4_Symbolic] Adding p4_symbolic/symbolic/parser.h [Evaluate parsers symbolically.] (#823) Co-authored-by: kishanps --- p4_symbolic/symbolic/parser.h | 50 +++++++++++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) create mode 100644 p4_symbolic/symbolic/parser.h diff --git a/p4_symbolic/symbolic/parser.h b/p4_symbolic/symbolic/parser.h new file mode 100644 index 00000000..b069089f --- /dev/null +++ b/p4_symbolic/symbolic/parser.h @@ -0,0 +1,50 @@ +// Copyright 2024 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 PINS_P4_SYMBOLIC_SYMBOLIC_PARSER_H_ +#define PINS_P4_SYMBOLIC_SYMBOLIC_PARSER_H_ + +#include "p4_symbolic/ir/ir.pb.h" +#include "p4_symbolic/symbolic/symbolic.h" +#include "z3++.h" + +namespace p4_symbolic { +namespace symbolic { +namespace parser { + +// Returns a Z3 bit-vector representing the `parser_error` field value of the +// given `error_name`. +absl::StatusOr GetErrorCodeExpression(const ir::P4Program &program, + const std::string &error_name); + +// Evaluates all parsers in the P4 program and returns a map of symbolic headers +// that captures the effect of parser execution given the `ingress_headers`. +// +// Returns error if a header field is not a free bit-vector variable upon +// extracting its header, which could mean the header is extracted twice. We +// require extracting to the same header happens at most once to avoid loops. +// +// If a parser error occurs, the `standard_metadata.parser_error` field will be +// set to the corresponding error code and the function returns immediately. The +// current implementation may only result in 2 types of parser error, NoError +// and NoMatch. +absl::StatusOr EvaluateParsers( + const ir::P4Program &program, + const SymbolicPerPacketState &ingress_headers); + +} // namespace parser +} // namespace symbolic +} // namespace p4_symbolic + +#endif // PINS_P4_SYMBOLIC_SYMBOLIC_PARSER_H_ From 80bedd1031d1586d4266d84ed9155c15c50ec68e Mon Sep 17 00:00:00 2001 From: VSuryaprasad-hcl <159443973+VSuryaprasad-HCL@users.noreply.github.com> Date: Wed, 11 Dec 2024 02:47:22 +0000 Subject: [PATCH 10/13] [P4_Symbolic] Adding p4_symbolic/symbolic/parser.cc [Evaluate parsers symbolically.] (#824) Co-authored-by: kishanps --- p4_symbolic/symbolic/parser.cc | 333 +++++++++++++++++++++++++++++++++ 1 file changed, 333 insertions(+) create mode 100644 p4_symbolic/symbolic/parser.cc diff --git a/p4_symbolic/symbolic/parser.cc b/p4_symbolic/symbolic/parser.cc new file mode 100644 index 00000000..f0c9fa3c --- /dev/null +++ b/p4_symbolic/symbolic/parser.cc @@ -0,0 +1,333 @@ +// Copyright 2024 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_symbolic/symbolic/parser.h" + +#include +#include + +#include "absl/status/status.h" +#include "absl/strings/str_format.h" +#include "gutil/status.h" +#include "p4_pdpi/internal/ordered_map.h" +#include "p4_symbolic/ir/ir.h" +#include "p4_symbolic/ir/ir.pb.h" +#include "p4_symbolic/symbolic/action.h" +#include "p4_symbolic/symbolic/operators.h" +#include "p4_symbolic/symbolic/symbolic.h" +#include "p4_symbolic/symbolic/util.h" +#include "p4_symbolic/z3_util.h" +#include "z3++.h" + +namespace p4_symbolic::symbolic::parser { + +namespace { + +// Evaluates the given "extract" parser operation. +// We assume that the input packet has enough bits for the extract operation, +// and so the operation never fails. The symbolic evaluation is implemented by +// setting the `$valid$` and `$extracted$` fields to `guard` and then verifying +// that all fields within the extracted header are free bit-vector variables. +// Reference: go/p4-symbolic-parser-support. +absl::Status EvaluateExtractParserOperation( + const ir::P4Program &program, + const ir::ParserOperation::Extract &extract_op, + SymbolicPerPacketState &state, const z3::expr &guard) { + // The extracted header must exists. + const std::string &header_name = extract_op.header().header_name(); + auto it = program.headers().find(header_name); + if (it == program.headers().end()) { + return gutil::NotFoundErrorBuilder() << "Header not found: " << header_name; + } + + // Set the "valid" and "extracted" fields of the header to `guard`. + RETURN_IF_ERROR(state.Set(header_name, kValidPseudoField, + Z3Context().bool_val(true), guard)); + RETURN_IF_ERROR(state.Set(header_name, kExtractedPseudoField, + Z3Context().bool_val(true), guard)); + + // Verify if all fields of the header are single, free bit-vector variables. + for (const auto &[field_name, ir_field] : Ordered(it->second.fields())) { + std::string field_full_name = + absl::StrFormat("%s.%s", header_name, field_name); + ASSIGN_OR_RETURN(const z3::expr &field, state.Get(field_full_name)); + if (field.to_string() != field_full_name || !field.is_bv() || + field.get_sort().bv_size() != + static_cast(ir_field.bitwidth())) { + return gutil::InvalidArgumentErrorBuilder() + << "Field '" << field_full_name + << "' should be a free bit-vector. Found: " << field; + } + } + + return absl::OkStatus(); +} + +// Constructs a symbolic expression corresponding to the given R-value of the +// set operation. +absl::StatusOr EvaluateSetOperationRValue( + const ir::ParserOperation::Set &set_op, const SymbolicPerPacketState &state, + const action::ActionContext &context) { + switch (set_op.rvalue_case()) { + case ir::ParserOperation::Set::RvalueCase::kFieldRvalue: { + return action::EvaluateFieldValue(set_op.field_rvalue(), state, context); + } + case ir::ParserOperation::Set::RvalueCase::kHexstrRvalue: { + return action::EvaluateHexStr(set_op.hexstr_rvalue()); + } + case ir::ParserOperation::Set::RvalueCase::kLookaheadRvalue: { + return gutil::UnimplementedErrorBuilder() + << "Lookahead R-values for set operations are not supported."; + } + case ir::ParserOperation::Set::RvalueCase::kExpressionRvalue: { + return action::EvaluateRExpression(set_op.expression_rvalue(), state, + context); + } + default: { + return gutil::InvalidArgumentErrorBuilder() + << "Invalid R-value type of a set operation: " + << set_op.DebugString(); + } + } +} + +// Evaluates the given "set" parser operation. +absl::Status EvaluateSetParserOperation(const ir::ParserOperation::Set &set_op, + SymbolicPerPacketState &state, + const action::ActionContext &context, + const z3::expr &guard) { + // Get the field name of the L-value and the expression of the R-value. + const ir::FieldValue &field_value = set_op.lvalue(); + ASSIGN_OR_RETURN(z3::expr rvalue, + EvaluateSetOperationRValue(set_op, state, context)); + // Set the header field to the symbolic expression. + RETURN_IF_ERROR(state.Set(field_value.header_name(), field_value.field_name(), + rvalue, guard)); + return absl::OkStatus(); +} + +// Evaluates the given "primitive" parser operation. +absl::Status EvaluatePrimitiveParserOperation( + const ir::ParserOperation::Primitive &primitive, + SymbolicPerPacketState &state, action::ActionContext &context, + const z3::expr &guard) { + switch (primitive.statement_case()) { + case ir::ParserOperation::Primitive::StatementCase::kAssignment: { + return action::EvaluateAssignmentStatement(primitive.assignment(), &state, + &context, guard); + } + default: { + return gutil::UnimplementedErrorBuilder() + << "Parse state '" << context.action_name + << "' contains unsupported primitive parser operation: " + << primitive.DebugString(); + } + } + + return absl::OkStatus(); +} + +// Evaluates the given parser operation. +absl::Status EvaluateParserOperation(const ir::P4Program &program, + const ir::ParserOperation &op, + SymbolicPerPacketState &state, + action::ActionContext &context, + const z3::expr &guard) { + switch (op.operation_case()) { + case ir::ParserOperation::OperationCase::kExtract: { + return EvaluateExtractParserOperation(program, op.extract(), state, + guard); + } + case ir::ParserOperation::OperationCase::kSet: { + return EvaluateSetParserOperation(op.set(), state, context, guard); + } + case ir::ParserOperation::OperationCase::kPrimitive: { + return EvaluatePrimitiveParserOperation(op.primitive(), state, context, + guard); + } + default: { + return gutil::InvalidArgumentErrorBuilder() + << "Parser operation must be set as one of [extract, set, " + "primitive]. Found: " + << op.DebugString(); + } + } + + return absl::OkStatus(); +} + +// Constructs the expression that encodes the match condition for the given +// `ir_value` and `ir_mask`. Note that this builds the match condition for one +// transition only regardless of whether the key matches other transitions or +// not. +absl::StatusOr ConstructHexStringMatchCondition( + const ir::HexstrValue &ir_value, const ir::HexstrValue &ir_mask, + const z3::expr &transition_key) { + ASSIGN_OR_RETURN(z3::expr value, action::EvaluateHexStr(ir_value)); + + if (ir_mask.value().empty()) { + // The mask is not set. The key is matched against a single value. + // Reference: + // https://p4.org/p4-spec/docs/P4-16-v-1.2.3.html#sec-singleton-set + return operators::Eq(value, transition_key); + } else { + // The mask is set. A key is said to be matched if and only if (key & mask + // == value & mask). Reference: + // https://p4.org/p4-spec/docs/P4-16-v-1.2.3.html#sec-cubes + ASSIGN_OR_RETURN(z3::expr mask, action::EvaluateHexStr(ir_mask)); + ASSIGN_OR_RETURN(z3::expr masked_value, operators::BitAnd(value, mask)); + ASSIGN_OR_RETURN(z3::expr masked_key, + operators::BitAnd(transition_key, mask)); + return operators::Eq(masked_value, masked_key); + } +} + +// Constructs the match conditions for each transition in the given +// `parse_state`. Note that it builds the match condition of each transition +// independently regardless of whether the transition key in the given +// `parse_state` matches another transition or not. +absl::StatusOr> ConstructMatchConditions( + const ir::ParseState &parse_state, const SymbolicPerPacketState &state, + const z3::expr &guard) { + // Collect all transition key fields to construct the transition key. + z3::expr_vector key_fields(Z3Context()); + + for (const ir::ParserTransitionKeyField &key_field : + parse_state.transition_key_fields()) { + if (key_field.key_field_case() != + ir::ParserTransitionKeyField::KeyFieldCase::kField) { + return gutil::UnimplementedErrorBuilder() + << "Transition key field must be a header field. Found: " + << key_field.DebugString(); + } + + const ir::FieldValue &field_value = key_field.field(); + ASSIGN_OR_RETURN( + z3::expr key_field_expr, + state.Get(field_value.header_name(), field_value.field_name())); + key_fields.push_back(std::move(key_field_expr)); + } + + // Construct the transition key by concatenating all the key fields. + absl::optional transition_key; + if (!key_fields.empty()) { + // This is necessary because if `key_fields` is empty, which can be the case + // for parse states that have only one default transition, `z3::concat` will + // trigger an assertion failure. + transition_key = z3::concat(key_fields); + } + + // Build the match condition for each transition. + std::vector match_conditions; + match_conditions.reserve(parse_state.transitions_size()); + + for (int i = 0; i < parse_state.transitions_size(); ++i) { + const ir::ParserTransition &transition = parse_state.transitions(i); + + switch (transition.transition_case()) { + case ir::ParserTransition::TransitionCase::kDefaultTransition: { + // The P4 compiler should have removed the transitions specified after a + // default transition. Therefore, if there is a default transition in + // the IR, it must be the last transition of the current parse state. + if (i != parse_state.transitions_size() - 1) { + return gutil::InvalidArgumentErrorBuilder() + << "A default transition is not the last transition in a " + "parse state. Found: " + << parse_state.DebugString(); + } + + // The key set of a default transition specified by the keywords + // "default" or "_" is referred to as the "universal set" that contains + // any transition key, which means the match condition is always true. + // Ref: https://p4.org/p4-spec/docs/P4-16-v-1.2.3.html#sec-universal-set + match_conditions.push_back(Z3Context().bool_val(true)); + break; + } + case ir::ParserTransition::TransitionCase::kHexStringTransition: { + // Make sure there is the transition key to be matched against. + if (!transition_key.has_value()) { + return gutil::NotFoundErrorBuilder() + << "No transition key specified but hex string transitions " + "exist."; + } + + ASSIGN_OR_RETURN( + z3::expr match_condition, + ConstructHexStringMatchCondition( + transition.hex_string_transition().value(), + transition.hex_string_transition().mask(), *transition_key)); + match_conditions.push_back(std::move(match_condition)); + break; + } + default: { + return gutil::InvalidArgumentErrorBuilder() + << "Transition type must default or hex string. Found: " + << transition.DebugString(); + } + } + } + + return match_conditions; +} + +// Constructs the transition guard for each transition given their +// `match_conditions`. The transition guard of a given transition `i` is defined +// as: `guard` && match_conditions[i] && (!match_conditions[j] for all j < i). +// Namely, the match condition of the given transition `i` is true, while the +// match conditions of all previous (higher-priority) transitions are false. +// This ensures that the transition guards of all transitions from the same +// parse state are mutual exclusive. +std::vector ConstructTransitionGuards( + const std::vector &match_conditions, const z3::expr &guard) { + std::vector transition_guards; + transition_guards.reserve(match_conditions.size()); + z3::expr cumulative_reachability_condition = guard; + + for (const auto &match_condition : match_conditions) { + transition_guards.push_back(cumulative_reachability_condition && + match_condition); + cumulative_reachability_condition = + cumulative_reachability_condition && (!match_condition); + } + + return transition_guards; +} + +// Constructs the fall-through guard. The fall-through guard encodes the path +// condition where all previous transitions in a "select" expression did not get +// matched with the transition key, which may happen, for example, in the +// following P4 program, if the `ether_type` of the input packet does not match +// any of the specified match conditions. +// +// ```p4 +// state parse_ethernet { +// packet.extract(header.ethernet); +// transition select(header.ethernet.ether_type) { +// ETHERTYPE_IPV4: parse_ipv4; +// ETHERTYPE_IPV6: parse_ipv6; +// } +// } +// ``` +z3::expr ConstructFallThroughGuard( + const std::vector &match_conditions, const z3::expr &guard) { + z3::expr fall_through_guard = guard; + + for (const auto &match_condition : match_conditions) { + fall_through_guard = fall_through_guard && (!match_condition); + } + + return fall_through_guard; +} + +} // namespace p4_symbolic::symbolic::parser From 83d429bcadf9bc4b6903bbf5969e52a1aab6b841 Mon Sep 17 00:00:00 2001 From: VSuryaprasad-hcl <159443973+VSuryaprasad-HCL@users.noreply.github.com> Date: Wed, 11 Dec 2024 02:48:38 +0000 Subject: [PATCH 11/13] [P4_Symbolic] Modified p4_symbolic/symbolic/parser.cc [Evaluate parsers symbolically.] (#825) Co-authored-by: kishanps --- p4_symbolic/symbolic/parser.cc | 171 +++++++++++++++++++++++++++++++++ 1 file changed, 171 insertions(+) diff --git a/p4_symbolic/symbolic/parser.cc b/p4_symbolic/symbolic/parser.cc index f0c9fa3c..c08967b5 100644 --- a/p4_symbolic/symbolic/parser.cc +++ b/p4_symbolic/symbolic/parser.cc @@ -330,4 +330,175 @@ z3::expr ConstructFallThroughGuard( return fall_through_guard; } +// Obtains the next state name of the given `transition`. +absl::StatusOr GetNextState( + const ir::ParserTransition &transition) { + switch (transition.transition_case()) { + case ir::ParserTransition::TransitionCase::kDefaultTransition: { + return transition.default_transition().next_state(); + } + case ir::ParserTransition::TransitionCase::kHexStringTransition: { + return transition.hex_string_transition().next_state(); + } + default: { + return gutil::InvalidArgumentErrorBuilder() + << "Transition type must default or hex string. Found: " + << transition.DebugString(); + } + } +} + +absl::Status SetParserError(const ir::P4Program &program, + SymbolicPerPacketState &state, + const std::string &error_name, + const z3::expr &guard) { + ASSIGN_OR_RETURN(z3::expr error_code, + GetErrorCodeExpression(program, error_name)); + return state.Set(kParserErrorField, std::move(error_code), guard); +} + +// Evaluates the parse state with the given `state_name` in the given parser. +absl::Status EvaluateParseState(const ir::P4Program &program, + const ir::Parser &parser, + const std::string &state_name, + SymbolicPerPacketState &state, + const z3::expr &guard) { + // Base case. We got to the end of the parser execution path. + if (state_name == ir::EndOfParser()) { + return absl::OkStatus(); + } + + // Get the parse state with the given state name. + auto it = parser.parse_states().find(state_name); + if (it == parser.parse_states().end()) { + return gutil::NotFoundErrorBuilder() + << "Parse state not found: " << state_name; + } + + const ir::ParseState &parse_state = it->second; + + // We evaluate a parse state by first evaluating all the parser operations + // defined in this state, and then evaluating each transition conditionally, + // where it transitions into the next parse state recursively. For optimized + // symbolic execution (go/optimized-symbolic-execution), we evaluate each + // transition conditionally only when the next state of the transition is not + // the merge point. Once all such transitions are evaluated, the execution + // continues at the merge point. The condition of each transition is + // constructed according to the P4 semantics. The match conditions of + // different transitions may overlap, meaning that more than one transition + // may be valid for the same transition key, in which case, the priority of + // the transitions is defined by the order in which they are specified in the + // original P4 program. + // See go/p4-symbolic-parser-support. + // b/285404691: Note that in the current implementation, a parse state may + // still be evaluated more than once. + + // Evaluate the parser operations in this parse state. + action::ActionContext fake_context = {state_name, {}}; + for (const ir::ParserOperation &op : parse_state.parser_ops()) { + RETURN_IF_ERROR( + EvaluateParserOperation(program, op, state, fake_context, guard)); + } + + // Construct the match condition of each transition. + ASSIGN_OR_RETURN(std::vector match_conditions, + ConstructMatchConditions(parse_state, state, guard)); + // Construct the transition guard of each transition. + std::vector transition_guards = + ConstructTransitionGuards(match_conditions, guard); + + const std::string &merge_point = + parse_state.optimized_symbolic_execution_info().merge_point(); + + // Evaluate each next state that is not the merge point. + for (size_t i = 0; i < transition_guards.size(); ++i) { + ASSIGN_OR_RETURN(std::string next_state, + GetNextState(parse_state.transitions(i))); + if (next_state != merge_point) { + RETURN_IF_ERROR(EvaluateParseState(program, parser, next_state, state, + transition_guards[i])); + } + } + + z3::expr merge_point_guard = guard; + + // If the last transition is not a default transition, i.e., the last match + // condition is not TRUE, it is possible that a packet does not match any + // valid transition of the parse state. In that case, the P4 program will mark + // the `standard_metadata.parser_error` as `error.NoMatch`, skip the rest of + // the parser, and then continue to the ingress pipeline directly. + // Reference: https://p4.org/p4-spec/docs/P4-16-v-1.2.3.html#sec-select. + // + // We construct the fall-through guard and then evaluate the fall-through + // scenario. Technically we can always evaluate the fall-through scenario and + // encode it in the symbolic state, but if there is a default transition, the + // fall-through guard will always be evaluated to FALSE, so we evaluate this + // conditionally to save Z3 some effort. + if (!match_conditions.empty() && + match_conditions.back() != Z3Context().bool_val(true)) { + z3::expr fall_through_guard = + ConstructFallThroughGuard(match_conditions, guard); + + // Here we apply the side effect for the fall-through scenario. The "next + // state" in this case will be the end of parser, so there is no need to do + // anything else. The fall-through guard should be sufficient to ensure that + // if the fall-through guard is true, no other transitions will happen. + RETURN_IF_ERROR( + SetParserError(program, state, "NoMatch", fall_through_guard)); + + // If a fall-through is possible, the `merge_point_guard` should be the + // negation of the `fall_through_guard` under the current `guard`. + merge_point_guard = guard && !fall_through_guard; + } + + // Continue the evaluation at the merge point or terminate this execution + // path, where the merge point is guaranteed to be evaluated through a + // different path. + if (parse_state.optimized_symbolic_execution_info() + .continue_to_merge_point()) { + return EvaluateParseState(program, parser, merge_point, state, + merge_point_guard); + } else { + return absl::OkStatus(); + } +} + +} // namespace + +absl::StatusOr GetErrorCodeExpression(const ir::P4Program &program, + const std::string &error_name) { + // Obtain the error code from the given `error_name`. + auto err_it = program.errors().find(error_name); + if (err_it == program.errors().end()) { + return gutil::NotFoundErrorBuilder() << "Error not found: " << error_name; + } + unsigned int error_code = err_it->second.value(); + + // Obtain the bitwidth of the `parser_error` field + ASSIGN_OR_RETURN(unsigned int bitwidth, + util::GetFieldBitwidth(kParserErrorField, program)); + + return Z3Context().bv_val(error_code, bitwidth); +} + +absl::StatusOr EvaluateParsers( + const ir::P4Program &program, + const SymbolicPerPacketState &ingress_headers) { + // Make sure there is exactly one parser in the P4-Symbolic IR. + if (program.parsers_size() != 1) { + return gutil::InvalidArgumentErrorBuilder() + << "Invalid number of parsers: " << program.parsers_size(); + } + + // Duplicate the symbolic headers for evaluating the parsers. This is to + // preserve the symbolic state of the ingress packet before entering the + // parsers. + SymbolicPerPacketState parsed_headers = ingress_headers; + const ir::Parser &parser = program.parsers().begin()->second; + RETURN_IF_ERROR(EvaluateParseState(program, parser, parser.initial_state(), + parsed_headers, + Z3Context().bool_val(true))); + return parsed_headers; +} + } // namespace p4_symbolic::symbolic::parser From 3627e03d81d3a5109b6183828455c50da18835a8 Mon Sep 17 00:00:00 2001 From: bibhuprasad-hcl <161687009+bibhuprasad-hcl@users.noreply.github.com> Date: Wed, 11 Dec 2024 02:49:57 +0000 Subject: [PATCH 12/13] [Thinkit] Adding p4rt_fixed_table_programming_helper_test.cc to test/lib (#826) Co-authored-by: Srikishen Pondicherry Shanmugam --- tests/lib/BUILD.bazel | 15 ++ ...4rt_fixed_table_programming_helper_test.cc | 223 ++++++++++++++++++ 2 files changed, 238 insertions(+) create mode 100644 tests/lib/p4rt_fixed_table_programming_helper_test.cc diff --git a/tests/lib/BUILD.bazel b/tests/lib/BUILD.bazel index d6e7f410..130c8c4f 100644 --- a/tests/lib/BUILD.bazel +++ b/tests/lib/BUILD.bazel @@ -52,6 +52,21 @@ cc_library( ], ) +cc_test( + name = "p4rt_fixed_table_programming_helper_test", + srcs = ["p4rt_fixed_table_programming_helper_test.cc"], + deps = [ + ":p4rt_fixed_table_programming_helper", + "//gutil:status_matchers", + "//p4_pdpi:ir_cc_proto", + "//sai_p4/instantiations/google:instantiations", + "//sai_p4/instantiations/google:sai_p4info_cc", + "@com_github_p4lang_p4runtime//:p4runtime_cc_proto", + "@com_google_absl//absl/status", + "@com_google_googletest//:gtest_main", + ], +) + cc_library( name = "switch_test_setup_helpers", testonly = True, diff --git a/tests/lib/p4rt_fixed_table_programming_helper_test.cc b/tests/lib/p4rt_fixed_table_programming_helper_test.cc new file mode 100644 index 00000000..f11c2cc0 --- /dev/null +++ b/tests/lib/p4rt_fixed_table_programming_helper_test.cc @@ -0,0 +1,223 @@ +// Copyright 2024 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 "tests/lib/p4rt_fixed_table_programming_helper.h" + +#include "absl/status/status.h" +#include "gmock/gmock.h" +#include "gtest/gtest.h" +#include "gutil/status_matchers.h" +#include "p4/v1/p4runtime.pb.h" +#include "p4_pdpi/ir.pb.h" +#include "sai_p4/instantiations/google/instantiations.h" +#include "sai_p4/instantiations/google/sai_p4info.h" + +namespace gpins { +namespace { + +using ::gutil::StatusIs; +using ::testing::HasSubstr; + +MATCHER_P(HasExactMatch, value, "") { + for (const auto& match_field : arg.entity().table_entry().match()) { + if (match_field.exact().value() == value) { + return true; + } + } + return false; +} + +MATCHER_P2(HasLpmMatch, value, prefix, "") { + for (const auto& match_field : arg.entity().table_entry().match()) { + if (match_field.lpm().value() == value && + match_field.lpm().prefix_len() == prefix) { + return true; + } + } + return false; +} + +MATCHER_P2(HasTernaryMatch, value, mask, "") { + for (const auto& match_field : arg.entity().table_entry().match()) { + if (match_field.ternary().value() == value && + match_field.ternary().mask() == mask) { + return true; + } + } + return false; +} + +MATCHER_P(HasOptionalMatch, value, "") { + for (const auto& match_field : arg.entity().table_entry().match()) { + if (match_field.optional().value() == value) { + return true; + } + } + return false; +} + +MATCHER_P(HasActionParam, value, "") { + for (const auto& action_param : + arg.entity().table_entry().action().action().params()) { + if (action_param.value() == value) { + return true; + } + } + return false; +} + +// The L3 route programming tests verify that a given P4Info can translate all +// the flows needed to do L3 routing. +using L3RouteProgrammingTest = testing::TestWithParam; + +TEST_P(L3RouteProgrammingTest, RouterInterfaceId) { + ASSERT_OK_AND_ASSIGN(p4::v1::Update pi_update, + pins::RouterInterfaceTableUpdate( + sai::GetIrP4Info(GetParam()), p4::v1::Update::INSERT, + /*router_interface_id=*/"rid-0", + /*port=*/"1", + /*src_mac=*/"00:01:02:03:04:05")); + + EXPECT_THAT(pi_update, HasExactMatch("rid-0")); + EXPECT_THAT(pi_update, HasActionParam("1")); + EXPECT_THAT(pi_update, HasActionParam("\001\002\003\004\005")); +} + +TEST_P(L3RouteProgrammingTest, RouterInterfaceIdFailsWithInvalidMacAddress) { + EXPECT_THAT(pins::RouterInterfaceTableUpdate(sai::GetIrP4Info(GetParam()), + p4::v1::Update::INSERT, + /*router_interface_id=*/"rid-0", + /*port=*/"1", + /*src_mac=*/"invalid_format"), + StatusIs(absl::StatusCode::kInvalidArgument, + HasSubstr("Invalid MAC address"))); +} + +TEST_P(L3RouteProgrammingTest, NeighborId) { + ASSERT_OK_AND_ASSIGN( + p4::v1::Update pi_update, + pins::NeighborTableUpdate(sai::GetIrP4Info(GetParam()), p4::v1::Update::INSERT, + /*router_interface_id=*/"rid-1", + /*neighbor_id=*/"::1", + /*dst_mac=*/"00:01:02:03:04:05")); + + EXPECT_THAT(pi_update, HasExactMatch("rid-1")); + EXPECT_THAT(pi_update, HasActionParam("\001\002\003\004\005")); +} + +TEST_P(L3RouteProgrammingTest, NeighborIdFailsWithInvalidMacAddress) { + EXPECT_THAT( + pins::NeighborTableUpdate(sai::GetIrP4Info(GetParam()), p4::v1::Update::INSERT, + /*router_interface_id=*/"rid-1", + /*neighbor_id=*/"peer-1", + /*dst_mac=*/"invalid_format"), + StatusIs(absl::StatusCode::kInvalidArgument, + HasSubstr("Invalid MAC address"))); +} + + +TEST_P(L3RouteProgrammingTest, VrfTableAddFailsWithEmptyId) { + EXPECT_THAT( + pins::VrfTableUpdate(sai::GetIrP4Info(GetParam()), p4::v1::Update::INSERT, + /*vrf_id=*/""), + StatusIs(absl::StatusCode::kInvalidArgument)); +} +TEST_P(L3RouteProgrammingTest, Ipv4TableDoesNotRequireAnAction) { + // The helper class will assume a default (e.g. drop). + ASSERT_OK_AND_ASSIGN( + p4::v1::Update pi_update, + pins::Ipv4TableUpdate(sai::GetIrP4Info(GetParam()), p4::v1::Update::INSERT, + pins::IpTableOptions{.vrf_id = "vrf-0"})); + + EXPECT_THAT(pi_update, HasExactMatch("vrf-0")); +} + +TEST_P(L3RouteProgrammingTest, Ipv4TableWithSetNexthopAction) { + ASSERT_OK_AND_ASSIGN( + p4::v1::Update pi_update, + pins::Ipv4TableUpdate(sai::GetIrP4Info(GetParam()), p4::v1::Update::INSERT, + pins::IpTableOptions{ + .vrf_id = "vrf-0", + .dst_addr_lpm = std::make_pair("10.1.1.1", 32), + .action = pins::IpTableOptions::Action::kSetNextHopId, + .nexthop_id = "nexthop-0", + })); + + EXPECT_THAT(pi_update, HasExactMatch("vrf-0")); + EXPECT_THAT(pi_update, HasLpmMatch("\n\001\001\001", 32)); + EXPECT_THAT(pi_update, HasActionParam("nexthop-0")); +} + +TEST_P(L3RouteProgrammingTest, Ipv4TableEntryFailsWihInvalidParameters) { + EXPECT_THAT( + pins::Ipv4TableUpdate(sai::GetIrP4Info(GetParam()), p4::v1::Update::INSERT, + pins::IpTableOptions{ + .vrf_id = "vrf-0", + .action = pins::IpTableOptions::Action::kDrop, + .nexthop_id = "nexthop-0", + }), + StatusIs(absl::StatusCode::kInvalidArgument, + HasSubstr("Expected 0 parameters"))); +} + +TEST_P(L3RouteProgrammingTest, L3AdmitTableWithoutInPort) { + ASSERT_OK_AND_ASSIGN( + p4::v1::Update pi_update, + pins::L3AdmitTableUpdate(sai::GetIrP4Info(GetParam()), p4::v1::Update::INSERT, + pins::L3AdmitOptions{ + .priority = 10, + .dst_mac = std::make_pair("01:02:03:04:05:06", + "FF:FF:FF:FF:FF:FF"), + })); + + EXPECT_THAT(pi_update, HasTernaryMatch("\001\002\003\004\005\006", + "\377\377\377\377\377\377")); +} + +TEST_P(L3RouteProgrammingTest, L3AdmitTableWithInPort) { + ASSERT_OK_AND_ASSIGN( + p4::v1::Update pi_update, + pins::L3AdmitTableUpdate(sai::GetIrP4Info(GetParam()), p4::v1::Update::INSERT, + pins::L3AdmitOptions{ + .priority = 10, + .dst_mac = std::make_pair("01:02:03:04:05:06", + "FF:FF:FF:FF:FF:FF"), + .in_port = "in-port-1", + })); + + EXPECT_THAT(pi_update, HasOptionalMatch("in-port-1")); + EXPECT_THAT(pi_update, HasTernaryMatch("\001\002\003\004\005\006", + "\377\377\377\377\377\377")); +} + +TEST_P(L3RouteProgrammingTest, L3AdmitTableMustSetPriority) { + EXPECT_THAT( + pins::L3AdmitTableUpdate(sai::GetIrP4Info(GetParam()), p4::v1::Update::INSERT, + pins::L3AdmitOptions{ + .dst_mac = std::make_pair("01:02:03:04:05:06", + "FF:FF:FF:FF:FF:FF"), + }), + StatusIs(absl::StatusCode::kInvalidArgument, + HasSubstr("require a positive non-zero priority"))); +} + +INSTANTIATE_TEST_SUITE_P( + L3RouteProgrammingTestInstance, L3RouteProgrammingTest, + testing::Values(sai::Instantiation::kMiddleblock, + sai::Instantiation::kFabricBorderRouter), + [](const testing::TestParamInfo& param) { + return sai::InstantiationToString(param.param); + }); + +} // namespace +} // namespace gpins From 7eea0f4331445832549888152303adb32262d00e Mon Sep 17 00:00:00 2001 From: VSuryaprasad-hcl <159443973+VSuryaprasad-HCL@users.noreply.github.com> Date: Wed, 11 Dec 2024 02:50:48 +0000 Subject: [PATCH 13/13] [P4_Symbolic] Adding p4_symbolic/symbolic/parser_test.cc [Evaluate parsers symbolically.] (#827) Co-authored-by: kishanps --- p4_symbolic/symbolic/parser_test.cc | 316 ++++++++++++++++++++++++++++ 1 file changed, 316 insertions(+) create mode 100644 p4_symbolic/symbolic/parser_test.cc diff --git a/p4_symbolic/symbolic/parser_test.cc b/p4_symbolic/symbolic/parser_test.cc new file mode 100644 index 00000000..de18af82 --- /dev/null +++ b/p4_symbolic/symbolic/parser_test.cc @@ -0,0 +1,316 @@ +// Copyright 2024 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_symbolic/symbolic/parser.h" + +#include +#include + +#include "absl/container/btree_map.h" +#include "absl/strings/str_replace.h" +#include "absl/strings/string_view.h" +#include "absl/strings/substitute.h" +#include "glog/logging.h" +#include "gmock/gmock.h" +#include "gtest/gtest.h" +#include "gutil/proto.h" +#include "gutil/status_matchers.h" +#include "p4_symbolic/ir/ir.h" +#include "p4_symbolic/ir/ir.pb.h" +#include "p4_symbolic/symbolic/symbolic.h" +#include "p4_symbolic/symbolic/v1model.h" +#include "p4_symbolic/z3_util.h" +#include "z3++.h" + +namespace p4_symbolic::symbolic::parser { +namespace { + +using gutil::StatusIs; + +constexpr absl::string_view kErrors = R"pb( + errors { + key: "NoError" + value { name: "NoError" } + } + errors { + key: "PacketTooShort" + value { name: "PacketTooShort" value: 1 } + } + errors { + key: "NoMatch" + value { name: "NoMatch" value: 2 } + } + errors { + key: "StackOutOfBounds" + value { name: "StackOutOfBounds" value: 3 } + } + errors { + key: "HeaderTooShort" + value { name: "HeaderTooShort" value: 4 } + } + errors { + key: "ParserTimeout" + value { name: "ParserTimeout" value: 5 } + } + errors { + key: "ParserInvalidArgument" + value { name: "ParserInvalidArgument" value: 6 } + } +)pb"; + +constexpr absl::string_view kHeaders = R"pb( + headers { + key: "ethernet" + value { + name: "ethernet_t" + id: 4 + fields { + key: "dst_addr" + value { name: "dst_addr" bitwidth: 48 } + } + fields { + key: "ether_type" + value { name: "ether_type" bitwidth: 16 } + } + fields { + key: "src_addr" + value { name: "src_addr" bitwidth: 48 } + } + } + } + headers { + key: "ipv4" + value { + name: "ipv4_t" + id: 5 + fields { + key: "do_not_fragment" + value { name: "do_not_fragment" bitwidth: 1 } + } + fields { + key: "dscp" + value { name: "dscp" bitwidth: 6 } + } + fields { + key: "dst_addr" + value { name: "dst_addr" bitwidth: 32 } + } + fields { + key: "ecn" + value { name: "ecn" bitwidth: 2 } + } + fields { + key: "frag_offset" + value { name: "frag_offset" bitwidth: 13 } + } + fields { + key: "header_checksum" + value { name: "header_checksum" bitwidth: 16 } + } + fields { + key: "identification" + value { name: "identification" bitwidth: 16 } + } + fields { + key: "ihl" + value { name: "ihl" bitwidth: 4 } + } + fields { + key: "more_fragments" + value { name: "more_fragments" bitwidth: 1 } + } + fields { + key: "protocol" + value { name: "protocol" bitwidth: 8 } + } + fields { + key: "reserved" + value { name: "reserved" bitwidth: 1 } + } + fields { + key: "src_addr" + value { name: "src_addr" bitwidth: 32 } + } + fields { + key: "total_len" + value { name: "total_len" bitwidth: 16 } + } + fields { + key: "ttl" + value { name: "ttl" bitwidth: 8 } + } + fields { + key: "version" + value { name: "version" bitwidth: 4 } + } + } + } + headers { + key: "standard_metadata" + value { + name: "standard_metadata" + id: 1 + fields { + key: "parser_error" + value { name: "parser_error" bitwidth: 32 } + } + } + } +)pb"; + +absl::StatusOr ParseProgramTextProto( + absl::string_view ir_program_text_proto) { + ASSIGN_OR_RETURN( + ir::P4Program program, + gutil::ParseTextProto(absl::StrCat( + kErrors, kHeaders, + absl::StrReplaceAll( + ir_program_text_proto, + { + {"$eoparser", absl::Substitute("\"$0\"", ir::EndOfParser())}, + {"$eop", absl::Substitute("\"$0\"", ir::EndOfPipeline())}, + })))); + return ir::Dataplane{std::move(program)}; +} + +constexpr absl::string_view kProgramWithTwoParsers = R"pb( + parsers { + key: "parser1" + value { + name: "parser1" + initial_state: "start" + parse_states { + key: "start" + value { + name: "start" + transitions { default_transition { next_state: $eoparser } } + } + } + } + } + parsers { + key: "parser2" + value { + name: "parser2" + initial_state: "start" + parse_states { + key: "start" + value { + name: "start" + transitions { default_transition { next_state: $eoparser } } + } + } + } + } +)pb"; + +TEST(EvaluateParsers, ReturnsErrorForMoreThanOneParser) { + ASSERT_OK_AND_ASSIGN(const ir::Dataplane data_plane, + ParseProgramTextProto(kProgramWithTwoParsers)); + ASSERT_OK_AND_ASSIGN(SymbolicPerPacketState ingress_headers, + SymbolicGuardedMap::CreateSymbolicGuardedMap( + data_plane.program.headers())); + EXPECT_THAT(EvaluateParsers(data_plane.program, ingress_headers), + StatusIs(absl::StatusCode::kInvalidArgument, + "Invalid number of parsers: 2")); +} + +TEST(EvaluateParsers, ReturnsErrorForLessThanOneParser) { + ASSERT_OK_AND_ASSIGN(const ir::Dataplane data_plane, + ParseProgramTextProto("")); + ASSERT_OK_AND_ASSIGN(SymbolicPerPacketState ingress_headers, + SymbolicGuardedMap::CreateSymbolicGuardedMap( + data_plane.program.headers())); + EXPECT_THAT(EvaluateParsers(data_plane.program, ingress_headers), + StatusIs(absl::StatusCode::kInvalidArgument, + "Invalid number of parsers: 0")); +} + +constexpr absl::string_view kProgramWithUnknownParseState = R"pb( + parsers { + key: "parser" + value { + name: "parser" + initial_state: "start" + parse_states { + key: "start" + value { + name: "start" + transitions { default_transition { next_state: "unknown" } } + } + } + } + } +)pb"; + +TEST(EvaluateParsers, ReturnsErrorForUnknownParseState) { + ASSERT_OK_AND_ASSIGN(const ir::Dataplane data_plane, + ParseProgramTextProto(kProgramWithUnknownParseState)); + ASSERT_OK_AND_ASSIGN(SymbolicPerPacketState ingress_headers, + SymbolicGuardedMap::CreateSymbolicGuardedMap( + data_plane.program.headers())); + EXPECT_THAT( + EvaluateParsers(data_plane.program, ingress_headers), + StatusIs(absl::StatusCode::kNotFound, "Parse state not found: unknown")); +} + +constexpr absl::string_view kProgramWithUnknownHeaderExtract = R"pb( + parsers { + key: "parser" + value { + name: "parser" + initial_state: "start" + parse_states { + key: "start" + value { + name: "start" + parser_ops { extract { header { header_name: "unknown" } } } + transitions { default_transition { next_state: $eoparser } } + } + } + } + } +)pb"; + +TEST(EvaluateParsers, ReturnsErrorForUnknownHeaderExtract) { + ASSERT_OK_AND_ASSIGN(const ir::Dataplane data_plane, + ParseProgramTextProto(kProgramWithUnknownHeaderExtract)); + ASSERT_OK_AND_ASSIGN(SymbolicPerPacketState ingress_headers, + SymbolicGuardedMap::CreateSymbolicGuardedMap( + data_plane.program.headers())); + EXPECT_THAT( + EvaluateParsers(data_plane.program, ingress_headers), + StatusIs(absl::StatusCode::kNotFound, "Header not found: unknown")); +} + +constexpr absl::string_view kProgramExtractEthernet = R"pb( + parsers { + key: "parser" + value { + name: "parser" + initial_state: "start" + parse_states { + key: "start" + value { + name: "start" + parser_ops { extract { header { header_name: "ethernet" } } } + transitions { default_transition { next_state: $eoparser } } + } + } + } + } +)pb"; + +} // namespace +} // namespace p4_symbolic::symbolic::parser