Begin work on general NT validator

This commit is contained in:
Bruno Martins
2019-05-10 15:51:39 -04:00
committed by mdavidsaver
parent 2d186d40d5
commit b76c91a885
7 changed files with 439 additions and 262 deletions

View File

@ -21,6 +21,7 @@ INC += pv/ntcontinuum.h
INC += pv/nthistogram.h
INC += pv/nturi.h
INC += pv/ntndarrayAttribute.h
INC += pv/ntvalidator.h
LIBSRCS += ntutils.cpp
LIBSRCS += ntid.cpp
@ -41,6 +42,7 @@ LIBSRCS += ntcontinuum.cpp
LIBSRCS += nthistogram.cpp
LIBSRCS += nturi.cpp
LIBSRCS += ntndarrayAttribute.cpp
LIBSRCS += ntvalidator.cpp
LIBRARY = nt

View File

@ -6,135 +6,91 @@
#include <algorithm>
#include <epicsThread.h>
#include <pv/lock.h>
#include <pv/sharedPtr.h>
#define epicsExportSharedSymbols
#include <pv/ntndarray.h>
#include <pv/ntndarrayAttribute.h>
#include <pv/ntutils.h>
#include <pv/ntvalidator.h>
using namespace std;
using namespace epics::pvData;
namespace epics { namespace nt {
static NTFieldPtr ntField = NTField::get();
namespace detail {
static FieldCreatePtr fieldCreate = getFieldCreate();
static PVDataCreatePtr pvDataCreate = getPVDataCreate();
static StructureConstPtr *base_s;
static epicsThreadOnceId base_once = EPICS_THREAD_ONCE_INIT;
static Mutex mutex;
void NTNDArrayBuilder::once(void *)
{
FieldBuilderPtr fb(FieldBuilder::begin());
StandardFieldPtr standardField = getStandardField();
for (int i = pvBoolean; i < pvString; ++i) {
ScalarType st = static_cast<ScalarType>(i);
fb->addArray(std::string(ScalarTypeFunc::name(st)) + "Value", st);
}
UnionConstPtr value(fb->createUnion());
StructureConstPtr codec(fb->setId("codec_t")->
add("name", pvString)->
add("parameters", getFieldCreate()->createVariantUnion())->
createStructure());
StructureConstPtr dimension(fb->setId("dimension_t")->
add("size", pvInt)->
add("offset", pvInt)->
add("fullSize", pvInt)->
add("binning", pvInt)->
add("reverse", pvBoolean)->
createStructure());
StructureConstPtr attribute(NTNDArrayAttribute::createBuilder()->createStructure());
base_s = new StructureConstPtr;
*base_s = fb->setId(NTNDArray::URI)->
add("value", value)->
add("codec", codec)->
add("compressedSize", pvLong)->
add("uncompressedSize", pvLong)->
addArray("dimension", dimension)->
add("uniqueId", pvInt)->
add("dataTimeStamp", standardField->timeStamp())->
addArray("attribute", attribute)->
createStructure();
}
StructureConstPtr NTNDArrayBuilder::createStructure()
{
enum
{
DISCRIPTOR_INDEX,
TIMESTAMP_INDEX,
ALARM_INDEX,
DISPLAY_INDEX
};
epicsThreadOnce(&base_once, &NTNDArrayBuilder::once, 0);
const size_t NUMBER_OF_INDICES = DISPLAY_INDEX+1;
const size_t NUMBER_OF_STRUCTURES = 1 << NUMBER_OF_INDICES;
StandardFieldPtr standardField(getStandardField());
FieldBuilderPtr fb(FieldBuilder::begin(*base_s));
Lock xx(mutex);
if (descriptor)
fb->add("descriptor", pvString);
static StructureConstPtr ntndarrayStruc[NUMBER_OF_STRUCTURES];
static UnionConstPtr valueType;
static StructureConstPtr codecStruc;
static StructureConstPtr dimensionStruc;
static StructureConstPtr attributeStruc;
if (alarm)
fb->add("alarm", standardField->alarm());
StructureConstPtr returnedStruc;
if (timeStamp)
fb->add("timeStamp", standardField->timeStamp());
size_t index = 0;
if (descriptor) index |= 1 << DISCRIPTOR_INDEX;
if (timeStamp) index |= 1 << TIMESTAMP_INDEX;
if (alarm) index |= 1 << ALARM_INDEX;
if (display) index |= 1 << DISPLAY_INDEX;
if (display)
fb->add("display", standardField->display());
bool isExtended = !extraFieldNames.empty();
size_t extraCount = extraFieldNames.size();
for (size_t i = 0; i< extraCount; i++)
fb->add(extraFieldNames[i], extraFields[i]);
if (isExtended || !ntndarrayStruc[index])
{
StandardFieldPtr standardField = getStandardField();
FieldBuilderPtr fb = fieldCreate->createFieldBuilder();
if (!valueType)
{
for (int i = pvBoolean; i < pvString; ++i)
{
ScalarType st = static_cast<ScalarType>(i);
fb->addArray(std::string(ScalarTypeFunc::name(st)) + "Value", st);
}
valueType = fb->createUnion();
}
if (!codecStruc)
{
codecStruc = fb->setId("codec_t")->
add("name", pvString)->
add("parameters", fieldCreate->createVariantUnion())->
createStructure();
}
if (!dimensionStruc)
{
dimensionStruc = fb->setId("dimension_t")->
add("size", pvInt)->
add("offset", pvInt)->
add("fullSize", pvInt)->
add("binning", pvInt)->
add("reverse", pvBoolean)->
createStructure();
}
if (!attributeStruc)
{
attributeStruc = NTNDArrayAttribute::createBuilder()->createStructure();
}
fb->setId(NTNDArray::URI)->
add("value", valueType)->
add("codec", codecStruc)->
add("compressedSize", pvLong)->
add("uncompressedSize", pvLong)->
addArray("dimension", dimensionStruc)->
add("uniqueId", pvInt)->
add("dataTimeStamp", standardField->timeStamp())->
addArray("attribute", attributeStruc);
if (descriptor)
fb->add("descriptor", pvString);
if (alarm)
fb->add("alarm", standardField->alarm());
if (timeStamp)
fb->add("timeStamp", standardField->timeStamp());
if (display)
fb->add("display", standardField->display());
size_t extraCount = extraFieldNames.size();
for (size_t i = 0; i< extraCount; i++)
fb->add(extraFieldNames[i], extraFields[i]);
returnedStruc = fb->createStructure();
if (!isExtended)
ntndarrayStruc[index] = returnedStruc;
}
else
{
return ntndarrayStruc[index];
}
return returnedStruc;
return fb->createStructure();
}
NTNDArrayBuilder::shared_pointer NTNDArrayBuilder::addDescriptor()
@ -192,95 +148,11 @@ NTNDArrayBuilder::shared_pointer NTNDArrayBuilder::add(string const & name, Fiel
return shared_from_this();
}
}
const std::string NTNDArray::URI("epics:nt/NTNDArray:1.0");
const std::string ntAttrStr("epics:nt/NTAttribute:1.0");
static FieldCreatePtr fieldCreate = getFieldCreate();
static PVDataCreatePtr pvDataCreate = getPVDataCreate();
class NTValueType
{
public:
static bool isCompatible(UnionConstPtr const &u)
{
if(!u.get()) return false;
if (u->getID() != Union::defaultId()) return false;
if (u->isVariant()) return false;
for (int i = pvBoolean; i != pvString; ++i)
{
ScalarType scalarType = static_cast<ScalarType>(i);
std::string name(ScalarTypeFunc::name(scalarType));
name += "Value";
ScalarArrayConstPtr scalarField = u->getField<ScalarArray>(name);
if (scalarField.get() == 0 ||
scalarField->getElementType() != scalarType)
return false;
}
return true;
}
};
class NTCodec
{
public:
static bool isCompatible(StructureConstPtr const &structure)
{
if(!structure.get()) return false;
if (structure->getID() != "codec_t") return false;
ScalarConstPtr scalarField = structure->getField<Scalar>("name");
if (scalarField.get() == 0 || scalarField->getScalarType() != pvString)
return false;
UnionConstPtr paramField = structure->getField<Union>("parameters");
if (paramField.get() == 0 || !paramField->isVariant())
return false;
return true;
}
};
class NTDimension
{
public:
static bool isCompatible(StructureConstPtr const &structure)
{
if(!structure.get()) return false;
if (structure->getID() != "dimension_t") return false;
ScalarConstPtr scalarField = structure->getField<Scalar>("size");
if (scalarField.get() == 0 || scalarField->getScalarType() != pvInt)
return false;
scalarField = structure->getField<Scalar>("offset");
if (scalarField.get() == 0 || scalarField->getScalarType() != pvInt)
return false;
scalarField = structure->getField<Scalar>("fullSize");
if (scalarField.get() == 0 || scalarField->getScalarType() != pvInt)
return false;
scalarField = structure->getField<Scalar>("binning");
if (scalarField.get() == 0 || scalarField->getScalarType() != pvInt)
return false;
scalarField = structure->getField<Scalar>("reverse");
if (scalarField.get() == 0 || scalarField->getScalarType() != pvBoolean)
return false;
return true;
}
};
NTNDArray::shared_pointer NTNDArray::wrap(PVStructurePtr const & pvStructure)
{
if(!isCompatible(pvStructure)) return shared_pointer();
@ -302,84 +174,47 @@ bool NTNDArray::is_a(PVStructurePtr const & pvStructure)
return is_a(pvStructure->getStructure());
}
static Validator::Definition* definition;
static epicsThreadOnceId definition_once = EPICS_THREAD_ONCE_INIT;
static void definition_init(void *)
{
StructureConstPtr structure(
NTNDArray::createBuilder()->
addDescriptor()->
addAlarm()->
addDisplay()->
addTimeStamp()->
createStructure());
definition = new Validator::Definition;
definition->structure = std::static_pointer_cast<const Field>(structure);
// TODO: these should be all getFieldT
definition->optional.insert(structure->getField("descriptor").get());
definition->optional.insert(structure->getField("alarm").get());
definition->optional.insert(structure->getField("timeStamp").get());
definition->optional.insert(structure->getField("display").get());
// TODO: the following should be a helper in NTNDArrayAttribute
StructureConstPtr attribute(
std::static_pointer_cast<const StructureArray>(
structure->getField("attribute")
)->getStructure()
);
definition->optional.insert(attribute->getField("tags").get());
definition->optional.insert(attribute->getField("alarm").get());
definition->optional.insert(attribute->getField("timeStamp").get());
}
bool NTNDArray::isCompatible(StructureConstPtr const &structure)
{
if(!structure.get()) return false;
UnionConstPtr valueField = structure->getField<Union>("value");
if(!NTValueType::isCompatible(valueField)) return false;
epicsThreadOnce(&definition_once, &definition_init, 0);
StructureConstPtr codecField = structure->getField<Structure>("codec");
if(!NTCodec::isCompatible(codecField)) return false;
ScalarConstPtr compressedSizeField = structure->getField<Scalar>("compressedSize");
if (compressedSizeField.get() == 0)
return false;
if (compressedSizeField->getScalarType() != pvLong)
return false;
ScalarConstPtr uncompressedSizeField = structure->getField<Scalar>("uncompressedSize");
if (uncompressedSizeField.get() == 0)
return false;
if (uncompressedSizeField->getScalarType() != pvLong)
return false;
StructureArrayConstPtr dimensionField = structure->getField<StructureArray>("dimension");
if (dimensionField.get() == 0)
return false;
StructureConstPtr dimElementStruc = dimensionField->getStructure();
if(!NTDimension::isCompatible(dimElementStruc))
return false;
NTFieldPtr ntField = NTField::get();
StructureConstPtr dataTimeStampField = structure->getField<Structure>(
"dataTimeStamp");
if (dataTimeStampField.get() == 0 || !ntField->isTimeStamp(dataTimeStampField))
return false;
ScalarConstPtr uniqueIdField = structure->getField<Scalar>("uniqueId");
if (uniqueIdField.get() == 0)
return false;
if (uniqueIdField->getScalarType() != pvInt)
return false;
StructureArrayConstPtr attributeField = structure->getField<StructureArray>( "attribute");
if (!attributeField)
return false;
if (!NTNDArrayAttribute::isCompatible(attributeField->getStructure()))
return false;
FieldConstPtr field = structure->getField("descriptor");
if (field.get())
{
ScalarConstPtr descriptorField = structure->getField<Scalar>("descriptor");
if (!descriptorField || descriptorField->getScalarType() != pvString)
return false;
}
field = structure->getField("alarm");
if (field.get() && !ntField->isAlarm(field))
return false;
field = structure->getField("timeStamp");
if (field.get() && !ntField->isTimeStamp(field))
return false;
field = structure->getField("display");
if (field.get() && !ntField->isDisplay(field))
return false;
return true;
return Validator::isCompatible(*definition, *structure);
}
@ -390,7 +225,6 @@ bool NTNDArray::isCompatible(PVStructurePtr const & pvStructure)
return isCompatible(pvStructure->getStructure());
}
bool NTNDArray::isValid()
{
int64 valueSize = getValueSize();

231
src/ntvalidator.cpp Normal file
View File

@ -0,0 +1,231 @@
/* ntvalidator.cpp */
/*
* Copyright information and license terms for this software can be
* found in the file LICENSE that is included with the distribution
*/
#include <sstream>
#include <algorithm>
#include <deque>
#define epicsExportSharedSymbols
#include <pv/ntvalidator.h>
#include <pv/bitSet.h>
namespace epics { namespace nt {
using epics::pvData::Type;
using epics::pvData::Field;
using epics::pvData::Union;
using epics::pvData::UnionArray;
using epics::pvData::Structure;
using epics::pvData::StructureConstPtr;
using epics::pvData::StructureArray;
using epics::pvData::StringArray;
using epics::pvData::FieldConstPtr;
using epics::pvData::FieldConstPtrArray;
using epics::pvData::BitSet;
using std::string;
struct Helper
{
Validator::Definition const & definition;
std::deque<string> path;
Validator::Result result;
Helper(Validator::Definition const & definition) : definition(definition) {}
bool isOptional(Field const & field) const
{
return definition.optional.find(&field) != definition.optional.end();
}
string getCurrentPath(void) const
{
std::ostringstream os;
// TODO: is there a better string join?
std::deque<string>::const_iterator it;
for (it = path.cbegin(); it != path.cend(); ++it) {
if (it != path.cbegin())
os << ".";
os << *it;
}
return os.str();
}
void appendError(Validator::ErrorType type)
{
result.errors.push_back(Validator::Error(getCurrentPath(), type));
}
void appendError(Validator::ErrorType type, string const & field_name)
{
path.push_back(field_name);
appendError(type);
path.pop_back();
}
bool validate(Union const & ref, Union const & un)
{
// TODO: Validator Errors for unions could be more granular
if (&un == &ref)
return true;
if (ref.isVariant() != un.isVariant()) {
appendError(Validator::ErrorType::INCORRECT_TYPE);
return false;
}
if (ref.isVariant())
return true;
size_t numfields = ref.getNumberFields();
if (un.getNumberFields() != numfields) {
appendError(Validator::ErrorType::INCORRECT_TYPE);
return false;
}
StringArray const & rnames = ref.getFieldNames();
StringArray const & unames = un.getFieldNames();
if (!std::equal(rnames.cbegin(), rnames.cend(), unames.cend())) {
appendError(Validator::ErrorType::INCORRECT_TYPE);
return false;
}
FieldConstPtrArray const & rfields = ref.getFields();
FieldConstPtrArray const & ufields = un.getFields();
bool fields_ok = true;
for (size_t i = 0; i < numfields; ++i) {
path.push_back(rnames[i]);
fields_ok = fields_ok && validate(*rfields[i], *ufields[i]);
path.pop_back();
}
return fields_ok;
}
bool validate(Structure const & ref, Structure const & struc)
{
if (&struc == &ref)
return true;
StringArray const & rnames = ref.getFieldNames();
StringArray const & snames = struc.getFieldNames();
// TODO: This is naive O(N^2), replace with better algorithm
// TODO: This assumes getField doesn't fail (there's no Field::getFieldT)
bool fields_ok = true;
StringArray::const_iterator ri;
for (ri = rnames.cbegin(); ri != rnames.cend(); ++ri) {
FieldConstPtr rfield(ref.getField(*ri));
if (std::find(snames.cbegin(), snames.cend(), *ri) == snames.end()) {
if (isOptional(*rfield))
continue;
appendError(Validator::ErrorType::MISSING_FIELD, *ri);
fields_ok = false;
} else {
path.push_back(*ri);
if (!validate(*rfield, *struc.getField(*ri))) {
appendError(Validator::ErrorType::INCORRECT_TYPE, *ri);
fields_ok = false;
}
path.pop_back();
}
}
return fields_ok;
}
bool validate(Field const & reference, Field const & field)
{
Type referenceType = reference.getType();
if (referenceType != field.getType())
return false;
switch(referenceType) {
case Type::scalar:
case Type::scalarArray:
return true;
case Type::structure:
return validate(
static_cast<Structure const &>(reference),
static_cast<Structure const &>(field)
);
case Type::structureArray:
return validate(
*static_cast<StructureArray const &>(reference).getStructure(),
*static_cast<StructureArray const &>(field).getStructure()
);
case Type::union_:
return validate(
static_cast<Union const &>(reference),
static_cast<Union const &>(field)
);
case Type::unionArray:
return validate(
*static_cast<UnionArray const &>(reference).getUnion(),
*static_cast<UnionArray const &>(field).getUnion()
);
default:
throw std::logic_error("Unknown Field type");
return false;
}
}
bool validate(Field const & field)
{
return validate(*definition.structure, field);
}
};
Validator::Result Validator::validate(Validator::Definition const & definition,
Field const & field)
{
Helper helper(definition);
helper.validate(field);
return helper.result;
}
bool Validator::isCompatible(Validator::Definition const & definition,
Field const & field)
{
return Helper(definition).validate(field);
}
bool Validator::isCompatible(Validator::Definition const & definition,
StructureConstPtr const & structure)
{
FieldConstPtr field(std::static_pointer_cast<const Field>(structure));
return Validator::isCompatible(definition, *field);
}
std::ostream& operator<<(std::ostream& o, const Validator::Error& error)
{
switch (error.type) {
case Validator::ErrorType::MISSING_FIELD:
return o << "Missing field '" << error.path << "'";
case Validator::ErrorType::INCORRECT_TYPE:
return o << "Field '" << error.path << "' has incorrect type";
default:
return o << "Unknown error " << error.type << " in field '"
<< error.path << "'";
}
}
}}

View File

@ -101,6 +101,7 @@ namespace detail {
private:
NTNDArrayBuilder();
static void once(void*);
void reset();

68
src/pv/ntvalidator.h Normal file
View File

@ -0,0 +1,68 @@
/* ntvalidator.h */
/*
* Copyright information and license terms for this software can be
* found in the file LICENSE that is included with the distribution
*/
#ifndef NTVALIDATOR_H
#define NTVALIDATOR_H
#include <string>
#include <set>
#include <shareLib.h>
#include <pv/pvIntrospect.h>
namespace epics { namespace nt {
/**
* @brief Validation methods for NT types.
*
* @author bsm
*/
class epicsShareClass Validator {
public:
struct Definition {
epics::pvData::FieldConstPtr structure;
std::set<epics::pvData::Field const *> optional;
};
enum ErrorType { MISSING_FIELD, INCORRECT_TYPE };
struct Error {
std::string path;
ErrorType type;
Error(std::string const & path, ErrorType type) : path(path), type(type) {};
};
struct Result {
std::vector<Error> errors;
bool valid(void) const { return errors.empty(); }
};
/**
* Checks whether a Field is compatible with a NT definition.
* @param definition the definition of the reference structure
* @param field the Field being tested for compatibility
* @return true if field is compatible, false otherwise
*/
static Result validate(Definition const & definition,
epics::pvData::Field const & field);
static bool isCompatible(Definition const & definition,
epics::pvData::Field const & field);
static bool isCompatible(Definition const & definition,
epics::pvData::StructureConstPtr const & structure);
private:
Validator();
};
epicsShareExtern std::ostream& operator<<(std::ostream& o, const Validator::Error& error);
}}
#endif /* NTVALIDATOR_H */

View File

@ -72,6 +72,10 @@ TESTPROD_HOST += ntutilsTest
ntutilsTest_SRCS = ntutilsTest.cpp
TESTS += ntutilsTest
TESTPROD_HOST += ntvalidatorTest
ntutilsTest_SRCS = ntvalidatorTest.cpp
TESTS += ntvalidatorTest
TESTSCRIPTS_HOST += $(TESTS:%=%.t)
include $(TOP)/configure/RULES

37
test/ntvalidatorTest.cpp Normal file
View File

@ -0,0 +1,37 @@
/*
* Copyright information and license terms for this software can be
* found in the file LICENSE that is included with the distribution
*/
#include <epicsUnitTest.h>
#include <testMain.h>
#include <pv/ntvalidator.h>
#include <pv/ntndarray.h>
using namespace epics::nt;
using epics::pvData::StructureConstPtr;
using epics::pvData::Field;
void test_isCompatible()
{
testDiag("test_isCompatible");
StructureConstPtr ref(NTNDArray::createBuilder()->addAlarm()->createStructure());
Validator::Definition def;
def.structure = std::static_pointer_cast<const Field>(ref);
def.optional.insert(ref->getField("alarm").get());
StructureConstPtr struc(NTNDArray::createBuilder()->createStructure());
testOk1(Validator::isCompatible(def, struc));
}
MAIN(testNTValidator) {
testPlan(1);
test_isCompatible();
return testDone();
}