Skip to content
Draft
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
9 changes: 9 additions & 0 deletions lib/bond/CsProtocol.bond
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,13 @@ struct M365a
2: optional uint64 msp;
}

struct MetaData
{
// TODO(privacy-parity): best-effort field layout. Confirm the canonical Common
// Schema ext.metadata field ordinals/types before relying on the wire contract.
1: optional uint64 privTags;
}

struct Xbl
{
5: optional map<string, string> claims;
Expand Down Expand Up @@ -354,6 +361,8 @@ struct Record
35: optional vector<Service> extService;
36: optional vector<Cs> extCs;
37: optional vector<M365a> extM365a;
// TODO(privacy-parity): 38 is a best-effort ordinal for ext.metadata; confirm against canonical CS.
38: optional vector<MetaData> extMetadata;
Comment on lines 362 to +365

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Acknowledged - the ordinal (38) and layout are intentionally best-effort and clearly marked (TODO(privacy-parity) + the PR's reviewer caveat). This is a deliberate choice for this PR: it is not meant to merge until the canonical ext.metadata wire contract is confirmed. Gating behind a feature flag is a reasonable alternative if preferred - happy to do that instead of relying on the caveat.

41: optional vector<Data> ext;
42: optional vector<Mscv> extMscv;
43: optional vector<IntWeb> extIntWeb;
Expand Down
66 changes: 66 additions & 0 deletions lib/bond/generated/CsProtocol_readers.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -722,6 +722,51 @@ bool Deserialize(TReader& reader, ::CsProtocol::M365a& value, bool isBase)
return true;
}

template<typename TReader>
bool Deserialize(TReader& reader, ::CsProtocol::MetaData& value, bool isBase)
{
if (!reader.ReadStructBegin(isBase)) {
return false;
}

uint8_t type;
uint16_t id;
for (;;) {
if (!reader.ReadFieldBegin(type, id)) {
return false;
}

if (type == BT_STOP || type == BT_STOP_BASE) {
if (isBase != (type == BT_STOP_BASE)) {
return false;
}
break;
}

switch (id) {
case 1: {
if (!reader.ReadUInt64(value.privTags)) {
return false;
}
break;
}

default:
return false;
}

if (!reader.ReadFieldEnd()) {
return false;
}
}

if (!reader.ReadStructEnd(isBase)) {
return false;
}

return true;
}

template<typename TReader>
bool Deserialize(TReader& reader, ::CsProtocol::Xbl& value, bool isBase)
{
Expand Down Expand Up @@ -2867,6 +2912,27 @@ bool Deserialize(TReader& reader, ::CsProtocol::Record& value, bool isBase)
break;
}

case 38: {
uint32_t size4;
uint8_t type4;
if (!reader.ReadContainerBegin(size4, type4)) {
return false;
}
if (type4 != BT_STRUCT) {
return false;
}
value.extMetadata.resize(size4);
for (unsigned i4 = 0; i4 < size4; i4++) {
if (!Deserialize(reader, value.extMetadata[i4], false)) {
return false;
}
}
if (!reader.ReadContainerEnd()) {
return false;
}
break;
}

case 41: {
uint32_t size4;
uint8_t type4;
Expand Down
28 changes: 28 additions & 0 deletions lib/bond/generated/CsProtocol_writers.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -541,6 +541,22 @@ void Serialize(TWriter& writer, ::CsProtocol::M365a const& value, bool isBase)
writer.WriteStructEnd(isBase);
}

template<typename TWriter>
void Serialize(TWriter& writer, ::CsProtocol::MetaData const& value, bool isBase)
{
writer.WriteStructBegin(nullptr, isBase);

if (value.privTags != 0) {
writer.WriteFieldBegin(BT_UINT64, 1, nullptr);
writer.WriteUInt64(value.privTags);
writer.WriteFieldEnd();
} else {
writer.WriteFieldOmitted(BT_UINT64, 1, nullptr);
}

writer.WriteStructEnd(isBase);
}

template<typename TWriter>
void Serialize(TWriter& writer, ::CsProtocol::Xbl const& value, bool isBase)
{
Expand Down Expand Up @@ -1898,6 +1914,18 @@ void Serialize(TWriter& writer, ::CsProtocol::Record const& value, bool isBase)
writer.WriteFieldOmitted(BT_LIST, 37, nullptr);
}

if (!value.extMetadata.empty()) {
writer.WriteFieldBegin(BT_LIST, 38, nullptr);
writer.WriteContainerBegin(value.extMetadata.size(), BT_STRUCT);
for (auto const& item2 : value.extMetadata) {
Serialize(writer, item2, false);
}
writer.WriteContainerEnd();
writer.WriteFieldEnd();
} else {
writer.WriteFieldOmitted(BT_LIST, 38, nullptr);
}

if (!value.ext.empty()) {
writer.WriteFieldBegin(BT_LIST, 41, nullptr);
writer.WriteContainerBegin(value.ext.size(), BT_STRUCT);
Expand Down
18 changes: 18 additions & 0 deletions lib/decorators/EventPropertiesDecorator.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

#include "IDecorator.hpp"
#include "EventProperties.hpp"
#include "CommonFields.h"
#include "CorrelationVector.hpp"
#include "utils/Utils.hpp"

Expand Down Expand Up @@ -172,6 +173,23 @@ namespace MAT_NS_BEGIN {
}
const auto &k = kv.first;
const auto &v = kv.second;

// Route the privacy tag (EventInfo.PrivTags) into ext.metadata.privTags so the
// cross-platform bond path carries it the same way the UTC path does. extMetadata
// is the single source of truth: a well-typed int64 value is moved out of Part C
// (the JSON path reads it from extMetadata too). Non-int64 values are left to be
// handled as an ordinary property below.
// TODO(privacy-parity): confirm the canonical Common Schema ext.metadata wire contract.
if (k == COMMONFIELDS_EVENT_PRIVTAGS && v.type == EventProperty::TYPE_INT64)
{
if (record.extMetadata.empty())
{
record.extMetadata.push_back(::CsProtocol::MetaData());
}
record.extMetadata[0].privTags = static_cast<uint64_t>(v.as_int64);
Comment on lines +185 to +189

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed - extMetadata is now the single source of truth. The decorator moves a well-typed int64 EventInfo.PrivTags into ext.metadata.privTags and no longer leaves it in Part C (the continue is back), so the Bond payload no longer carries it twice.

continue;
}
Comment on lines +189 to +191

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right - addressed in the same push. Since the decorator now moves EventInfo.PrivTags out of Part C into record.extMetadata, I updated JsonFormatter::getJsonFormattedEvent to read privTags from source->extMetadata[0].privTags instead of data[0].properties, so the JSON path keeps emitting ext.metadata.privTags and both paths now source from the same place.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Update for the final approach: I removed the continue, so EventInfo.PrivTags is now ADDITIONALLY mirrored into ext.metadata.privTags but kept in Part C. The JSON path reads it from Part C unchanged, so nothing is dropped.


if (v.piiKind != PiiKind_None)
{
if (v.piiKind == PiiKind::CustomerContentKind_GenericData)
Expand Down
19 changes: 19 additions & 0 deletions lib/include/public/CsProtocol_types.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -330,6 +330,22 @@ struct M365a {
}
};

struct MetaData {
// 1: optional uint64 privTags
// TODO(privacy-parity): best-effort layout; confirm canonical Common Schema ext.metadata.
uint64_t privTags = 0;

bool operator==(MetaData const& other) const
{
return (privTags == other.privTags);
}

bool operator!=(MetaData const& other) const
{
return !(*this == other);
}
};

struct Xbl {
// 5: optional map<string, string> claims
std::map<std::string, std::string> claims;
Expand Down Expand Up @@ -1012,6 +1028,8 @@ struct Record {
#endif
// 37: optional vector<M365a> extM365a
std::vector< ::CsProtocol::M365a> extM365a;
// 38: optional vector<MetaData> extMetadata
std::vector< ::CsProtocol::MetaData> extMetadata;
// 41: optional vector<Data> ext
std::vector< ::CsProtocol::Data> ext;
Comment on lines 1029 to 1034

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Acknowledged - adding extMetadata to the public CsProtocol::Record is an intended, necessary part of carrying privTags in ext.metadata on the bond path. It is a new optional trailing field (existing members are not reordered), but it is part of the same wire/schema change this PR explicitly marks as best-effort and not-for-merge until the canonical ext.metadata contract - and its ABI/compat implications - are confirmed. Flagging it alongside the ordinal caveat rather than resolving it here.

#ifdef HAVE_CS4_FULL
Expand Down Expand Up @@ -1065,6 +1083,7 @@ struct Record {
&& (extCs == other.extCs)
#endif
&& (extM365a == other.extM365a)
&& (extMetadata == other.extMetadata)
&& (ext == other.ext)
#ifdef HAVE_CS4_FULL
&& (extMscv == other.extMscv)
Expand Down
9 changes: 5 additions & 4 deletions lib/system/JsonFormatter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -148,9 +148,11 @@ namespace MAT_NS_BEGIN
ans["iKey"] = iKey;
if (!source->cV.empty())
ans[CorrelationVector::PropertyName] = source->cV;
if (source->data[0].properties.find(COMMONFIELDS_EVENT_PRIVTAGS) != source->data[0].properties.end()) {
ans["ext"]["metadata"]["privTags"] = source->data[0].properties[COMMONFIELDS_EVENT_PRIVTAGS].longValue;
source->data[0].properties.erase(COMMONFIELDS_EVENT_PRIVTAGS);
// For well-typed int64 values, privTags is carried in record.extMetadata (populated by
// EventPropertiesDecorator); emit it from there whenever present. Non-int64 values are left
// as ordinary Part C properties and serialized through the normal data path below.
if (!source->extMetadata.empty()) {
ans["ext"]["metadata"]["privTags"] = source->extMetadata[0].privTags;
}
addExtApp(ans, source->extApp);
addExtNet(ans, source->extNet);
Expand Down Expand Up @@ -178,7 +180,6 @@ namespace MAT_NS_BEGIN
source->data[0].properties.erase(COMMONFIELDS_APP_VERSION);
source->data[0].properties.erase(COMMONFIELDS_EVENT_NAME);
source->data[0].properties.erase(COMMONFIELDS_EVENT_INITID);
source->data[0].properties.erase(COMMONFIELDS_EVENT_PRIVTAGS);
source->data[0].properties.erase(COMMONFIELDS_METADATA_VIEWINGPRODUCERID);
source->data[0].properties.erase(COMMONFIELDS_METADATA_VIEWINGCATEGORY);
source->data[0].properties.erase(COMMONFIELDS_METADATA_VIEWINGPAYLOADDECODERPATH);
Expand Down
38 changes: 38 additions & 0 deletions tests/unittests/EventPropertiesDecoratorTests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -545,3 +545,41 @@ TEST(EventPropertiesDecoratorTests, DropPiiPartA_StripsValues)
EXPECT_THAT(record->extSdk[0].installId, Eq(""));
EXPECT_THAT(record->cV, Eq(""));
}

TEST(EventPropertiesDecoratorTests, Decorate_PrivTags_RoutedToExtMetadata)
{
NullLogManager logManager;
EventPropertiesDecorator decorator(logManager);
Record record;
EventProperties props{"TestEvent"};
const int64_t privTags = 0x0000000800000002;
props.SetProperty(COMMONFIELDS_EVENT_PRIVTAGS, privTags);
EventLatency latency = EventLatency::EventLatency_Normal;

EXPECT_TRUE(decorator.decorate(record, latency, props));

// EventInfo.PrivTags is routed into ext.metadata.privTags (single source of truth) ...
ASSERT_THAT(record.extMetadata, SizeIs(1));
EXPECT_THAT(record.extMetadata[0].privTags, Eq(static_cast<uint64_t>(privTags)));
// ... and is not also emitted as a Part C property.
EXPECT_THAT(record.data[0].properties.find(COMMONFIELDS_EVENT_PRIVTAGS),
Eq(record.data[0].properties.end()));
}

TEST(EventPropertiesDecoratorTests, Decorate_PrivTags_NonInt64_FallsThroughToPartC)
{
NullLogManager logManager;
EventPropertiesDecorator decorator(logManager);
Record record;
EventProperties props{"TestEvent"};
// A non-int64 PrivTags value must not be routed via the inactive union member;
// it falls through to normal Part C property handling instead.
props.SetProperty(COMMONFIELDS_EVENT_PRIVTAGS, std::string{"not-an-int"});
EventLatency latency = EventLatency::EventLatency_Normal;

EXPECT_TRUE(decorator.decorate(record, latency, props));

EXPECT_THAT(record.extMetadata, SizeIs(0));
EXPECT_THAT(record.data[0].properties.find(COMMONFIELDS_EVENT_PRIVTAGS),
Ne(record.data[0].properties.end()));
}
Loading