diff --git a/src/core/ddsi/CMakeLists.txt b/src/core/ddsi/CMakeLists.txt index 06c09fe..988b58c 100644 --- a/src/core/ddsi/CMakeLists.txt +++ b/src/core/ddsi/CMakeLists.txt @@ -70,6 +70,7 @@ PREPEND(hdrs_private_ddsi "${CMAKE_CURRENT_LIST_DIR}/include/dds/ddsi" ddsi_raweth.h ddsi_ipaddr.h ddsi_mcgroup.h + ddsi_plist_generic.h ddsi_serdata.h ddsi_sertopic.h ddsi_serdata_default.h @@ -129,3 +130,8 @@ install( DIRECTORY "${CMAKE_CURRENT_LIST_DIR}/include/dds" DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}" COMPONENT dev) + +# TODO: improve test inclusion. +if((BUILD_TESTING) AND ((NOT DEFINED MSVC_VERSION) OR (MSVC_VERSION GREATER "1800"))) + add_subdirectory("${CMAKE_CURRENT_LIST_DIR}/tests") +endif() diff --git a/src/core/ddsi/include/dds/ddsi/ddsi_plist_generic.h b/src/core/ddsi/include/dds/ddsi/ddsi_plist_generic.h new file mode 100644 index 0000000..0ee2537 --- /dev/null +++ b/src/core/ddsi/include/dds/ddsi/ddsi_plist_generic.h @@ -0,0 +1,56 @@ +/* + * Copyright(c) 2019 ADLINK Technology Limited and others + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the Eclipse Distribution License + * v. 1.0 which is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause + */ +#ifndef DDSI_PLIST_GENERIC_H +#define DDSI_PLIST_GENERIC_H + +#include +#include + +#include "dds/export.h" + +#include "dds/ddsrt/attributes.h" + +#if defined (__cplusplus) +extern "C" { +#endif + +/* Instructions for the generic serializer (&c) that handles most parameters. + The "packed" attribute means single-byte instructions on GCC and Clang. */ +enum pserop { + XSTOP, + XO, /* octet sequence */ + XS, /* string */ + XE1, XE2, XE3, /* enum 0..1, 0..2, 0..3 */ + Xi, Xix2, Xix3, Xix4, /* int32_t, 1 .. 4 in a row */ + Xu, Xux2, Xux3, Xux4, Xux5, /* uint32_t, 1 .. 5 in a row */ + XD, XDx2, /* duration, 1 .. 2 in a row */ + Xo, Xox2, /* octet, 1 .. 2 in a row */ + Xb, Xbx2, /* boolean, 1 .. 2 in a row */ + XbCOND, /* boolean: compare to ignore remainder if false (for use_... flags) */ + XbPROP, /* boolean: omit in serialized form; skip serialization if false; always true on deserialize */ + XG, /* GUID */ + XK, /* keyhash */ + XQ /* arbitary non-nested sequence */ +} ddsrt_attribute_packed; + +DDS_EXPORT void plist_fini_generic (void * __restrict dst, const enum pserop *desc, bool aliased); +DDS_EXPORT dds_return_t plist_deser_generic (void * __restrict dst, const void * __restrict src, size_t srcsize, bool bswap, const enum pserop * __restrict desc); +DDS_EXPORT dds_return_t plist_ser_generic (void **dst, size_t *dstsize, const void *src, const enum pserop * __restrict desc); +DDS_EXPORT dds_return_t plist_unalias_generic (void * __restrict dst, const enum pserop * __restrict desc); +DDS_EXPORT bool plist_equal_generic (const void *srcx, const void *srcy, const enum pserop * __restrict desc); +DDS_EXPORT size_t plist_memsize_generic (const enum pserop * __restrict desc); + +#if defined (__cplusplus) +} +#endif + +#endif diff --git a/src/core/ddsi/src/q_plist.c b/src/core/ddsi/src/q_plist.c index 1b09428..e7f8d69 100644 --- a/src/core/ddsi/src/q_plist.c +++ b/src/core/ddsi/src/q_plist.c @@ -38,6 +38,8 @@ #include "dds/ddsrt/avl.h" #include "dds/ddsi/q_misc.h" /* for vendor_is_... */ +#include "dds/ddsi/ddsi_plist_generic.h" + /* I am tempted to change LENGTH_UNLIMITED to 0 in the API (with -1 supported for backwards compatibility) ... on the wire however it must be -1 */ @@ -88,25 +90,6 @@ struct flagset { uint64_t wanted; }; -/* Instructions for the generic serializer (&c) that handles most parameters. - The "packed" attribute means single-byte instructions on GCC and Clang. */ -enum pserop { - XSTOP, - XO, /* octet sequence */ - XS, /* string */ - XE1, XE2, XE3, /* enum 0..1, 0..2, 0..3 */ - Xi, Xix2, Xix3, Xix4, /* int32_t, 1 .. 4 in a row */ - Xu, Xux2, Xux3, Xux4, Xux5, /* uint32_t, 1 .. 5 in a row */ - XD, XDx2, /* duration, 1 .. 2 in a row */ - Xo, Xox2, /* octet, 1 .. 2 in a row */ - Xb, Xbx2, /* boolean, 1 .. 2 in a row */ - XbCOND, /* boolean: compare to ignore remainder if false (for use_... flags) */ - XbPROP, /* boolean: omit in serialized form; skip serialization if false; always true on deserialize */ - XG, /* GUID */ - XK, /* keyhash */ - XQ /* arbitary non-nested sequence */ -} ddsrt_attribute_packed; - struct piddesc { nn_parameterid_t pid; /* parameter id or PID_PAD if strictly local */ uint16_t flags; /* see PDF_xxx flags */ @@ -359,6 +342,11 @@ static size_t ser_generic_srcsize (const enum pserop * __restrict desc) #undef SIMPLE } +size_t plist_memsize_generic (const enum pserop * __restrict desc) +{ + return ser_generic_srcsize (desc); +} + static void fini_generic_embeddable (void * __restrict dst, size_t * __restrict dstoff, const enum pserop *desc, const enum pserop * const desc_end, bool aliased) { #define COMPLEX(basecase_, type_, cleanup_unaliased_, cleanup_always_) do { \ @@ -546,7 +534,10 @@ static dds_return_t deser_generic (void * __restrict dst, size_t * __restrict ds { size_t elem_off = i * elem_size; if (deser_generic (x->value, &elem_off, flagset, flag, dd, srcoff, desc + 1) < 0) + { + ddsrt_free (x->value); goto fail; + } } *dstoff += sizeof (*x); while (*++desc != XSTOP) { } @@ -563,6 +554,22 @@ fail: return DDS_RETCODE_BAD_PARAMETER; } +dds_return_t plist_deser_generic (void * __restrict dst, const void * __restrict src, size_t srcsize, bool bswap, const enum pserop * __restrict desc) +{ + struct dd dd = { + .buf = src, + .bufsz = srcsize, + .bswap = bswap, + .protocol_version = {0,0}, + .vendorid = NN_VENDORID_ECLIPSE, + .factory = NULL + }; + uint64_t present = 0, aliased = 0; + struct flagset fs = { .present = &present, .aliased = &aliased, .wanted = 1 }; + size_t dstoff = 0, srcoff = 0; + return deser_generic (dst, &dstoff, &fs, 1, &dd, &srcoff, desc); +} + static void ser_generic_size_embeddable (size_t *dstoff, const void *src, size_t srcoff, const enum pserop * __restrict desc) { #define COMPLEX(basecase_, type_, dstoff_update_) do { \ @@ -766,7 +773,20 @@ static dds_return_t ser_generic (struct nn_xmsg *xmsg, nn_parameterid_t pid, con return ser_generic_embeddable (data, &dstoff, src, srcoff, desc); } -static dds_return_t unalias_generic (void * __restrict dst, size_t * __restrict dstoff, const enum pserop * __restrict desc) +dds_return_t plist_ser_generic (void **dst, size_t *dstsize, const void *src, const enum pserop * __restrict desc) +{ + const size_t srcoff = 0; + size_t dstoff = 0; + dds_return_t ret; + *dstsize = ser_generic_size (src, srcoff, desc); + if ((*dst = ddsrt_malloc (*dstsize == 0 ? 1 : *dstsize)) == NULL) + return DDS_RETCODE_OUT_OF_RESOURCES; + ret = ser_generic_embeddable (*dst, &dstoff, src, srcoff, desc); + assert (dstoff == *dstsize); + return ret; +} + +static dds_return_t unalias_generic (void * __restrict dst, size_t * __restrict dstoff, bool gen_seq_aliased, const enum pserop * __restrict desc) { #define COMPLEX(basecase_, type_, ...) do { \ type_ *x = deser_generic_dst (dst, dstoff, alignof (type_)); \ @@ -795,10 +815,21 @@ static dds_return_t unalias_generic (void * __restrict dst, size_t * __restrict case XK: SIMPLE (XK, nn_keyhash_t); break; case XQ: COMPLEX (XQ, ddsi_octetseq_t, if (x->length) { const size_t elem_size = ser_generic_srcsize (desc + 1); - x->value = ddsrt_memdup (x->value, x->length * elem_size); + if (gen_seq_aliased) + { + /* The memory for the elements of a generic sequence are owned by the plist, the only aliased bits + are the strings (XS) and octet sequences (XO) embedded in the elements. So in principle, an + unalias operation on a generic sequence should only operate on the elements of the sequence, + not on the sequence buffer itself. + + However, the "mergein_missing" operation (and consequently the copy & dup operations) memcpy the + source, then pretend it is aliased. In this case, the sequence buffer is aliased, rather than + private, and hence a new copy needs to be allocated. */ + x->value = ddsrt_memdup (x->value, x->length * elem_size); + } for (uint32_t i = 0; i < x->length; i++) { size_t elem_off = i * elem_size; - unalias_generic (x->value, &elem_off, desc + 1); + unalias_generic (x->value, &elem_off, gen_seq_aliased, desc + 1); } }); while (*++desc != XSTOP) { } break; } @@ -808,6 +839,12 @@ static dds_return_t unalias_generic (void * __restrict dst, size_t * __restrict #undef COMPLEX } +dds_return_t plist_unalias_generic (void * __restrict dst, const enum pserop * __restrict desc) +{ + size_t dstoff = 0; + return unalias_generic (dst, &dstoff, false, desc); +} + static bool unalias_generic_required (const enum pserop * __restrict desc) { while (*desc != XSTOP) @@ -835,6 +872,12 @@ static dds_return_t fini_generic (void * __restrict dst, size_t * __restrict dst return 0; } +void plist_fini_generic (void * __restrict dst, const enum pserop *desc, bool aliased) +{ + size_t dstoff = 0; + fini_generic_embeddable (dst, &dstoff, desc, NULL, aliased); +} + static dds_return_t valid_generic (const void *src, size_t srcoff, const enum pserop * __restrict desc) { #define COMPLEX(basecase_, type_, cond_stmts_) do { \ @@ -921,9 +964,9 @@ static bool equal_generic (const void *srcx, const void *srcy, size_t srcoff, co return true; }); break; - case XbPROP: TRIVIAL (Xb, unsigned char); break; - case XG: SIMPLE (XG, ddsi_guid_t, memcmp (x, y, sizeof (*x))); break; - case XK: SIMPLE (XK, nn_keyhash_t, memcmp (x, y, sizeof (*x))); break; + case XbPROP: TRIVIAL (XbPROP, unsigned char); break; + case XG: SIMPLE (XG, ddsi_guid_t, memcmp (x, y, sizeof (*x)) == 0); break; + case XK: SIMPLE (XK, nn_keyhash_t, memcmp (x, y, sizeof (*x)) == 0); break; case XQ: COMPLEX (XQ, ddsi_octetseq_t, { if (x->length != y->length) return false; @@ -943,6 +986,11 @@ static bool equal_generic (const void *srcx, const void *srcy, size_t srcoff, co #undef COMPLEX } +bool plist_equal_generic (const void *srcx, const void *srcy, const enum pserop * __restrict desc) +{ + return equal_generic (srcx, srcy, 0, desc); +} + #define membersize(type, member) sizeof (((type *) 0)->member) #define ENTRY(PFX_, NAME_, member_, flag_, validate_, ...) \ { PID_##NAME_, flag_, PFX_##_##NAME_, #NAME_, offsetof (struct nn_plist, member_), \ @@ -1409,7 +1457,7 @@ static void plist_or_xqos_unalias (void * __restrict dst, size_t shift) if ((*fs->present & entry->present_flag) && (*fs->aliased & entry->present_flag)) { if (!(entry->flags & PDF_FUNCTION)) - unalias_generic (dst, &dstoff, entry->op.desc); + unalias_generic (dst, &dstoff, false, entry->op.desc); else if (entry->op.f.unalias) entry->op.f.unalias (dst, &dstoff); *fs->aliased &= ~entry->present_flag; @@ -1467,15 +1515,15 @@ static void plist_or_xqos_mergein_missing (void * __restrict dst, const void * _ if (!(*fs_dst->present & entry->present_flag) && (*fs_src->present & mask & entry->present_flag)) { /* bitwise copy, mark as aliased & unalias; have to unalias fields one-by-one rather than - do this for all fields and call "unalias" on the entire object because fields that are - already present may be aliased, and it would be somewhat impolite to change that. + do this for all fields and call "unalias" on the entire object because fields that are + already present may be aliased, and it would be somewhat impolite to change that. - Note: dst & src have the same type, so offset in src is the same; - Note: unalias may have to look at */ + Note: dst & src have the same type, so offset in src is the same; + Note: unalias may have to look at */ memcpy ((char *) dst + dstoff, (const char *) src + dstoff, entry->size); *fs_dst->present |= entry->present_flag; if (!(entry->flags & PDF_FUNCTION)) - unalias_generic (dst, &dstoff, entry->op.desc); + unalias_generic (dst, &dstoff, true, entry->op.desc); else if (entry->op.f.unalias) entry->op.f.unalias (dst, &dstoff); } diff --git a/src/core/ddsi/tests/CMakeLists.txt b/src/core/ddsi/tests/CMakeLists.txt new file mode 100644 index 0000000..7aa0300 --- /dev/null +++ b/src/core/ddsi/tests/CMakeLists.txt @@ -0,0 +1,22 @@ +# +# Copyright(c) 2006 to 2018 ADLINK Technology Limited and others +# +# This program and the accompanying materials are made available under the +# terms of the Eclipse Public License v. 2.0 which is available at +# http://www.eclipse.org/legal/epl-2.0, or the Eclipse Distribution License +# v. 1.0 which is available at +# http://www.eclipse.org/org/documents/edl-v10.php. +# +# SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause +# +include(CUnit) + +set(ddsi_test_sources + "plist_generic.c" + "plist.c") + +add_cunit_executable(cunit_ddsi ${ddsi_test_sources}) +target_include_directories( + cunit_ddsi PRIVATE + "$") +target_link_libraries(cunit_ddsi PRIVATE ddsc) diff --git a/src/core/ddsi/tests/plist.c b/src/core/ddsi/tests/plist.c new file mode 100644 index 0000000..603d07a --- /dev/null +++ b/src/core/ddsi/tests/plist.c @@ -0,0 +1,170 @@ +/* + * Copyright(c) 2019 ADLINK Technology Limited and others + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the Eclipse Distribution License + * v. 1.0 which is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause + */ + +#include "CUnit/Theory.h" +#include "dds/ddsrt/heap.h" +#include "dds/ddsrt/string.h" +#include "dds/ddsrt/endian.h" +#include "dds/ddsi/q_xqos.h" +#include "dds/ddsi/q_plist.h" + +CU_Test (ddsi_plist, unalias_copy_merge) +{ + /* one int, one string and one string sequence covers most cases */ + nn_plist_t p0, p0memcpy; + char *p0strs[3]; + nn_plist_init_empty (&p0); + p0.present = PP_PRISMTECH_PROCESS_ID | PP_ENTITY_NAME; + p0.aliased = PP_ENTITY_NAME; + p0.process_id = 0x12345678; + p0.entity_name = "nemo"; + p0.qos.present = QP_PARTITION; + p0.qos.aliased = QP_PARTITION; + p0.qos.partition.n = 3; + p0.qos.partition.strs = ddsrt_malloc (p0.qos.partition.n * sizeof (*p0.qos.partition.strs)); + p0strs[0] = p0.qos.partition.strs[0] = "aap"; + p0strs[1] = p0.qos.partition.strs[1] = "noot"; + p0strs[2] = p0.qos.partition.strs[2] = "mies"; + memcpy (&p0memcpy, &p0, sizeof (p0)); + + /* manually alias one, so we can free it*/ + nn_plist_t p0alias; + memcpy (&p0alias, &p0, sizeof (p0)); + p0alias.qos.partition.strs = ddsrt_memdup (p0alias.qos.partition.strs, p0.qos.partition.n * sizeof (*p0.qos.partition.strs)); + nn_plist_fini (&p0alias); + CU_ASSERT (memcmp (&p0, &p0memcpy, sizeof (p0)) == 0); + CU_ASSERT_STRING_EQUAL (p0.entity_name, "nemo"); + CU_ASSERT_STRING_EQUAL (p0.qos.partition.strs[0], p0strs[0]); + CU_ASSERT_STRING_EQUAL (p0.qos.partition.strs[1], p0strs[1]); + CU_ASSERT_STRING_EQUAL (p0.qos.partition.strs[2], p0strs[2]); + + /* copy an aliased one; the original must be unchanged, the copy unaliased */ + nn_plist_t p1; + nn_plist_init_empty (&p1); + nn_plist_copy (&p1, &p0); + CU_ASSERT (memcmp (&p0, &p0memcpy, sizeof (p0)) == 0); + CU_ASSERT (p1.present == p0.present); + CU_ASSERT (p1.aliased == 0); + CU_ASSERT (p1.qos.present == p0.qos.present); + CU_ASSERT (p1.qos.aliased == 0); + CU_ASSERT (p1.process_id == p0.process_id); + CU_ASSERT (p1.entity_name != p0.entity_name); + CU_ASSERT_STRING_EQUAL (p1.entity_name, p0.entity_name); + CU_ASSERT (p1.qos.partition.n == p0.qos.partition.n); + CU_ASSERT (p1.qos.partition.strs != p0.qos.partition.strs); + CU_ASSERT (p1.qos.partition.strs[0] != p0.qos.partition.strs[0]); + CU_ASSERT (p1.qos.partition.strs[1] != p0.qos.partition.strs[1]); + CU_ASSERT (p1.qos.partition.strs[2] != p0.qos.partition.strs[2]); + CU_ASSERT_STRING_EQUAL (p1.qos.partition.strs[0], p0.qos.partition.strs[0]); + CU_ASSERT_STRING_EQUAL (p1.qos.partition.strs[1], p0.qos.partition.strs[1]); + CU_ASSERT_STRING_EQUAL (p1.qos.partition.strs[2], p0.qos.partition.strs[2]); + + /* merge-in missing ones from an aliased copy: original must remain unchanged; + existing ones should stay without touching "aliased" only new ones are + added as unaliased ones */ + nn_plist_t p2, p2memcpy; + nn_plist_init_empty (&p2); + p2.present = PP_ENTITY_NAME; + p2.aliased = PP_ENTITY_NAME; + p2.entity_name = "omen"; + memcpy (&p2memcpy, &p2, sizeof (p2)); + nn_plist_mergein_missing (&p2, &p0, p0.present, p0.qos.present); + CU_ASSERT (memcmp (&p0, &p0memcpy, sizeof (p0)) == 0); + CU_ASSERT (p2.present == p0.present); + CU_ASSERT (p2.aliased == p2memcpy.aliased); + CU_ASSERT (p2.qos.present == p0.qos.present); + CU_ASSERT (p2.qos.aliased == p2memcpy.qos.aliased); + CU_ASSERT (p2.process_id == p0.process_id); + CU_ASSERT (p2.entity_name == p2memcpy.entity_name); + CU_ASSERT_STRING_EQUAL (p2.entity_name, "omen"); + CU_ASSERT (p2.qos.partition.n == p0.qos.partition.n); + CU_ASSERT (p2.qos.partition.strs != p0.qos.partition.strs); + CU_ASSERT (p2.qos.partition.strs[0] != p0.qos.partition.strs[0]); + CU_ASSERT (p2.qos.partition.strs[1] != p0.qos.partition.strs[1]); + CU_ASSERT (p2.qos.partition.strs[2] != p0.qos.partition.strs[2]); + CU_ASSERT_STRING_EQUAL (p2.qos.partition.strs[0], p0.qos.partition.strs[0]); + CU_ASSERT_STRING_EQUAL (p2.qos.partition.strs[1], p0.qos.partition.strs[1]); + CU_ASSERT_STRING_EQUAL (p2.qos.partition.strs[2], p0.qos.partition.strs[2]); + + /* unalias of p0, partition.strs mustn't change, because it, unlike its elements, wasn't aliased */ + nn_plist_unalias (&p0); + CU_ASSERT (p0.present == p0memcpy.present); + CU_ASSERT (p0.aliased == 0); + CU_ASSERT (p0.qos.present == p0memcpy.qos.present); + CU_ASSERT (p0.qos.aliased == 0); + CU_ASSERT (p0.process_id == p0memcpy.process_id); + CU_ASSERT (p0.entity_name != p0memcpy.entity_name); + CU_ASSERT_STRING_EQUAL (p0.entity_name, p0memcpy.entity_name); + CU_ASSERT (p0.qos.partition.n == p0memcpy.qos.partition.n); + CU_ASSERT (p0.qos.partition.strs == p0memcpy.qos.partition.strs); + CU_ASSERT (p0.qos.partition.strs[0] != p0strs[0]); + CU_ASSERT (p0.qos.partition.strs[1] != p0strs[1]); + CU_ASSERT (p0.qos.partition.strs[2] != p0strs[2]); + CU_ASSERT_STRING_EQUAL (p0.qos.partition.strs[0], p0strs[0]); + CU_ASSERT_STRING_EQUAL (p0.qos.partition.strs[1], p0strs[1]); + CU_ASSERT_STRING_EQUAL (p0.qos.partition.strs[2], p0strs[2]); + memcpy (&p0memcpy, &p0, sizeof (p0)); + + /* copy an aliased one; the original must be unchanged, the copy unaliased */ + nn_plist_t p3; + nn_plist_init_empty (&p3); + nn_plist_copy (&p3, &p0); + CU_ASSERT (memcmp (&p0, &p0memcpy, sizeof (p0)) == 0); + CU_ASSERT (p3.present == p0.present); + CU_ASSERT (p3.aliased == 0); + CU_ASSERT (p3.qos.present == p0.qos.present); + CU_ASSERT (p3.qos.aliased == 0); + CU_ASSERT (p3.process_id == p0.process_id); + CU_ASSERT (p3.entity_name != p0.entity_name); + CU_ASSERT_STRING_EQUAL (p3.entity_name, p0.entity_name); + CU_ASSERT (p3.qos.partition.n == p0.qos.partition.n); + CU_ASSERT (p3.qos.partition.strs != p0.qos.partition.strs); + CU_ASSERT (p3.qos.partition.strs[0] != p0.qos.partition.strs[0]); + CU_ASSERT (p3.qos.partition.strs[1] != p0.qos.partition.strs[1]); + CU_ASSERT (p3.qos.partition.strs[2] != p0.qos.partition.strs[2]); + CU_ASSERT_STRING_EQUAL (p3.qos.partition.strs[0], p0.qos.partition.strs[0]); + CU_ASSERT_STRING_EQUAL (p3.qos.partition.strs[1], p0.qos.partition.strs[1]); + CU_ASSERT_STRING_EQUAL (p3.qos.partition.strs[2], p0.qos.partition.strs[2]); + + /* merge-in missing ones from an aliased copy: original must remain unchanged; + existing ones should stay without touching "aliased" only new ones are + added as unaliased ones */ + nn_plist_t p4, p4memcpy; + nn_plist_init_empty (&p4); + p4.present = PP_ENTITY_NAME; + p4.aliased = PP_ENTITY_NAME; + p4.entity_name = "omen"; + memcpy (&p4memcpy, &p4, sizeof (p4)); + nn_plist_mergein_missing (&p4, &p0, p0.present, p0.qos.present); + CU_ASSERT (memcmp (&p0, &p0memcpy, sizeof (p0)) == 0); + CU_ASSERT (p4.present == p0.present); + CU_ASSERT (p4.aliased == p4memcpy.aliased); + CU_ASSERT (p4.qos.present == p0.qos.present); + CU_ASSERT (p4.qos.aliased == p4memcpy.qos.aliased); + CU_ASSERT (p4.process_id == p0.process_id); + CU_ASSERT (p4.entity_name == p4memcpy.entity_name); + CU_ASSERT_STRING_EQUAL (p4.entity_name, "omen"); + CU_ASSERT (p4.qos.partition.n == p0.qos.partition.n); + CU_ASSERT (p4.qos.partition.strs != p0.qos.partition.strs); + CU_ASSERT (p4.qos.partition.strs[0] != p0.qos.partition.strs[0]); + CU_ASSERT (p4.qos.partition.strs[1] != p0.qos.partition.strs[1]); + CU_ASSERT (p4.qos.partition.strs[2] != p0.qos.partition.strs[2]); + CU_ASSERT_STRING_EQUAL (p4.qos.partition.strs[0], p0.qos.partition.strs[0]); + CU_ASSERT_STRING_EQUAL (p4.qos.partition.strs[1], p0.qos.partition.strs[1]); + CU_ASSERT_STRING_EQUAL (p4.qos.partition.strs[2], p0.qos.partition.strs[2]); + + nn_plist_fini (&p0); + nn_plist_fini (&p1); + nn_plist_fini (&p2); + nn_plist_fini (&p3); + nn_plist_fini (&p4); +} diff --git a/src/core/ddsi/tests/plist_generic.c b/src/core/ddsi/tests/plist_generic.c new file mode 100644 index 0000000..9d50270 --- /dev/null +++ b/src/core/ddsi/tests/plist_generic.c @@ -0,0 +1,238 @@ +/* + * Copyright(c) 2019 ADLINK Technology Limited and others + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the Eclipse Distribution License + * v. 1.0 which is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause + */ + +#include "CUnit/Theory.h" +#include "dds/ddsrt/heap.h" +#include "dds/ddsrt/endian.h" +#include "dds/ddsi/q_xqos.h" +#include "dds/ddsi/ddsi_plist_generic.h" + +struct desc { + const enum pserop desc[10]; + const void *data; + size_t exp_sersize; + const unsigned char *exp_ser; + + /* XbPROP means expectation after deser may be different from input, if exp_data + is NULL, use "data", else use "exp_data" */ + const void *exp_data; +}; + +struct desc_invalid { + const enum pserop desc[20]; + size_t sersize; + const unsigned char *ser; +}; + +#if DDSRT_ENDIAN == DDSRT_BIG_ENDIAN +#define SER32(v) \ + (unsigned char)((uint32_t)(v) >> 24), \ + (unsigned char)(((uint32_t)(v) >> 16) & 0xff), \ + (unsigned char)(((uint32_t)(v) >> 8) & 0xff), \ + (unsigned char)((uint32_t)(v) & 0xff) +#define SER32BE(v) SER32(v) +#else +#define SER32(v) \ + (unsigned char)((uint32_t)(v) & 0xff), \ + (unsigned char)(((uint32_t)(v) >> 8) & 0xff), \ + (unsigned char)(((uint32_t)(v) >> 16) & 0xff), \ + (unsigned char)((uint32_t)(v) >> 24) +#define SER32BE(v) \ + (unsigned char)((uint32_t)(v) >> 24), \ + (unsigned char)(((uint32_t)(v) >> 16) & 0xff), \ + (unsigned char)(((uint32_t)(v) >> 8) & 0xff), \ + (unsigned char)((uint32_t)(v) & 0xff) +#endif + +typedef unsigned char raw[]; +typedef uint32_t raw32[]; +typedef ddsi_octetseq_t oseq; + +struct desc descs[] = { + { {XSTOP}, (raw){0}, 0, (raw){0} }, + { {XO,XSTOP}, &(oseq){0, NULL }, 4, (raw){SER32(0)} }, + { {XO,XSTOP}, &(oseq){1, (raw){3} }, 5, (raw){SER32(1), 3} }, + { {XS,XSTOP}, &(char *[]){""}, 5, (raw){SER32(1), 0} }, + { {XS,XSTOP}, &(char *[]){"meow"}, 9, (raw){SER32(5), 'm','e','o','w',0} }, + { {XE1,XSTOP}, (raw32){1}, 4, (raw){SER32(1)} }, + { {XE2,XSTOP}, (raw32){2}, 4, (raw){SER32(2)} }, + { {XE3,XSTOP}, (raw32){3}, 4, (raw){SER32(3)} }, + { {Xi,XSTOP}, (raw32){1}, 4, (raw){SER32(1)} }, + { {Xix2,XSTOP}, (raw32){2,3}, 8, (raw){SER32(2), SER32(3)} }, + { {Xix3,XSTOP}, (raw32){4,5,6}, 12, (raw){SER32(4), SER32(5), SER32(6)} }, + { {Xix4,XSTOP}, (raw32){7,8,9,10}, 16, (raw){SER32(7), SER32(8), SER32(9), SER32(10)} }, + { {Xu,XSTOP}, (raw32){1}, 4, (raw){SER32(1)} }, + { {Xux2,XSTOP}, (raw32){2,3}, 8, (raw){SER32(2), SER32(3)} }, + { {Xux3,XSTOP}, (raw32){4,5,6}, 12, (raw){SER32(4), SER32(5), SER32(6)} }, + { {Xux4,XSTOP}, (raw32){7,8,9,10}, 16, (raw){SER32(7), SER32(8), SER32(9), SER32(10)} }, + { {Xux5,XSTOP}, (raw32){7,8,9,10,11}, 20, (raw){SER32(7), SER32(8), SER32(9), SER32(10), SER32(11)} }, + { {XD,XSTOP}, (uint64_t[]){314159265358979324}, + /* note: fractional part depends on rounding rule used for converting nanoseconds to NTP time + Cyclone currently rounds up, so we have to do that too */ + 8, (raw){SER32(314159265), SER32(1541804457)} }, + { {XD,XSTOP}, (uint64_t[]){DDS_NEVER}, + 8, (raw){SER32(INT32_MAX), SER32(UINT32_MAX)} }, + { {XDx2,XSTOP}, (uint64_t[]){314159265358979324, 271828182845904524}, + 16, (raw){SER32(314159265), SER32(1541804457), SER32(271828182), SER32(3633132267)} }, + { {Xo,XSTOP}, (raw){31}, 1, (raw){31} }, + { {Xox2,XSTOP}, (raw){31,13}, 2, (raw){31,13} }, + { {Xb,XSTOP}, (raw){1}, 1, (raw){1} }, + { {Xbx2,XSTOP}, (raw){1,0}, 2, (raw){1,0} }, + { {XG,XSTOP}, (raw32){3,4,5,0x1c1}, 16, (raw){SER32BE(3), SER32BE(4), SER32BE(5), SER32BE(0x1c1) } }, + { {XK,XSTOP}, (raw){1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16}, + 16, (raw){1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16} }, + { {XQ,Xo,XSTOP,XSTOP}, &(oseq){3, (raw){1,2,3}}, + 7, (raw){SER32(3), 1,2,3} }, + { {XQ,XS,XSTOP,XSTOP}, &(ddsi_stringseq_t){2, (char*[]){"tree","flower"}}, + 27, (raw){SER32(2), SER32(5),'t','r','e','e',0, 0,0,0, SER32(7), 'f','l','o','w','e','r',0} }, + { {Xb,XQ,XbPROP,XS,Xo,XSTOP,XSTOP}, + &(struct{char b; oseq seq;}){1, {5, (unsigned char *)(struct{char b;char *s;uint8_t o;}[]){ + {0,"apple",1}, {1,"orange",2}, {0,"cherry",3}, {1,"fig",4}, {1,"prune",5}}}}, + 43, (raw){1, 0,0,0, SER32(3), + SER32(7), 'o','r','a','n','g','e',0, 2, + SER32(4), 'f','i','g',0, 4, 0,0,0, + SER32(6), 'p','r','u','n','e',0, 5 + }, + &(struct{char b; oseq seq;}){1, {3, (unsigned char *)(struct{char b;char *s;uint8_t o;}[]){ + {1,"orange",2}, {1,"fig",4}, {1,"prune",5}}}}, + } +}; + +CU_Test (ddsi_plist_generic, ser_and_deser) +{ + union { + uint64_t u; + void *p; + char buf[256]; + } mem; + + for (size_t i = 0; i < sizeof (descs) / sizeof (descs[0]); i++) + { + size_t memsize; + void *ser; + size_t sersize; + dds_return_t ret; + ret = plist_ser_generic (&ser, &sersize, descs[i].data, descs[i].desc); + if (ret != DDS_RETCODE_OK) + CU_ASSERT_FATAL (ret == DDS_RETCODE_OK); + if (sersize != descs[i].exp_sersize) + CU_ASSERT (sersize == descs[i].exp_sersize); + /* if sizes don't match, still check prefix */ + size_t cmpsize = (sersize < descs[i].exp_sersize) ? sersize : descs[i].exp_sersize; + if (memcmp (ser, descs[i].exp_ser, cmpsize) != 0) + { + printf ("memcmp i = %zu\n", i); + for (size_t k = 0; k < cmpsize; k++) + printf (" %3zu %02x %02x\n", k, ((unsigned char *)ser)[k], descs[i].exp_ser[k]); + CU_ASSERT (!(bool)"memcmp"); + } + /* check */ + memsize = plist_memsize_generic (descs[i].desc); + if (memsize > sizeof (mem)) + CU_ASSERT_FATAL (memsize <= sizeof (mem)); + /* memset to zero for used part so padding is identical to compiler inserted padding, + but to something unlikely for the remainder */ + memset (mem.buf, 0, memsize); + memset (mem.buf + memsize, 0xee, sizeof (mem) - memsize); + ret = plist_deser_generic (&mem, ser, sersize, false, descs[i].desc); + if (ret != DDS_RETCODE_OK) + CU_ASSERT_FATAL (ret == DDS_RETCODE_OK); + /* the compare function should be happy with it */ + if (!plist_equal_generic (descs[i].exp_data ? descs[i].exp_data : descs[i].data, &mem, descs[i].desc)) + CU_ASSERT (!(bool)"plist_equal_generic"); + /* content should be identical except when an XO, XS or XQ is present (because the first two + alias the serialised form and XQ to freshly allocated memory), so we do a limited check */ + bool can_memcmp = true; + for (const enum pserop *op = descs[i].desc; *op != XSTOP && can_memcmp; op++) + if (*op == XS || *op == XO || *op == XQ) + can_memcmp = false; + if (can_memcmp && memcmp (descs[i].exp_data ? descs[i].exp_data : descs[i].data, &mem, memsize) != 0) + CU_ASSERT (!(bool)"memcmp"); + /* rely on mem checkers to find memory leaks, incorrect free, etc. */ + plist_fini_generic (&mem, descs[i].desc, true); + ddsrt_free (ser); + } +} + +CU_Test (ddsi_plist_generic, unalias) +{ + union { + uint64_t u; + void *p; + char buf[256]; + } mem; + + for (size_t i = 0; i < sizeof (descs) / sizeof (descs[0]); i++) + { + void *ser; + size_t sersize; + dds_return_t ret; + (void) plist_ser_generic (&ser, &sersize, descs[i].data, descs[i].desc); + (void) plist_deser_generic (&mem, ser, sersize, false, descs[i].desc); + /* after unaliasing, the data should be valid even when the serialised form has been overwritten or freed */ + ret = plist_unalias_generic (&mem, descs[i].desc); + CU_ASSERT_FATAL (ret == DDS_RETCODE_OK); + memset (ser, 0xee, sersize); + ddsrt_free (ser); + if (!plist_equal_generic (descs[i].exp_data ? descs[i].exp_data : descs[i].data, &mem, descs[i].desc)) + CU_ASSERT (!(bool)"plist_equal_generic"); + plist_fini_generic (&mem, descs[i].desc, false); + } +} + +struct desc_invalid descs_invalid[] = { + { {Xb,XSTOP}, 1, (raw){2} }, // 2 is not a valid boolean + { {XS,XSTOP}, 8, (raw){SER32(5), 'm','e','o','w',0} }, // short input + { {XS,XSTOP}, 8, (raw){SER32(4), 'm','e','o','w',0} }, // not terminated + { {XG,XSTOP}, 15, (raw){SER32BE(3), SER32BE(4), SER32BE(5), SER32BE(0x100) } }, // short input + { {XK,XSTOP}, 15, (raw){1,2,3,4,5,6,7,8,9,10,11,12,13,14,15} }, // short input + { {XQ,Xo,XSTOP,XSTOP}, 7, (raw){SER32(4), 1,2,3} }, // short input + { {XQ,XS,XSTOP,XSTOP}, // padding missing, short input + 24, (raw){SER32(2), SER32(5),'t','r','e','e',0, SER32(7), 'f','l','o','w','e','r',0} }, + { {Xb,XQ,XbPROP,XS,Xo,XSTOP,XSTOP}, + 43, (raw){1, 0,0,0, SER32(3), + SER32(7), 'o','r','a','n','g','e',0, 2, + SER32(4), 'f','i','g',0, 4, 0,0,0, + SER32(7), 'p','r','u','n','e',0, 5 // string not terminated + } }, + { {Xb, XQ,XbPROP,XS,Xo,XSTOP, XQ,XbPROP,XS,Xo,XSTOP, XSTOP}, + 43, (raw){1, 0,0,0, + /* first sequence is valid */ + SER32(3), + SER32(7), 'o','r','a','n','g','e',0, 2, + SER32(4), 'f','i','g',0, 4, 0,0,0, + SER32(6), 'p','r','u','n','e',0, 5, + /* second sequence is invalid */ + 0, /* pad */ + SER32(3), + SER32(7), 'o','r','a','n','g','e',0, 2, + SER32(4), 'f','i','g',0, 4, 0,0,0, + SER32(7), 'p','r','u','n','e',0, 5 // string not terminated + } } +}; + +CU_Test (ddsi_plist_generic, invalid_input) +{ + union { + uint64_t u; + void *p; + char buf[256]; + } mem; + + for (size_t i = 0; i < sizeof (descs_invalid) / sizeof (descs_invalid[0]); i++) + { + dds_return_t ret; + ret = plist_deser_generic (&mem, descs_invalid[i].ser, descs_invalid[i].sersize, false, descs_invalid[i].desc); + if (ret == DDS_RETCODE_OK) + CU_ASSERT_FATAL (ret != DDS_RETCODE_OK); + } +}