diff --git a/src/Makefile b/src/Makefile index bd83c05..39fc5a2 100644 --- a/src/Makefile +++ b/src/Makefile @@ -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 diff --git a/src/ntndarray.cpp b/src/ntndarray.cpp index 08fe5f0..22844f8 100644 --- a/src/ntndarray.cpp +++ b/src/ntndarray.cpp @@ -6,135 +6,91 @@ #include +#include + #include +#include #define epicsExportSharedSymbols #include #include #include +#include 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(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(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(i); - std::string name(ScalarTypeFunc::name(scalarType)); - name += "Value"; - ScalarArrayConstPtr scalarField = u->getField(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("name"); - if (scalarField.get() == 0 || scalarField->getScalarType() != pvString) - return false; - - UnionConstPtr paramField = structure->getField("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("size"); - if (scalarField.get() == 0 || scalarField->getScalarType() != pvInt) - return false; - - scalarField = structure->getField("offset"); - if (scalarField.get() == 0 || scalarField->getScalarType() != pvInt) - return false; - - scalarField = structure->getField("fullSize"); - if (scalarField.get() == 0 || scalarField->getScalarType() != pvInt) - return false; - - scalarField = structure->getField("binning"); - if (scalarField.get() == 0 || scalarField->getScalarType() != pvInt) - return false; - - scalarField = structure->getField("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(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( + 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("value"); - if(!NTValueType::isCompatible(valueField)) return false; + epicsThreadOnce(&definition_once, &definition_init, 0); - StructureConstPtr codecField = structure->getField("codec"); - if(!NTCodec::isCompatible(codecField)) return false; - - ScalarConstPtr compressedSizeField = structure->getField("compressedSize"); - if (compressedSizeField.get() == 0) - return false; - - if (compressedSizeField->getScalarType() != pvLong) - return false; - - - ScalarConstPtr uncompressedSizeField = structure->getField("uncompressedSize"); - if (uncompressedSizeField.get() == 0) - return false; - - if (uncompressedSizeField->getScalarType() != pvLong) - return false; - - StructureArrayConstPtr dimensionField = structure->getField("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( - "dataTimeStamp"); - if (dataTimeStampField.get() == 0 || !ntField->isTimeStamp(dataTimeStampField)) - return false; - - - ScalarConstPtr uniqueIdField = structure->getField("uniqueId"); - if (uniqueIdField.get() == 0) - return false; - - if (uniqueIdField->getScalarType() != pvInt) - return false; - - - StructureArrayConstPtr attributeField = structure->getField( "attribute"); - if (!attributeField) - return false; - - if (!NTNDArrayAttribute::isCompatible(attributeField->getStructure())) - return false; - - - FieldConstPtr field = structure->getField("descriptor"); - if (field.get()) - { - ScalarConstPtr descriptorField = structure->getField("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(); diff --git a/src/ntvalidator.cpp b/src/ntvalidator.cpp new file mode 100644 index 0000000..3693cba --- /dev/null +++ b/src/ntvalidator.cpp @@ -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 +#include +#include + +#define epicsExportSharedSymbols +#include +#include + +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 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::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(reference), + static_cast(field) + ); + + case Type::structureArray: + return validate( + *static_cast(reference).getStructure(), + *static_cast(field).getStructure() + ); + + case Type::union_: + return validate( + static_cast(reference), + static_cast(field) + ); + + case Type::unionArray: + return validate( + *static_cast(reference).getUnion(), + *static_cast(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(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 << "'"; + } +} + +}} diff --git a/src/pv/ntndarray.h b/src/pv/ntndarray.h index 3d00b3b..b81055b 100644 --- a/src/pv/ntndarray.h +++ b/src/pv/ntndarray.h @@ -101,6 +101,7 @@ namespace detail { private: NTNDArrayBuilder(); + static void once(void*); void reset(); diff --git a/src/pv/ntvalidator.h b/src/pv/ntvalidator.h new file mode 100644 index 0000000..6cceaf6 --- /dev/null +++ b/src/pv/ntvalidator.h @@ -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 +#include + +#include +#include + + +namespace epics { namespace nt { + +/** + * @brief Validation methods for NT types. + * + * @author bsm + */ + +class epicsShareClass Validator { +public: + struct Definition { + epics::pvData::FieldConstPtr structure; + std::set 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 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 */ diff --git a/test/Makefile b/test/Makefile index 7241b9c..e225719 100644 --- a/test/Makefile +++ b/test/Makefile @@ -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 diff --git a/test/ntvalidatorTest.cpp b/test/ntvalidatorTest.cpp new file mode 100644 index 0000000..3f1afc4 --- /dev/null +++ b/test/ntvalidatorTest.cpp @@ -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 +#include + +#include +#include + +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(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(); +} + +