From 4b8d200831ce464b6fbb141785ecf0245e8c26ca Mon Sep 17 00:00:00 2001 From: OpenAI Codex Date: Sun, 13 Sep 2026 10:18:33 +0800 Subject: [PATCH] fix: close STEP-073 QBIN2 V4 validator audit --- .../field/field_profile_validator.hpp | 3 + .../field/field_profile_validator.cpp | 61 ++++++++++++++- test/particle/field_runtime.test.cpp | 75 ++++++++++++++++--- 3 files changed, 126 insertions(+), 13 deletions(-) diff --git a/include/mbgl/particle/field/field_profile_validator.hpp b/include/mbgl/particle/field/field_profile_validator.hpp index b763e51f0..3a474e072 100644 --- a/include/mbgl/particle/field/field_profile_validator.hpp +++ b/include/mbgl/particle/field/field_profile_validator.hpp @@ -18,6 +18,9 @@ enum class FieldProfileValidationCode : uint8_t { UndeclaredRequiredPlane, AdditionalPlaneNotAllowed, InvalidVelocityGroup, + // A reserved Native Core semanticID was declared with a descriptor that + // conflicts with its protocol-defined physical meaning. + SemanticContractMismatch, InvalidCoreUnit, UnsupportedSharedValidityMode, TimeRelationMismatch, diff --git a/src/mbgl/particle/field/field_profile_validator.cpp b/src/mbgl/particle/field/field_profile_validator.cpp index 598399c1a..1cf295350 100644 --- a/src/mbgl/particle/field/field_profile_validator.cpp +++ b/src/mbgl/particle/field/field_profile_validator.cpp @@ -1,6 +1,9 @@ #include #include +#include +#include +#include #include namespace mbgl::particle { @@ -8,6 +11,43 @@ namespace { using PlaneRef = const DecodedFieldPlane*; +// These are protocol-reserved Core semantic contracts, deliberately separate +// from product/profile resolution. Unknown semantic IDs remain extensible. +struct CoreSemanticContract { + uint16_t semanticID; + FieldValueClass valueClass; + uint16_t unitID; + FieldComponent component; + bool groupMustBeZero; +}; + +constexpr CoreSemanticContract CoreSemanticContracts[] = { + {1, FieldValueClass::VelocityComponent, FieldUnitMetersPerSecond, FieldComponent::UEast, false}, + {2, FieldValueClass::VelocityComponent, FieldUnitMetersPerSecond, FieldComponent::VNorth, false}, + {3, FieldValueClass::Validity, FieldUnitNone, FieldComponent::None, true}, + {10, FieldValueClass::DirectionAngle, FieldUnitDegrees, FieldComponent::None, true}, + {11, FieldValueClass::DirectionAngle, FieldUnitDegrees, FieldComponent::None, true}, +}; + +const CoreSemanticContract* coreSemanticContract(const uint16_t semanticID) { + const auto found = std::find_if(std::begin(CoreSemanticContracts), std::end(CoreSemanticContracts), + [semanticID](const CoreSemanticContract& contract) { + return contract.semanticID == semanticID; + }); + return found == std::end(CoreSemanticContracts) ? nullptr : &*found; +} + +bool matchesCoreSemanticContract(const uint16_t semanticID, + const FieldValueClass valueClass, + const uint16_t unitID, + const uint16_t groupID, + const FieldComponent component) { + const auto* contract = coreSemanticContract(semanticID); + return !contract || + (contract->valueClass == valueClass && contract->unitID == unitID && contract->component == component && + (!contract->groupMustBeZero || groupID == 0)); +} + bool sameKey(const FieldPlaneRequirement& requirement, const DecodedFieldPlane& plane) { return requirement.semanticID == plane.key.semanticID && requirement.groupID == plane.key.groupID; } @@ -26,6 +66,12 @@ bool isNumeric(const FieldDType dtype) { return dtype != FieldDType::Bitset; } +bool directionScaleIsCanonical(const float scale) { + constexpr float canonical = 360.0f / 65536.0f; + constexpr float tolerance = 4.0f * std::numeric_limits::epsilon(); + return std::fabs(scale - canonical) <= tolerance; +} + } // namespace FieldProfileValidationResult FieldProfileValidator::validate(const ResolvedFieldProfileContract& contract, @@ -34,10 +80,13 @@ FieldProfileValidationResult FieldProfileValidator::validate(const ResolvedField return result(FieldProfileValidationCode::UnsupportedContractSchema, "contractSchemaVersion"); if (!contract.isStructurallyValid()) return result(FieldProfileValidationCode::InvalidContract, "contract"); if (!frame.isStructurallyValid()) return result(FieldProfileValidationCode::InvalidFrame, "frame"); + for (const auto& required : contract.requiredPlanes) { + if (!matchesCoreSemanticContract(required.semanticID, required.valueClass, required.unitID, required.groupID, + required.component)) + return result(FieldProfileValidationCode::SemanticContractMismatch, "required Core semantic descriptor"); + } if (!contract.sharedValidityRequired) return result(FieldProfileValidationCode::UnsupportedSharedValidityMode, "sharedValidityRequired=false"); - if (frame.validityPlane.valueClass != FieldValueClass::Validity) - return result(FieldProfileValidationCode::InvalidCoreUnit, "validity value class"); if ((contract.timeRelation == FieldTimeRelation::ValidGteReference && frame.frameContentIdentity.validTimeMs < frame.referenceTimeMs) || @@ -60,6 +109,9 @@ FieldProfileValidationResult FieldProfileValidator::validate(const ResolvedField } for (const auto plane : actual) { + if (!matchesCoreSemanticContract(plane->key.semanticID, plane->valueClass, plane->unitID, plane->key.groupID, + plane->key.component)) + return result(FieldProfileValidationCode::SemanticContractMismatch, "actual Core semantic descriptor"); const auto declared = std::find_if(contract.requiredPlanes.begin(), contract.requiredPlanes.end(), [plane](const FieldPlaneRequirement& requirement) { return matches(requirement, *plane); @@ -74,8 +126,9 @@ FieldProfileValidationResult FieldProfileValidator::validate(const ResolvedField plane->key.component != FieldComponent::None) return result(FieldProfileValidationCode::InvalidCoreUnit, "validity descriptor"); } else if (plane->valueClass == FieldValueClass::DirectionAngle) { - if (plane->unitID != FieldUnitDegrees || !isNumeric(plane->dtype)) - return result(FieldProfileValidationCode::InvalidCoreUnit, "direction unit/dtype"); + if (plane->unitID != FieldUnitDegrees || plane->key.component != FieldComponent::None || plane->key.groupID != 0 || + plane->dtype != FieldDType::UInt16 || plane->offset != 0.0f || !directionScaleIsCanonical(plane->scale)) + return result(FieldProfileValidationCode::InvalidCoreUnit, "direction circular quantization descriptor"); } else if (plane->valueClass == FieldValueClass::VelocityComponent) { if (plane->unitID != FieldUnitMetersPerSecond || !isNumeric(plane->dtype)) return result(FieldProfileValidationCode::InvalidCoreUnit, "velocity unit/dtype"); diff --git a/test/particle/field_runtime.test.cpp b/test/particle/field_runtime.test.cpp index 46a3a70ee..e9037596e 100644 --- a/test/particle/field_runtime.test.cpp +++ b/test/particle/field_runtime.test.cpp @@ -90,34 +90,36 @@ TEST(ParticleFieldProfileValidator, EnforcesVelocityDirectionAndValidityCoreRule EXPECT_EQ(particle::FieldUnitMetersPerSecond, 1); EXPECT_EQ(particle::FieldUnitDegrees, 4); auto c = contract(); auto f = frame(); const auto bytes = f.planes.front().storage; - f.planes.push_back(plane(10, particle::FieldValueClass::VelocityComponent, particle::FieldUnitMetersPerSecond, 3, + f.planes.push_back(plane(100, particle::FieldValueClass::VelocityComponent, particle::FieldUnitMetersPerSecond, 3, particle::FieldComponent::UEast, particle::FieldDType::Int16, false, bytes)); EXPECT_EQ(validate(c, f), particle::FieldProfileValidationCode::InvalidVelocityGroup); - f.planes.push_back(plane(11, particle::FieldValueClass::VelocityComponent, particle::FieldUnitMetersPerSecond, 3, + f.planes.push_back(plane(101, particle::FieldValueClass::VelocityComponent, particle::FieldUnitMetersPerSecond, 3, particle::FieldComponent::VNorth, particle::FieldDType::Int16, false, bytes)); EXPECT_EQ(validate(c, f), particle::FieldProfileValidationCode::Accepted); f.planes.back().unitID = 99; EXPECT_EQ(validate(c, f), particle::FieldProfileValidationCode::InvalidCoreUnit); f = frame(); - f.planes.push_back(plane(10, particle::FieldValueClass::VelocityComponent, particle::FieldUnitMetersPerSecond, 0, + f.planes.push_back(plane(100, particle::FieldValueClass::VelocityComponent, particle::FieldUnitMetersPerSecond, 0, particle::FieldComponent::UEast, particle::FieldDType::Int16, false, bytes)); EXPECT_EQ(validate(c, f), particle::FieldProfileValidationCode::InvalidVelocityGroup); f = frame(); - f.planes.push_back(plane(10, particle::FieldValueClass::VelocityComponent, particle::FieldUnitMetersPerSecond, 3, + f.planes.push_back(plane(100, particle::FieldValueClass::VelocityComponent, particle::FieldUnitMetersPerSecond, 3, particle::FieldComponent::UEast, particle::FieldDType::Int16, false, bytes)); - f.planes.push_back(plane(11, particle::FieldValueClass::VelocityComponent, particle::FieldUnitMetersPerSecond, 3, + f.planes.push_back(plane(101, particle::FieldValueClass::VelocityComponent, particle::FieldUnitMetersPerSecond, 3, particle::FieldComponent::UEast, particle::FieldDType::Int16, false, bytes)); - f.planes.push_back(plane(12, particle::FieldValueClass::VelocityComponent, particle::FieldUnitMetersPerSecond, 3, + f.planes.push_back(plane(102, particle::FieldValueClass::VelocityComponent, particle::FieldUnitMetersPerSecond, 3, particle::FieldComponent::VNorth, particle::FieldDType::Int16, false, bytes)); EXPECT_EQ(validate(c, f), particle::FieldProfileValidationCode::InvalidVelocityGroup); - f = frame(); f.planes.front().valueClass = particle::FieldValueClass::DirectionAngle; + f = frame(); f.planes.front().key.semanticID = 10; f.planes.front().valueClass = particle::FieldValueClass::DirectionAngle; f.planes.front().unitID = particle::FieldUnitDegrees; - c.requiredPlanes.front().valueClass = particle::FieldValueClass::DirectionAngle; + f.planes.front().dtype = particle::FieldDType::UInt16; + f.planes.front().scale = 360.0f / 65536.0f; + c.requiredPlanes.front().semanticID = 10; c.requiredPlanes.front().valueClass = particle::FieldValueClass::DirectionAngle; c.requiredPlanes.front().unitID = particle::FieldUnitDegrees; EXPECT_EQ(validate(c, f), particle::FieldProfileValidationCode::Accepted); f.planes.front().unitID = 2; c.requiredPlanes.front().unitID = 2; - EXPECT_EQ(validate(c, f), particle::FieldProfileValidationCode::InvalidCoreUnit); + EXPECT_EQ(validate(c, f), particle::FieldProfileValidationCode::SemanticContractMismatch); c = contract(); f = frame(); f.validityPlane.dtype = particle::FieldDType::UInt8; EXPECT_EQ(validate(c, f), particle::FieldProfileValidationCode::InvalidCoreUnit); f = frame(); f.validityPlane.unitID = 1; @@ -126,6 +128,61 @@ TEST(ParticleFieldProfileValidator, EnforcesVelocityDirectionAndValidityCoreRule EXPECT_EQ(validate(c, f), particle::FieldProfileValidationCode::InvalidCoreUnit); } +TEST(ParticleFieldProfileValidator, EnforcesReservedCoreSemanticContractsFromProtocolTable) { + auto c = contract(); auto f = frame(); + // Contract and frame agree on the spoof, but reserved semantic 1 remains + // VELOCITY_COMPONENT/MPS/U_EAST and cannot become an opaque scalar. + c.requiredPlanes.front().semanticID = 1; + f.planes.front().key.semanticID = 1; + EXPECT_EQ(validate(c, f), particle::FieldProfileValidationCode::SemanticContractMismatch); + + c = contract(); f = frame(); + c.requiredPlanes.front() = {2, particle::FieldValueClass::VelocityComponent, particle::FieldUnitMetersPerSecond, 4, + particle::FieldComponent::UEast, true}; + f.planes.front() = plane(2, particle::FieldValueClass::VelocityComponent, particle::FieldUnitMetersPerSecond, 4, + particle::FieldComponent::UEast, particle::FieldDType::Int16, true, + f.planes.front().storage, "u-spoof"); + EXPECT_EQ(validate(c, f), particle::FieldProfileValidationCode::SemanticContractMismatch); + + c = contract(); f = frame(); + c.requiredPlanes.back().semanticID = 3; + c.requiredPlanes.back().valueClass = particle::FieldValueClass::Scalar; + f.validityPlane.key.semanticID = 3; + f.validityPlane.valueClass = particle::FieldValueClass::Scalar; + EXPECT_EQ(validate(c, f), particle::FieldProfileValidationCode::SemanticContractMismatch); +} + +TEST(ParticleFieldProfileValidator, EnforcesDirectionCircularQuantizationContract) { + auto c = contract(); auto f = frame(); + c.requiredPlanes.front() = {10, particle::FieldValueClass::DirectionAngle, particle::FieldUnitDegrees, 0, + particle::FieldComponent::None, true}; + f.planes.front() = plane(10, particle::FieldValueClass::DirectionAngle, particle::FieldUnitDegrees, 0, + particle::FieldComponent::None, particle::FieldDType::UInt16, true, + f.planes.front().storage, "direction"); + f.planes.front().scale = 360.0f / 65536.0f; + EXPECT_EQ(validate(c, f), particle::FieldProfileValidationCode::Accepted); + + f.planes.front().unitID = particle::FieldUnitMetersPerSecond; + c.requiredPlanes.front().unitID = particle::FieldUnitMetersPerSecond; + EXPECT_EQ(validate(c, f), particle::FieldProfileValidationCode::SemanticContractMismatch); + f.planes.front().unitID = particle::FieldUnitDegrees; + c.requiredPlanes.front().unitID = particle::FieldUnitDegrees; + f.planes.front().key.component = particle::FieldComponent::UEast; + EXPECT_EQ(validate(c, f), particle::FieldProfileValidationCode::SemanticContractMismatch); + f.planes.front().key.component = particle::FieldComponent::None; + f.planes.front().key.groupID = 1; + EXPECT_EQ(validate(c, f), particle::FieldProfileValidationCode::SemanticContractMismatch); + f.planes.front().key.groupID = 0; + f.planes.front().dtype = particle::FieldDType::UInt8; + EXPECT_EQ(validate(c, f), particle::FieldProfileValidationCode::InvalidCoreUnit); + f.planes.front().dtype = particle::FieldDType::UInt16; + f.planes.front().scale = 0.1f; + EXPECT_EQ(validate(c, f), particle::FieldProfileValidationCode::InvalidCoreUnit); + f.planes.front().scale = 360.0f / 65536.0f; + f.planes.front().offset = 1.0f; + EXPECT_EQ(validate(c, f), particle::FieldProfileValidationCode::InvalidCoreUnit); +} + TEST(ParticleFieldProfileValidator, UsesSemanticAndGroupAsTheOnlyPlaneIdentity) { auto f = frame(); auto duplicate = f.planes.front();