fix: close STEP-073 QBIN2 V4 validator audit
This commit is contained in:
@@ -18,6 +18,9 @@ enum class FieldProfileValidationCode : uint8_t {
|
|||||||
UndeclaredRequiredPlane,
|
UndeclaredRequiredPlane,
|
||||||
AdditionalPlaneNotAllowed,
|
AdditionalPlaneNotAllowed,
|
||||||
InvalidVelocityGroup,
|
InvalidVelocityGroup,
|
||||||
|
// A reserved Native Core semanticID was declared with a descriptor that
|
||||||
|
// conflicts with its protocol-defined physical meaning.
|
||||||
|
SemanticContractMismatch,
|
||||||
InvalidCoreUnit,
|
InvalidCoreUnit,
|
||||||
UnsupportedSharedValidityMode,
|
UnsupportedSharedValidityMode,
|
||||||
TimeRelationMismatch,
|
TimeRelationMismatch,
|
||||||
|
|||||||
@@ -1,6 +1,9 @@
|
|||||||
#include <mbgl/particle/field/field_profile_validator.hpp>
|
#include <mbgl/particle/field/field_profile_validator.hpp>
|
||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
|
#include <cmath>
|
||||||
|
#include <iterator>
|
||||||
|
#include <limits>
|
||||||
#include <map>
|
#include <map>
|
||||||
|
|
||||||
namespace mbgl::particle {
|
namespace mbgl::particle {
|
||||||
@@ -8,6 +11,43 @@ namespace {
|
|||||||
|
|
||||||
using PlaneRef = const DecodedFieldPlane*;
|
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) {
|
bool sameKey(const FieldPlaneRequirement& requirement, const DecodedFieldPlane& plane) {
|
||||||
return requirement.semanticID == plane.key.semanticID && requirement.groupID == plane.key.groupID;
|
return requirement.semanticID == plane.key.semanticID && requirement.groupID == plane.key.groupID;
|
||||||
}
|
}
|
||||||
@@ -26,6 +66,12 @@ bool isNumeric(const FieldDType dtype) {
|
|||||||
return dtype != FieldDType::Bitset;
|
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
|
} // namespace
|
||||||
|
|
||||||
FieldProfileValidationResult FieldProfileValidator::validate(const ResolvedFieldProfileContract& contract,
|
FieldProfileValidationResult FieldProfileValidator::validate(const ResolvedFieldProfileContract& contract,
|
||||||
@@ -34,10 +80,13 @@ FieldProfileValidationResult FieldProfileValidator::validate(const ResolvedField
|
|||||||
return result(FieldProfileValidationCode::UnsupportedContractSchema, "contractSchemaVersion");
|
return result(FieldProfileValidationCode::UnsupportedContractSchema, "contractSchemaVersion");
|
||||||
if (!contract.isStructurallyValid()) return result(FieldProfileValidationCode::InvalidContract, "contract");
|
if (!contract.isStructurallyValid()) return result(FieldProfileValidationCode::InvalidContract, "contract");
|
||||||
if (!frame.isStructurallyValid()) return result(FieldProfileValidationCode::InvalidFrame, "frame");
|
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)
|
if (!contract.sharedValidityRequired)
|
||||||
return result(FieldProfileValidationCode::UnsupportedSharedValidityMode, "sharedValidityRequired=false");
|
return result(FieldProfileValidationCode::UnsupportedSharedValidityMode, "sharedValidityRequired=false");
|
||||||
if (frame.validityPlane.valueClass != FieldValueClass::Validity)
|
|
||||||
return result(FieldProfileValidationCode::InvalidCoreUnit, "validity value class");
|
|
||||||
|
|
||||||
if ((contract.timeRelation == FieldTimeRelation::ValidGteReference &&
|
if ((contract.timeRelation == FieldTimeRelation::ValidGteReference &&
|
||||||
frame.frameContentIdentity.validTimeMs < frame.referenceTimeMs) ||
|
frame.frameContentIdentity.validTimeMs < frame.referenceTimeMs) ||
|
||||||
@@ -60,6 +109,9 @@ FieldProfileValidationResult FieldProfileValidator::validate(const ResolvedField
|
|||||||
}
|
}
|
||||||
|
|
||||||
for (const auto plane : actual) {
|
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(),
|
const auto declared = std::find_if(contract.requiredPlanes.begin(), contract.requiredPlanes.end(),
|
||||||
[plane](const FieldPlaneRequirement& requirement) {
|
[plane](const FieldPlaneRequirement& requirement) {
|
||||||
return matches(requirement, *plane);
|
return matches(requirement, *plane);
|
||||||
@@ -74,8 +126,9 @@ FieldProfileValidationResult FieldProfileValidator::validate(const ResolvedField
|
|||||||
plane->key.component != FieldComponent::None)
|
plane->key.component != FieldComponent::None)
|
||||||
return result(FieldProfileValidationCode::InvalidCoreUnit, "validity descriptor");
|
return result(FieldProfileValidationCode::InvalidCoreUnit, "validity descriptor");
|
||||||
} else if (plane->valueClass == FieldValueClass::DirectionAngle) {
|
} else if (plane->valueClass == FieldValueClass::DirectionAngle) {
|
||||||
if (plane->unitID != FieldUnitDegrees || !isNumeric(plane->dtype))
|
if (plane->unitID != FieldUnitDegrees || plane->key.component != FieldComponent::None || plane->key.groupID != 0 ||
|
||||||
return result(FieldProfileValidationCode::InvalidCoreUnit, "direction unit/dtype");
|
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) {
|
} else if (plane->valueClass == FieldValueClass::VelocityComponent) {
|
||||||
if (plane->unitID != FieldUnitMetersPerSecond || !isNumeric(plane->dtype))
|
if (plane->unitID != FieldUnitMetersPerSecond || !isNumeric(plane->dtype))
|
||||||
return result(FieldProfileValidationCode::InvalidCoreUnit, "velocity unit/dtype");
|
return result(FieldProfileValidationCode::InvalidCoreUnit, "velocity unit/dtype");
|
||||||
|
|||||||
@@ -90,34 +90,36 @@ TEST(ParticleFieldProfileValidator, EnforcesVelocityDirectionAndValidityCoreRule
|
|||||||
EXPECT_EQ(particle::FieldUnitMetersPerSecond, 1);
|
EXPECT_EQ(particle::FieldUnitMetersPerSecond, 1);
|
||||||
EXPECT_EQ(particle::FieldUnitDegrees, 4);
|
EXPECT_EQ(particle::FieldUnitDegrees, 4);
|
||||||
auto c = contract(); auto f = frame(); const auto bytes = f.planes.front().storage;
|
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));
|
particle::FieldComponent::UEast, particle::FieldDType::Int16, false, bytes));
|
||||||
EXPECT_EQ(validate(c, f), particle::FieldProfileValidationCode::InvalidVelocityGroup);
|
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));
|
particle::FieldComponent::VNorth, particle::FieldDType::Int16, false, bytes));
|
||||||
EXPECT_EQ(validate(c, f), particle::FieldProfileValidationCode::Accepted);
|
EXPECT_EQ(validate(c, f), particle::FieldProfileValidationCode::Accepted);
|
||||||
f.planes.back().unitID = 99;
|
f.planes.back().unitID = 99;
|
||||||
EXPECT_EQ(validate(c, f), particle::FieldProfileValidationCode::InvalidCoreUnit);
|
EXPECT_EQ(validate(c, f), particle::FieldProfileValidationCode::InvalidCoreUnit);
|
||||||
f = frame();
|
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));
|
particle::FieldComponent::UEast, particle::FieldDType::Int16, false, bytes));
|
||||||
EXPECT_EQ(validate(c, f), particle::FieldProfileValidationCode::InvalidVelocityGroup);
|
EXPECT_EQ(validate(c, f), particle::FieldProfileValidationCode::InvalidVelocityGroup);
|
||||||
f = frame();
|
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));
|
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));
|
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));
|
particle::FieldComponent::VNorth, particle::FieldDType::Int16, false, bytes));
|
||||||
EXPECT_EQ(validate(c, f), particle::FieldProfileValidationCode::InvalidVelocityGroup);
|
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;
|
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;
|
c.requiredPlanes.front().unitID = particle::FieldUnitDegrees;
|
||||||
EXPECT_EQ(validate(c, f), particle::FieldProfileValidationCode::Accepted);
|
EXPECT_EQ(validate(c, f), particle::FieldProfileValidationCode::Accepted);
|
||||||
f.planes.front().unitID = 2;
|
f.planes.front().unitID = 2;
|
||||||
c.requiredPlanes.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;
|
c = contract(); f = frame(); f.validityPlane.dtype = particle::FieldDType::UInt8;
|
||||||
EXPECT_EQ(validate(c, f), particle::FieldProfileValidationCode::InvalidCoreUnit);
|
EXPECT_EQ(validate(c, f), particle::FieldProfileValidationCode::InvalidCoreUnit);
|
||||||
f = frame(); f.validityPlane.unitID = 1;
|
f = frame(); f.validityPlane.unitID = 1;
|
||||||
@@ -126,6 +128,61 @@ TEST(ParticleFieldProfileValidator, EnforcesVelocityDirectionAndValidityCoreRule
|
|||||||
EXPECT_EQ(validate(c, f), particle::FieldProfileValidationCode::InvalidCoreUnit);
|
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) {
|
TEST(ParticleFieldProfileValidator, UsesSemanticAndGroupAsTheOnlyPlaneIdentity) {
|
||||||
auto f = frame();
|
auto f = frame();
|
||||||
auto duplicate = f.planes.front();
|
auto duplicate = f.planes.front();
|
||||||
|
|||||||
Reference in New Issue
Block a user