diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 293e5089cf1..c4de6fa3b62 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -69,6 +69,40 @@ jobs: - name: Run thrift version run: /usr/local/bin/thrift -version + - name: Test Delphi UUIDv8 GUID determinism + run: | + # Run the Delphi generator twice on the same input and verify identical output. + # Tests: cross-platform determinism, service inheritance, struct field-order. + THRIFT=/usr/local/bin/thrift + INPUT=test/delphi/UuidV8Test.thrift + + mkdir -p /tmp/delphi-guid-run1 /tmp/delphi-guid-run2 + $THRIFT --gen delphi --out /tmp/delphi-guid-run1 $INPUT + $THRIFT --gen delphi --out /tmp/delphi-guid-run2 $INPUT + diff -r /tmp/delphi-guid-run1 /tmp/delphi-guid-run2 + + # Also verify GUIDs are non-empty (grep for the GUID attribute pattern) + grep -qP "^\s+\['\{[0-9A-F]{8}-[0-9A-F]{4}-8[0-9A-F]{3}-[89AB][0-9A-F]{3}-[0-9A-F]{12}\}'\]" \ + /tmp/delphi-guid-run1/UuidV8Test.pas \ + && echo "UUIDv8 GUIDs found in output" \ + || { echo "ERROR: No UUIDv8 GUIDs found in Delphi output"; exit 1; } + + # Verify that Point and PointReversed have DIFFERENT GUIDs (field-order sensitivity) + POINT_GUID=$(grep -A1 "IPoint = interface" /tmp/delphi-guid-run1/UuidV8Test.pas | grep "^\s*\['" | tr -d ' ') + POINTREV_GUID=$(grep -A1 "IPointReversed = interface" /tmp/delphi-guid-run1/UuidV8Test.pas | grep "^\s*\['" | tr -d ' ') + [ "$POINT_GUID" != "$POINTREV_GUID" ] \ + && echo "Field-order sensitivity OK (Point vs PointReversed GUIDs differ)" \ + || { echo "ERROR: Point and PointReversed have the same GUID — field order not hashed"; exit 1; } + + # Verify that BaseService and ExtendedService have DIFFERENT GUIDs (parent hash included) + BASE_GUID=$(grep -A1 "Iface = interface$" /tmp/delphi-guid-run1/UuidV8Test.pas | grep "^\s*\['" | head -1 | tr -d ' ') + EXT_GUID=$(grep -A1 "Iface = interface(.*Base" /tmp/delphi-guid-run1/UuidV8Test.pas | grep "^\s*\['" | head -1 | tr -d ' ') + [ "$BASE_GUID" != "$EXT_GUID" ] \ + && echo "Parent-hash inclusion OK (BaseService vs ExtendedService GUIDs differ)" \ + || { echo "ERROR: BaseService and ExtendedService have the same GUID — parent not hashed"; exit 1; } + + echo "All Delphi UUIDv8 GUID determinism tests passed." + # only upload while building ubuntu-24.04 - name: Archive built thrift compiler if: matrix.os == 'ubuntu-24.04' diff --git a/LICENSE b/LICENSE index 2bc6fbbf65c..f308fa849ed 100644 --- a/LICENSE +++ b/LICENSE @@ -304,3 +304,12 @@ For t_cl_generator.cc * Copyright (c) 2006- Facebook --------------------------------------------------- + +--------------------------------------------------- +For compiler/cpp/src/thrift/generate/sha256.h + +SHA-256 implementation by Brad Conte (brad AT bradconte.com). +Source: https://github.com/B-Con/crypto-algorithms +The author has placed this code in the public domain (no copyright claimed). +No algorithmic changes were made; the file was adapted to a C++ header-only +form for inclusion in the Thrift compiler. diff --git a/compiler/cpp/src/thrift/generate/sha256.h b/compiler/cpp/src/thrift/generate/sha256.h new file mode 100644 index 00000000000..34708000aa3 --- /dev/null +++ b/compiler/cpp/src/thrift/generate/sha256.h @@ -0,0 +1,173 @@ +/********************************************************************* +* Filename: sha256.h / sha256.c (combined header-only) +* Author: Brad Conte (brad AT bradconte.com) +* Copyright: +* Disclaimer: This code is presented "as is" without any guarantees. +* Details: Defines the API for the corresponding SHA-256 implementation. +* SHA-256 is one of the three algorithms in the SHA2 +* specification. The others, SHA-384 and SHA-512, are not +* offered in this implementation. +* Algorithm specification can be found here: +* * http://csrc.nist.gov/publications/fips/fips180-2/fips180-2withchangenotice.pdf +* This implementation uses little endian byte order. +* +* Source: https://github.com/B-Con/crypto-algorithms +* Public domain — no copyright claimed by the author. +* +* Modifications for Apache Thrift: +* - Combined .h and .c into a single header-only file. +* - Added C++ wrapper (thrift_generator::sha256) returning std::vector. +* - No algorithmic changes. +*********************************************************************/ + +#pragma once + +#include +#include +#include +#include +#include + +/****************************** MACROS ******************************/ +#define THRIFT_SHA256_ROTLEFT(a,b) (((a) << (b)) | ((a) >> (32-(b)))) +#define THRIFT_SHA256_ROTRIGHT(a,b) (((a) >> (b)) | ((a) << (32-(b)))) + +#define THRIFT_SHA256_CH(x,y,z) (((x) & (y)) ^ (~(x) & (z))) +#define THRIFT_SHA256_MAJ(x,y,z) (((x) & (y)) ^ ((x) & (z)) ^ ((y) & (z))) +#define THRIFT_SHA256_EP0(x) (THRIFT_SHA256_ROTRIGHT(x,2) ^ THRIFT_SHA256_ROTRIGHT(x,13) ^ THRIFT_SHA256_ROTRIGHT(x,22)) +#define THRIFT_SHA256_EP1(x) (THRIFT_SHA256_ROTRIGHT(x,6) ^ THRIFT_SHA256_ROTRIGHT(x,11) ^ THRIFT_SHA256_ROTRIGHT(x,25)) +#define THRIFT_SHA256_SIG0(x) (THRIFT_SHA256_ROTRIGHT(x,7) ^ THRIFT_SHA256_ROTRIGHT(x,18) ^ ((x) >> 3)) +#define THRIFT_SHA256_SIG1(x) (THRIFT_SHA256_ROTRIGHT(x,17) ^ THRIFT_SHA256_ROTRIGHT(x,19) ^ ((x) >> 10)) + +/**************************** DATA TYPES ****************************/ +#define THRIFT_SHA256_BLOCK_SIZE 32 + +typedef struct { + unsigned char data[64]; + unsigned int datalen; + unsigned long long bitlen; + unsigned int state[8]; +} THRIFT_SHA256_CTX; + +/**************************** VARIABLES *****************************/ +// clang-format off +static const unsigned int thrift_sha256_k[64] = { + 0x428a2f98u,0x71374491u,0xb5c0fbcfu,0xe9b5dba5u,0x3956c25bu,0x59f111f1u,0x923f82a4u,0xab1c5ed5u, + 0xd807aa98u,0x12835b01u,0x243185beu,0x550c7dc3u,0x72be5d74u,0x80deb1feu,0x9bdc06a7u,0xc19bf174u, + 0xe49b69c1u,0xefbe4786u,0x0fc19dc6u,0x240ca1ccu,0x2de92c6fu,0x4a7484aau,0x5cb0a9dcu,0x76f988dau, + 0x983e5152u,0xa831c66du,0xb00327c8u,0xbf597fc7u,0xc6e00bf3u,0xd5a79147u,0x06ca6351u,0x14292967u, + 0x27b70a85u,0x2e1b2138u,0x4d2c6dfcu,0x53380d13u,0x650a7354u,0x766a0abbu,0x81c2c92eu,0x92722c85u, + 0xa2bfe8a1u,0xa81a664bu,0xc24b8b70u,0xc76c51a3u,0xd192e819u,0xd6990624u,0xf40e3585u,0x106aa070u, + 0x19a4c116u,0x1e376c08u,0x2748774cu,0x34b0bcb5u,0x391c0cb3u,0x4ed8aa4au,0x5b9cca4fu,0x682e6ff3u, + 0x748f82eeu,0x78a5636fu,0x84c87814u,0x8cc70208u,0x90befffau,0xa4506cebu,0xbef9a3f7u,0xc67178f2u +}; +// clang-format on + +/*********************** FUNCTION DEFINITIONS ***********************/ +inline void thrift_sha256_transform(THRIFT_SHA256_CTX *ctx, const unsigned char data[]) +{ + unsigned int a, b, c, d, e, f, g, h, i, j, t1, t2, m[64]; + + for (i = 0, j = 0; i < 16; ++i, j += 4) + m[i] = ((unsigned int)data[j] << 24) | ((unsigned int)data[j+1] << 16) + | ((unsigned int)data[j+2] << 8) | ((unsigned int)data[j+3]); + for (; i < 64; ++i) + m[i] = THRIFT_SHA256_SIG1(m[i-2]) + m[i-7] + THRIFT_SHA256_SIG0(m[i-15]) + m[i-16]; + + a = ctx->state[0]; b = ctx->state[1]; c = ctx->state[2]; d = ctx->state[3]; + e = ctx->state[4]; f = ctx->state[5]; g = ctx->state[6]; h = ctx->state[7]; + + for (i = 0; i < 64; ++i) { + t1 = h + THRIFT_SHA256_EP1(e) + THRIFT_SHA256_CH(e,f,g) + thrift_sha256_k[i] + m[i]; + t2 = THRIFT_SHA256_EP0(a) + THRIFT_SHA256_MAJ(a,b,c); + h = g; g = f; f = e; e = d + t1; + d = c; c = b; b = a; a = t1 + t2; + } + + ctx->state[0] += a; ctx->state[1] += b; ctx->state[2] += c; ctx->state[3] += d; + ctx->state[4] += e; ctx->state[5] += f; ctx->state[6] += g; ctx->state[7] += h; +} + +inline void thrift_sha256_init(THRIFT_SHA256_CTX *ctx) +{ + ctx->datalen = 0; + ctx->bitlen = 0; + ctx->state[0] = 0x6a09e667u; + ctx->state[1] = 0xbb67ae85u; + ctx->state[2] = 0x3c6ef372u; + ctx->state[3] = 0xa54ff53au; + ctx->state[4] = 0x510e527fu; + ctx->state[5] = 0x9b05688cu; + ctx->state[6] = 0x1f83d9abu; + ctx->state[7] = 0x5be0cd19u; +} + +inline void thrift_sha256_update(THRIFT_SHA256_CTX *ctx, const unsigned char data[], size_t len) +{ + for (size_t i = 0; i < len; ++i) { + ctx->data[ctx->datalen] = data[i]; + ctx->datalen++; + if (ctx->datalen == 64) { + thrift_sha256_transform(ctx, ctx->data); + ctx->bitlen += 512; + ctx->datalen = 0; + } + } +} + +inline void thrift_sha256_final(THRIFT_SHA256_CTX *ctx, unsigned char hash[]) +{ + unsigned int i = ctx->datalen; + + if (ctx->datalen < 56) { + ctx->data[i++] = 0x80u; + while (i < 56) ctx->data[i++] = 0x00u; + } else { + ctx->data[i++] = 0x80u; + while (i < 64) ctx->data[i++] = 0x00u; + thrift_sha256_transform(ctx, ctx->data); + memset(ctx->data, 0, 56); + } + + ctx->bitlen += ctx->datalen * 8; + ctx->data[63] = (unsigned char)(ctx->bitlen); + ctx->data[62] = (unsigned char)(ctx->bitlen >> 8); + ctx->data[61] = (unsigned char)(ctx->bitlen >> 16); + ctx->data[60] = (unsigned char)(ctx->bitlen >> 24); + ctx->data[59] = (unsigned char)(ctx->bitlen >> 32); + ctx->data[58] = (unsigned char)(ctx->bitlen >> 40); + ctx->data[57] = (unsigned char)(ctx->bitlen >> 48); + ctx->data[56] = (unsigned char)(ctx->bitlen >> 56); + thrift_sha256_transform(ctx, ctx->data); + + for (i = 0; i < 4; ++i) { + hash[i] = (ctx->state[0] >> (24 - i * 8)) & 0xffu; + hash[i + 4] = (ctx->state[1] >> (24 - i * 8)) & 0xffu; + hash[i + 8] = (ctx->state[2] >> (24 - i * 8)) & 0xffu; + hash[i + 12] = (ctx->state[3] >> (24 - i * 8)) & 0xffu; + hash[i + 16] = (ctx->state[4] >> (24 - i * 8)) & 0xffu; + hash[i + 20] = (ctx->state[5] >> (24 - i * 8)) & 0xffu; + hash[i + 24] = (ctx->state[6] >> (24 - i * 8)) & 0xffu; + hash[i + 28] = (ctx->state[7] >> (24 - i * 8)) & 0xffu; + } +} + +/******************** C++ convenience wrapper ********************/ +namespace thrift_generator { + +/** Compute SHA-256 of arbitrary bytes. Returns a 32-byte digest. */ +inline std::vector sha256(const uint8_t* data, size_t len) { + THRIFT_SHA256_CTX ctx; + thrift_sha256_init(&ctx); + thrift_sha256_update(&ctx, reinterpret_cast(data), len); + std::vector digest(THRIFT_SHA256_BLOCK_SIZE); + thrift_sha256_final(&ctx, digest.data()); + return digest; +} + +/** Convenience overload: compute SHA-256 of a std::string. */ +inline std::vector sha256(const std::string& s) { + return sha256(reinterpret_cast(s.data()), s.size()); +} + +} // namespace thrift_generator diff --git a/compiler/cpp/src/thrift/generate/t_delphi_generator.cc b/compiler/cpp/src/thrift/generate/t_delphi_generator.cc index cc6a460aded..384c9e1f621 100644 --- a/compiler/cpp/src/thrift/generate/t_delphi_generator.cc +++ b/compiler/cpp/src/thrift/generate/t_delphi_generator.cc @@ -34,8 +34,11 @@ #include #include +#include + #include "thrift/platform.h" #include "thrift/generate/t_oop_generator.h" +#include "thrift/generate/sha256.h" #ifdef _WIN32 #include @@ -74,6 +77,7 @@ class t_delphi_generator : public t_oop_generator { async_ = false; com_types_ = false; rtti_ = false; + guid_v4_ = false; for( iter = parsed_options.begin(); iter != parsed_options.end(); ++iter) { if( iter->first.compare("register_types") == 0) { register_types_ = true; @@ -91,6 +95,8 @@ class t_delphi_generator : public t_oop_generator { com_types_ = true; } else if( iter->first.compare("rtti") == 0) { rtti_ = true; + } else if( iter->first.compare("guid_v4") == 0) { + guid_v4_ = true; } else { throw "unknown option delphi:" + iter->first; } @@ -235,6 +241,12 @@ class t_delphi_generator : public t_oop_generator { void generate_service_interface(t_service* tservice); void generate_service_interface(t_service* tservice, bool for_async); void generate_guid(std::ostream& out); + void generate_guid_v8(std::ostream& out, t_service* tservice); + void generate_guid_v8(std::ostream& out, t_struct* tstruct); + std::string type_name_for_guid(t_type* ttype); + std::string canonical_service_string(t_service* tservice); + std::string canonical_struct_string(t_struct* tstruct); + std::vector program_namespace_uuid(); void generate_service_helpers(t_service* tservice); void generate_service_client(t_service* tservice); void generate_service_server(t_service* tservice); @@ -432,6 +444,7 @@ class t_delphi_generator : public t_oop_generator { bool async_; bool com_types_; bool rtti_; + bool guid_v4_; void indent_up_impl() { ++indent_impl_; }; void indent_down_impl() { --indent_impl_; }; std::string indent_impl() { @@ -1641,7 +1654,11 @@ void t_delphi_generator::generate_delphi_struct_definition(ostream& out, } indent_up(); - generate_guid(out); + if (guid_v4_) { + generate_guid(out); + } else { + generate_guid_v8(out, tstruct); + } for (m_iter = members.begin(); m_iter != members.end(); ++m_iter) { generate_delphi_property_reader_definition(out, *m_iter, false); @@ -1903,7 +1920,11 @@ void t_delphi_generator::generate_service_interface(t_service* tservice, bool fo } indent_up(); - generate_guid(s_service); + if (guid_v4_) { + generate_guid(s_service); + } else { + generate_guid_v8(s_service, tservice); + } vector functions = tservice->get_functions(); vector::iterator f_iter; for (f_iter = functions.begin(); f_iter != functions.end(); ++f_iter) { @@ -1935,6 +1956,168 @@ void t_delphi_generator::generate_guid(std::ostream& out) { #endif } +// --------------------------------------------------------------------------- +// UUIDv8 helpers (RFC 9562) +// --------------------------------------------------------------------------- + +// Compute UUIDv8 from a 16-byte namespace UUID and an arbitrary name string. +// Hashes (namespace || name) with SHA-256, then applies version=8 and +// variant=0b10 bit fields per RFC 9562 §5.8. +static std::vector compute_uuid_v8(const std::vector& ns_bytes, + const std::string& name) { + // SHA-256 input = namespace_bytes || name_bytes + std::string input(ns_bytes.begin(), ns_bytes.end()); + input += name; + std::vector h = thrift_generator::sha256(input); + // Apply version (4 high bits of byte 6 = 0x8) and variant (2 high bits of byte 8 = 0b10) + h[6] = static_cast(0x80u | (h[6] & 0x0Fu)); + h[8] = static_cast(0x80u | (h[8] & 0x3Fu)); + h.resize(16); + return h; +} + +// Format 16 UUID bytes as a Delphi GUID attribute string: +// ['{XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX}'] +static std::string uuid_bytes_to_delphi_guid(const std::vector& b) { + std::ostringstream ss; + ss << std::uppercase << std::hex << std::setfill('0'); + ss << "['{"; + for (int i = 0; i < 4; ++i) { ss << std::setw(2) << static_cast(b[i]); } + ss << '-'; + for (int i = 4; i < 6; ++i) { ss << std::setw(2) << static_cast(b[i]); } + ss << '-'; + for (int i = 6; i < 8; ++i) { ss << std::setw(2) << static_cast(b[i]); } + ss << '-'; + for (int i = 8; i < 10; ++i) { ss << std::setw(2) << static_cast(b[i]); } + ss << '-'; + for (int i = 10; i < 16; ++i) { ss << std::setw(2) << static_cast(b[i]); } + ss << "}']"; + return ss.str(); +} + +// The well-known RFC 4122 DNS namespace UUID bytes (6ba7b810-9dad-11d1-80b4-00c04fd430c8). +static const uint8_t DNS_NAMESPACE_BYTES[16] = { + 0x6b,0xa7,0xb8,0x10, 0x9d,0xad, 0x11,0xd1, 0x80,0xb4, 0x00,0xc0,0x4f,0xd4,0x30,0xc8 +}; + +// Returns the Thrift root namespace UUID: UUIDv8(DNS_NAMESPACE, "thrift.apache.org") +static std::vector thrift_root_namespace() { + static std::vector root; // computed once + if (root.empty()) { + std::vector dns_ns(DNS_NAMESPACE_BYTES, DNS_NAMESPACE_BYTES + 16); + root = compute_uuid_v8(dns_ns, "thrift.apache.org"); + } + return root; +} + +// Returns program-level namespace UUID: UUIDv8(ROOT, program_name). +std::vector t_delphi_generator::program_namespace_uuid() { + return compute_uuid_v8(thrift_root_namespace(), program_->get_name()); +} + +// Utility: convert a vector of bytes to a lowercase hex string. +static std::string bytes_to_hex(const std::vector& bytes) { + std::ostringstream ss; + ss << std::hex << std::setfill('0'); + for (auto b : bytes) { ss << std::setw(2) << static_cast(b); } + return ss.str(); +} + +// Returns the Thrift IDL canonical type name for GUID hashing. +// Resolves typedefs. Uses built-in keyword form, qualified names for user types, +// and container notation (list, map, set). +std::string t_delphi_generator::type_name_for_guid(t_type* ttype) { + ttype = ttype->get_true_type(); + if (ttype->is_base_type()) { + t_base_type* btype = static_cast(ttype); + switch (btype->get_base()) { + case t_base_type::TYPE_VOID: return "void"; + case t_base_type::TYPE_BOOL: return "bool"; + case t_base_type::TYPE_I8: return "i8"; + case t_base_type::TYPE_I16: return "i16"; + case t_base_type::TYPE_I32: return "i32"; + case t_base_type::TYPE_I64: return "i64"; + case t_base_type::TYPE_DOUBLE: return "double"; + case t_base_type::TYPE_UUID: return "uuid"; + case t_base_type::TYPE_STRING: + return btype->is_binary() ? "binary" : "string"; + default: + throw "compiler error: unknown base type in type_name_for_guid"; + } + } else if (ttype->is_map()) { + t_map* tmap = static_cast(ttype); + return "map<" + type_name_for_guid(tmap->get_key_type()) + "," + + type_name_for_guid(tmap->get_val_type()) + ">"; + } else if (ttype->is_set()) { + t_set* tset = static_cast(ttype); + return "set<" + type_name_for_guid(tset->get_elem_type()) + ">"; + } else if (ttype->is_list()) { + t_list* tlist = static_cast(ttype); + return "list<" + type_name_for_guid(tlist->get_elem_type()) + ">"; + } else { + // Struct, exception, enum, service — use fully qualified name when from another program + if (ttype->get_program() != nullptr && ttype->get_program() != program_) { + return ttype->get_program()->get_name() + "." + ttype->get_name(); + } + return ttype->get_name(); + } +} + +// Builds the canonical string for a struct used in UUID hashing. +std::string t_delphi_generator::canonical_struct_string(t_struct* tstruct) { + std::string prog_ns_hex = bytes_to_hex(program_namespace_uuid()); + std::string canonical = prog_ns_hex + "\n" + tstruct->get_name() + "\n"; + const vector& members = tstruct->get_members(); + for (vector::const_iterator m = members.begin(); m != members.end(); ++m) { + canonical += (*m)->get_name() + ":" + type_name_for_guid((*m)->get_type()) + "\n"; + } + return canonical; +} + +// Builds the canonical string for a service used in UUID hashing. +// Recursively includes the SHA-256 of the parent service's canonical string (if any). +std::string t_delphi_generator::canonical_service_string(t_service* tservice) { + std::string prog_ns_hex = bytes_to_hex(program_namespace_uuid()); + + std::string parent_hash; + if (tservice->get_extends() != nullptr) { + std::string parent_canonical = canonical_service_string(tservice->get_extends()); + parent_hash = bytes_to_hex(thrift_generator::sha256(parent_canonical)); + } + + std::string canonical = prog_ns_hex + "\n" + tservice->get_name() + "\n" + + parent_hash + "\n"; + + vector functions = tservice->get_functions(); + for (vector::const_iterator f = functions.begin(); f != functions.end(); ++f) { + std::string line; + if ((*f)->is_oneway()) { line += "oneway:"; } + line += (*f)->get_name() + ":" + type_name_for_guid((*f)->get_returntype()); + const vector& args = (*f)->get_arglist()->get_members(); + for (vector::const_iterator a = args.begin(); a != args.end(); ++a) { + line += ":" + type_name_for_guid((*a)->get_type()); + } + canonical += line + "\n"; + } + return canonical; +} + +// Emits a deterministic UUIDv8 GUID attribute for a service interface. +void t_delphi_generator::generate_guid_v8(std::ostream& out, t_service* tservice) { + std::vector prog_ns = program_namespace_uuid(); + std::string canonical = canonical_service_string(tservice); + std::vector uuid = compute_uuid_v8(prog_ns, canonical); + indent(out) << uuid_bytes_to_delphi_guid(uuid) << '\n'; +} + +// Emits a deterministic UUIDv8 GUID attribute for a struct interface. +void t_delphi_generator::generate_guid_v8(std::ostream& out, t_struct* tstruct) { + std::vector prog_ns = program_namespace_uuid(); + std::string canonical = canonical_struct_string(tstruct); + std::vector uuid = compute_uuid_v8(prog_ns, canonical); + indent(out) << uuid_bytes_to_delphi_guid(uuid) << '\n'; +} + void t_delphi_generator::generate_service_helpers(t_service* tservice) { vector functions = tservice->get_functions(); vector::iterator f_iter; @@ -3942,4 +4125,5 @@ THRIFT_REGISTER_GENERATOR( " async: Generate IAsync interface to use Parallel Programming Library (XE7+ only).\n" " com_types: Use COM-compatible data types (e.g. WideString).\n" " old_names: Compatibility: generate \"reserved\" identifiers with '_' postfix instead of '&' prefix.\n" - " rtti: Activate {$TYPEINFO} and {$RTTI} at the generated API interfaces.\n") + " rtti: Activate {$TYPEINFO} and {$RTTI} at the generated API interfaces.\n" + " guid_v4: Use random UUIDv4 (Windows only, legacy). Default: stable UUIDv8.\n") diff --git a/test/delphi/UuidV8Test.thrift b/test/delphi/UuidV8Test.thrift new file mode 100644 index 00000000000..b3f1c702d3f --- /dev/null +++ b/test/delphi/UuidV8Test.thrift @@ -0,0 +1,58 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + +/** + * Test file for UUIDv8 deterministic GUID generation. + * Covers: struct field-order sensitivity, service inheritance (parent hash), + * and various field/return types. + */ + +namespace delphi UuidV8Test + +// --- Structs --- + +struct Point { + 1: i32 x + 2: i32 y +} + +// Same fields as Point but in reverse order — must produce a different GUID +struct PointReversed { + 1: i32 y + 2: i32 x +} + +struct Container { + 1: list items + 2: map counts + 3: set flags +} + +// --- Services --- + +service BaseService { + void ping() + string echo(1: string msg) +} + +// Extends BaseService — GUID must incorporate parent's hash +service ExtendedService extends BaseService { + i32 add(1: i32 a, 2: i32 b) + oneway void fire(1: string event) +}