From 30fc208d8e39583067ff329c7f3a30cb9c37bb39 Mon Sep 17 00:00:00 2001 From: fruffy-g <147277354+fruffy-g@users.noreply.github.com> Date: Wed, 27 Dec 2023 08:56:58 -0500 Subject: [PATCH] [P4Testgen] Add support for @format annotations in P4 programs for protobuf-ir tests (#4276) * Add conversion functions. * Add unit tests. * Review comments. * Remove some duplicate code. --- backends/p4tools/common/lib/format_int.cpp | 80 ++++++++++ backends/p4tools/common/lib/format_int.h | 19 +++ .../modules/testgen/targets/bmv2/concolic.cpp | 23 +-- .../modules/testgen/targets/bmv2/concolic.h | 7 +- .../bmv2/contrib/bmv2_hash/calculations.cpp | 14 +- .../bmv2/contrib/bmv2_hash/calculations.h | 12 +- .../targets/bmv2/test_backend/protobuf_ir.cpp | 143 ++++++++++++++++-- .../targets/bmv2/test_backend/protobuf_ir.h | 22 ++- .../modules/testgen/test/lib/format_int.cpp | 103 +++++++++++++ 9 files changed, 374 insertions(+), 49 deletions(-) diff --git a/backends/p4tools/common/lib/format_int.cpp b/backends/p4tools/common/lib/format_int.cpp index eb93e158177..236342a1715 100644 --- a/backends/p4tools/common/lib/format_int.cpp +++ b/backends/p4tools/common/lib/format_int.cpp @@ -4,6 +4,7 @@ #include #include #include +#include #include #include @@ -268,4 +269,83 @@ std::string insertHexSeparators(const std::string &dataStr) { return insertSeparators(dataStr, "\\x", 2, false); } +std::vector convertBigIntToBytes(const big_int &dataInt, int targetWidthBits, + bool padLeft) { + /// Chunk size is 8 bits, i.e., a byte. + constexpr uint8_t chunkSize = 8U; + + std::vector bytes; + // Convert the input bit width to bytes and round up. + size_t targetWidthBytes = (targetWidthBits + chunkSize - 1) / chunkSize; + boost::multiprecision::export_bits(dataInt, std::back_inserter(bytes), chunkSize); + // If the number of bytes produced by the export is lower than the desired width pad the byte + // array with zeroes. + auto diff = targetWidthBytes - bytes.size(); + if (targetWidthBytes > bytes.size() && diff > 0UL) { + for (size_t i = 0; i < diff; ++i) { + if (padLeft) { + bytes.insert(bytes.begin(), 0); + } else { + bytes.push_back(0); + } + } + } + return bytes; +} + +std::optional convertToIpv4String(const std::vector &byteArray) { + constexpr uint8_t ipv4ByteSize = 4U; + + if (byteArray.size() != ipv4ByteSize) { + ::error("Invalid IPv4 address byte array of size %1%", byteArray.size()); + return std::nullopt; + } + + std::stringstream ss; + for (int i = 0; i < ipv4ByteSize; ++i) { + if (i > 0) { + ss << "."; + } + ss << static_cast(byteArray[i]); + } + return ss.str(); +} + +std::optional convertToIpv6String(const std::vector &byteArray) { + /// Chunk size is 8 bits, i.e., a byte. + constexpr uint8_t chunkSize = 8U; + constexpr uint8_t ipv6ByteSize = 16U; + if (byteArray.size() != ipv6ByteSize) { + ::error("Invalid IPv6 address byte array of size %1%", byteArray.size()); + return std::nullopt; + } + + std::stringstream ss; + for (int i = 0; i < ipv6ByteSize; i += 2) { + if (i > 0) { + ss << ":"; + } + + uint16_t segment = (static_cast(byteArray[i]) << chunkSize) | byteArray[i + 1]; + ss << std::hex << std::setw(4) << std::setfill('0') << segment; + } + return ss.str(); +} + +std::optional convertToMacString(const std::vector &byteArray) { + constexpr uint8_t macByteSize = 6U; + if (byteArray.size() != macByteSize) { + ::error("Invalid MAC address byte array of size %1%", byteArray.size()); + return std::nullopt; + } + + std::stringstream ss; + for (int i = 0; i < macByteSize; ++i) { + if (i > 0) { + ss << ":"; + } + ss << std::hex << std::setw(2) << std::setfill('0') << static_cast(byteArray[i]); + } + return ss.str(); +} } // namespace P4Tools diff --git a/backends/p4tools/common/lib/format_int.h b/backends/p4tools/common/lib/format_int.h index 271aaf43e89..ead545cfa26 100644 --- a/backends/p4tools/common/lib/format_int.h +++ b/backends/p4tools/common/lib/format_int.h @@ -77,6 +77,25 @@ std::string insertOctalSeparators(const std::string &dataStr); /// Takes a hex-formatted string as input and inserts slashes as separators. std::string insertHexSeparators(const std::string &dataStr); +/// Converts a big integer input into a vector of bytes with the size targetWidthBits /8. +/// If the integer value is smaller than the request bit size and @param padLeft is false, padding +/// is added on the right-hand side of the vector (the "least-significant" side). Otherwise, padding +/// is added on the left-hand side, (the "most-significant side") +std::vector convertBigIntToBytes(const big_int &dataInt, int targetWidthBits, + bool padLeft = false); + +/// Convert a byte array into a IPv4 string of the form "x.x.x.x". +/// @returns std::nullopt if the conversion fails. +std::optional convertToIpv4String(const std::vector &byteArray); + +/// Convert a byte array into a IPv4 string of the form "xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:xxxx". +/// @returns std::nullopt if the conversion fails. +std::optional convertToIpv6String(const std::vector &byteArray); + +/// Convert a byte array into a MAC string of the form "xx:xx:xx:xx:xx:xx". +/// @returns std::nullopt if the conversion fails. +std::optional convertToMacString(const std::vector &byteArray); + } // namespace P4Tools #endif /* BACKENDS_P4TOOLS_COMMON_LIB_FORMAT_INT_H_ */ diff --git a/backends/p4tools/modules/testgen/targets/bmv2/concolic.cpp b/backends/p4tools/modules/testgen/targets/bmv2/concolic.cpp index bba6e353006..d6b6f17487f 100644 --- a/backends/p4tools/modules/testgen/targets/bmv2/concolic.cpp +++ b/backends/p4tools/modules/testgen/targets/bmv2/concolic.cpp @@ -13,12 +13,12 @@ #include #include +#include "backends/p4tools/common/lib/format_int.h" #include "backends/p4tools/common/lib/model.h" #include "ir/irutils.h" #include "ir/vector.h" #include "lib/cstring.h" #include "lib/exceptions.h" -#include "lib/log.h" #include "backends/p4tools/modules/testgen/lib/concolic.h" #include "backends/p4tools/modules/testgen/lib/exceptions.h" @@ -28,23 +28,6 @@ namespace P4Tools::P4Testgen::Bmv2 { -std::vector Bmv2Concolic::convertBigIntToBytes(big_int &dataInt, int targetWidthBits) { - std::vector bytes; - // Convert the input bit width to bytes and round up. - size_t targetWidthBytes = (targetWidthBits + CHUNK_SIZE - 1) / CHUNK_SIZE; - boost::multiprecision::export_bits(dataInt, std::back_inserter(bytes), CHUNK_SIZE); - // If the number of bytes produced by the export is lower than the desired width pad the byte - // array with zeroes. - auto diff = targetWidthBytes - bytes.size(); - if (targetWidthBytes > bytes.size() && diff > 0UL) { - for (size_t i = 0; i < diff; ++i) { - bytes.insert(bytes.begin(), 0); - } - } - - return bytes; -} - big_int Bmv2Concolic::computeChecksum(const std::vector &exprList, const Model &finalModel, int algo, Model::ExpressionMap *resolvedExpressions) { @@ -80,7 +63,7 @@ big_int Bmv2Concolic::computeChecksum(const std::vector TESTGEN_UNIMPLEMENTED("Algorithm %1% not implemented for hash.", algo); } - std::vector bytes; + std::vector bytes; if (!exprList.empty()) { const auto *concatExpr = exprList.at(0); for (size_t idx = 1; idx < exprList.size(); idx++) { @@ -102,7 +85,7 @@ big_int Bmv2Concolic::computeChecksum(const std::vector } auto dataInt = IR::getBigIntFromLiteral(finalModel.evaluate(concatExpr, true, resolvedExpressions)); - bytes = convertBigIntToBytes(dataInt, concatWidth); + bytes = convertBigIntToBytes(dataInt, concatWidth, true); } return checksumFun(bytes.data(), bytes.size()); } diff --git a/backends/p4tools/modules/testgen/targets/bmv2/concolic.h b/backends/p4tools/modules/testgen/targets/bmv2/concolic.h index e98a85d482f..127599f18f2 100644 --- a/backends/p4tools/modules/testgen/targets/bmv2/concolic.h +++ b/backends/p4tools/modules/testgen/targets/bmv2/concolic.h @@ -16,7 +16,7 @@ namespace P4Tools::P4Testgen::Bmv2 { class Bmv2Concolic : public Concolic { private: /// In the behavioral model, checksum functions have the following signature. - using ChecksumFunction = std::function; + using ChecksumFunction = std::function; /// Chunk size is 8 bits, i.e., a byte. static constexpr int CHUNK_SIZE = 8; @@ -53,11 +53,6 @@ class Bmv2Concolic : public Concolic { static const IR::Expression *setAndComputePayload( const Model &finalModel, ConcolicVariableMap *resolvedConcolicVariables, int payloadSize); - /// Converts a big integer input into a vector of bytes. This byte vector is fed into the - /// hash function. - /// This function mimics the conversion of data structures to bytes in the behavioral model. - static std::vector convertBigIntToBytes(big_int &dataInt, int targetWidthBits); - public: /// @returns the concolic functions that are implemented for this particular target. static const ConcolicMethodImpls::ImplList *getBmv2ConcolicMethodImpls(); diff --git a/backends/p4tools/modules/testgen/targets/bmv2/contrib/bmv2_hash/calculations.cpp b/backends/p4tools/modules/testgen/targets/bmv2/contrib/bmv2_hash/calculations.cpp index 5f9303ff307..ad8b516f14a 100644 --- a/backends/p4tools/modules/testgen/targets/bmv2/contrib/bmv2_hash/calculations.cpp +++ b/backends/p4tools/modules/testgen/targets/bmv2/contrib/bmv2_hash/calculations.cpp @@ -91,7 +91,7 @@ static uint32_t table_crc32[256] = { 0x89B8FD09, 0x8D79E0BE, 0x803AC667, 0x84FBDBD0, 0x9ABC8BD5, 0x9E7D9662, 0x933EB0BB, 0x97FFAD0C, 0xAFB010B1, 0xAB710D06, 0xA6322BDF, 0xA2F33668, 0xBCB4666D, 0xB8757BDA, 0xB5365D03, 0xB1F740B4}; -uint16_t BMv2Hash::crc16(const char *buf, size_t len) { +uint16_t BMv2Hash::crc16(const uint8_t *buf, size_t len) { uint16_t remainder = 0x0000; uint16_t final_xor_value = 0x0000; for (unsigned int byte = 0; byte < len; byte++) { @@ -101,7 +101,7 @@ uint16_t BMv2Hash::crc16(const char *buf, size_t len) { return reflect(remainder, 16) ^ final_xor_value; } -uint32_t BMv2Hash::crc32(const char *buf, size_t len) { +uint32_t BMv2Hash::crc32(const uint8_t *buf, size_t len) { uint32_t remainder = 0xFFFFFFFF; uint32_t final_xor_value = 0xFFFFFFFF; for (unsigned int byte = 0; byte < len; byte++) { @@ -111,17 +111,17 @@ uint32_t BMv2Hash::crc32(const char *buf, size_t len) { return reflect(remainder, 32) ^ final_xor_value; } -uint16_t BMv2Hash::crcCCITT(const char *buf, size_t len) { +uint16_t BMv2Hash::crcCCITT(const uint8_t *buf, size_t len) { uint16_t remainder = 0xFFFF; uint16_t final_xor_value = 0x0000; for (unsigned int byte = 0; byte < len; byte++) { - int data = static_cast(buf[byte]) ^ (remainder >> 8); + int data = static_cast(buf[byte]) ^ (remainder >> 8); remainder = table_crcCCITT[data] ^ (remainder << 8); } return remainder ^ final_xor_value; } -uint16_t BMv2Hash::csum16(const char *buf, size_t len) { +uint16_t BMv2Hash::csum16(const uint8_t *buf, size_t len) { uint64_t sum = 0; const uint64_t *b = reinterpret_cast(buf); uint32_t t1, t2; @@ -165,7 +165,7 @@ uint16_t BMv2Hash::csum16(const char *buf, size_t len) { return ntohs(~t3); } -uint16_t BMv2Hash::xor16(const char *buf, size_t len) { +uint16_t BMv2Hash::xor16(const uint8_t *buf, size_t len) { uint16_t mask = 0x00ff; uint16_t final_xor_value = 0x0000; unsigned int byte = 0; @@ -185,7 +185,7 @@ uint16_t BMv2Hash::xor16(const char *buf, size_t len) { return final_xor_value; } -uint64_t BMv2Hash::identity(const char *buf, size_t len) { +uint64_t BMv2Hash::identity(const uint8_t *buf, size_t len) { uint64_t res = 0ULL; for (size_t i = 0; i < std::min(sizeof(res), len); i++) { if (i > 0) res <<= 8; diff --git a/backends/p4tools/modules/testgen/targets/bmv2/contrib/bmv2_hash/calculations.h b/backends/p4tools/modules/testgen/targets/bmv2/contrib/bmv2_hash/calculations.h index 87575b2f96b..3218dfe8bcf 100644 --- a/backends/p4tools/modules/testgen/targets/bmv2/contrib/bmv2_hash/calculations.h +++ b/backends/p4tools/modules/testgen/targets/bmv2/contrib/bmv2_hash/calculations.h @@ -48,17 +48,17 @@ class BMv2Hash { } public: - static uint16_t crc16(const char *buf, size_t len); + static uint16_t crc16(const uint8_t *buf, size_t len); - static uint32_t crc32(const char *buf, size_t len); + static uint32_t crc32(const uint8_t *buf, size_t len); - static uint16_t crcCCITT(const char *buf, size_t len); + static uint16_t crcCCITT(const uint8_t *buf, size_t len); - static uint16_t csum16(const char *buf, size_t len); + static uint16_t csum16(const uint8_t *buf, size_t len); - static uint16_t xor16(const char *buf, size_t len); + static uint16_t xor16(const uint8_t *buf, size_t len); - static uint64_t identity(const char *buf, size_t len); + static uint64_t identity(const uint8_t *buf, size_t len); }; } // namespace P4Tools::P4Testgen::Bmv2 diff --git a/backends/p4tools/modules/testgen/targets/bmv2/test_backend/protobuf_ir.cpp b/backends/p4tools/modules/testgen/targets/bmv2/test_backend/protobuf_ir.cpp index 0fe412f3c6c..d708b3e7f93 100644 --- a/backends/p4tools/modules/testgen/targets/bmv2/test_backend/protobuf_ir.cpp +++ b/backends/p4tools/modules/testgen/targets/bmv2/test_backend/protobuf_ir.cpp @@ -9,14 +9,40 @@ #include #include "backends/p4tools/common/lib/util.h" +#include "lib/exceptions.h" #include "lib/log.h" #include "nlohmann/json.hpp" +#include "backends/p4tools/modules/testgen/lib/exceptions.h" +#include "backends/p4tools/modules/testgen/targets/bmv2/test_spec.h" + namespace P4Tools::P4Testgen::Bmv2 { ProtobufIr::ProtobufIr(std::filesystem::path basePath, std::optional seed) : Bmv2TestFramework(std::move(basePath), seed) {} +std::string ProtobufIr::getFormatOfNode(const IR::IAnnotated *node) { + const auto *formatAnnotation = node->getAnnotation("format"); + std::string formatString = "hex_str"; + if (formatAnnotation != nullptr) { + BUG_CHECK(formatAnnotation->body.size() == 1, + "@format annotation can only have one member."); + auto formatString = formatAnnotation->body.at(0)->text; + if (formatString == "IPV4_ADDRESS") { + formatString = "ipv4"; + } else if (formatString == "IPV6_ADDRESS") { + formatString = "ipv6"; + } else if (formatString == "MAC_ADDRESS") { + formatString = "mac"; + } else if (formatString == "HEX_STR") { + formatString = "hex_str"; + } else { + TESTGEN_UNIMPLEMENTED("Unsupported @format string %1%", formatString); + } + } + return formatString; +} + std::string ProtobufIr::getTestCaseTemplate() { static std::string TEST_CASE( R"""(# proto-file: p4testgen_ir.proto @@ -61,7 +87,7 @@ entities { # Match field {{r.field_name}} matches { name: "{{r.field_name}}" - exact: { hex_str: "{{r.value}}" } + exact: { {{r.format}}: "{{r.value}}" } } ## endfor ## for r in rule.rules.optional_matches @@ -69,7 +95,7 @@ entities { matches { name: "{{r.field_name}}" optional { - value: { hex_str: "{{r.value}}" } + value: { {{r.format}}: "{{r.value}}" } } } ## endfor @@ -78,8 +104,8 @@ entities { matches { name: "{{r.field_name}}" range { - low: { hex_str: "{{r.lo}}" } - high: { hex_str: "{{r.hi}}" } + low: { {{r.format}}: "{{r.lo}}" } + high: { {{r.format}}: "{{r.hi}}" } } } ## endfor @@ -88,8 +114,8 @@ entities { matches { name: "{{r.field_name}}" ternary { - value: { hex_str: "{{r.value}}" } - mask: { hex_str: "{{r.mask}}" } + value: { {{r.format}}: "{{r.value}}" } + mask: { {{r.format}}: "{{r.mask}}" } } } ## endfor @@ -98,7 +124,7 @@ entities { matches { name: "{{r.field_name}}" lpm { - value: { hex_str: "{{r.value}}" } + value: { {{r.format}}: "{{r.value}}" } prefix_length: {{r.prefix_len}} } } @@ -113,7 +139,7 @@ entities { # Param {{act_param.param}} params { name: "{{act_param.param}}" - value: { hex_str: "{{act_param.value}}" } + value: { {{act_param.format}}: "{{act_param.value}}" } } ## endfor } @@ -126,7 +152,7 @@ entities { # Param {{act_param.param}} params { name: "{{act_param.param}}" - value: { hex_str: "{{act_param.value}}" } + value: { {{act_param.format}}: "{{act_param.value}}" } } ## endfor } @@ -140,6 +166,105 @@ entities { return TEST_CASE; } +std::string ProtobufIr::formatNetworkValue(const std::string &type, const IR::Expression *value) { + if (type == "hex_str") { + return formatHexExpr(value); + } + // At this point, any value must be a constant. + const auto *constant = value->checkedTo(); + if (type == "ipv4") { + auto convertedString = convertToIpv4String(convertBigIntToBytes(constant->value, 32)); + if (convertedString.has_value()) { + return convertedString.value(); + } + BUG("Failed to convert \"%1%\" to an IPv4 string.", value); + } + if (type == "ipv6") { + auto convertedString = convertToIpv6String(convertBigIntToBytes(constant->value, 128)); + if (convertedString.has_value()) { + return convertedString.value(); + } + BUG("Failed to convert \"%1%\" to an IPv6 string.", value); + } + if (type == "mac") { + auto convertedString = convertToMacString(convertBigIntToBytes(constant->value, 48)); + if (convertedString.has_value()) { + return convertedString.value(); + } + BUG("Failed to convert \"%1%\" to an MAC string.", value); + } + TESTGEN_UNIMPLEMENTED("Unsupported network value type %1%", type); +} + +void ProtobufIr::createKeyMatch(cstring fieldName, const TableMatch &fieldMatch, + inja::json &rulesJson) { + inja::json j; + j["field_name"] = fieldName; + j["format"] = getFormatOfNode(fieldMatch.getKey()); + + if (const auto *elem = fieldMatch.to()) { + j["value"] = formatNetworkValue(j["format"], elem->getEvaluatedValue()).c_str(); + rulesJson["single_exact_matches"].push_back(j); + } else if (const auto *elem = fieldMatch.to()) { + j["lo"] = formatNetworkValue(j["format"], elem->getEvaluatedLow()).c_str(); + j["hi"] = formatNetworkValue(j["format"], elem->getEvaluatedHigh()).c_str(); + rulesJson["range_matches"].push_back(j); + } else if (const auto *elem = fieldMatch.to()) { + j["value"] = formatNetworkValue(j["format"], elem->getEvaluatedValue()).c_str(); + j["mask"] = formatNetworkValue(j["format"], elem->getEvaluatedMask()).c_str(); + rulesJson["ternary_matches"].push_back(j); + // If the rule has a ternary match we need to add the priority. + rulesJson["needs_priority"] = true; + } else if (const auto *elem = fieldMatch.to()) { + j["value"] = formatNetworkValue(j["format"], elem->getEvaluatedValue()).c_str(); + j["prefix_len"] = elem->getEvaluatedPrefixLength()->value.str(); + rulesJson["lpm_matches"].push_back(j); + } else if (const auto *elem = fieldMatch.to()) { + j["value"] = formatNetworkValue(j["format"], elem->getEvaluatedValue()).c_str(); + if (elem->addAsExactMatch()) { + j["use_exact"] = "True"; + } else { + j["use_exact"] = "False"; + } + rulesJson["needs_priority"] = true; + rulesJson["optional_matches"].push_back(j); + } else { + TESTGEN_UNIMPLEMENTED("Unsupported table key match type \"%1%\"", + fieldMatch.getObjectName()); + } +} + +inja::json ProtobufIr::getControlPlaneForTable(const TableMatchMap &matches, + const std::vector &args) const { + inja::json rulesJson; + + rulesJson["single_exact_matches"] = inja::json::array(); + rulesJson["multiple_exact_matches"] = inja::json::array(); + rulesJson["range_matches"] = inja::json::array(); + rulesJson["ternary_matches"] = inja::json::array(); + rulesJson["lpm_matches"] = inja::json::array(); + rulesJson["optional_matches"] = inja::json::array(); + + rulesJson["act_args"] = inja::json::array(); + rulesJson["needs_priority"] = false; + + // Iterate over the match fields and segregate them. + for (const auto &match : matches) { + createKeyMatch(match.first, *match.second, rulesJson); + } + + for (const auto &actArg : args) { + inja::json j; + j["format"] = getFormatOfNode(actArg.getActionParam()); + + j["param"] = actArg.getActionParamName().c_str(); + j["value"] = formatNetworkValue(j["format"], actArg.getEvaluatedValue()).c_str(); + rulesJson["act_args"].push_back(j); + } + + return rulesJson; +} + void ProtobufIr::emitTestcase(const TestSpec *testSpec, cstring selectedBranches, size_t testId, const std::string &testCase, float currentCoverage) { inja::json dataJson; diff --git a/backends/p4tools/modules/testgen/targets/bmv2/test_backend/protobuf_ir.h b/backends/p4tools/modules/testgen/targets/bmv2/test_backend/protobuf_ir.h index bd0a7c950b7..5bc70da8ac3 100644 --- a/backends/p4tools/modules/testgen/targets/bmv2/test_backend/protobuf_ir.h +++ b/backends/p4tools/modules/testgen/targets/bmv2/test_backend/protobuf_ir.h @@ -21,10 +21,18 @@ class ProtobufIr : public Bmv2TestFramework { explicit ProtobufIr(std::filesystem::path basePath, std::optional seed = std::nullopt); - /// Produce a ProtobufIr test. + virtual ~ProtobufIr() = default; + ProtobufIr(const ProtobufIr &) = default; + ProtobufIr(ProtobufIr &&) = default; + ProtobufIr &operator=(const ProtobufIr &) = default; + ProtobufIr &operator=(ProtobufIr &&) = default; + void outputTest(const TestSpec *spec, cstring selectedBranches, size_t testId, float currentCoverage) override; + [[nodiscard]] inja::json getControlPlaneForTable( + const TableMatchMap &matches, const std::vector &args) const override; + private: /// Emits a test case. /// @param testId specifies the test name. @@ -36,6 +44,18 @@ class ProtobufIr : public Bmv2TestFramework { /// @returns the inja test case template as a string. static std::string getTestCaseTemplate(); + + /// Tries to find the @format annotation of a node and, if present, returns the format specified + /// in this annotation. Returns "hex" by default. + static std::string getFormatOfNode(const IR::IAnnotated *node); + + /// Converts an IR::Expression into a formatted string value. The format depends on @param type. + static std::string formatNetworkValue(const std::string &type, const IR::Expression *value); + + /// Fill in @param rulesJson by iterating over @param fieldMatch and creating the appropriate + /// match key. + static void createKeyMatch(cstring fieldName, const TableMatch &fieldMatch, + inja::json &rulesJson); }; } // namespace P4Tools::P4Testgen::Bmv2 diff --git a/backends/p4tools/modules/testgen/test/lib/format_int.cpp b/backends/p4tools/modules/testgen/test/lib/format_int.cpp index fc7089483df..06049ba4b57 100644 --- a/backends/p4tools/modules/testgen/test/lib/format_int.cpp +++ b/backends/p4tools/modules/testgen/test/lib/format_int.cpp @@ -16,6 +16,10 @@ namespace Test { namespace { +using P4Tools::convertBigIntToBytes; +using P4Tools::convertToIpv4String; +using P4Tools::convertToIpv6String; +using P4Tools::convertToMacString; using P4Tools::formatBinExpr; using P4Tools::formatHexExpr; using P4Tools::formatOctalExpr; @@ -250,6 +254,105 @@ TEST_F(FormatTest, Format04) { } } +// Tests for conversion of integers into IPv4 addresses. +TEST_F(FormatTest, TestIPv4Conversion) { + { + std::vector bytes = {192, 168, 1, 1}; + auto result = convertToIpv4String(bytes); + ASSERT_TRUE(result.has_value()); + EXPECT_EQ(result, "192.168.1.1"); + } + { + auto result = convertToIpv4String(convertBigIntToBytes(big_int("3232235777"), 32)); + ASSERT_TRUE(result.has_value()); + EXPECT_EQ(result, "192.168.1.1"); + } + { + auto result = convertToIpv4String(convertBigIntToBytes(big_int("65535"), 32)); + ASSERT_TRUE(result.has_value()); + EXPECT_EQ(result, "255.255.0.0"); + } + { + std::vector bytes = {0x01, 0x00, 0x00, 0x00}; + auto result = convertToIpv4String(bytes); + ASSERT_TRUE(result.has_value()); + EXPECT_EQ(result, "1.0.0.0"); + } + { + auto result = convertToIpv4String(convertBigIntToBytes(big_int("1"), 32)); + ASSERT_TRUE(result.has_value()); + EXPECT_EQ(result, "1.0.0.0"); + } +} + +TEST_F(FormatTest, TestIPv6Conversion) { + { + std::vector bytes = { + 0x20, 0x01, 0x0d, 0xb8, 0x85, 0xa3, 0x00, 0x00, + 0x00, 0x00, 0x8a, 0x2e, 0x03, 0x70, 0x73, 0x34, + }; + auto result = convertToIpv6String(bytes); + ASSERT_TRUE(result.has_value()); + EXPECT_EQ(result, "2001:0db8:85a3:0000:0000:8a2e:0370:7334"); + } + { + auto result = convertToIpv6String( + convertBigIntToBytes(big_int("42540766452641154071740215577757643572"), 128)); + ASSERT_TRUE(result.has_value()); + EXPECT_EQ(result, "2001:0db8:85a3:0000:0000:8a2e:0370:7334"); + } + { + auto result = + convertToIpv6String(convertBigIntToBytes(big_int("18446744073709551615"), 128)); + ASSERT_TRUE(result.has_value()); + EXPECT_EQ(result, "ffff:ffff:ffff:ffff:0000:0000:0000:0000"); + } + { + std::vector bytes = { + 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + }; + auto result = convertToIpv6String(bytes); + ASSERT_TRUE(result.has_value()); + EXPECT_EQ(result, "0100:0000:0000:0000:0000:0000:0000:0000"); + } + { + auto result = convertToIpv6String(convertBigIntToBytes(big_int("1"), 128)); + ASSERT_TRUE(result.has_value()); + EXPECT_EQ(result, "0100:0000:0000:0000:0000:0000:0000:0000"); + } +} + +TEST_F(FormatTest, TestMACConversion) { + { + std::vector bytes = {0xFF, 0x11, 0x22, 0x33, 0x44, 0x55}; + auto result = convertToMacString(bytes); + ASSERT_TRUE(result.has_value()); + EXPECT_EQ(result, "ff:11:22:33:44:55"); + } + { + auto result = convertToMacString(convertBigIntToBytes(big_int("280449053312085"), 48)); + ASSERT_TRUE(result.has_value()); + EXPECT_EQ(result, "ff:11:22:33:44:55"); + } + { + auto result = convertToMacString(convertBigIntToBytes(big_int("4294967295"), 48)); + ASSERT_TRUE(result.has_value()); + EXPECT_EQ(result, "ff:ff:ff:ff:00:00"); + } + { + std::vector bytes = {0x01, 0x00, 0x00, 0x00, 0x00, 0x00}; + auto result = convertToMacString(bytes); + ASSERT_TRUE(result.has_value()); + EXPECT_EQ(result, "01:00:00:00:00:00"); + } + { + auto result = convertToMacString(convertBigIntToBytes(big_int("1"), 48)); + ASSERT_TRUE(result.has_value()); + EXPECT_EQ(result, "01:00:00:00:00:00"); + } +} + } // anonymous namespace } // namespace Test