/** * 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 "dataimpl.h" #include "pvaproto.h" namespace { using namespace pvxs; using namespace pvxs::impl; 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); } template void testFromBytes(bool be, const char(&input)[N], Fn&& fn) { std::vector buf(input, input+N-1); FixedBuf S(be, buf); fn(S); testCase(S.good() && S.empty())<<"Deserialize \""<(), 0xdeadbeef); } { TypeStore ctxt; auto val = nt::NTScalar{TypeCode::UInt32}.build().create(); testFromBytes(true, "\x02 \x01\x0bhello world\x00\x00\x00\xab", [&val, &ctxt](Buffer& buf) { from_wire_valid(buf, ctxt, val); }); testOk1(!val["value"].isMarked()); testOk1(!!val["timeStamp.nanoseconds"].isMarked()); testOk1(!!val["alarm.message"].isMarked()); testEq(val["value"].as(), 0u); testEq(val["timeStamp.nanoseconds"].as(), 0xabu); testEq(val["alarm.message"].as(), "hello world"); } } TypeDef simpledef(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"), }), }); void testSimpleDef() { testDiag("%s", __func__); auto val = simpledef.create(); testEq(std::string(SB()<<"\n"< 10 [10] any -> 7 [7] anya -> 8 [8] arbitrary -> 5 [5] arbitrary.sarr -> 6 [6] choice -> 9 [9] timeStamp -> 2 [2] timeStamp.nanoseconds -> 4 [4] timeStamp.secondsPastEpoch -> 3 [3] value -> 1 [1] value : 1 [1] timeStamp : 2 [2] arbitrary : 5 [5] any : 7 [7] anya : 8 [8] choice : 9 [9] achoice : 10 [10] [1] double[] parent=[0] [1:2) [2] struct time_t parent=[0] [2:5) nanoseconds -> 2 [4] secondsPastEpoch -> 1 [3] secondsPastEpoch : 1 [3] nanoseconds : 2 [4] [3] uint64_t parent=[2] [3:4) [4] uint32_t parent=[2] [4:5) [5] struct parent=[0] [5:7) sarr -> 1 [6] sarr : 1 [6] [6] struct[] parent=[5] [6:7) [0] struct parent=[0] [0:2) value -> 1 [1] value : 1 [1] [1] uint32_t parent=[0] [1:2) [7] any parent=[0] [7:8) [8] any[] parent=[0] [8:9) [9] union parent=[0] [9:10) a -> 0 [0] b -> 1 [1] a : 0 [0] [0] float parent=[0] [0:1) b : 1 [1] [0] string parent=[0] [0:1) [10] union[] parent=[0] [10:11) [0] union parent=[0] [0:1) x -> 0 [0] y -> 1 [1] x : 0 [0] [0] string parent=[0] [0:1) y : 1 [1] [0] string parent=[0] [0:1) )out")<<"Actual:\n"< arr(3); arr[0] = fld.allocMember(); arr[1] = fld.allocMember(); // leave [2] as null arr[0]["value"] = 0xdeadbeef; arr[1]["value"] = 0x1badface; fld = arr.freeze().castTo(); 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 = simpledef.create(); val["choice->b"] = "test"; testOk1(!!val["choice"].isMarked()); testToBytes(true, [&val](Buffer& buf) { to_wire_valid(buf, val); }, "\x02\x00\x02\x01\x04test"); } { auto val = simpledef.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 = arr.freeze().castTo(); testToBytes(true, [&val](Buffer& buf) { to_wire_valid(buf, val); }, "\x02\x00\x04\x03\x01\x00\x04theX\x01\x01\x04theY\x00"); } // Any { auto val = simpledef.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 = simpledef.create(); val["any"].mark(); testToBytes(true, [&val](Buffer& buf) { to_wire_valid(buf, val); }, "\x01\x80\xff"); } // Any[] { auto val = simpledef.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 = arr.freeze().castTo(); 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"); } } void testDeserialize2() { testDiag("%s", __func__); { TypeStore ctxt; auto val = simpledef.create(); testFromBytes(true, "\x01@\x03\x01\xde\xad\xbe\xef\x01\x1b\xad\xfa\xce\x00", [&val, &ctxt](Buffer& buf) { from_wire_valid(buf, ctxt, val); }); testOk1(!val["value"].isMarked()); testOk1(!!val["arbitrary.sarr"].isMarked()); testEq(val["arbitrary.sarr"].as>().size(), 3u*sizeof(Value)); testEq(val["arbitrary.sarr[0].value"].as(), 0xdeadbeef); testEq(val["arbitrary.sarr[1]"]["value"].as(), 0x1badfaceu); testEq(val["arbitrary.sarr[2].value"].type(), TypeCode::Null); } { TypeStore ctxt; auto val = simpledef.create(); testFromBytes(true, "\x02\x00\x02\x01\x04test", [&val, &ctxt](Buffer& buf) { from_wire_valid(buf, ctxt, val); }); testOk1(!val["value"].isMarked()); testOk1(!!val["choice"].isMarked()); testEq(val["choice"].as(), "test"); } { TypeStore ctxt; auto val = simpledef.create(); testFromBytes(true, "\x02\x00\x04\x03\x01\x00\x04theX\x01\x01\x04theY\x00", [&val, &ctxt](Buffer& buf) { from_wire_valid(buf, ctxt, val); }); testOk1(!val["value"].isMarked()); testOk1(!!val["achoice"].isMarked()); testEq(val["achoice"].as>().size(), 3u*sizeof(Value)); testEq(val["achoice[0]"].as(), "theX"); testEq(val["achoice[1]"].as(), "theY"); testEq(val["achoice[2]"].type(), TypeCode::Null); } { TypeStore ctxt; auto val = simpledef.create(); testFromBytes(true, "\x01\x80\x26\x60\x0d\xf0\x0d", [&val, &ctxt](Buffer& buf) { from_wire_valid(buf, ctxt, val); }); testOk1(!val["value"].isMarked()); testOk1(!!val["any"].isMarked()); testEq(val["any"].as(), 0x600df00du); } { TypeStore ctxt; auto val = simpledef.create(); testFromBytes(true, "\x01\x80\xff", [&val, &ctxt](Buffer& buf) { from_wire_valid(buf, ctxt, val); }); testOk1(!val["value"].isMarked()); testOk1(!!val["any"].isMarked()); //testEq(val["any"].type(), TypeCode::Null); determine type _inside_ Any? } { TypeStore ctxt; auto val = simpledef.create(); testFromBytes(true, "\x02\x00\x01\x03\x01\x26\x00\x00\x00\x7b\x01\x80\x00\x01\x01q\x60\x04theq\x00", [&val, &ctxt](Buffer& buf) { from_wire_valid(buf, ctxt, val); }); testOk1(!val["value"].isMarked()); testOk1(!!val["anya"].isMarked()); testEq(val["anya"].as>().size(), 3u*sizeof(Value)); testEq(val["anya[0]"].as(), 0x7bu); testEq(val["anya[1].q"].as(), "theq"); testEq(val["anya[2]"].type(), TypeCode::Null); } } void testDeserialize3() { testDiag("%s", __func__); { TypeStore ctxt; Value val; testFromBytes(false, "\xfd\x02\x00\x80\x00\x01\x06\x72\x65\x63\x6f\x72\x64\xfd\x03\x00\x80\x00" "\x01\x08\x5f\x6f\x70\x74\x69\x6f\x6e\x73\xfd\x04\x00\x80\x00\x02\x09\x71" "\x75\x65\x75\x65\x53\x69\x7a\x65\x60\x08\x70\x69\x70\x65\x6c\x69\x6e\x65" "\x60\x01\x34\x04\x74\x72\x75\x65" , [&val, &ctxt](Buffer& buf) { from_wire_type_value(buf, ctxt, val); }); testShow()<(), "true"); testEq(val["record._options.queueSize"].as(), "4"); } } 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); from_wire(buf, descs, cache); testOk1(buf.good()); testEq(buf.size(), 0u)<<"Of "<second.size(), 4u); } } if(testOk1(!descs.empty())) { testEq(descs.size(), descs.front().size()); } // cat < 2 [2] secondsPastEpoch -> 1 [1] userTag -> 3 [3] secondsPastEpoch : 1 [1] nanoSeconds : 2 [2] userTag : 3 [3] [1] int64_t parent=[0] [1:2) [2] int32_t parent=[0] [2:3) [3] int32_t parent=[0] [3:4) )out" )<<"Actual:\n"< msg(NTScalar, NTScalar+sizeof(NTScalar)-1); std::vector descs; TypeStore cache; { FixedBuf buf(true, msg); from_wire(buf, descs, cache); testOk1(buf.good()); testEq(buf.size(), 0u)<<"remaining of "< 2 [2] alarm.message -> 5 [5] alarm.severity -> 3 [3] alarm.status -> 4 [4] timeStamp -> 6 [6] timeStamp.nanoseconds -> 8 [8] timeStamp.secondsPastEpoch -> 7 [7] timeStamp.userTag -> 9 [9] value -> 1 [1] value : 1 [1] alarm : 2 [2] timeStamp : 6 [6] [1] double[] parent=[0] [1:2) [2] struct alarm_t parent=[0] [2:6) message -> 3 [5] severity -> 1 [3] status -> 2 [4] severity : 1 [3] status : 2 [4] message : 3 [5] [3] int32_t parent=[2] [3:4) [4] int32_t parent=[2] [4:5) [5] string parent=[2] [5:6) [6] struct time_t parent=[0] [6:10) nanoseconds -> 2 [8] secondsPastEpoch -> 1 [7] userTag -> 3 [9] secondsPastEpoch : 1 [7] nanoseconds : 2 [8] userTag : 3 [9] [7] int64_t parent=[6] [7:8) [8] int32_t parent=[6] [8:9) [9] int32_t parent=[6] [9:10) )out" )<<"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); from_wire(buf, descs, cache); testOk1(buf.good()); testEq(buf.size(), 0u)<<"remaining of "< 12 [12] alarm.message -> 15 [15] alarm.severity -> 13 [13] alarm.status -> 14 [14] attribute -> 21 [21] codec -> 2 [2] codec.name -> 3 [3] codec.parameters -> 4 [4] compressedSize -> 5 [5] dataTimeStamp -> 8 [8] dataTimeStamp.nanoseconds -> 10 [10] dataTimeStamp.secondsPastEpoch -> 9 [9] dataTimeStamp.userTag -> 11 [11] dimension -> 20 [20] timeStamp -> 16 [16] timeStamp.nanoseconds -> 18 [18] timeStamp.secondsPastEpoch -> 17 [17] timeStamp.userTag -> 19 [19] uncompressedSize -> 6 [6] uniqueId -> 7 [7] value -> 1 [1] value : 1 [1] codec : 2 [2] compressedSize : 5 [5] uncompressedSize : 6 [6] uniqueId : 7 [7] dataTimeStamp : 8 [8] alarm : 12 [12] timeStamp : 16 [16] dimension : 20 [20] attribute : 21 [21] [1] union parent=[0] [1:2) booleanValue -> 0 [0] byteValue -> 1 [1] doubleValue -> 10 [10] floatValue -> 9 [9] intValue -> 3 [3] longValue -> 4 [4] shortValue -> 2 [2] ubyteValue -> 5 [5] uintValue -> 7 [7] ulongValue -> 8 [8] ushortValue -> 6 [6] booleanValue : 0 [0] [0] bool[] parent=[0] [0:1) byteValue : 1 [1] [0] int8_t[] parent=[0] [0:1) shortValue : 2 [2] [0] int16_t[] parent=[0] [0:1) intValue : 3 [3] [0] int32_t[] parent=[0] [0:1) longValue : 4 [4] [0] int64_t[] parent=[0] [0:1) ubyteValue : 5 [5] [0] uint8_t[] parent=[0] [0:1) ushortValue : 6 [6] [0] uint16_t[] parent=[0] [0:1) uintValue : 7 [7] [0] uint32_t[] parent=[0] [0:1) ulongValue : 8 [8] [0] uint64_t[] parent=[0] [0:1) floatValue : 9 [9] [0] float[] parent=[0] [0:1) doubleValue : 10 [10] [0] double[] parent=[0] [0:1) [2] struct codec_t parent=[0] [2:5) name -> 1 [3] parameters -> 2 [4] name : 1 [3] parameters : 2 [4] [3] string parent=[2] [3:4) [4] any parent=[2] [4:5) [5] int64_t parent=[0] [5:6) [6] int64_t parent=[0] [6:7) [7] int32_t parent=[0] [7:8) [8] struct time_t parent=[0] [8:12) nanoseconds -> 2 [10] secondsPastEpoch -> 1 [9] userTag -> 3 [11] secondsPastEpoch : 1 [9] nanoseconds : 2 [10] userTag : 3 [11] [9] int64_t parent=[8] [9:10) [10] int32_t parent=[8] [10:11) [11] int32_t parent=[8] [11:12) [12] struct alarm_t parent=[0] [12:16) message -> 3 [15] severity -> 1 [13] status -> 2 [14] severity : 1 [13] status : 2 [14] message : 3 [15] [13] int32_t parent=[12] [13:14) [14] int32_t parent=[12] [14:15) [15] string parent=[12] [15:16) [16] struct time_t parent=[0] [16:20) nanoseconds -> 2 [18] secondsPastEpoch -> 1 [17] userTag -> 3 [19] secondsPastEpoch : 1 [17] nanoseconds : 2 [18] userTag : 3 [19] [17] int64_t parent=[16] [17:18) [18] int32_t parent=[16] [18:19) [19] int32_t parent=[16] [19:20) [20] struct[] parent=[0] [20:21) [0] struct dimension_t parent=[0] [0:6) binning -> 4 [4] fullSize -> 3 [3] offset -> 2 [2] reverse -> 5 [5] size -> 1 [1] size : 1 [1] offset : 2 [2] fullSize : 3 [3] binning : 4 [4] reverse : 5 [5] [1] int32_t parent=[0] [1:2) [2] int32_t parent=[0] [2:3) [3] int32_t parent=[0] [3:4) [4] int32_t parent=[0] [4:5) [5] bool parent=[0] [5:6) [21] struct[] parent=[0] [21:22) [0] struct epics:nt/NTAttribute:1.0 parent=[0] [0:15) alarm -> 5 [5] alarm.message -> 8 [8] alarm.severity -> 6 [6] alarm.status -> 7 [7] descriptor -> 4 [4] name -> 1 [1] source -> 14 [14] sourceType -> 13 [13] tags -> 3 [3] timestamp -> 9 [9] timestamp.nanoseconds -> 11 [11] timestamp.secondsPastEpoch -> 10 [10] timestamp.userTag -> 12 [12] value -> 2 [2] name : 1 [1] value : 2 [2] tags : 3 [3] descriptor : 4 [4] alarm : 5 [5] timestamp : 9 [9] sourceType : 13 [13] source : 14 [14] [1] string parent=[0] [1:2) [2] any parent=[0] [2:3) [3] string[] parent=[0] [3:4) [4] string parent=[0] [4:5) [5] struct alarm_t parent=[0] [5:9) message -> 3 [8] severity -> 1 [6] status -> 2 [7] severity : 1 [6] status : 2 [7] message : 3 [8] [6] int32_t parent=[5] [6:7) [7] int32_t parent=[5] [7:8) [8] string parent=[5] [8:9) [9] struct time_t parent=[0] [9:13) nanoseconds -> 2 [11] secondsPastEpoch -> 1 [10] userTag -> 3 [12] secondsPastEpoch : 1 [10] nanoseconds : 2 [11] userTag : 3 [12] [10] int64_t parent=[9] [10:11) [11] int32_t parent=[9] [11:12) [12] int32_t parent=[9] [12:13) [13] int32_t parent=[0] [13:14) [14] string parent=[0] [14:15) )out" )<<"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); } // test the common case for a pvRequest of caching an empty Struct void testEmptyRequest() { testDiag("%s", __func__); TypeStore registry; std::vector descs1; { uint8_t msg[] = "\xfd\x02\x00\x80\x00\x00"; FixedBuf buf(false, msg); from_wire(buf, descs1, registry); testOk1(buf.good()); testEq(buf.size(), 0u)<<"remaining of "< descs2; { uint8_t msg[] = "\xfe\x02\x00"; FixedBuf buf(false, msg); from_wire(buf, descs2, registry); testOk1(buf.good()); testEq(buf.size(), 0u)<<"remaining of "<