Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 34 additions & 0 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down
9 changes: 9 additions & 0 deletions LICENSE
Original file line number Diff line number Diff line change
Expand Up @@ -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.
173 changes: 173 additions & 0 deletions compiler/cpp/src/thrift/generate/sha256.h
Original file line number Diff line number Diff line change
@@ -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<uint8_t>.
* - No algorithmic changes.
*********************************************************************/

#pragma once

#include <cstring>
#include <string>
#include <vector>
#include <cstdint>
#include <cstddef>

/****************************** 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<uint8_t> sha256(const uint8_t* data, size_t len) {
THRIFT_SHA256_CTX ctx;
thrift_sha256_init(&ctx);
thrift_sha256_update(&ctx, reinterpret_cast<const unsigned char*>(data), len);
std::vector<uint8_t> 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<uint8_t> sha256(const std::string& s) {
return sha256(reinterpret_cast<const uint8_t*>(s.data()), s.size());
}

} // namespace thrift_generator
Loading
Loading