diff --git a/src/dataencode.cpp b/src/dataencode.cpp index 1f9270f..642ddcb 100644 --- a/src/dataencode.cpp +++ b/src/dataencode.cpp @@ -7,6 +7,8 @@ #ifndef DATAENCODE_H #define DATAENCODE_H +#include + #include #include #include @@ -62,7 +64,10 @@ void from_wire(Buffer& buf, TypeDeserContext& ctxt, unsigned depth) 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) { + if(code == TypeCode::Null) { + return; + + } else if(code.code==0xfd) { // update cache uint16_t key=0; from_wire(buf, key); @@ -175,7 +180,7 @@ void from_wire(Buffer& buf, TypeDeserContext& ctxt, unsigned depth) default: // not handling fixed/bounded // other types have simple/single node description - switch(code.code&~0x08) { + switch(code.scalarOf().code) { case TypeCode::Bool: case TypeCode::Int8: case TypeCode::Int16: @@ -200,6 +205,486 @@ void from_wire(Buffer& buf, TypeDeserContext& ctxt, unsigned depth) } } +namespace { +template +void to_wire(Buffer& buf, const shared_array& varr) +{ + auto arr = shared_array_static_cast(varr); + to_wire(buf, Size{arr.size()}); + for(auto i : range(arr.size())) { + to_wire(buf, C(arr[i])); + } +} + +template +void from_wire(Buffer& buf, shared_array& varr) +{ + Size slen; + from_wire(buf, slen); + shared_array arr(slen.size); + for(auto i : range(arr.size())) { + C temp{}; + from_wire(buf, temp); + arr[i] = temp; + } +} +} + +// serialize a field and all children (if Compound) +static +void to_wire_field(Buffer& buf, const FieldDesc* desc, const FieldStorage* store) +{ + switch(store->code) { + case StoreType::Null: + switch(desc->code.code) { + case TypeCode::Struct: { + auto& top = *store->top; + // serialize entire sub-structure + for(auto off : range(desc->offset+1u, desc->next_offset)) { + auto cdesc = desc + top.member_indicies[off]; + auto cstore = store + off; + if(cdesc->code!=TypeCode::Struct) + to_wire_field(buf, cdesc, cstore); + } + } + return; + default: break; + } + break; + case StoreType::Real: { + auto& fld = store->as(); + switch(desc->code.code) { + case TypeCode::Float32: to_wire(buf, float(fld)); return; + case TypeCode::Float64: to_wire(buf, double(fld)); return; + default: break; + } + } + break; + case StoreType::Integer: { + auto& fld = store->as(); + switch(desc->code.code) { + case TypeCode::Int8: to_wire(buf, int8_t (fld)); return; + case TypeCode::Int16: to_wire(buf, int16_t(fld)); return; + case TypeCode::Int32: to_wire(buf, int32_t(fld)); return; + case TypeCode::Int64: to_wire(buf, int64_t(fld)); return; + default: break; + } + } + break; + case StoreType::UInteger: { + auto& fld = store->as(); + switch(desc->code.code) { + case TypeCode::Bool: to_wire(buf, uint8_t (fld!=0)); return; + case TypeCode::UInt8: to_wire(buf, uint8_t (fld)); return; + case TypeCode::UInt16: to_wire(buf, uint16_t(fld)); return; + case TypeCode::UInt32: to_wire(buf, uint32_t(fld)); return; + case TypeCode::UInt64: to_wire(buf, uint64_t(fld)); return; + default: break; + } + } + break; + case StoreType::String: { + auto& fld = store->as(); + switch(desc->code.code) { + case TypeCode::String: to_wire(buf, fld); return; + default: break; + } + } + break; + case StoreType::Compound: { + auto& fld = store->as(); + switch (desc->code.code) { + case TypeCode::Union: + if(!fld) { + // implied NULL Union member + to_wire(buf, Size{size_t(-1)}); + + } else { + size_t index = 0u; + for(auto& pair : desc->miter) { + if(fld._desc()== desc+pair.second) + break; + index++; + } + if(index>=desc->miter.size()) + throw std::logic_error("Union contains non-member type"); + to_wire(buf, Size{index}); + to_wire_field(buf, fld._desc(), fld._store()); + } + return; + + case TypeCode::Any: + if(!fld) { + to_wire(buf, uint8_t(0xff)); + + } else { + to_wire(buf, fld._desc()); + to_wire_field(buf, fld._desc(), fld._store()); + } + return; + default: break; + } + } + break; + case StoreType::Array: { + auto& fld = store->as>(); + switch (desc->code.code) { + case TypeCode::BoolA: + to_wire(buf, fld); + return; + case TypeCode::Int8: + case TypeCode::UInt8: + to_wire(buf, fld); + return; + case TypeCode::Int16: + case TypeCode::UInt16: + to_wire(buf, fld); + return; + case TypeCode::Int32: + case TypeCode::UInt32: + case TypeCode::Float32: + to_wire(buf, fld); + return; + case TypeCode::Int64: + case TypeCode::UInt64: + case TypeCode::Float64: + to_wire(buf, fld); + return; + case TypeCode::StringA: + to_wire(buf, fld); + return; + case TypeCode::StructA:{ + auto arr = shared_array_static_cast(fld); + to_wire(buf, Size{arr.size()}); + for(auto& elem : arr) { + if(!elem) { + to_wire(buf, uint8_t(0u)); + } else { + to_wire(buf, uint8_t(1u)); + assert(elem._desc()==desc+1); + to_wire_field(buf, elem._desc(), elem._store()); + } + } + } + return; + case TypeCode::UnionA: { + auto arr = shared_array_static_cast(fld); + to_wire(buf, Size{arr.size()}); + for(auto& elem : arr) { + if(!elem) { + to_wire(buf, uint8_t(0u)); + } else { + to_wire(buf, uint8_t(1u)); + + to_wire_field(buf, elem._desc(), elem._store()); + } + } + } + return; + case TypeCode::AnyA:{ + auto arr = shared_array_static_cast(fld); + to_wire(buf, Size{arr.size()}); + for(auto& elem : arr) { + if(!elem) { + to_wire(buf, uint8_t(0u)); + } else { + to_wire(buf, uint8_t(1u)); + + to_wire(buf, elem._desc()); + to_wire_field(buf, elem._desc(), elem._store()); + } + } + } + return; + default: break; + } + break; + } + } // end case + + assert(false); + buf.fault(); +} + +void to_wire_full(Buffer& buf, const Value& val) +{ + assert(!!val); + + to_wire_field(buf, val._desc(), val._store()); +} + +void to_wire_valid(Buffer& buf, const Value& val) +{ + auto desc = val._desc(); + assert(!!desc); + auto top = val._store()->top; + + to_wire(buf, top->valid); + top->valid.resize(top->members.size()); + + // iterate marked fields + for(auto bit = top->valid.findSet(desc->offset); + bitnext_offset; + bit = top->valid.findSet(bit+1)) + { + to_wire_field(buf, desc + top->member_indicies[bit], val._store()+bit); + } +} + +namespace { +template +T from_wire_as(Buffer& buf) +{ + T ret{}; + from_wire(buf, ret); + return ret; +} +} + +static +void from_wire_field(Buffer& buf, TypeStore& ctxt, const FieldDesc* desc, FieldStorage* store) +{ + switch(store->code) { + case StoreType::Null: + switch(desc->code.code) { + case TypeCode::Struct: { + auto& top = *store->top; + // serialize entire sub-structure + for(auto off : range(desc->offset+1u, desc->next_offset)) { + auto cdesc = desc + top.member_indicies[off]; + auto cstore = store + off; + if(cdesc->code!=TypeCode::Struct) + from_wire_field(buf, ctxt, cdesc, cstore); + } + } + return; + default: break; + } + break; + case StoreType::Real: { + auto& fld = store->as(); + switch(desc->code.code) { + case TypeCode::Float32: fld = from_wire_as(buf); return; + case TypeCode::Float64: fld = from_wire_as(buf); return; + default: break; + } + } + break; + case StoreType::Integer: { + auto& fld = store->as(); + switch(desc->code.code) { + case TypeCode::Int8: fld = from_wire_as(buf); return; + case TypeCode::Int16: fld = from_wire_as(buf); return; + case TypeCode::Int32: fld = from_wire_as(buf); return; + case TypeCode::Int64: fld = from_wire_as(buf); return; + default: break; + } + } + break; + case StoreType::UInteger: { + auto& fld = store->as(); + switch(desc->code.code) { + case TypeCode::Bool: fld = 0!=from_wire_as(buf); return; + case TypeCode::UInt8: fld = from_wire_as(buf); return; + case TypeCode::UInt16: fld = from_wire_as(buf); return; + case TypeCode::UInt32: fld = from_wire_as(buf); return; + case TypeCode::UInt64: fld = from_wire_as(buf); return; + default: break; + } + } + break; + case StoreType::String: { + auto& fld = store->as(); + switch(desc->code.code) { + case TypeCode::String: from_wire(buf, fld); return; + default: break; + } + } + break; + case StoreType::Compound: { + auto& fld = store->as(); + switch (desc->code.code) { + case TypeCode::Union: { + Size select{}; + from_wire(buf, select); + if(select.size==size_t(-1)) { + fld = Value(); + return; + + } else if(select.size < desc->miter.size()) { + std::shared_ptr stype(store->top->desc, + desc + desc->miter[select.size].second); // alias + fld = Value(stype); + + from_wire_field(buf, ctxt, fld._desc(), fld._store()); + return; + } + } + break; + + case TypeCode::Any: { + std::shared_ptr> descs(new std::vector); + TypeDeserContext dc{*descs, ctxt}; + + from_wire(buf, dc); + + if(descs->empty()) { + fld = Value(); + return; + + } else { + std::shared_ptr stype(descs, descs->data()); // alias + fld = Value(stype); + + from_wire_field(buf, ctxt, fld._desc(), fld._store()); + return; + + } + } + break; + + default: break; + } + } + break; + case StoreType::Array: { + auto& fld = store->as>(); + switch (desc->code.code) { + case TypeCode::BoolA: + from_wire(buf, fld); + return; + case TypeCode::Int8: + case TypeCode::UInt8: + from_wire(buf, fld); + return; + case TypeCode::Int16: + case TypeCode::UInt16: + from_wire(buf, fld); + return; + case TypeCode::Int32: + case TypeCode::UInt32: + case TypeCode::Float32: + from_wire(buf, fld); + return; + case TypeCode::Int64: + case TypeCode::UInt64: + case TypeCode::Float64: + from_wire(buf, fld); + return; + case TypeCode::StringA: + from_wire(buf, fld); + return; + case TypeCode::StructA:{ + Size alen{}; + from_wire(buf, alen); + shared_array arr(alen.size); + std::shared_ptr etype(store->top->desc, + desc + 1); // alias + for(auto& elem : arr) { + if(from_wire_as(buf)!=0) { // strictly 1 or 0 + elem = Value(etype); + + from_wire_field(buf, ctxt, elem._desc(), elem._store()); + } + } + + fld = shared_array_static_cast(freeze(std::move(arr))); + } + return; + case TypeCode::UnionA: { + Size alen{}; + from_wire(buf, alen); + shared_array arr(alen.size); + auto cdesc = desc+1; + + for(auto& elem : arr) { + if(from_wire_as(buf)!=0) { // strictly 1 or 0 + Size select{}; + from_wire(buf, select); + + if(select.size==size_t(-1)) { + // null element. treated the same as 0 case (which is what actually happens) + + } else if(select.size < cdesc->miter.size()) { + std::shared_ptr stype(store->top->desc, + cdesc + cdesc->miter[select.size].second); // alias + elem = Value(stype); + + from_wire_field(buf, ctxt, elem._desc(), elem._store()); + return; + + } else { + // invalid selector + buf.fault(); + break; + } + } + } + + fld = shared_array_static_cast(freeze(std::move(arr))); + } + return; + case TypeCode::AnyA:{ + Size alen{}; + from_wire(buf, alen); + shared_array arr(alen.size); + + for(auto& elem : arr) { + if(from_wire_as(buf)!=0) { // strictly 1 or 0 + std::shared_ptr> descs(new std::vector); + TypeDeserContext dc{*descs, ctxt}; + + from_wire(buf, dc); + if(!descs->empty()) { + std::shared_ptr stype(descs, descs->data()); // alias + elem = Value(stype); + + from_wire_field(buf, ctxt, elem._desc(), elem._store()); + } + } + } + + fld = shared_array_static_cast(freeze(std::move(arr))); + } + return; + default: break; + } + break; + + } + } // end case + + assert(false); + buf.fault(); +} + +void from_wire_full(Buffer& buf, TypeStore& ctxt, Value& val) +{ + assert(!!val); + + from_wire_field(buf, ctxt, val._desc(), val._store()); +} + +void from_wire_valid(Buffer& buf, TypeStore& ctxt, Value& val) +{ + auto desc = val._desc(); + assert(!!desc); + auto top = val._store()->top; + + from_wire(buf, top->valid); + // encoding rounds # of bits to whole bytes, so we may trim + top->valid.resize(top->members.size()); + if(!buf.good()) + return; + + for(auto bit = top->valid.findSet(desc->offset); + bitnext_offset; + bit = top->valid.findSet(bit+1)) + { + from_wire_field(buf, ctxt, desc + top->member_indicies[bit], val._store()+bit); + } +} + }} // namespace pvxs::impl #endif // DATAENCODE_H diff --git a/src/dataimpl.h b/src/dataimpl.h index b8ea0de..7a0abbc 100644 --- a/src/dataimpl.h +++ b/src/dataimpl.h @@ -99,6 +99,9 @@ struct FieldStorage { T& as() { return *reinterpret_cast(&store); } template const T& as() const { return *reinterpret_cast(&store); } + + inline uint8_t* buffer() { return reinterpret_cast(&store); } + inline const uint8_t* buffer() const { return reinterpret_cast(&store); } }; // hidden (publically) management of an allocated Struct @@ -117,6 +120,20 @@ static_assert (std::is_standard_layout{}, "Needed for offsetof()"); using Type = std::shared_ptr; + +PVXS_API +void to_wire_full(Buffer& buf, const Value& val); + +PVXS_API +void to_wire_valid(Buffer& buf, const Value& val); + +PVXS_API +void from_wire_full(Buffer& buf, TypeStore& ctxt, Value& val); + +PVXS_API +void from_wire_valid(Buffer& buf, TypeStore& ctxt, Value& val); + + PVXS_API void FieldDesc_calculate_offset(FieldDesc* top); diff --git a/src/pvaproto.h b/src/pvaproto.h index 56d7253..c2e0bd7 100644 --- a/src/pvaproto.h +++ b/src/pvaproto.h @@ -251,6 +251,10 @@ void to_wire(Buffer& buf, const Size& size) buf.push(254); to_wire(buf, uint32_t(size.size)); + } else if(size.size==size_t(-1)) { + // special "null" used to encode empty Union + buf.push(255); + } else { buf.fault(); } @@ -268,10 +272,8 @@ void from_wire(Buffer& buf, Size& size) size.size = s; } else if(s==255) { - // "null" size. not sure it is used. - // Replicate weirdness of pvDataCPP - // FIXME this is almost certainly a bug - size.size = -1; + // special "null" used to encode empty Union + size.size = size_t(-1); } else if(s==254) { uint32_t ls = 0; diff --git a/test/Makefile b/test/Makefile index 515d41b..8becaf3 100644 --- a/test/Makefile +++ b/test/Makefile @@ -38,6 +38,10 @@ TESTPROD += testtype testtype_SRCS += testtype.cpp TESTS += testtype +TESTPROD += testdata +testdata_SRCS += testdata.cpp +TESTS += testdata + TESTPROD += dummyserv dummyserv_SRCS += dummyserv.cpp # not a unittest diff --git a/test/testdata.cpp b/test/testdata.cpp new file mode 100644 index 0000000..c36824b --- /dev/null +++ b/test/testdata.cpp @@ -0,0 +1,207 @@ +/** + * 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 +#include "utilpvt.h" +#include "pvaproto.h" +#include "dataimpl.h" + +using namespace pvxs; +namespace { + +template +testCase +testBytes(const std::vector& actual, const char(&buf)[N]) +{ + bool ok = actual.size()==(N-1) && std::equal(actual.begin(), + actual.end(), + (const uint8_t*)buf); + + testCase ret(ok); + ret<<"Expect: \""< +void testToBytes(bool be, Fn&& fn, const char(&expect)[N]) +{ + std::vector buf; + VectorOutBuf S(be, buf); + fn(S); + buf.resize(buf.size()-S.size()); + testBytes(buf, expect); +} + +void testSerialize1() +{ + testDiag("%s", __func__); + + auto val = nt::NTScalar{TypeCode::UInt32}.build().create(); + + testToBytes(true, [&val](Buffer& buf) { + to_wire_full(buf, val); + }, "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"); + + testToBytes(true, [&val](Buffer& buf) { + to_wire_valid(buf, val); + }, "\x00"); + + val["value"] = 0xdeadbeef; + + testToBytes(true, [&val](Buffer& buf) { + to_wire_valid(buf, val); + }, "\x01\x02\xde\xad\xbe\xef"); + + val["value"].unmark(); + + testToBytes(true, [&val](Buffer& buf) { + to_wire_valid(buf, val); + }, "\x00"); + + val["timeStamp.nanoseconds"] = 0xab; + val["alarm.message"] = "hello world"; + + testToBytes(true, [&val](Buffer& buf) { + to_wire_valid(buf, val); + }, "\x02 \x01\x0bhello world\x00\x00\x00\xab"); +} + +void testSerialize2() +{ + testDiag("%s", __func__); + + TypeDef def(TypeCode::Struct, "simple_t", { + Member(TypeCode::Float64A, "value"), + Member(TypeCode::Struct, "timeStamp", "time_t", { + Member(TypeCode::UInt64, "secondsPastEpoch"), + Member(TypeCode::UInt32, "nanoseconds"), + }), + Member(TypeCode::Struct, "arbitrary", { + Member(TypeCode::StructA, "sarr", { + Member(TypeCode::UInt32, "value"), + }), + }), + Member(TypeCode::Any, "any"), + Member(TypeCode::AnyA, "anya"), + Member(TypeCode::Union, "choice", { + Member(TypeCode::Float32, "a"), + Member(TypeCode::String, "b"), + }), + Member(TypeCode::UnionA, "achoice", { + Member(TypeCode::String, "x"), + Member(TypeCode::String, "y"), + }), + }); + + { + auto val = def.create(); + + auto fld = val["arbitrary.sarr"]; + shared_array arr(3); + arr[0] = fld.allocMember(); + arr[1] = fld.allocMember(); + // leave [2] as null + arr[0]["value"] = 0xdeadbeef; + arr[1]["value"] = 0x1badface; + + fld.from(shared_array_static_cast(freeze(std::move(arr)))); + + testToBytes(true, [&val](Buffer& buf) { + to_wire_valid(buf, val); + }, "\x01@\x03\x01\xde\xad\xbe\xef\x01\x1b\xad\xfa\xce\x00"); + } + + { + auto val = def.create(); + + val["choice->b"] = "test"; + val["choice"].mark(); // TODO: auto mark back through union + + testToBytes(true, [&val](Buffer& buf) { + to_wire_valid(buf, val); + }, "\x02\x00\x02\x01\x04test"); + } + + { + auto val = def.create(); + + auto fld = val["achoice"]; + shared_array arr(3); + arr[0] = fld.allocMember(); + arr[1] = fld.allocMember(); + // leave [2] as null + arr[0]["->x"] = "theX"; + arr[1]["->y"] = "theY"; + + fld.from(shared_array_static_cast(freeze(std::move(arr)))); + + testToBytes(true, [&val](Buffer& buf) { + to_wire_valid(buf, val); + }, "\x02\x00\x04\x03\x01\x00\x04theX\x01\x01\x04theY\x00"); + } + + // Any + { + auto val = def.create(); + + auto v = TypeDef(TypeCode::UInt32).create(); + v = 0x600df00d; + + val["any"].from(v); + + testToBytes(true, [&val](Buffer& buf) { + to_wire_valid(buf, val); + }, "\x01\x80\x26\x60\x0d\xf0\x0d"); + } + + // Any + { + auto val = def.create(); + val["any"].mark(); + + testToBytes(true, [&val](Buffer& buf) { + to_wire_valid(buf, val); + }, "\x01\x80\xff"); + } + + // Any[] + { + auto val = def.create(); + + auto fld = val["anya"]; + shared_array arr(3); + arr[0] = TypeDef(TypeCode::UInt32).create(); + arr[1] = TypeDef(TypeCode::Struct, {Member(TypeCode::String, "q")}).create(); + // leave [2] as null + + arr[0] = 0x7b; + arr[1]["q"] = "theq"; + + fld.from(shared_array_static_cast(freeze(std::move(arr)))); + + testToBytes(true, [&val](Buffer& buf) { + to_wire_valid(buf, val); + }, "\x02\x00\x01\x03\x01\x26\x00\x00\x00\x7b\x01\x80\x00\x01\x01q\x60\x04theq\x00"); + } +} + +} // namespace + +MAIN(testdata) +{ + testPlan(11); + testSerialize1(); + testSerialize2(); + cleanup_for_valgrind(); + return testDone(); +}