start PVD
This commit is contained in:
@@ -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
|
||||
|
||||
+1039
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,214 @@
|
||||
/**
|
||||
* 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 DATAENCODE_H
|
||||
#define DATAENCODE_H
|
||||
|
||||
#include <stdexcept>
|
||||
#include <functional>
|
||||
#include <ostream>
|
||||
#include <list>
|
||||
#include <map>
|
||||
#include <utility>
|
||||
#include <type_traits>
|
||||
#include <memory>
|
||||
|
||||
#include <pvxs/data.h>
|
||||
#include <pvxs/sharedArray.h>
|
||||
#include "pvaproto.h"
|
||||
#include "utilpvt.h"
|
||||
#include "dataimpl.h"
|
||||
|
||||
namespace pvxs {
|
||||
namespace impl {
|
||||
|
||||
template<typename Buf>
|
||||
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<uint16_t, std::vector<FieldDesc>> TypeStore;
|
||||
|
||||
struct TypeDeserContext {
|
||||
std::vector<FieldDesc>& descs;
|
||||
TypeStore& cache;
|
||||
};
|
||||
|
||||
template<typename Buf>
|
||||
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<std::string>{}(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<std::string>{}(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
|
||||
+114
@@ -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 <string>
|
||||
#include <map>
|
||||
|
||||
#include <pvxs/data.h>
|
||||
#include <pvxs/sharedArray.h>
|
||||
|
||||
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<std::string, size_t> mlookup;
|
||||
// child iteration. child# -> ("sub", rel index)
|
||||
std::vector<std::pair<std::string, size_t>> 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<typename T>
|
||||
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<const void> 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<FieldStorage>
|
||||
*/
|
||||
std::aligned_union<8,
|
||||
double, // Real
|
||||
uint64_t, // Bool, Integer
|
||||
std::string, // String
|
||||
Value, // Union, Any
|
||||
shared_array<const void> // array of POD, std::string, or std::shared_ptr<Value>
|
||||
>::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<typename T>
|
||||
T& as() { return *reinterpret_cast<T*>(&store); }
|
||||
template<typename T>
|
||||
const T& as() const { return *reinterpret_cast<const T*>(&store); }
|
||||
};
|
||||
|
||||
struct StructTop {
|
||||
std::vector<bool> valid;
|
||||
std::shared_ptr<const FieldDesc> desc;
|
||||
std::vector<FieldStorage> members;
|
||||
};
|
||||
static_assert (std::is_standard_layout<StructTop>{}, "Needed for offsetof()");
|
||||
|
||||
using Type = std::shared_ptr<const FieldDesc>;
|
||||
|
||||
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
|
||||
@@ -43,7 +43,6 @@ struct default_delete<evbuffer> {
|
||||
}
|
||||
|
||||
namespace pvxs {namespace impl {
|
||||
using namespace pvxs;
|
||||
|
||||
//! unique_ptr which is never constructed with NULL
|
||||
template<typename T>
|
||||
|
||||
+381
@@ -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 <stdexcept>
|
||||
#include <vector>
|
||||
#include <ostream>
|
||||
#include <memory>
|
||||
#include <typeinfo>
|
||||
#include <tuple>
|
||||
|
||||
#include <pvxs/version.h>
|
||||
#include <pvxs/sharedArray.h>
|
||||
|
||||
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<const void>
|
||||
};
|
||||
|
||||
namespace impl {
|
||||
struct FieldStorage;
|
||||
struct FieldDesc;
|
||||
|
||||
//! maps T to one of the types which can be stored in the FieldStorage::store union
|
||||
//! typename StorageMap<T>::store_t is, if existant, is one such type.
|
||||
//! Can store_t shall be cast-able to/from T.
|
||||
//! StorageMap<T>::code is the associated StoreType.
|
||||
template<typename T, typename Enable=void>
|
||||
struct StorageMap;
|
||||
|
||||
// map signed integers to int64_t
|
||||
template<typename T>
|
||||
struct StorageMap<T, typename std::enable_if<std::is_integral<T>{} && std::is_signed<T>{}>::type>
|
||||
{ typedef int64_t store_t; static constexpr StoreType code{StoreType::Integer}; };
|
||||
|
||||
// map unsigned integers, and bool, to uint64_t
|
||||
template<typename T>
|
||||
struct StorageMap<T, typename std::enable_if<std::is_integral<T>{} && !std::is_signed<T>{}>::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<typename T>
|
||||
struct StorageMap<T, typename std::enable_if<std::is_floating_point<T>{}>::type>
|
||||
{ typedef double store_t; static constexpr StoreType code{StoreType::Real}; };
|
||||
|
||||
template<>
|
||||
struct StorageMap<std::string>
|
||||
{ typedef std::string store_t; static constexpr StoreType code{StoreType::String}; };
|
||||
|
||||
template<>
|
||||
struct StorageMap<char*>
|
||||
{ typedef std::string store_t; static constexpr StoreType code{StoreType::String}; };
|
||||
|
||||
template<>
|
||||
struct StorageMap<shared_array<const void>>
|
||||
{ typedef shared_array<const void> store_t; static constexpr StoreType code{StoreType::Array}; };
|
||||
|
||||
template<>
|
||||
struct StorageMap<Value>
|
||||
{ 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<<order()
|
||||
uint8_t order() const { return code&3; }
|
||||
//! Size in bytes for simple kinds (Bool, Integer, Real)
|
||||
uint8_t size() const { return 1u<<order(); }
|
||||
//! For Integer kind
|
||||
bool isunsigned() const { return code&0x04; }
|
||||
//! For all
|
||||
bool isarray() const { return code&0x08; }
|
||||
|
||||
constexpr TypeCode() :code(Null) {}
|
||||
constexpr explicit TypeCode(uint8_t c) :code(code_t(c)) {}
|
||||
constexpr TypeCode(code_t c) :code(c) {}
|
||||
|
||||
//! associated array of type
|
||||
constexpr TypeCode arrayOf() const {return TypeCode{uint8_t(code|0x08)};}
|
||||
//! associated not array of type
|
||||
constexpr TypeCode scalarOf() const {return TypeCode{uint8_t(code&~0x08)};}
|
||||
|
||||
//! name string. eg. "bool" or "uint8_t"
|
||||
PVXS_API const char* name() const;
|
||||
};
|
||||
|
||||
inline bool operator==(TypeCode lhs, TypeCode rhs) {
|
||||
return lhs.code==rhs.code;
|
||||
}
|
||||
inline bool operator!=(TypeCode lhs, TypeCode rhs) {
|
||||
return lhs.code!=rhs.code;
|
||||
}
|
||||
inline std::ostream& operator<<(std::ostream& strm, TypeCode c) {
|
||||
strm<<c.name();
|
||||
return strm;
|
||||
}
|
||||
|
||||
class PVXS_API TypeDef
|
||||
{
|
||||
public:
|
||||
struct Node;
|
||||
private:
|
||||
struct NodeDeletor {
|
||||
PVXS_API void operator()(Node *p);
|
||||
};
|
||||
std::unique_ptr<Node, NodeDeletor> 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<impl::FieldStorage> store;
|
||||
const impl::FieldDesc* desc; // owned thourgh StructTop (aliased as FieldStorage)
|
||||
public:
|
||||
constexpr Value() :desc(nullptr) {}
|
||||
explicit Value(const std::shared_ptr<const impl::FieldDesc>& 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<typename T>
|
||||
// inline bool tryAs(T& val) const {
|
||||
// return tryCopyOut(&val, std::type_index(typeid(std::decay<T>::type)));
|
||||
// }
|
||||
|
||||
/** Extract value from field.
|
||||
*/
|
||||
template<typename T>
|
||||
inline T as() const {
|
||||
typedef impl::StorageMap<typename std::decay<T>::type> map_t;
|
||||
typename map_t::store_t ret;
|
||||
copyOut(&ret, map_t::code);
|
||||
return ret;
|
||||
}
|
||||
// template<typename T>
|
||||
// void as(T& val) const {
|
||||
// copyOut(&val, std::type_index(typeid(typename std::decay<T>::type)));
|
||||
// }
|
||||
|
||||
// template<typename T>
|
||||
// inline bool tryFrom(const T& val) {
|
||||
// return tryCopyIn(&val, std::type_index(typeid(std::decay<T>::type)));
|
||||
// }
|
||||
|
||||
template<typename T>
|
||||
void from(const T& val) {
|
||||
typedef impl::StorageMap<typename std::decay<T>::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<typename T>
|
||||
Value& operator=(const T& val) {
|
||||
from<T>(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<typename V> friend class _iterator;
|
||||
// these cheat on const-ness
|
||||
void _step(const Value& child, bool next) const;
|
||||
void _first_child(const Value& child) const;
|
||||
|
||||
template<typename V>
|
||||
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<Value> iterator;
|
||||
typedef _iterator<const Value> 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
|
||||
+3
-2
@@ -97,7 +97,7 @@ testCase testEq(const char *sLHS, const LHS& lhs, const char *sRHS, const RHS& r
|
||||
ret<<") == "<<sRHS<<" (";
|
||||
test_print<RHS>::op(ret, rhs);
|
||||
ret<<") ";
|
||||
return std::move(ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
template<typename LHS, typename RHS>
|
||||
@@ -109,7 +109,7 @@ testCase testNotEq(const char *sLHS, const LHS& lhs, const char *sRHS, const RHS
|
||||
ret<<") != "<<sRHS<<" (";
|
||||
test_print<RHS>::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
|
||||
|
||||
+1
-1
@@ -84,7 +84,7 @@ class PVXS_API UDPListener
|
||||
std::shared_ptr<UDPCollector> collector;
|
||||
const SockAddr dest;
|
||||
bool active;
|
||||
friend struct UDPCollector;
|
||||
friend class UDPCollector;
|
||||
friend struct UDPManager;
|
||||
|
||||
public:
|
||||
|
||||
@@ -14,6 +14,8 @@
|
||||
#include <epicsStdlib.h>
|
||||
|
||||
#include <pvxs/util.h>
|
||||
#include <pvxs/sharedArray.h>
|
||||
#include <pvxs/data.h>
|
||||
#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<const void>& arr)
|
||||
{
|
||||
switch(arr.original_type()) {
|
||||
case ArrayType::Null: strm<<"[null]"; break;
|
||||
#define CASE(CODE, Type) case ArrayType::CODE: strm<<shared_array_static_cast<const Type>(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<void>& arr)
|
||||
{
|
||||
switch(arr.original_type()) {
|
||||
case ArrayType::Null: strm<<"[null]"; break;
|
||||
#define CASE(CODE, Type) case ArrayType::CODE: strm<<shared_array_static_cast<Type>(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)
|
||||
|
||||
+4
-3
@@ -23,7 +23,6 @@
|
||||
#include <pvxs/util.h>
|
||||
|
||||
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<typename T>
|
||||
SB& operator<<(T i) { strm<<i; return *this; }
|
||||
SB& operator<<(const T& i) { strm<<i; return *this; }
|
||||
};
|
||||
|
||||
namespace detail {
|
||||
@@ -144,6 +143,8 @@ public:
|
||||
|
||||
void logger_shutdown();
|
||||
|
||||
}} // namespace pvxs::impl
|
||||
} // namespace impl
|
||||
using namespace impl;
|
||||
} // namespace pvxs
|
||||
|
||||
#endif // UTILPVT_H
|
||||
|
||||
@@ -26,6 +26,14 @@ TESTPROD += testshared
|
||||
testshared_SRCS += testshared.cpp
|
||||
TESTS += testshared
|
||||
|
||||
TESTPROD += testxcode
|
||||
testxcode_SRCS += testxcode.cpp
|
||||
TESTS += testxcode
|
||||
|
||||
TESTPROD += testdata
|
||||
testdata_SRCS += testdata.cpp
|
||||
TESTS += testdata
|
||||
|
||||
TESTPROD += dummyserv
|
||||
dummyserv_SRCS += dummyserv.cpp
|
||||
# not a unittest
|
||||
|
||||
@@ -0,0 +1,313 @@
|
||||
/**
|
||||
* 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 <testMain.h>
|
||||
|
||||
#include <epicsUnitTest.h>
|
||||
|
||||
#include <pvxs/unittest.h>
|
||||
#include <pvxs/data.h>
|
||||
#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<NoConvert>([&val]() {
|
||||
val.from(4.2);
|
||||
});
|
||||
}
|
||||
|
||||
{
|
||||
auto val = top["value"];
|
||||
testOk1(!!val.valid());
|
||||
testOk1(!val.isMarked());
|
||||
val.from(4.2);
|
||||
testEq(val.as<double>(), 4.2);
|
||||
testOk1(!!val.isMarked());
|
||||
}
|
||||
|
||||
testEq(std::string(SB()<<top),
|
||||
"struct \"simple_t\" {\n"
|
||||
" double value = 4.2\n"
|
||||
"}\n");
|
||||
}
|
||||
|
||||
void testTypeDef()
|
||||
{
|
||||
testDiag("%s()", __func__);
|
||||
|
||||
testEq(std::string(SB()<<TypeDef()),
|
||||
"<Empty>\n");
|
||||
|
||||
testEq(std::string(SB()<<TypeDef(TypeCode::Struct, "simple_t")),
|
||||
"struct \"simple_t\"\n");
|
||||
|
||||
TypeDef def(TypeCode::Struct, "simple_t");
|
||||
|
||||
def.begin()
|
||||
.insert("value", nullptr, TypeCode::Float64A)
|
||||
.insert("timeStamp", "time_t", TypeCode::Struct)
|
||||
.seek("timeStamp")
|
||||
.insert("secondsPastEpoch", TypeCode::UInt64)
|
||||
.insert("nanoseconds", TypeCode::UInt32)
|
||||
.up() // up one level
|
||||
.insert("arbitrary", TypeCode::Struct)
|
||||
.seek("arbitrary")
|
||||
.insert("sarr", TypeCode::StructA)
|
||||
.seek("sarr")
|
||||
.insert("value", TypeCode::Float64)
|
||||
.reset() // back to top
|
||||
.insert("any", TypeCode::Any)
|
||||
.insert("anya", TypeCode::AnyA)
|
||||
.insert("choice", TypeCode::Union)
|
||||
.seek("choice")
|
||||
.insert("a", TypeCode::Float32)
|
||||
.insert("b", TypeCode::String)
|
||||
.reset()
|
||||
.insert("achoice", TypeCode::UnionA)
|
||||
.seek("achoice")
|
||||
.insert("x", TypeCode::Float32)
|
||||
.insert("y", TypeCode::Float32)
|
||||
;
|
||||
|
||||
testShow()<<def;
|
||||
|
||||
testEq(std::string(SB()<<def),
|
||||
"struct \"simple_t\" {\n"
|
||||
" double[] value\n"
|
||||
" struct \"time_t\" {\n"
|
||||
" uint64_t secondsPastEpoch\n"
|
||||
" uint32_t nanoseconds\n"
|
||||
" } timeStamp\n"
|
||||
" struct {\n"
|
||||
" struct[] {\n"
|
||||
" double value\n"
|
||||
" } sarr\n"
|
||||
" } arbitrary\n"
|
||||
" any any\n"
|
||||
" any[] anya\n"
|
||||
" union {\n"
|
||||
" float a\n"
|
||||
" string b\n"
|
||||
" } choice\n"
|
||||
" union[] {\n"
|
||||
" float x\n"
|
||||
" float y\n"
|
||||
" } achoice\n"
|
||||
"}\n"
|
||||
);
|
||||
|
||||
auto val = def.create();
|
||||
|
||||
testOk1(!!val.valid());
|
||||
testShow()<<val._desc();
|
||||
testEq(std::string(SB()<<val._desc()),
|
||||
"[0] struct simple_t <0:11> [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<double> arr({1.0, 2.0});
|
||||
|
||||
val["value"].from(shared_array_static_cast<const void>(freeze(std::move(arr))));
|
||||
}
|
||||
// Struct[]
|
||||
{
|
||||
auto fld = val["arbitrary.sarr"];
|
||||
shared_array<Value> 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<const void>(frozen);
|
||||
// fld.from(varr);
|
||||
fld.from(shared_array_static_cast<const void>(freeze(std::move(arr))));
|
||||
|
||||
testEq(val["arbitrary.sarr[1]value"].as<double>(), 2.0);
|
||||
}
|
||||
|
||||
// Union
|
||||
val["choice->b"] = "test";
|
||||
// Union[]
|
||||
{
|
||||
auto fld = val["achoice"];
|
||||
shared_array<Value> 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<const void>(freeze(std::move(arr))));
|
||||
|
||||
testEq(fld["[1]"].as<double>(), 5.0);
|
||||
testEq(val["achoice[1]"].as<double>(), 5.0);
|
||||
testEq(val["achoice[1]->y"].as<double>(), 5.0);
|
||||
}
|
||||
|
||||
// Any
|
||||
{
|
||||
auto v = TypeDef(TypeCode::UInt32).create();
|
||||
v = 42u;
|
||||
|
||||
val["any"].from(v);
|
||||
|
||||
testEq(v.as<uint64_t>(), 42u);
|
||||
}
|
||||
|
||||
// Any[]
|
||||
{
|
||||
auto fld = val["anya"];
|
||||
shared_array<Value> 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<const void>(freeze(std::move(arr))));
|
||||
|
||||
testEq(fld["[0]"].as<uint64_t>(), 123u);
|
||||
testEq(fld["[1]q"].as<std::string>(), "theq");
|
||||
}
|
||||
|
||||
testShow()<<val;
|
||||
testEq(std::string(SB()<<val),
|
||||
"struct \"simple_t\" {\n"
|
||||
" double[] value = {2}[1, 2]\n"
|
||||
" struct \"time_t\" {\n"
|
||||
" uint64_t secondsPastEpoch = 1311768467463790320\n"
|
||||
" uint32_t nanoseconds = 0\n"
|
||||
" } timeStamp\n"
|
||||
" struct {\n"
|
||||
" struct[] sarr [\n"
|
||||
" struct {\n"
|
||||
" double value = 1\n"
|
||||
" }\n"
|
||||
" struct {\n"
|
||||
" double value = 2\n"
|
||||
" }\n"
|
||||
" null\n"
|
||||
" ]\n"
|
||||
" } arbitrary\n"
|
||||
" any any uint32_t = 42\n"
|
||||
" any[] anya [\n"
|
||||
" uint32_t = 123\n"
|
||||
" struct {\n"
|
||||
" string q = \"theq\"\n"
|
||||
" }\n"
|
||||
" null\n"
|
||||
" ]\n"
|
||||
" union choice.b string = \"test\"\n"
|
||||
" union[] achoice [\n"
|
||||
" union.x float = 4\n"
|
||||
" union.y float = 5\n"
|
||||
" null\n"
|
||||
" ]\n"
|
||||
"}\n");
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
MAIN(testdata)
|
||||
{
|
||||
testPlan(23);
|
||||
showSize();
|
||||
testBasic();
|
||||
testTypeDef();
|
||||
cleanup_for_valgrind();
|
||||
return testDone();
|
||||
}
|
||||
+1
-1
@@ -12,7 +12,7 @@
|
||||
#include <pvxs/log.h>
|
||||
#include <evhelper.h>
|
||||
|
||||
using namespace pvxs::impl;
|
||||
using namespace pvxs;
|
||||
namespace {
|
||||
|
||||
struct my_special_error : public std::runtime_error
|
||||
|
||||
+1
-1
@@ -16,7 +16,7 @@
|
||||
#include <pvxs/log.h>
|
||||
|
||||
namespace {
|
||||
using namespace pvxs::impl;
|
||||
using namespace pvxs;
|
||||
|
||||
void test_udp()
|
||||
{
|
||||
|
||||
+1
-1
@@ -20,7 +20,7 @@
|
||||
#include <udp_collector.h>
|
||||
|
||||
namespace {
|
||||
using namespace pvxs::impl;
|
||||
using namespace pvxs;
|
||||
|
||||
void testBeacon(bool be)
|
||||
{
|
||||
|
||||
@@ -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 <epicsUnitTest.h>
|
||||
#include <testMain.h>
|
||||
|
||||
#include <pvxs/util.h>
|
||||
#include <pvxs/unittest.h>
|
||||
#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<uint8_t> 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<FieldDesc> descs;
|
||||
TypeStore cache;
|
||||
|
||||
{
|
||||
FixedBuf<uint8_t> 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 "<<msg.size();
|
||||
}
|
||||
|
||||
testEq(cache.size(), 1u);
|
||||
{
|
||||
auto it = cache.find(1);
|
||||
if(testOk1(it!=cache.end())) {
|
||||
testEq(it->second.size(), 4u);
|
||||
}
|
||||
}
|
||||
|
||||
if(testOk1(!descs.empty())) {
|
||||
testEq(descs.size(), descs.front().size());
|
||||
}
|
||||
|
||||
// cat <<EOF | sed -e 's|"|\\"|g' -e 's|^# | "|' -e 's|$|\\n"|g'
|
||||
// paste in Actual
|
||||
testEq(std::string(SB()<<descs.data()),
|
||||
"[0] struct timeStamp_t <0:4> [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"<<descs.data();
|
||||
}
|
||||
|
||||
/* epics:nt/NTScalarArray:1.0
|
||||
* double[] value
|
||||
* alarm_t alarm
|
||||
* int severity
|
||||
* int status
|
||||
* string message
|
||||
* time_t timeStamp
|
||||
* long secondsPastEpoch
|
||||
* int nanoseconds
|
||||
* int userTag
|
||||
*/
|
||||
const uint8_t NTScalar[] = "\x80\x1a""epics:nt/NTScalarArray:1.0\x03"
|
||||
"\x05""valueK"
|
||||
"\x05""alarm\x80\x07""alarm_t\x03"
|
||||
"\x08""severity\""
|
||||
"\x06""status\""
|
||||
"\x07""message`"
|
||||
"\ttimeStamp\x80\x06""time_t\x03"
|
||||
"\x10""secondsPastEpoch#"
|
||||
"\x0b""nanoseconds\""
|
||||
"\x07""userTag\"";
|
||||
|
||||
void testXCodeNTScalar()
|
||||
{
|
||||
testDiag("%s", __func__);
|
||||
|
||||
std::vector<uint8_t> msg(NTScalar, NTScalar+sizeof(NTScalar)-1);
|
||||
std::vector<FieldDesc> descs;
|
||||
TypeStore cache;
|
||||
{
|
||||
FixedBuf<uint8_t> 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 "<<msg.size();
|
||||
}
|
||||
|
||||
if(testOk1(!descs.empty())) {
|
||||
testEq(descs.size(), descs.front().size());
|
||||
}
|
||||
|
||||
testEq(std::string(SB()<<descs.data()),
|
||||
"[0] struct epics:nt/NTScalarArray:1.0 <0:10> [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"<<descs.data();
|
||||
|
||||
testDiag("Round trip back to bytes");
|
||||
std::vector<uint8_t> 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<uint8_t> msg(NTNDArray, NTNDArray+sizeof(NTNDArray)-1);
|
||||
std::vector<FieldDesc> descs;
|
||||
TypeStore cache;
|
||||
{
|
||||
FixedBuf<uint8_t> 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 "<<msg.size();
|
||||
}
|
||||
|
||||
if(testOk1(!descs.empty())) {
|
||||
testEq(descs.size(), descs.front().size());
|
||||
}
|
||||
|
||||
testEq(std::string(SB()<<descs.data()),
|
||||
"[0] struct epics:nt/NTNDArray:1.0 <0:22> [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"<<descs.data();
|
||||
|
||||
testDiag("Round trip back to bytes");
|
||||
std::vector<uint8_t> 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();
|
||||
}
|
||||
+1
-1
@@ -30,7 +30,7 @@
|
||||
#include <utilpvt.h>
|
||||
#include <pvaproto.h>
|
||||
|
||||
namespace pva = pvxs::impl;
|
||||
namespace pva = pvxs;
|
||||
namespace {
|
||||
|
||||
DEFINE_LOGGER(out, "pvxvct");
|
||||
|
||||
Reference in New Issue
Block a user