diff --git a/dvaas/BUILD.bazel b/dvaas/BUILD.bazel index b1541e7e..6fa44fd6 100644 --- a/dvaas/BUILD.bazel +++ b/dvaas/BUILD.bazel @@ -143,16 +143,12 @@ cc_library( "//gutil:proto", "//gutil:status", "//p4_pdpi:ir_cc_proto", + "//p4_pdpi/packetlib", "//p4_pdpi/packetlib:packetlib_cc_proto", - "@com_google_absl//absl/container:flat_hash_set", + "@com_google_absl//absl/container:btree", "@com_google_absl//absl/status", "@com_google_absl//absl/status:statusor", "@com_google_absl//absl/strings", - "@com_google_absl//absl/strings:str_format", - "@com_google_absl//absl/types:optional", - "@com_google_absl//absl/types:span", - "@com_google_googletest//:gtest", - "@com_google_protobuf//:protobuf", "@com_googlesource_code_re2//:re2", ], ) @@ -251,8 +247,12 @@ cc_test( srcs = ["test_vector_test.cc"], deps = [ ":test_vector", + ":test_vector_cc_proto", "//gutil:status_matchers", + "//gutil:testing", + "//p4_pdpi/packetlib", "//p4_pdpi/packetlib:packetlib_cc_proto", + "@com_google_absl//absl/status", "@com_google_googletest//:gtest_main", ], ) diff --git a/dvaas/test_vector.cc b/dvaas/test_vector.cc index 3f02e55f..e4a771c1 100644 --- a/dvaas/test_vector.cc +++ b/dvaas/test_vector.cc @@ -17,10 +17,14 @@ #include #include +#include "absl/status/status.h" #include "absl/status/statusor.h" +#include "absl/strings/escaping.h" #include "absl/strings/str_cat.h" +#include "dvaas/test_vector.pb.h" #include "gutil/proto.h" #include "gutil/status.h" +#include "p4_pdpi/packetlib/packetlib.h" #include "p4_pdpi/packetlib/packetlib.pb.h" #include "re2/re2.h" @@ -53,4 +57,64 @@ std::ostream& operator<<(std::ostream& os, const SwitchOutput& output) { return os << output.DebugString(); } +absl::Status UpdateTestTag(packetlib::Packet& packet, int new_tag) { + // Make a new input packet with updated payload. + std::string new_payload = packet.payload(); + if (!RE2::Replace(&new_payload, *kTestPacketIdRegexp, + MakeTestPacketTagFromUniqueId(new_tag))) { + return gutil::InvalidArgumentErrorBuilder() + << "Test packets must contain a tag of the form '" + << kTestPacketIdRegexp->pattern() + << "' in their payload, but the given packet with payload '" + << packet.payload() << "' does not:\n" + << gutil::PrintTextProto(packet); + } + packet.set_payload(new_payload); + bool status; + ASSIGN_OR_RETURN(status, PadPacketToMinimumSize(packet), + _.LogError() << "Failed to pad packet for tag: " << new_tag); + ASSIGN_OR_RETURN(status, UpdateAllComputedFields(packet), + _.LogError() + << "Failed to update payload for tag: " << new_tag); + + return absl::OkStatus(); +} + +// Returns a serialization of the given `packet` as a hexstring. +absl::StatusOr SerializeAsHexString( + const packetlib::Packet& packet) { + ASSIGN_OR_RETURN(std::string serialized_packet, + packetlib::RawSerializePacket(packet), + _ << " where packet = " << packet.DebugString()); + return absl::BytesToHexString(serialized_packet); +} + +absl::Status UpdateTestTag(PacketTestVector& packet_test_vector, int new_tag) { + // Updates the payload of the SwitchInput. + dvaas::Packet& input_packet = + *packet_test_vector.mutable_input()->mutable_packet(); + RETURN_IF_ERROR(UpdateTestTag(*input_packet.mutable_parsed(), new_tag)); + ASSIGN_OR_RETURN(const std::string input_packet_updated_hexstr, + SerializeAsHexString(input_packet.parsed())); + input_packet.set_hex(input_packet_updated_hexstr); + + // Update the payload of the SwitchOutput. + for (SwitchOutput& output_packet : + *packet_test_vector.mutable_acceptable_outputs()) { + for (dvaas::Packet& packet_out : *output_packet.mutable_packets()) { + RETURN_IF_ERROR(UpdateTestTag(*packet_out.mutable_parsed(), new_tag)); + ASSIGN_OR_RETURN(const std::string packet_out_updated_hexstr, + SerializeAsHexString(packet_out.parsed())); + packet_out.set_hex(packet_out_updated_hexstr); + } + for (dvaas::PacketIn& packet_in : *output_packet.mutable_packet_ins()) { + RETURN_IF_ERROR(UpdateTestTag(*packet_in.mutable_parsed(), new_tag)); + ASSIGN_OR_RETURN(const std::string packet_in_updated_hexstr, + SerializeAsHexString(packet_in.parsed())); + packet_in.set_hex(packet_in_updated_hexstr); + } + } + return absl::OkStatus(); +} + } // namespace dvaas diff --git a/dvaas/test_vector.h b/dvaas/test_vector.h index ffbcad09..f0ac3dd3 100644 --- a/dvaas/test_vector.h +++ b/dvaas/test_vector.h @@ -17,16 +17,12 @@ #include #include -#include #include "absl/container/btree_map.h" -#include "absl/container/flat_hash_set.h" +#include "absl/status/status.h" #include "absl/status/statusor.h" -#include "absl/types/optional.h" #include "dvaas/test_vector.pb.h" -#include "google/protobuf/descriptor.h" #include "p4_pdpi/ir.pb.h" -#include "re2/re2.h" namespace dvaas { @@ -47,6 +43,11 @@ absl::StatusOr ExtractTestPacketTag(const packetlib::Packet& packet); // Needed to make gUnit produce human-readable output in open source. std::ostream& operator<<(std::ostream& os, const SwitchOutput& output); +// Updates the test tag (to `new_tag`) and all computed fields of all packets +// (input, acceptable outputs) in the given `packet_test_vectr`. Returns an +// error if the packets are not already tagged. +absl::Status UpdateTestTag(PacketTestVector& packet_test_vector, int new_tag); + using PacketTestVectorById = absl::btree_map; } // namespace dvaas diff --git a/dvaas/test_vector_test.cc b/dvaas/test_vector_test.cc index 28dd28d6..0aa60947 100644 --- a/dvaas/test_vector_test.cc +++ b/dvaas/test_vector_test.cc @@ -1,8 +1,26 @@ +// 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 "dvaas/test_vector.h" +#include "absl/status/status.h" +#include "dvaas/test_vector.pb.h" #include "gmock/gmock.h" #include "gtest/gtest.h" #include "gutil/status_matchers.h" +#include "gutil/testing.h" +#include "p4_pdpi/packetlib/packetlib.h" #include "p4_pdpi/packetlib/packetlib.pb.h" namespace dvaas { @@ -15,9 +33,248 @@ TEST(MakeTestPacketTag, RoundTripsWithExtractTestPacketTag) { for (int test_packet_id : {0, 1, 2, 42, 1234}) { packetlib::Packet packet; packet.set_payload(MakeTestPacketTagFromUniqueId(test_packet_id)); - EXPECT_THAT(ExtractTestPacketTag(packet), IsOkAndHolds(Eq(test_packet_id))); + ASSERT_THAT(ExtractTestPacketTag(packet), IsOkAndHolds(Eq(test_packet_id))); } } +TEST(UpdateTestPacketTag, YieldsValidPacketTestVectorWithUpdatedTag) { + auto test_vector = gutil::ParseProtoOrDie(R"pb( + input { + type: DATAPLANE + packet { + port: "29" + parsed { + headers { + ethernet_header { + ethernet_destination: "02:1a:0a:d0:62:8b" + ethernet_source: "36:47:08:6f:88:a1" + ethertype: "0x86dd" + } + } + headers { + ipv6_header { + version: "0x6" + dscp: "0x1a" + ecn: "0x0" + flow_label: "0x00000" + payload_length: "0x0025" + next_header: "0x11" + hop_limit: "0x20" + ipv6_source: "2000::" + ipv6_destination: "2800:3f0:c200:800::2000" + } + } + headers { + udp_header { + source_port: "0x0000" + destination_port: "0x03ea" + length: "0x0025" + checksum: "0x3712" + } + } + payload: "test packet #1: Dummy payload" + } + hex: "021a0ad0628b3647086f88a186dd668000000025112020000000000000000000000000000000280003f0c20008000000000000002000000003ea0025371274657374207061636b65742023313a2044756d6d79207061796c6f6164" + } + } + acceptable_outputs { + packets { + port: "12" + parsed { + headers { + ethernet_header { + ethernet_destination: "02:1a:0a:d0:62:8b" + ethernet_source: "36:47:08:6f:88:a1" + ethertype: "0x86dd" + } + } + headers { + ipv6_header { + version: "0x6" + dscp: "0x1a" + ecn: "0x0" + flow_label: "0x00000" + payload_length: "0x0025" + next_header: "0x11" + hop_limit: "0x20" + ipv6_source: "2000::" + ipv6_destination: "2800:3f0:c200:800::2000" + } + } + headers { + udp_header { + source_port: "0x0000" + destination_port: "0x03ea" + length: "0x0025" + checksum: "0x3712" + } + } + payload: "test packet #1: Dummy payload" + } + hex: "021a0ad0628b3647086f88a186dd668000000025112020000000000000000000000000000000280003f0c20008000000000000002000000003ea0025371274657374207061636b65742023313a2044756d6d79207061796c6f6164" + } + packets { + port: "12" + parsed { + headers { + ethernet_header { + ethernet_destination: "02:1a:0a:d0:62:8b" + ethernet_source: "36:47:08:6f:88:a1" + ethertype: "0x86dd" + } + } + headers { + ipv6_header { + version: "0x6" + dscp: "0x1a" + ecn: "0x0" + flow_label: "0x00000" + payload_length: "0x0025" + next_header: "0x11" + hop_limit: "0x20" + ipv6_source: "2000::" + ipv6_destination: "2800:3f0:c200:800::2000" + } + } + headers { + udp_header { + source_port: "0x0000" + destination_port: "0x03ea" + length: "0x0025" + checksum: "0x3712" + } + } + payload: "test packet #1: Dummy payload" + } + hex: "021a0ad0628b3647086f88a186dd668000000025112020000000000000000000000000000000280003f0c20008000000000000002000000003ea0025371274657374207061636b65742023313a2044756d6d79207061796c6f6164" + } + packet_ins { + metadata { + name: "ingress_port" + value { str: "9" } + } + metadata { + name: "target_egress_port" + value { str: "6" } + } + parsed { + headers { + ethernet_header { + ethernet_destination: "02:1a:0a:d0:62:8b" + ethernet_source: "36:47:08:6f:88:a1" + ethertype: "0x86dd" + } + } + headers { + ipv6_header { + version: "0x6" + dscp: "0x1a" + ecn: "0x0" + flow_label: "0x00000" + payload_length: "0x0025" + next_header: "0x11" + hop_limit: "0x20" + ipv6_source: "2000::" + ipv6_destination: "2800:3f0:c200:800::2000" + } + } + headers { + udp_header { + source_port: "0x0000" + destination_port: "0x03ea" + length: "0x0025" + checksum: "0x3712" + } + } + payload: "test packet #1: Dummy payload" + } + hex: "021a0ad0628b3647086f88a186dd668000000025112020000000000000000000000000000000280003f0c20008000000000000002000000003ea0025371274657374207061636b65742023313a2044756d6d79207061796c6f6164" + } + packet_ins { + metadata { + name: "ingress_port" + value { str: "9" } + } + metadata { + name: "target_egress_port" + value { str: "6" } + } + parsed { + headers { + ethernet_header { + ethernet_destination: "02:1a:0a:d0:62:8b" + ethernet_source: "36:47:08:6f:88:a1" + ethertype: "0x86dd" + } + } + headers { + ipv6_header { + version: "0x6" + dscp: "0x1a" + ecn: "0x0" + flow_label: "0x00000" + payload_length: "0x0025" + next_header: "0x11" + hop_limit: "0x20" + ipv6_source: "2000::" + ipv6_destination: "2800:3f0:c200:800::2000" + } + } + headers { + udp_header { + source_port: "0x0000" + destination_port: "0x03ea" + length: "0x0025" + checksum: "0x3712" + } + } + payload: "test packet #1: Dummy payload" + } + hex: "021a0ad0628b3647086f88a186dd668000000025112020000000000000000000000000000000280003f0c20008000000000000002000000003ea0025371274657374207061636b65742023313a2044756d6d79207061796c6f6164" + } + } + )pb"); + PacketTestVector updated_test_vector = test_vector; + int kNewTag = 2000000; + ASSERT_OK(UpdateTestTag(test_vector, kNewTag)); + + // Check if all the tags were updated, including the hex and payload. + ASSERT_OK(packetlib::ValidatePacket(test_vector.input().packet().parsed())); + ASSERT_THAT(ExtractTestPacketTag(test_vector.input().packet().parsed()), + IsOkAndHolds(Eq(kNewTag))); + ASSERT_NE(test_vector.input().packet().hex(), + updated_test_vector.input().packet().hex()); + for (int i = 0; i < test_vector.acceptable_outputs().size(); ++i) { + const SwitchOutput& acceptable_outputs = test_vector.acceptable_outputs(i); + for (int j = 0; j < acceptable_outputs.packets().size(); ++j) { + const Packet& packet = acceptable_outputs.packets(j); + ASSERT_OK(packetlib::ValidatePacket(packet.parsed())); + ASSERT_THAT(ExtractTestPacketTag(packet.parsed()), + IsOkAndHolds(Eq(kNewTag))); + ASSERT_NE(packet.hex(), + updated_test_vector.acceptable_outputs(i).packets(j).hex()); + } + for (int j = 0; j < acceptable_outputs.packet_ins().size(); ++j) { + const PacketIn& packet_in = acceptable_outputs.packet_ins(j); + ASSERT_OK(packetlib::ValidatePacket(packet_in.parsed())); + ASSERT_THAT(ExtractTestPacketTag(packet_in.parsed()), + IsOkAndHolds(Eq(kNewTag))); + ASSERT_NE(packet_in.hex(), + updated_test_vector.acceptable_outputs(i).packet_ins(j).hex()); + } + } +} + +TEST(UpdateTestPacketTag, FailsForPacketWithNoTag) { + auto test_vector = gutil::ParseProtoOrDie(R"pb( + input { + type: DATAPLANE + packet { parsed { payload: "test packet" } } + } + )pb"); + ASSERT_THAT(UpdateTestTag(test_vector, /*new_tag=*/0), + gutil::StatusIs(absl::StatusCode::kInvalidArgument)); +} + } // namespace } // namespace dvaas