fix: close STEP-073 QBIN2 V4 validator audit
This commit is contained in:
@@ -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,
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
#include <mbgl/particle/field/field_profile_validator.hpp>
|
||||
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <iterator>
|
||||
#include <limits>
|
||||
#include <map>
|
||||
|
||||
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<float>::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");
|
||||
|
||||
@@ -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();
|
||||
|
||||
Reference in New Issue
Block a user