From 801d295c1f53da906c5a79da1bc2d379833a798e Mon Sep 17 00:00:00 2001 From: Michael Davidsaver Date: Wed, 20 Nov 2019 10:53:29 -0800 Subject: [PATCH] start PVD --- src/Makefile | 5 + src/data.cpp | 1039 +++++++++++++++++++++++++++++++++++++++++++ src/dataencode.h | 214 +++++++++ src/dataimpl.h | 114 +++++ src/evhelper.h | 1 - src/pvxs/data.h | 381 ++++++++++++++++ src/pvxs/unittest.h | 5 +- src/udp_collector.h | 2 +- src/util.cpp | 72 +++ src/utilpvt.h | 7 +- test/Makefile | 8 + test/testdata.cpp | 313 +++++++++++++ test/testev.cpp | 2 +- test/testsock.cpp | 2 +- test/testudp.cpp | 2 +- test/testxcode.cpp | 459 +++++++++++++++++++ tools/pvxvct.cpp | 2 +- 17 files changed, 2617 insertions(+), 11 deletions(-) create mode 100644 src/data.cpp create mode 100644 src/dataencode.h create mode 100644 src/dataimpl.h create mode 100644 src/pvxs/data.h create mode 100644 test/testdata.cpp create mode 100644 test/testxcode.cpp diff --git a/src/Makefile b/src/Makefile index 48ca053..f48f52e 100644 --- a/src/Makefile +++ b/src/Makefile @@ -37,12 +37,17 @@ INC += pvxs/version.h INC += pvxs/versionNum.h INC += pvxs/log.h INC += pvxs/unittest.h +INC += pvxs/util.h +INC += pvxs/sharedArray.h +INC += pvxs/data.h +INC += pvxs/server.h LIBRARY = pvxs LIB_SRCS += log.cpp LIB_SRCS += unittest.cpp LIB_SRCS += util.cpp +LIB_SRCS += data.cpp LIB_SRCS += evhelper.cpp LIB_SRCS += udp_collector.cpp LIB_SRCS += server.cpp diff --git a/src/data.cpp b/src/data.cpp new file mode 100644 index 0000000..35d546d --- /dev/null +++ b/src/data.cpp @@ -0,0 +1,1039 @@ +/** + * Copyright - See the COPYRIGHT that is included with this distribution. + * pvxs is distributed subject to a Software License Agreement found + * in file LICENSE that is included with this distribution. + */ + +#include +#include + +#include + +#include "dataimpl.h" +#include "utilpvt.h" + +namespace pvxs { + +bool TypeCode::valid() const +{ + if((code&0x10) && code!=Null) + return false; // fixed size not supported + switch(scalarOf().code) { +#define CASE(CASE, LBL) case CASE: + CASE(Bool, "bool"); + CASE(Int8, "int8_t"); + CASE(Int16, "int16_t"); + CASE(Int32, "int32_t"); + CASE(Int64, "int64_t"); + CASE(UInt8, "uint8_t"); + CASE(UInt16, "uint16_t"); + CASE(UInt32, "uint32_t"); + CASE(UInt64, "uint64_t"); + CASE(Float32,"float"); + CASE(Float64,"double"); + CASE(String, "string"); + CASE(Any, "any"); + CASE(Union, "union"); + CASE(Struct, "struct"); + CASE(Null, "null"); +#undef CASE + return true; + default: + return false; + } +} + +const char* TypeCode::name() const +{ + switch(code) { +#define CASE(CASE, LBL) case TypeCode::CASE: return LBL + CASE(Bool, "bool"); + CASE(Int8, "int8_t"); + CASE(Int16, "int16_t"); + CASE(Int32, "int32_t"); + CASE(Int64, "int64_t"); + CASE(UInt8, "uint8_t"); + CASE(UInt16, "uint16_t"); + CASE(UInt32, "uint32_t"); + CASE(UInt64, "uint64_t"); + CASE(Float32,"float"); + CASE(Float64,"double"); + CASE(String, "string"); + CASE(Any, "any"); + CASE(Union, "union"); + CASE(Struct, "struct"); + CASE(Null, "null"); +#undef CASE +#define CASE(CASE, LBL) case TypeCode::CASE##A: return LBL "[]" + CASE(Bool, "bool"); + CASE(Int8, "int8_t"); + CASE(Int16, "int16_t"); + CASE(Int32, "int32_t"); + CASE(Int64, "int64_t"); + CASE(UInt8, "uint8_t"); + CASE(UInt16, "uint16_t"); + CASE(UInt32, "uint32_t"); + CASE(UInt64, "uint64_t"); + CASE(Float32,"float"); + CASE(Float64,"double"); + CASE(String, "string"); + CASE(Any, "any"); + CASE(Union, "union"); + CASE(Struct, "struct"); +#undef CASE + } + return "\?\?\?_t"; +} + +struct TypeDef::Node { + Node * const parent; + std::string id; + TypeCode code; + std::vector> children; + explicit Node(Node *parent) :parent(parent) {} + Node(Node* parent, const char *id, TypeCode code) :parent(parent), id(id?id:""), code(code) {} + + decltype (TypeDef::root) clone(Node *new_parent) const { + decltype (TypeDef::root) ret{new Node(new_parent, id.c_str(), code)}; + ret->children.reserve(children.size()); + for(auto& pair : children) { + ret->children.emplace_back(pair.first, pair.second->clone(ret.get())); + } + return ret; + } +}; + +void TypeDef::NodeDeletor::operator()(Node *p) +{ + delete p; +} + +static +void node_validate(const TypeDef::Node* parent, const char *id, TypeCode code) +{ + if(id && code!=TypeCode::Struct && code!=TypeCode::Union) + throw std::runtime_error("Only Struct or Union may have an ID"); + if(parent) { + auto c = parent->code.scalarOf(); + if(c!=TypeCode::Struct && c!=TypeCode::Union) + throw std::runtime_error("Only (array of) Struct or Union may have members"); + } +} + +static +void name_validate(const char *name) +{ + // [a-zA-Z_][a-zA-Z0-9_]* + + if(!name || name[0]=='\0') + throw std::runtime_error("empty field name not allowed"); + for(size_t i=0; name[i]; i++) { + char c = name[i]; + if(c>='0' && c<='9' && i>0) { + // number ok after first + } else if((c>='a' && c<='z') || (c>='A' && c<='Z')) { + // alphas ok + } else { + switch(c) { + case '_': + break; + default: + throw std::runtime_error(SB()<<"invalid field name \""<code; + node.id = desc->id; + node.children.reserve(desc->miter.size()); + for(auto& pair : desc->miter) { + node.children.emplace_back(pair.first, + decltype (node.children)::value_type::second_type{new TypeDef::Node(&node)}); + copy_tree(desc+pair.second, *node.children.back().second); + } +} + +TypeDef::TypeDef(const Value& o) +{ + if(o.desc) { + root.reset(new Node(nullptr)); + copy_tree(o.desc, *root); + } +} + +TypeDef::~TypeDef() {} + +TypeDef TypeDef::clone() const +{ + TypeDef ret; + if(root) { + ret.root = root->clone(nullptr); + } + return ret; +} + +TypeDef::Cursor TypeDef::begin() +{ + if(!root) + throw std::runtime_error("Can't edit empty TypeDef"); + Cursor ret; + ret.owner = this; + ret.reset(); + return ret; +} + +static +void build_tree(std::vector& desc, const TypeDef::Node& node) +{ + auto code = node.code; + if(node.code==TypeCode::StructA || node.code==TypeCode::UnionA) { + + desc.emplace_back(); + auto& fld = desc.back(); + fld.code = node.code; + // struct/union array have no ID + fld.hash = node.code.code; + code = code.scalarOf(); + } + + const auto index = desc.size(); + desc.emplace_back(); + + { + auto& fld = desc.back(); + fld.code = code; + fld.id = node.id; + fld.hash = code.code ^ std::hash{}(fld.id); + } + + + for(auto& pair : node.children) { + const auto cindex = desc.size(); + + build_tree(desc, *pair.second); // recurse. may realloc desc + + auto& fld = desc[index]; + auto& child = desc[cindex]; + + fld.hash ^= std::hash{}(pair.first) ^ child.hash; + + fld.mlookup[pair.first] = cindex-index; + fld.miter.emplace_back(pair.first, cindex-index); + + std::string cname = pair.first+"."; + if(fld.code.code==TypeCode::Struct && fld.code==child.code) { + // propagate names from sub-struct + for(auto& cpair : child.mlookup) { + fld.mlookup[cname+cpair.first] = cindex-index+cpair.second; + } + } + } + + desc[index].num_index = desc.size()-index; + + if(node.code==TypeCode::StructA || node.code==TypeCode::UnionA) + { + desc[index-1].num_index = desc.size()-index+1; + } +} + +Value TypeDef::create() const +{ + if(!root) + throw std::logic_error("Empty TypeDef"); + + auto desc = std::make_shared>(); + build_tree(*desc, *root); + FieldDesc_calculate_offset(desc->data()); + + std::shared_ptr type(desc, desc->data()); // alias + return Value(type); +} + +TypeDef::Cursor& TypeDef::Cursor::seek(const char *name) +{ + while(name && name[0]) { + auto sep = strchr(name, '.'); + std::string fname; + if(sep) { + fname = std::string(name, sep-name); + name = sep+1; + } else { + fname = name; + name = nullptr; + } + + for(auto i : range(parent->children.size())) { + auto& pair = parent->children[i]; + if(pair.first==fname) { + auto code = pair.second->code.scalarOf(); + if(code==TypeCode::Union || code==TypeCode::Struct) { + parent = pair.second.get(); + index = parent->children.size(); + + } else if(sep) { + throw std::runtime_error("Can only seek through Struct/Union"); + + } else { + index = i; + } + } + } + } + + return *this; +} + +TypeDef::Cursor& TypeDef::Cursor::change(const char *id, TypeCode code) +{ + node_validate(parent, id, code); + + if(index>=parent->children.size()) { + throw std::runtime_error("Cursor does not select a field"); + } else { + auto& fld = parent->children[index]; + if(code.kind()!=Kind::Compound && !fld.second->children.empty()) + throw std::runtime_error("May not change type of Compound field w/ sub-fields"); + fld.second->id = id; + fld.second->code = code; + } + return *this; +} + +TypeDef::Cursor& TypeDef::Cursor::insert(const char *name, const char *id, TypeCode code) +{ + node_validate(parent, id, code); + name_validate(name); + + decltype (owner->root) node{new Node(parent, id, code)}; + + parent->children.emplace(parent->children.begin()+index, + name, std::move(node)); + index++; + return *this; +} + +TypeDef::Cursor& TypeDef::Cursor::add(const char *name, const TypeDef& def) +{ + name_validate(name); + + if(!def.root) + throw std::runtime_error("Empty TypeDef"); + + node_validate(parent, def.root->id.c_str(), def.root->code); + + parent->children.emplace(parent->children.begin()+index, + name, std::move(def.root->clone(parent))); + + return *this; +} + +TypeDef::Cursor& TypeDef::Cursor::up() +{ + if(parent!=owner->root.get()) { + parent = parent->parent; + index = parent->children.size(); + } else { + throw std::logic_error("Can't go up() from root"); + } + return *this; +} + +TypeDef::Cursor& TypeDef::Cursor::reset() +{ + parent = owner->root.get(); + index = parent->children.size(); + return *this; +} + +static +void indent(std::ostream& strm, unsigned level) { + for(auto i : range(level)) { + (void)i; + strm<<" "; + } +} + +static +void show_Node(std::ostream& strm, const std::string& name, const TypeDef::Node* node, unsigned level=0) +{ + strm<code; + if(!node->id.empty()) + strm<<" \""<id<<"\""; + if(!node->children.empty()) { + strm<<" {\n"; + for(auto& pair : node->children) { + indent(strm, level+1); + show_Node(strm, pair.first, pair.second.get(), level+1); + } + indent(strm, level); + strm.put('}'); + if(!name.empty()) + strm<<" "<\n"; + } else { + show_Node(strm, std::string(), def.root.get()); + } + return strm; +} + +NoConvert::NoConvert() + :std::runtime_error ("No conversion defined") +{} + +NoConvert::~NoConvert() {} + +Value::Value(const std::shared_ptr& desc) + :desc(nullptr) +{ + if(!desc) + return; + + auto top = std::make_shared(); + + top->desc = desc; + top->valid.resize(desc->next_offset-desc->offset, false); + top->members.resize(desc->next_offset-desc->offset); + { + auto& root = top->members[0]; + root.init(desc.get()); + root.top = top.get(); + } + + for(auto& pair : desc->mlookup) { + auto cfld = desc.get() + pair.second; + auto& mem = top->members.at(cfld->offset-desc->offset); + mem.top = top.get(); + mem.init(cfld); + } + + this->desc = desc.get(); + decltype (store) val(top, top->members.data()); // alias + this->store = std::move(val); +} + +Value::~Value() {} + +Value Value::cloneEmpty() const +{ + Value ret; + if(desc) { + decltype (store->top->desc) fld(store->top->desc, desc); + ret = Value(fld); + } + return ret; +} + +Value Value::clone() const +{ + Value ret; + if(desc) { + decltype (store->top->desc) fld(store->top->desc, desc); + ret = Value(fld); + //ret.assign(*this); + } + return ret; +} + +//Value& Value::assign(const Value& o) +//{ +// if(desc!=o.desc) +// throw std::runtime_error("Can only assign same TypeDef"); // TODO relax + +// return *this; +//} + +Value Value::allocMember() const +{ + // allocate member type for Struct[] or Union[] + if(!desc || (desc->code!=TypeCode::UnionA && desc->code!=TypeCode::StructA)) + throw std::runtime_error("allocMember() only meaningful for Struct[] or Union[]"); + + decltype (store->top->desc) fld(store->top->desc, desc+1); + return Value(fld); +} + +bool Value::isMarked(bool parents, bool children) const +{ + // TODO test parent and child mask + return desc ? store->top->valid[store->index()] : false; +} + +void Value::mark(bool v) +{ + if(desc) + store->top->valid[store->index()] = v; +} + +void Value::unmark(bool parents, bool children) +{ + // TODO clear parent and/or child mask + if(desc) + store->top->valid[store->index()] = false; +} + +TypeCode Value::type() const +{ + return desc ? desc->code : TypeCode::Null; +} + +StoreType Value::storageType() const +{ + return store ? store->code : StoreType::Null; +} + +const std::string& Value::id() const +{ + if(!desc) + throw std::runtime_error("Null Value"); + return desc->id; +} + +namespace { +// C-style cast between scalar storage types, and print to string (base 10) +template +bool copyOutScalar(const Src& src, void *ptr, StoreType type) +{ + switch(type) { + case StoreType::Real: *reinterpret_cast(ptr) = src; return true; + case StoreType::Integer: *reinterpret_cast(ptr) = src; return true; + case StoreType::UInteger: *reinterpret_cast(ptr) = src; return true; + case StoreType::String: *reinterpret_cast(ptr) = SB()<code) { + case StoreType::Real: if(copyOutScalar(store->as(), ptr, type)) return; else break; + case StoreType::Integer: if(copyOutScalar(store->as(), ptr, type)) return; else break; + case StoreType::UInteger: if(copyOutScalar(store->as(), ptr, type)) return; else break; + case StoreType::String: { + auto& src = store->as(); + + switch(type) { + case StoreType::String: *reinterpret_cast(ptr) = src; return; + // TODO: parse Integer/Real + default: + break; + } + break; + } + case StoreType::Array: { + auto& src = store->as>(); + switch (type) { + case StoreType::Array: *reinterpret_cast*>(ptr) = src; return; + // TODO: print array + // extract [0] as scalar? + default: + break; + } + break; + } + case StoreType::Compound: { + auto& src = store->as(); + if(type==StoreType::Compound) { + // extract Value + *reinterpret_cast(ptr) = src; + return; + + } else if(src) { + // automagic deref and delegate assign + src.copyOut(ptr, type); + return; + + } else { + throw NoConvert(); + } + + break; + } + case StoreType::Null: + break; + } + + throw NoConvert(); +} + +namespace { +// C-style cast between scalar storage types, and print to string (base 10) +template +bool copyInScalar(Dest& dest, const void *ptr, StoreType type) +{ + switch(type) { + case StoreType::Real: dest = *reinterpret_cast(ptr); return true; + case StoreType::Integer: dest = *reinterpret_cast(ptr); return true; + case StoreType::UInteger: dest = *reinterpret_cast(ptr); return true; + case StoreType::String: // TODO: parse from string + case StoreType::Null: + case StoreType::Compound: + case StoreType::Array: + break; + } + return false; +} +} + +void Value::copyIn(const void *ptr, StoreType type) +{ + if(!desc) + throw NoConvert(); + + switch(store->code) { + case StoreType::Real: if(!copyInScalar(store->as(), ptr, type)) throw NoConvert(); break; + case StoreType::Integer: if(!copyInScalar(store->as(), ptr, type)) throw NoConvert(); break; + case StoreType::UInteger: if(!copyInScalar(store->as(), ptr, type)) throw NoConvert(); break; + case StoreType::String: { + auto& dest = store->as(); + + switch(type) { + case StoreType::String: dest = *reinterpret_cast(ptr); break; + case StoreType::Integer: dest = SB()<<*reinterpret_cast(ptr); break; + case StoreType::UInteger: dest = SB()<<*reinterpret_cast(ptr); break; + case StoreType::Real: dest = SB()<<*reinterpret_cast(ptr); break; + default: + throw NoConvert(); + } + break; + } + case StoreType::Array: { + auto& dest = store->as>(); + switch (type) { + case StoreType::Array: { + auto& src = *reinterpret_cast*>(ptr); + if(src.original_type()==ArrayType::Null || src.empty()) { + // assignment from untyped or empty + dest.clear(); + + } else if(src.original_type()==ArrayType::Value && desc->code.kind()==Kind::Compound) { + // assign array of Struct/Union/Any + auto tsrc = shared_array_static_cast(src); + + if(desc->code!=TypeCode::AnyA) { + // enforce member type for Struct[] and Union[] + for(auto& val : tsrc) { + if(val.desc && val.desc!=desc+1) { + throw NoConvert(); + } + } + } + dest = src; + + } else if(src.original_type()!=ArrayType::Value && uint8_t(desc->code.code)==uint8_t(src.original_type())) { + // assign array of scalar w/o convert + dest = src; + + } else { + // TODO: alloc and convert + throw NoConvert(); + } + break; + } + default: + throw NoConvert(); + } + break; + } + case StoreType::Compound: + if(desc->code==TypeCode::Any) { + // assigning variant union. + if(type==StoreType::Compound) { + store->as() = *reinterpret_cast(ptr); + break; + } + } + // fall through + case StoreType::Null: + throw NoConvert(); + } + + store->top->valid[store->index()] = true; +} + +void Value::traverse(const std::string &expr, bool modify) +{ + size_t pos=0; + while(desc && poscode.code==TypeCode::Struct) { + // attempt traverse to member. + // expect: [0-9a-zA-Z_.]+[\[-$] + size_t sep = expr.find_first_of("[-", pos); + + decltype (desc->mlookup)::const_iterator it; + + if(sep>0 && (it=desc->mlookup.find(expr.substr(pos, sep-pos)))!=desc->mlookup.end()) { + // found it + auto next = desc+it->second; + auto offset = next->offset - desc->offset; + decltype(store) value(store, store.get()+offset); + store = std::move(value); + desc = next; + pos = sep; + + } else { + // no such member + store.reset(); + desc = nullptr; + } + + } else if(desc->code.code==TypeCode::Union || desc->code.code==TypeCode::Any) { + // attempt to traverse to (and maybe select) member + // expect: ->[0-9a-zA-Z_]+[.\[-$] + + if(expr.size()-pos >= 3 && expr[pos]=='-' && expr[pos+1]=='>') { + pos += 2; // skip past "->" + + if(desc->code.code==TypeCode::Any) { + // select member of Any (may be Null) + *this = store->as(); + + } else { + // select member of Union + size_t sep = expr.find_first_of("[-.", pos); + + decltype (desc->mlookup)::const_iterator it; + + if(sep>0 && (it=desc->mlookup.find(expr.substr(pos, sep-pos)))!=desc->mlookup.end()) { + // found it. + auto& fld = store->as(); + + if(modify || fld.desc==desc+it->second) { + // will select, or already selected + if(fld.desc!=desc+it->second) { + // select + std::shared_ptr mtype(store->top->desc, desc+it->second); + fld = Value(mtype); + } + pos = sep; + *this = fld; + + } else { + // traversing const Value, can't select Union + store.reset(); + desc = nullptr; + } + } + } + } else { + // expected "->" + store.reset(); + desc = nullptr; + } + + } else if(desc->code.isarray() && desc->code.kind()==Kind::Compound) { + // attempt to traverse into array of Struct, Union, or Any + // expect: \[[0-9]+\] + + size_t sep = expr.find_first_of(']', pos); + unsigned long long index=0; + + if(expr[pos]=='[' + && sep!=std::string::npos && sep-pos>=2 + && !epicsParseULLong(expr.substr(pos+1, sep-1-pos).c_str(), &index, 0, nullptr)) + { + auto& varr = store->as>(); + shared_array arr; + if((varr.original_type()==ArrayType::Value) + && index < (arr = shared_array_static_cast(varr)).size()) + { + *this = arr[index]; + pos = sep+1; + } else { + // wrong element type or out of range + store.reset(); + desc = nullptr; + } + + } else { + // syntax error + store.reset(); + desc = nullptr; + } + + } else { + // syntax error or wrong field type (can't index scalar array) + store.reset(); + desc = nullptr; + } + } +} + +Value Value::operator[](const char *name) +{ + Value ret(*this); + ret.traverse(name, true); + return ret; +} + +const Value Value::operator[](const char *name) const +{ + Value ret(*this); + ret.traverse(name, false); + return ret; +} + +static +void show_Value(std::ostream& strm, + const std::string& member, + const FieldDesc *desc, + const FieldStorage* store, + unsigned level=0) +{ + indent(strm, level); + if(!desc) { + strm<<"null\n"; + return; + } + + strm<code; + if(!desc->id.empty()) + strm<<" \""<id<<"\""; + if(!member.empty() && desc->code!=TypeCode::Struct) + strm<<" "<code) { + case StoreType::Null: + if(desc->code==TypeCode::Struct) { + strm<<" {\n"; + for(auto& pair : desc->miter) { + auto cdesc = desc + pair.second; + show_Value(strm, pair.first, cdesc, store - desc->offset + cdesc->offset, level+1); + } + indent(strm, level); + strm<<"}"; + if(!member.empty()) + strm<<" "<as())<<"\"\n"; break; + case StoreType::Compound: { + auto& fld = store->as(); + if(fld.valid() && desc->code==TypeCode::Union) { + for(auto& pair : desc->miter) { + if(desc + pair.second == fld._desc()) { + strm<<"."<as>(); + if(varr.original_type()!=ArrayType::Value) { + strm<<" = "<(varr); + strm<<" [\n"; + for(auto& val : arr) { + show_Value(strm, std::string(), val._desc(), val._store(), level+1); + } + indent(strm, level); + strm<<"]\n"; + } + } + break; + default: + strm<<"!!Invalid StoreType!! "<code)<<"\n"; + break; + } +} + +std::ostream& operator<<(std::ostream& strm, const Value& val) +{ + show_Value(strm, std::string(), val._desc(), val._store()); + return strm; +} + +namespace impl { + +void FieldStorage::init(const FieldDesc *desc) +{ + if(!desc || desc->code.kind()==Kind::Null || desc->code.code==TypeCode::Struct) { + this->code = StoreType::Null; + + } else if(desc->code.isarray()) { + this->code = StoreType::Array; + new(&store) shared_array(); + + } else { + switch(desc->code.kind()) { + case Kind::String: + new(&store) std::string(); + this->code = StoreType::String; + break; + case Kind::Compound: + new(&store) std::shared_ptr(); + this->code = StoreType::Compound; + break; + case Kind::Integer: + if(!desc->code.isunsigned()) { + as() = 0u; + this->code = StoreType::Integer; + break; + } + // fall trhough + case Kind::Bool: + as() = 0u; + this->code = StoreType::UInteger; + break; + case Kind::Real: + as() = 0.0; + this->code = StoreType::Real; + break; + default: + throw std::logic_error("FieldStore::init()"); + } + } +} + +void FieldStorage::deinit() +{ + switch(code) { + case StoreType::Null: + case StoreType::Integer: + case StoreType::UInteger: + case StoreType::Real: + break; + case StoreType::Array: + as>().~shared_array(); + break; + case StoreType::String: + as().~basic_string(); + break; + case StoreType::Compound: + as().~Value(); + break; + default: + throw std::logic_error("FieldStore::deinit()"); + } + code = StoreType::Null; +} + +FieldStorage::~FieldStorage() +{ + deinit(); +} + +size_t FieldStorage::index() const +{ + const size_t ret = this-top->members.data(); + assert(this==&top->members[ret]); + return ret; +} + +void FieldDesc_calculate_offset(FieldDesc* const top) +{ + top->offset = 0; + uint16_t offset = 1; + size_t index = 1; + while(index < top->size()) { + auto& fld = top[index]; + + switch (fld.code.code) { + case TypeCode::Struct: + if(top->code==fld.code) { + // sub-structure + fld.offset = offset++; + index++; + } else { + // structure inside union or array of struct + // new offset zero + FieldDesc_calculate_offset(top); + index+=top->size(); + } + break; + case TypeCode::Union: + // number in parent structure/union + fld.offset = offset++; + // new offset zero for each child + for(auto& pair : fld.miter) { + FieldDesc_calculate_offset(top+index+pair.second); + } + index += fld.size(); + break; + case TypeCode::StructA: + case TypeCode::UnionA: + // number in parent structure/union + fld.offset = offset++; + index++; + // new offset zero for child + FieldDesc_calculate_offset(top+index); + index += top[index].size(); + break; + default: + fld.offset = offset++; + index++; + break; + } + fld.next_offset = offset; + } + top->next_offset = offset; +} + +std::ostream& operator<<(std::ostream& strm, const FieldDesc* desc) +{ + for(auto idx : range(desc->size())) { + auto& fld = desc[idx]; + strm<<"["<" + " ["< "< +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include "pvaproto.h" +#include "utilpvt.h" +#include "dataimpl.h" + +namespace pvxs { +namespace impl { + +template +void to_wire(Buf& buf, const FieldDesc* cur) +{ + // we assume FieldDesc* is valid (checked on creation) + to_wire(buf, cur->code.code); + + // other than (array of) struct and union, encoding is simple + switch(cur->code.code) { + case TypeCode::StructA: + case TypeCode::UnionA: + to_wire(buf, cur+1); + break; + + case TypeCode::Struct: + case TypeCode::Union: + to_wire(buf, cur->id); + to_wire(buf, Size{cur->miter.size()}); + for(auto& pair : cur->miter) { + to_wire(buf, pair.first); + to_wire(buf, cur+pair.second); // jump forward in FieldDesc array and recurse! + } + break; + default: + break; + } +} + +typedef std::map> TypeStore; + +struct TypeDeserContext { + std::vector& descs; + TypeStore& cache; +}; + +template +void from_wire(Buf& buf, TypeDeserContext& ctxt, unsigned depth=0) +{ + if(!buf.good() || depth>20) { + buf.fault(); + return; + } + + TypeCode code; + from_wire(buf, code.code); + const size_t index = ctxt.descs.size(); // index of first node we add to ctxt.descs[] + + if(code.code==0xfd) { + // update cache + uint16_t key=0; + from_wire(buf, key); + from_wire(buf, ctxt, depth+1u); + if(!buf.good() || index==ctxt.descs.size()) { + buf.fault(); + return; + + } else { + auto& entry = ctxt.cache[key]; + // copy new node, and any decendents into cache + entry.resize(ctxt.descs.size()-index); + std::copy(ctxt.descs.begin()+index, + ctxt.descs.end(), + entry.begin()); + } + + } else if(code.code==0xfe) { + // fetch cache + uint16_t key=0; + from_wire(buf, key); + auto it = ctxt.cache.find(key); + if(it==ctxt.cache.end()) { + buf.fault(); + } + + if(!buf.good() || it->second.empty()) { + buf.fault(); + return; + + } else { + // copy from cache + ctxt.descs.resize(index+it->second.size()); + std::copy(it->second.begin(), + it->second.end(), + ctxt.descs.begin()+index); + } + + } else if(code.code!=0xff && code.code&0x10) { + // fixed length is deprecated + buf.fault(); + + } else { + // actual field + + ctxt.descs.emplace_back(); + { + auto& fld = ctxt.descs.back(); + + fld.code = code; + fld.hash = code.code; + } + + switch(code.code) { + case TypeCode::StructA: + case TypeCode::UnionA: + from_wire(buf, ctxt, depth+1); + if(!buf.good() || ctxt.descs.size()==index || ctxt.descs[index+1].code!=code.scalarOf()) { + buf.fault(); + return; + } + break; + + case TypeCode::Struct: + case TypeCode::Union: { + from_wire(buf, ctxt.descs.back().id); + + Size nfld{0}; + std::string name; + from_wire(buf, nfld); // number of children + { + auto& fld = ctxt.descs.back(); + + fld.miter.reserve(nfld.size); + fld.hash ^= std::hash{}(fld.id); + } + + for(auto i: range(nfld.size)) { + (void)i; + const size_t cindex = ctxt.descs.size(); // index of this child + from_wire(buf, name); + from_wire(buf, ctxt, depth+1); + if(!buf.good() || cindex>=ctxt.descs.size()) { + buf.fault(); + return; + } + + // descs may be re-allocated (invalidating previous refs.) + auto& fld = ctxt.descs[index]; + auto& cfld = ctxt.descs[cindex]; + + // update hash + // TODO investigate better ways to combine hashes + fld.hash ^= std::hash{}(name) ^ cfld.hash; + + // update field refs. + fld.miter.emplace_back(name, cindex-index); + fld.mlookup[name] = cindex-index; + name+='.'; + + if(code.code==TypeCode::Struct && code==cfld.code) { + // copy decendent indicies for sub-struct + for(auto& pair : cfld.mlookup) { + fld.mlookup[name+pair.first] = cindex + pair.second; + } + } + } + } + break; + default: + // not handling fixed/bounded + // other types have simple/single node description + switch(code.code&~0x08) { + case TypeCode::Bool: + case TypeCode::Int8: + case TypeCode::Int16: + case TypeCode::Int32: + case TypeCode::Int64: + case TypeCode::UInt8: + case TypeCode::UInt16: + case TypeCode::UInt32: + case TypeCode::UInt64: + case TypeCode::Float32: + case TypeCode::Float64: + case TypeCode::String: + case TypeCode::Any: + break; + default: + buf.fault(); + break; + } + } + + ctxt.descs[index].num_index = ctxt.descs.size()-index; + } +} + +}} // namespace pvxs::impl + +#endif // DATAENCODE_H diff --git a/src/dataimpl.h b/src/dataimpl.h new file mode 100644 index 0000000..7e1e291 --- /dev/null +++ b/src/dataimpl.h @@ -0,0 +1,114 @@ +/** + * Copyright - See the COPYRIGHT that is included with this distribution. + * pvxs is distributed subject to a Software License Agreement found + * in file LICENSE that is included with this distribution. + */ +#ifndef DATAIMPL_H +#define DATAIMPL_H + +#include +#include + +#include +#include + +namespace pvxs { +namespace impl { + +/** Describes a single field, leaf or otherwise, in a nested structure. + * + * FieldDesc are always stored depth first as a contigious array, + * with offset to decendent fields given as positive integers relative + * to the current field. (not possible to jump _back_) + * + * We deal with two different numeric values: + * 1. indicies in this FieldDesc array. found in FieldDesc::mlookup and FieldDesc::miter + * Relative to current position in FieldDesc array. (aka this+n) + * 2. offsets in associated FieldStorage array. found in FieldDesc::index + * Relative to current FieldDesc*. + */ +struct FieldDesc { + // type ID string (struct/union) + std::string id; + // Lookup of all decendent fields of this Structure or Union. + // "fld.sub.leaf" -> rel index + std::map mlookup; + // child iteration. child# -> ("sub", rel index) + std::vector> miter; + // hash of this type (aggragating from children) + // created using the code ^ id ^ (child_name ^ child_hash)*N + size_t hash; + // abs. offset in enclosing top Structure/Union. (not abs. offset of FieldDesc array) + // used to navigate FieldStorage array + size_t offset=0, next_offset=0; + // number of FieldDesc nodes which describe this node and decendents. Inclusive. always >=1 + uint16_t num_index=0; + TypeCode code{TypeCode::Null}; + + template + FieldDesc* operator[](T key) { + auto it = mlookup.find(key); + if(it!=mlookup.end()) { + return this+it.second; + } else { + return nullptr; + } + } + + // number of FieldDesc nodes which describe this node. Inclusive. always size()>=1 + inline size_t size() const { return num_index; } +}; + +struct StructTop; + +struct FieldStorage { + /* Storage for field value. depends on type code. + * + * All array types stored as shared_array which includes full type info + * Integers promoted to either int64_t or uint64_t. + * Bool promoted to uint64_t + * Reals promoted to double. + * String stored as std::string + * Compound (Struct, Union, Any) stored as shared_ptr + */ + std::aligned_union<8, + double, // Real + uint64_t, // Bool, Integer + std::string, // String + Value, // Union, Any + shared_array // array of POD, std::string, or std::shared_ptr + >::type store; + // index of this field in StructTop::members + StructTop *top; + StoreType code=StoreType::Null; // duplicates associated FieldDesc::code + + void init(const FieldDesc* desc); + void deinit(); + ~FieldStorage(); + + size_t index() const; + + template + T& as() { return *reinterpret_cast(&store); } + template + const T& as() const { return *reinterpret_cast(&store); } +}; + +struct StructTop { + std::vector valid; + std::shared_ptr desc; + std::vector members; +}; +static_assert (std::is_standard_layout{}, "Needed for offsetof()"); + +using Type = std::shared_ptr; + +PVXS_API +void FieldDesc_calculate_offset(FieldDesc* top); + +PVXS_API +std::ostream& operator<<(std::ostream& strm, const FieldDesc* desc); + +}} // namespace pvxs::impl + +#endif // DATAIMPL_H diff --git a/src/evhelper.h b/src/evhelper.h index 3004cfe..b899b16 100644 --- a/src/evhelper.h +++ b/src/evhelper.h @@ -43,7 +43,6 @@ struct default_delete { } namespace pvxs {namespace impl { -using namespace pvxs; //! unique_ptr which is never constructed with NULL template diff --git a/src/pvxs/data.h b/src/pvxs/data.h new file mode 100644 index 0000000..232d2e6 --- /dev/null +++ b/src/pvxs/data.h @@ -0,0 +1,381 @@ +/** + * Copyright - See the COPYRIGHT that is included with this distribution. + * pvxs is distributed subject to a Software License Agreement found + * in file LICENSE that is included with this distribution. + */ +#ifndef PVXS_DATA_H +#define PVXS_DATA_H + +#include +#include +#include +#include +#include +#include + +#include +#include + +namespace pvxs { +class Value; + +//! selector for union FieldStorage::store +enum struct StoreType : uint8_t { + Null, //!< no associate storage + UInteger, //!< uint64_t + Integer, //!< int64_t + Real, //!< double + String, //!< std::string + Compound, //!< Value + Array, //!< shared_array +}; + +namespace impl { +struct FieldStorage; +struct FieldDesc; + +//! maps T to one of the types which can be stored in the FieldStorage::store union +//! typename StorageMap::store_t is, if existant, is one such type. +//! Can store_t shall be cast-able to/from T. +//! StorageMap::code is the associated StoreType. +template +struct StorageMap; + +// map signed integers to int64_t +template +struct StorageMap{} && std::is_signed{}>::type> +{ typedef int64_t store_t; static constexpr StoreType code{StoreType::Integer}; }; + +// map unsigned integers, and bool, to uint64_t +template +struct StorageMap{} && !std::is_signed{}>::type> +{ typedef uint64_t store_t; static constexpr StoreType code{StoreType::UInteger}; }; + +// map floating point to double. (truncates long double, but then PVA doesn't >8 byte primatives anyway support anyway) +template +struct StorageMap{}>::type> +{ typedef double store_t; static constexpr StoreType code{StoreType::Real}; }; + +template<> +struct StorageMap +{ typedef std::string store_t; static constexpr StoreType code{StoreType::String}; }; + +template<> +struct StorageMap +{ typedef std::string store_t; static constexpr StoreType code{StoreType::String}; }; + +template<> +struct StorageMap> +{ typedef shared_array store_t; static constexpr StoreType code{StoreType::Array}; }; + +template<> +struct StorageMap +{ typedef Value store_t; static constexpr StoreType code{StoreType::Compound}; }; + +} // namespace impl + +//! Groups of related types +enum struct Kind : uint8_t { + Bool = 0x00, + Integer = 0x20, + Real = 0x40, + String = 0x60, + Compound = 0x80, + Null = 0xe0, +}; + +//! A particular type +struct TypeCode { + //! actual complete (scalar) type code. + enum code_t : uint8_t { + Bool = 0x00, + BoolA = 0x08, + Int8 = 0x20, + Int16 = 0x21, + Int32 = 0x22, + Int64 = 0x23, + UInt8 = 0x24, + UInt16 = 0x25, + UInt32 = 0x26, + UInt64 = 0x27, + Int8A = 0x28, + Int16A = 0x29, + Int32A = 0x2a, + Int64A = 0x2b, + UInt8A = 0x2c, + UInt16A = 0x2d, + UInt32A = 0x2e, + UInt64A = 0x2f, + Float32 = 0x42, + Float64 = 0x43, + Float32A= 0x4a, + Float64A= 0x4b, + String = 0x60, + StringA = 0x68, + Struct = 0x80, + Union = 0x81, + Any = 0x82, + StructA = 0x88, + UnionA = 0x89, + AnyA = 0x8a, + // 0xfd - cache update w/ full + // 0xfe - cache fetch + Null = 0xff, + }; + + //! the actual type code. eg. for switch() + code_t code; + + bool valid() const; + + Kind kind() const { return Kind(code&0xe0); } + //! size()==1< root; +public: + TypeDef() = default; + TypeDef(TypeCode code, const char *id=nullptr); + // moveable, not copyable + TypeDef(const TypeDef&) = delete; + TypeDef(TypeDef&&) = default; + TypeDef& operator=(const TypeDef&) = delete; + TypeDef& operator=(TypeDef&&) = default; + explicit TypeDef(const Value&); + ~TypeDef(); + + TypeDef clone() const; + + class PVXS_API Cursor { + friend class TypeDef; + TypeDef* owner; + Node* parent; + size_t index; // [0, parent->children.size()] (index==size() appends) + public: + + Cursor& seek(const char *name); + Cursor& change(const char *id, TypeCode code); + + Cursor& insert(const char *name, const char *id, TypeCode code); + inline Cursor& insert(const char *name, TypeCode code) { + return insert(name, nullptr, code); + } + + Cursor& add(const char *name, const TypeDef& def); + Cursor& up(); + Cursor& reset(); + TypeDef& end() { return *owner; } + //! shorthand for .end().create() + inline Value create(); + }; + friend class Cursor; + + Cursor begin(); + Value create() const; + + friend + std::ostream& operator<<(std::ostream& strm, const TypeDef&); +}; + +PVXS_API +std::ostream& operator<<(std::ostream& strm, const TypeDef&); + +struct PVXS_API NoConvert : public std::runtime_error +{ + explicit NoConvert(); + virtual ~NoConvert(); + TypeCode source; +}; + +//! pointer-like reference to a single data field +class PVXS_API Value { + friend class TypeDef; + std::shared_ptr store; + const impl::FieldDesc* desc; // owned thourgh StructTop (aliased as FieldStorage) +public: + constexpr Value() :desc(nullptr) {} + explicit Value(const std::shared_ptr& desc); + Value(const Value&) = default; + Value(Value&&) = default; + Value& operator=(const Value&) = default; + Value& operator=(Value&&) = default; + ~Value(); + + //! allocate new storage, with default values + Value cloneEmpty() const; + //! allocate new storage and copy in our values + Value clone() const; + //! copy values from other. Must have matching types. +// Value& assign(const Value&); + + Value allocMember() const; + + inline bool valid() const { return desc; } + inline explicit operator bool() const { return desc; } + + bool isMarked(bool parents=true, bool children=false) const; + void mark(bool v=true); + void unmark(bool parents=false, bool children=true); + + TypeCode type() const; + StoreType storageType() const; + const std::string& id() const; + + //! test for instance equality. + inline bool compareInst(const Value& o) { return store==o.store; } +// int compareValue(const Value&); +// int compareType(const Value&); + + // access to Value's ... value + // not for Struct + + // use with caution + void copyOut(void *ptr, StoreType type) const; + //bool tryCopyOut(void *ptr, impl::StoreType type) const; + void copyIn(const void *ptr, StoreType type); + //bool tryCopyIn(const void *ptr, impl::StoreType type); + +// template +// inline bool tryAs(T& val) const { +// return tryCopyOut(&val, std::type_index(typeid(std::decay::type))); +// } + + /** Extract value from field. + */ + template + inline T as() const { + typedef impl::StorageMap::type> map_t; + typename map_t::store_t ret; + copyOut(&ret, map_t::code); + return ret; + } +// template +// void as(T& val) const { +// copyOut(&val, std::type_index(typeid(typename std::decay::type))); +// } + +// template +// inline bool tryFrom(const T& val) { +// return tryCopyIn(&val, std::type_index(typeid(std::decay::type))); +// } + + template + void from(const T& val) { + typedef impl::StorageMap::type> map_t; + typename map_t::store_t norm(val); + copyIn(&norm, map_t::code); + } + + // TODO T=Value is ambigious with previous assignment operator + template + Value& operator=(const T& val) { + from(val); + return *this; + } + + // Struct/Union access +private: + void traverse(const std::string& expr, bool modify); +public: + + //! attempt to decend into sub-structure + Value operator[](const char *name); + inline Value operator[](const std::string& name) { return (*this)[name.c_str()]; } + const Value operator[](const char *name) const; + inline const Value operator[](const std::string& name) const { return (*this)[name.c_str()]; } + +private: + template friend class _iterator; + // these cheat on const-ness + void _step(const Value& child, bool next) const; + void _first_child(const Value& child) const; + + template + class _iterator { + V *parent; + V child; + friend class Value; + public: + _iterator() :parent(nullptr) {} + explicit _iterator(V* parent) :parent(parent) {} + + V& operator*() { return child; } + V* operator->() { return &child; } + + // ++(*this) + inline _iterator& operator++() { parent->_step(child, true); return *this; } + // (*this)++ + inline _iterator operator++(int) { _iterator ret(*this); parent->_step(child, true); return ret;} + // --(*this) + inline _iterator& operator--() { parent->_step(child, false); return *this; } + // (*this)-- + inline _iterator operator--(int) { _iterator ret(*this); parent->_step(child, false); return ret;} + + inline bool operator==(const _iterator& o) const { return child.compareInst(o.child)==0; } + inline bool operator!=(const _iterator& o) const { return !((*this)==o); } + }; +public: + typedef _iterator iterator; + typedef _iterator const_iterator; + + inline iterator begin() { iterator ret(this); _first_child(ret.child); return ret;} + inline const_iterator cbegin() const { const_iterator ret(this); _first_child(ret.child); return ret;} + inline const_iterator begin() const { return cbegin(); } + + inline iterator end() { iterator ret(this); return ret;} + inline const_iterator cend() const { const_iterator ret(this); return ret;} + inline const_iterator end() const { return cend(); } + + // impl cheating + const impl::FieldDesc* _desc() const { return desc; } + impl::FieldStorage* _store() const { return store.get(); } +}; + +PVXS_API +std::ostream& operator<<(std::ostream& strm, const Value& val); + +Value TypeDef::Cursor::create() { + return end().create(); +} + +} // namespace pvxs + +#endif // PVXS_DATA_H diff --git a/src/pvxs/unittest.h b/src/pvxs/unittest.h index 33ca6a8..9a458fe 100644 --- a/src/pvxs/unittest.h +++ b/src/pvxs/unittest.h @@ -97,7 +97,7 @@ testCase testEq(const char *sLHS, const LHS& lhs, const char *sRHS, const RHS& r ret<<") == "<::op(ret, rhs); ret<<") "; - return std::move(ret); + return ret; } template @@ -109,7 +109,7 @@ testCase testNotEq(const char *sLHS, const LHS& lhs, const char *sRHS, const RHS ret<<") != "<::op(ret, rhs); ret<<") "; - return std::move(ret); + return ret; } } // namespace detail @@ -133,5 +133,6 @@ testCase testThrows(FN fn) #define testEq(LHS, RHS) ::pvxs::detail::testEq(#LHS, LHS, #RHS, RHS) #define testNotEq(LHS, RHS) ::pvxs::detail::testNotEq(#LHS, LHS, #RHS, RHS) +#define testShow() ::pvxs::testCase() #endif // PVXS_UNITTEST_H diff --git a/src/udp_collector.h b/src/udp_collector.h index 1d85d7c..9c312fd 100644 --- a/src/udp_collector.h +++ b/src/udp_collector.h @@ -84,7 +84,7 @@ class PVXS_API UDPListener std::shared_ptr collector; const SockAddr dest; bool active; - friend struct UDPCollector; + friend class UDPCollector; friend struct UDPManager; public: diff --git a/src/util.cpp b/src/util.cpp index b66e312..9d00960 100644 --- a/src/util.cpp +++ b/src/util.cpp @@ -14,6 +14,8 @@ #include #include +#include +#include #include "utilpvt.h" #include "udp_collector.h" @@ -37,6 +39,76 @@ void cleanup_for_valgrind() impl::UDPManager::cleanup(); } +std::ostream& operator<<(std::ostream& strm, ArrayType code) +{ + switch(code) { +#define CASE(CODE) case ArrayType::CODE : strm<<#CODE; break + CASE(Null); + CASE(Bool); + CASE(UInt8); + CASE(UInt16); + CASE(UInt32); + CASE(UInt64); + CASE(Int8); + CASE(Int16); + CASE(Int32); + CASE(Int64); + CASE(Float); + CASE(Double); + CASE(Value); +#undef CASE + default: + strm<<"<\?\?\?>"; + } + return strm; +} + +std::ostream& operator<<(std::ostream& strm, const shared_array& arr) +{ + switch(arr.original_type()) { + case ArrayType::Null: strm<<"[null]"; break; +#define CASE(CODE, Type) case ArrayType::CODE: strm<(arr); break + CASE(Bool, bool); + CASE(UInt8, uint8_t); + CASE(UInt16, uint16_t); + CASE(UInt32, uint32_t); + CASE(UInt64, uint64_t); + CASE(Int8, int8_t); + CASE(Int16, int16_t); + CASE(Int32, int32_t); + CASE(Int64, int64_t); + CASE(Float, float); + CASE(Double, double); + CASE(String, std::string); + CASE(Value, Value); +#undef CASE + } + return strm; +} + +std::ostream& operator<<(std::ostream& strm, const shared_array& arr) +{ + switch(arr.original_type()) { + case ArrayType::Null: strm<<"[null]"; break; +#define CASE(CODE, Type) case ArrayType::CODE: strm<(arr); break + CASE(Bool, bool); + CASE(UInt8, uint8_t); + CASE(UInt16, uint16_t); + CASE(UInt32, uint32_t); + CASE(UInt64, uint64_t); + CASE(Int8, int8_t); + CASE(Int16, int16_t); + CASE(Int32, int32_t); + CASE(Int64, int64_t); + CASE(Float, float); + CASE(Double, double); + CASE(String, std::string); + CASE(Value, Value); +#undef CASE + } + return strm; +} + namespace detail { Escaper::Escaper(const char* v) diff --git a/src/utilpvt.h b/src/utilpvt.h index 27ab794..d0fb4b2 100644 --- a/src/utilpvt.h +++ b/src/utilpvt.h @@ -23,7 +23,6 @@ #include namespace pvxs {namespace impl { -using namespace pvxs; //! in-line string builder (eg. for exception messages) //! eg. @code throw std::runtime_error(SB()<<"Some message"<<42); @endcode @@ -32,7 +31,7 @@ struct SB { SB() {} operator std::string() const { return strm.str(); } template - SB& operator<<(T i) { strm< + +#include + +#include +#include +#include "utilpvt.h" +#include "dataimpl.h" + +using namespace pvxs; +namespace { + +void showSize() +{ + testDiag("%s()", __func__); +#define CASE(TYPE) testDiag("sizeof(" #TYPE ") = %u", unsigned(sizeof(TYPE))) + CASE(Value); + CASE(impl::FieldDesc); + CASE(impl::FieldStorage); + CASE(impl::StructTop); +#undef CASE +} + +void testBasic() +{ + testDiag("%s()", __func__); + + auto top = TypeDef(TypeCode::Struct, "simple_t") + .begin() + .insert("value", nullptr, TypeCode::Float64) + .create(); + + testOk1(top.valid()); + testEq(top.type(), TypeCode::Struct); + + { + auto val = top["missing"]; + testOk1(!val.valid()); + testOk1(!val.isMarked()); + + testThrows([&val]() { + val.from(4.2); + }); + } + + { + auto val = top["value"]; + testOk1(!!val.valid()); + testOk1(!val.isMarked()); + val.from(4.2); + testEq(val.as(), 4.2); + testOk1(!!val.isMarked()); + } + + testEq(std::string(SB()<\n"); + + testEq(std::string(SB()< [0:18)\n" + " achoice -> 14 [14]\n" + " any -> 9 [9]\n" + " anya -> 10 [10]\n" + " arbitrary -> 5 [5]\n" + " arbitrary.sarr -> 6 [6]\n" + " choice -> 11 [11]\n" + " timeStamp -> 2 [2]\n" + " timeStamp.nanoseconds -> 4 [4]\n" + " timeStamp.secondsPastEpoch -> 3 [3]\n" + " value -> 1 [1]\n" + " value : 1 [1]\n" + " timeStamp : 2 [2]\n" + " arbitrary : 5 [5]\n" + " any : 9 [9]\n" + " anya : 10 [10]\n" + " choice : 11 [11]\n" + " achoice : 14 [14]\n" + "[1] double[] <1:2> [1:2)\n" + "[2] struct time_t <2:3> [2:5)\n" + " nanoseconds -> 2 [4]\n" + " secondsPastEpoch -> 1 [3]\n" + " secondsPastEpoch : 1 [3]\n" + " nanoseconds : 2 [4]\n" + "[3] uint64_t <3:4> [3:4)\n" + "[4] uint32_t <4:5> [4:5)\n" + "[5] struct <5:6> [5:9)\n" + " sarr -> 1 [6]\n" + " sarr : 1 [6]\n" + "[6] struct[] <6:7> [6:9)\n" + "[7] struct <0:2> [7:9)\n" + " value -> 1 [8]\n" + " value : 1 [8]\n" + "[8] double <1:2> [8:9)\n" + "[9] any <7:8> [9:10)\n" + "[10] any[] <8:9> [10:11)\n" + "[11] union <9:10> [11:14)\n" + " a -> 1 [12]\n" + " b -> 2 [13]\n" + " a : 1 [12]\n" + " b : 2 [13]\n" + "[12] float <0:1> [12:13)\n" + "[13] string <0:1> [13:14)\n" + "[14] union[] <10:11> [14:18)\n" + "[15] union <0:3> [15:18)\n" + " x -> 1 [16]\n" + " y -> 2 [17]\n" + " x : 1 [16]\n" + " y : 2 [17]\n" + "[16] float <1:2> [16:17)\n" + "[17] float <2:3> [17:18)\n" + ""); + + // try to access all field Kinds + + // sub-struct and scalar + val["timeStamp.secondsPastEpoch"] = 0x123456789abcdef0ull; + // array of scalar + { + shared_array arr({1.0, 2.0}); + + val["value"].from(shared_array_static_cast(freeze(std::move(arr)))); + } + // Struct[] + { + auto fld = val["arbitrary.sarr"]; + shared_array arr(3); + arr[0] = fld.allocMember(); + arr[1] = fld.allocMember(); + // leave [2] as null + arr[0]["value"] = 1.0; + arr[1]["value"] = 2.0; + +// auto frozen = freeze(std::move(arr)); +// auto varr = shared_array_static_cast(frozen); +// fld.from(varr); + fld.from(shared_array_static_cast(freeze(std::move(arr)))); + + testEq(val["arbitrary.sarr[1]value"].as(), 2.0); + } + + // Union + val["choice->b"] = "test"; + // Union[] + { + auto fld = val["achoice"]; + shared_array arr(3); + arr[0] = fld.allocMember(); + arr[1] = fld.allocMember(); + // leave [2] as null + arr[0]["->x"] = 4.0; + arr[1]["->y"] = 5.0; + + fld.from(shared_array_static_cast(freeze(std::move(arr)))); + + testEq(fld["[1]"].as(), 5.0); + testEq(val["achoice[1]"].as(), 5.0); + testEq(val["achoice[1]->y"].as(), 5.0); + } + + // Any + { + auto v = TypeDef(TypeCode::UInt32).create(); + v = 42u; + + val["any"].from(v); + + testEq(v.as(), 42u); + } + + // Any[] + { + auto fld = val["anya"]; + shared_array arr(3); + arr[0] = TypeDef(TypeCode::UInt32).create(); + arr[1] = TypeDef(TypeCode::Struct) + .begin() + .insert("q", TypeCode::String) + .create(); + // leave [2] as null + + arr[0] = 123; + arr[1]["q"] = "theq"; + + fld.from(shared_array_static_cast(freeze(std::move(arr)))); + + testEq(fld["[0]"].as(), 123u); + testEq(fld["[1]q"].as(), "theq"); + } + + testShow()< #include -using namespace pvxs::impl; +using namespace pvxs; namespace { struct my_special_error : public std::runtime_error diff --git a/test/testsock.cpp b/test/testsock.cpp index 1ab2136..565063f 100644 --- a/test/testsock.cpp +++ b/test/testsock.cpp @@ -16,7 +16,7 @@ #include namespace { -using namespace pvxs::impl; +using namespace pvxs; void test_udp() { diff --git a/test/testudp.cpp b/test/testudp.cpp index 31c11a2..8b715be 100644 --- a/test/testudp.cpp +++ b/test/testudp.cpp @@ -20,7 +20,7 @@ #include namespace { -using namespace pvxs::impl; +using namespace pvxs; void testBeacon(bool be) { diff --git a/test/testxcode.cpp b/test/testxcode.cpp new file mode 100644 index 0000000..75482e7 --- /dev/null +++ b/test/testxcode.cpp @@ -0,0 +1,459 @@ +/** + * Copyright - See the COPYRIGHT that is included with this distribution. + * pvxs is distributed subject to a Software License Agreement found + * in file LICENSE that is included with this distribution. + */ + +#include +#include + +#include +#include +#include "dataencode.h" + +namespace { +using namespace pvxs; +using namespace pvxs::impl; + +void testDecode1() +{ + testDiag("%s", __func__); + /* From PVA proto doc + * + * timeStamp_t + * long secondsPastEpoch + * int nanoSeconds + * int userTag + */ + std::vector msg({ + // update cache with key 0 + 0xFD, 0x00, 0x01, + // structure + 0x80, + // ID "timeStamp_t" + 0x0B, 0x74, 0x69, 0x6D, 0x65, 0x53, 0x74, 0x61, 0x6D, 0x70, 0x5F, 0x74, + // 3 members + 0x03, + // "secondsPastEpoch" + 0x10, 0x73, 0x65, 0x63, 0x6F, 0x6E, 0x64, 0x73, 0x50, 0x61, 0x73, 0x74, 0x45, 0x70, 0x6F, 0x63, 0x68, + // integer signed 8 bytes + 0x23, + // "nanoSeconds" + 0x0B, 0x6E, 0x61, 0x6E, 0x6F, 0x53, 0x65, 0x63, 0x6F, 0x6E, 0x64, 0x73, + // integer signed 4 bytes + 0x22, + // "userTag" + 0x07, 0x75, 0x73, 0x65, 0x72, 0x54, 0x61, 0x67, + // integer signed 4 bytes + 0x22 + }); + + std::vector descs; + TypeStore cache; + + { + FixedBuf buf(true, msg); + TypeDeserContext ctxt{descs, cache}; + from_wire(buf, ctxt); + if(testOk1(buf.good())) + FieldDesc_calculate_offset(descs.data()); + testEq(buf.size(), 0u)<<"Of "<second.size(), 4u); + } + } + + if(testOk1(!descs.empty())) { + testEq(descs.size(), descs.front().size()); + } + + // cat < [0:4)\n" + " nanoSeconds -> 2 [2]\n" + " secondsPastEpoch -> 1 [1]\n" + " userTag -> 3 [3]\n" + " secondsPastEpoch : 1 [1]\n" + " nanoSeconds : 2 [2]\n" + " userTag : 3 [3]\n" + "[1] int64_t <1:2> [1:2)\n" + "[2] int32_t <2:3> [2:3)\n" + "[3] int32_t <3:4> [3:4)\n" + )<<"Actual:\n"< msg(NTScalar, NTScalar+sizeof(NTScalar)-1); + std::vector descs; + TypeStore cache; + { + FixedBuf buf(true, msg); + TypeDeserContext ctxt{descs, cache}; + from_wire(buf, ctxt); + if(testOk1(buf.good())) + FieldDesc_calculate_offset(descs.data()); + testEq(buf.size(), 0u)<<"remaining of "< [0:10)\n" + " alarm -> 2 [2]\n" + " alarm.message -> 5 [5]\n" + " alarm.severity -> 3 [3]\n" + " alarm.status -> 4 [4]\n" + " timeStamp -> 6 [6]\n" + " timeStamp.nanoseconds -> 8 [8]\n" + " timeStamp.secondsPastEpoch -> 7 [7]\n" + " timeStamp.userTag -> 9 [9]\n" + " value -> 1 [1]\n" + " value : 1 [1]\n" + " alarm : 2 [2]\n" + " timeStamp : 6 [6]\n" + "[1] double[] <1:2> [1:2)\n" + "[2] struct alarm_t <2:3> [2:6)\n" + " message -> 3 [5]\n" + " severity -> 1 [3]\n" + " status -> 2 [4]\n" + " severity : 1 [3]\n" + " status : 2 [4]\n" + " message : 3 [5]\n" + "[3] int32_t <3:4> [3:4)\n" + "[4] int32_t <4:5> [4:5)\n" + "[5] string <5:6> [5:6)\n" + "[6] struct time_t <6:7> [6:10)\n" + " nanoseconds -> 2 [8]\n" + " secondsPastEpoch -> 1 [7]\n" + " userTag -> 3 [9]\n" + " secondsPastEpoch : 1 [7]\n" + " nanoseconds : 2 [8]\n" + " userTag : 3 [9]\n" + "[7] int64_t <7:8> [7:8)\n" + "[8] int32_t <8:9> [8:9)\n" + "[9] int32_t <9:10> [9:10)\n" + )<<"Actual:\n"< out; + out.reserve(msg.size()); + + { + VectorOutBuf buf(true, out); + to_wire(buf, descs.data()); + testOk1(buf.good()); + out.resize(out.size()-buf.size()); + } + + testEq(msg.size(), out.size()); + testEq(msg, out); +} + +// has a bit of everything... (except array of union) +const uint8_t NTNDArray[] = "\x80\x16""epics:nt/NTNDArray:1.0\n" + "\x05value\x81\x00\x0b" + "\x0c""booleanValue\x08" + "\tbyteValue(" + "\nshortValue)" + "\x08intValue*" + "\tlongValue+" + "\nubyteValue," + "\x0bushortValue-" + "\tuintValue." + "\nulongValue/" + "\nfloatValueJ" + "\x0b""doubleValueK" + "\x05""codec\x80\x07""codec_t\x02" + "\x04name`" + "\nparameters\x82" + "\x0e""compressedSize#" + "\x10uncompressedSize#" + "\x08uniqueId\"" + "\rdataTimeStamp\x80\x06time_t\x03" + "\x10secondsPastEpoch#" + "\x0bnanoseconds\"" + "\x07userTag\"" + "\x05""alarm\x80\x07""alarm_t\x03" + "\x08severity\"" + "\x06status\"" + "\x07message`" + "\ttimeStamp\x80\x06time_t\x03" + "\x10secondsPastEpoch#" + "\x0bnanoseconds\"" + "\x07userTag\"" + "\tdimension\x88\x80\x0b""dimension_t\x05" + "\x04size\"" + "\x06offset\"" + "\x08""fullSize\"" + "\x07""binning\"" + "\x07reverse\x00" + "\tattribute\x88\x80\x18""epics:nt/NTAttribute:1.0\x08" + "\x04name`" + "\x05value\x82" + "\x04tagsh" + "\ndescriptor`" + "\x05""alarm\x80\x07""alarm_t\x03" + "\x08severity\"" + "\x06status\"" + "\x07message`" + "\ttimestamp\x80\x06time_t\x03" + "\x10secondsPastEpoch#" + "\x0bnanoseconds\"" + "\x07userTag\"" + "\nsourceType\"" + "\x06source`"; + +void testXCodeNTNDArray() +{ + testDiag("%s", __func__); + + std::vector msg(NTNDArray, NTNDArray+sizeof(NTNDArray)-1); + std::vector descs; + TypeStore cache; + { + FixedBuf buf(true, msg); + TypeDeserContext ctxt{descs, cache}; + from_wire(buf, ctxt); + if(testOk1(buf.good())) + FieldDesc_calculate_offset(descs.data()); + testEq(buf.size(), 0u)<<"remaining of "< [0:54)\n" + " alarm -> 23 [23]\n" + " alarm.message -> 26 [26]\n" + " alarm.severity -> 24 [24]\n" + " alarm.status -> 25 [25]\n" + " attribute -> 38 [38]\n" + " codec -> 13 [13]\n" + " codec.name -> 14 [14]\n" + " codec.parameters -> 15 [15]\n" + " compressedSize -> 16 [16]\n" + " dataTimeStamp -> 19 [19]\n" + " dataTimeStamp.nanoseconds -> 21 [21]\n" + " dataTimeStamp.secondsPastEpoch -> 20 [20]\n" + " dataTimeStamp.userTag -> 22 [22]\n" + " dimension -> 31 [31]\n" + " timeStamp -> 27 [27]\n" + " timeStamp.nanoseconds -> 29 [29]\n" + " timeStamp.secondsPastEpoch -> 28 [28]\n" + " timeStamp.userTag -> 30 [30]\n" + " uncompressedSize -> 17 [17]\n" + " uniqueId -> 18 [18]\n" + " value -> 1 [1]\n" + " value : 1 [1]\n" + " codec : 13 [13]\n" + " compressedSize : 16 [16]\n" + " uncompressedSize : 17 [17]\n" + " uniqueId : 18 [18]\n" + " dataTimeStamp : 19 [19]\n" + " alarm : 23 [23]\n" + " timeStamp : 27 [27]\n" + " dimension : 31 [31]\n" + " attribute : 38 [38]\n" + "[1] union <1:2> [1:13)\n" + " booleanValue -> 1 [2]\n" + " byteValue -> 2 [3]\n" + " doubleValue -> 11 [12]\n" + " floatValue -> 10 [11]\n" + " intValue -> 4 [5]\n" + " longValue -> 5 [6]\n" + " shortValue -> 3 [4]\n" + " ubyteValue -> 6 [7]\n" + " uintValue -> 8 [9]\n" + " ulongValue -> 9 [10]\n" + " ushortValue -> 7 [8]\n" + " booleanValue : 1 [2]\n" + " byteValue : 2 [3]\n" + " shortValue : 3 [4]\n" + " intValue : 4 [5]\n" + " longValue : 5 [6]\n" + " ubyteValue : 6 [7]\n" + " ushortValue : 7 [8]\n" + " uintValue : 8 [9]\n" + " ulongValue : 9 [10]\n" + " floatValue : 10 [11]\n" + " doubleValue : 11 [12]\n" + "[2] bool[] <0:1> [2:3)\n" + "[3] int8_t[] <0:1> [3:4)\n" + "[4] int16_t[] <0:1> [4:5)\n" + "[5] int32_t[] <0:1> [5:6)\n" + "[6] int64_t[] <0:1> [6:7)\n" + "[7] uint8_t[] <0:1> [7:8)\n" + "[8] uint16_t[] <0:1> [8:9)\n" + "[9] uint32_t[] <0:1> [9:10)\n" + "[10] uint64_t[] <0:1> [10:11)\n" + "[11] float[] <0:1> [11:12)\n" + "[12] double[] <0:1> [12:13)\n" + "[13] struct codec_t <2:3> [13:16)\n" + " name -> 1 [14]\n" + " parameters -> 2 [15]\n" + " name : 1 [14]\n" + " parameters : 2 [15]\n" + "[14] string <3:4> [14:15)\n" + "[15] any <4:5> [15:16)\n" + "[16] int64_t <5:6> [16:17)\n" + "[17] int64_t <6:7> [17:18)\n" + "[18] int32_t <7:8> [18:19)\n" + "[19] struct time_t <8:9> [19:23)\n" + " nanoseconds -> 2 [21]\n" + " secondsPastEpoch -> 1 [20]\n" + " userTag -> 3 [22]\n" + " secondsPastEpoch : 1 [20]\n" + " nanoseconds : 2 [21]\n" + " userTag : 3 [22]\n" + "[20] int64_t <9:10> [20:21)\n" + "[21] int32_t <10:11> [21:22)\n" + "[22] int32_t <11:12> [22:23)\n" + "[23] struct alarm_t <12:13> [23:27)\n" + " message -> 3 [26]\n" + " severity -> 1 [24]\n" + " status -> 2 [25]\n" + " severity : 1 [24]\n" + " status : 2 [25]\n" + " message : 3 [26]\n" + "[24] int32_t <13:14> [24:25)\n" + "[25] int32_t <14:15> [25:26)\n" + "[26] string <15:16> [26:27)\n" + "[27] struct time_t <16:17> [27:31)\n" + " nanoseconds -> 2 [29]\n" + " secondsPastEpoch -> 1 [28]\n" + " userTag -> 3 [30]\n" + " secondsPastEpoch : 1 [28]\n" + " nanoseconds : 2 [29]\n" + " userTag : 3 [30]\n" + "[28] int64_t <17:18> [28:29)\n" + "[29] int32_t <18:19> [29:30)\n" + "[30] int32_t <19:20> [30:31)\n" + "[31] struct[] <20:21> [31:38)\n" + "[32] struct dimension_t <0:6> [32:38)\n" + " binning -> 4 [36]\n" + " fullSize -> 3 [35]\n" + " offset -> 2 [34]\n" + " reverse -> 5 [37]\n" + " size -> 1 [33]\n" + " size : 1 [33]\n" + " offset : 2 [34]\n" + " fullSize : 3 [35]\n" + " binning : 4 [36]\n" + " reverse : 5 [37]\n" + "[33] int32_t <1:2> [33:34)\n" + "[34] int32_t <2:3> [34:35)\n" + "[35] int32_t <3:4> [35:36)\n" + "[36] int32_t <4:5> [36:37)\n" + "[37] bool <5:6> [37:38)\n" + "[38] struct[] <21:22> [38:54)\n" + "[39] struct epics:nt/NTAttribute:1.0 <0:15> [39:54)\n" + " alarm -> 5 [44]\n" + " alarm.message -> 47 [86]\n" + " alarm.severity -> 45 [84]\n" + " alarm.status -> 46 [85]\n" + " descriptor -> 4 [43]\n" + " name -> 1 [40]\n" + " source -> 14 [53]\n" + " sourceType -> 13 [52]\n" + " tags -> 3 [42]\n" + " timestamp -> 9 [48]\n" + " timestamp.nanoseconds -> 50 [89]\n" + " timestamp.secondsPastEpoch -> 49 [88]\n" + " timestamp.userTag -> 51 [90]\n" + " value -> 2 [41]\n" + " name : 1 [40]\n" + " value : 2 [41]\n" + " tags : 3 [42]\n" + " descriptor : 4 [43]\n" + " alarm : 5 [44]\n" + " timestamp : 9 [48]\n" + " sourceType : 13 [52]\n" + " source : 14 [53]\n" + "[40] string <1:2> [40:41)\n" + "[41] any <2:3> [41:42)\n" + "[42] string[] <3:4> [42:43)\n" + "[43] string <4:5> [43:44)\n" + "[44] struct alarm_t <5:6> [44:48)\n" + " message -> 3 [47]\n" + " severity -> 1 [45]\n" + " status -> 2 [46]\n" + " severity : 1 [45]\n" + " status : 2 [46]\n" + " message : 3 [47]\n" + "[45] int32_t <6:7> [45:46)\n" + "[46] int32_t <7:8> [46:47)\n" + "[47] string <8:9> [47:48)\n" + "[48] struct time_t <9:10> [48:52)\n" + " nanoseconds -> 2 [50]\n" + " secondsPastEpoch -> 1 [49]\n" + " userTag -> 3 [51]\n" + " secondsPastEpoch : 1 [49]\n" + " nanoseconds : 2 [50]\n" + " userTag : 3 [51]\n" + "[49] int64_t <10:11> [49:50)\n" + "[50] int32_t <11:12> [50:51)\n" + "[51] int32_t <12:13> [51:52)\n" + "[52] int32_t <13:14> [52:53)\n" + "[53] string <14:15> [53:54)\n" + )<<"Actual:\n"< out; + out.reserve(msg.size()); + + { + VectorOutBuf buf(true, out); + to_wire(buf, descs.data()); + testOk1(buf.good()); + out.resize(out.size()-buf.size()); + } + + testEq(msg.size(), out.size()); + testEq(msg, out); +} + +} // namespace + +MAIN(testxcode) +{ + testPlan(0); + testDecode1(); + testXCodeNTScalar(); + testXCodeNTNDArray(); + return testDone(); +} diff --git a/tools/pvxvct.cpp b/tools/pvxvct.cpp index 552b6da..479910b 100644 --- a/tools/pvxvct.cpp +++ b/tools/pvxvct.cpp @@ -30,7 +30,7 @@ #include #include -namespace pva = pvxs::impl; +namespace pva = pvxs; namespace { DEFINE_LOGGER(out, "pvxvct");