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..8853ef7 --- /dev/null +++ b/src/core/ddsi/include/dds/ddsi/ddsi_plist_generic.h @@ -0,0 +1,67 @@ +/* + * 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 + +#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 */ + Xopt, /* remainder is optional on deser, 0-init if not present */ +} ddsrt_attribute_packed; + +inline bool pserop_seralign_is_1 (enum pserop op) { + /* NB: XbPROP is never serialized, so its alignment is irrelevant. If ever there + is a need to allow calling this function when op = XbPROP, it needs to be changed + to taking the address of the pserop, and in that case inspect the following + operator */ + assert (op != XbPROP && op != Xopt && op != XSTOP); + return (op >= Xo && op <= XK); +} + +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/include/dds/ddsi/q_xqos.h b/src/core/ddsi/include/dds/ddsi/q_xqos.h index 7ae7a02..cecd24b 100644 --- a/src/core/ddsi/include/dds/ddsi/q_xqos.h +++ b/src/core/ddsi/include/dds/ddsi/q_xqos.h @@ -32,6 +32,41 @@ typedef ddsi_octetseq_t dds_userdata_qospolicy_t; typedef ddsi_octetseq_t dds_topicdata_qospolicy_t; typedef ddsi_octetseq_t dds_groupdata_qospolicy_t; +typedef struct dds_property { + /* The propagate boolean will not be send over the wire. + * When the value is 'false', the complete struct shouldn't be send. + * It has to be the first variable within the structure because it + * is mapped to XbPROP in the serialiser. */ + unsigned char propagate; + char *name; + char *value; +} dds_property_t; + +typedef struct dds_propertyseq { + uint32_t n; + dds_property_t *props; +} dds_propertyseq_t; + +typedef struct dds_binaryproperty { + /* The propagate boolean will not be send over the wire. + * When the value is 'false', the complete struct shouldn't be send. + * It has to be the first variable within the structure because it + * is mapped to XbPROP in the serialiser. */ + unsigned char propagate; + char *name; + ddsi_octetseq_t value; +} dds_binaryproperty_t; + +typedef struct dds_binarypropertyseq { + uint32_t n; + dds_binaryproperty_t *props; +} dds_binarypropertyseq_t; + +typedef struct dds_property_qospolicy { + dds_propertyseq_t value; + dds_binarypropertyseq_t binary_value; +} dds_property_qospolicy_t; + typedef struct dds_durability_qospolicy { dds_durability_kind_t kind; } dds_durability_qospolicy_t; @@ -212,6 +247,7 @@ typedef struct dds_ignorelocal_qospolicy { #define QP_PRISMTECH_SUBSCRIPTION_KEYS ((uint64_t)1 << 25) #define QP_PRISMTECH_ENTITY_FACTORY ((uint64_t)1 << 27) #define QP_CYCLONE_IGNORELOCAL ((uint64_t)1 << 30) +#define QP_PROPERTY_LIST ((uint64_t)1 << 31) /* Partition QoS is not RxO according to the specification (DDS 1.2, section 7.1.3), but communication will not take place unless it @@ -263,6 +299,7 @@ struct dds_qos { /*x xR*/dds_subscription_keys_qospolicy_t subscription_keys; /*x xR*/dds_reader_lifespan_qospolicy_t reader_lifespan; /* x */dds_ignorelocal_qospolicy_t ignorelocal; + /*xxx */dds_property_qospolicy_t property; }; struct nn_xmsg; diff --git a/src/core/ddsi/src/ddsi_serdata_default.c b/src/core/ddsi/src/ddsi_serdata_default.c index 622b0e2..39879f4 100644 --- a/src/core/ddsi/src/ddsi_serdata_default.c +++ b/src/core/ddsi/src/ddsi_serdata_default.c @@ -453,6 +453,7 @@ static struct ddsi_serdata *serdata_default_from_sample_plist (const struct ddsi #ifndef NDEBUG size_t keysize; #endif + assert(rawkey); switch (sample->keyparam) { case PID_PARTICIPANT_GUID: diff --git a/src/core/ddsi/src/q_plist.c b/src/core/ddsi/src/q_plist.c index be571f6..50a9721 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 */ - XZ, /* string sequence */ - XE1, XE2, XE3, /* enum 0..1, 0..2, 0..3 */ - Xl, /* length, int32_t, -1 or >= 1 */ - 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) */ - XG, /* GUID */ - XK /* keyhash */ -} ddsrt_attribute_packed; - struct piddesc { nn_parameterid_t pid; /* parameter id or PID_PAD if strictly local */ uint16_t flags; /* see PDF_xxx flags */ @@ -115,10 +98,10 @@ struct piddesc { size_t plist_offset; /* offset from start of nn_plist_t */ size_t size; /* in-memory size for copying */ union { - /* descriptor for generic code: 4 is enough for the current set of + /* descriptor for generic code: 12 is enough for the current set of parameters, compiler will warn if one ever tries to use more than - will fit; on non-GCC/Clang and 32-bits machines */ - const enum pserop desc[4]; + will fit */ + const enum pserop desc[12]; struct { dds_return_t (*deser) (void * __restrict dst, size_t * __restrict dstoff, struct flagset *flagset, uint64_t flag, const struct dd * __restrict dd, size_t * __restrict srcoff); dds_return_t (*ser) (struct nn_xmsg *xmsg, nn_parameterid_t pid, const void *src, size_t srcoff); @@ -131,6 +114,8 @@ struct piddesc { dds_return_t (*deser_validate_xform) (void * __restrict dst, const struct dd * __restrict dd); }; +extern inline bool pserop_seralign_is_1 (enum pserop op); + static void log_octetseq (uint32_t cat, const struct ddsrt_log_cfg *logcfg, uint32_t n, const unsigned char *xs); static dds_return_t validate_history_qospolicy (const dds_history_qospolicy_t *q); static dds_return_t validate_resource_limits_qospolicy (const dds_resource_limits_qospolicy_t *q); @@ -325,7 +310,47 @@ static dds_return_t fini_locator (void * __restrict dst, size_t * __restrict dst return 0; } -static void fini_generic_partial (void * __restrict dst, size_t * __restrict dstoff, const enum pserop *desc, const enum pserop * const desc_end, bool aliased) +static size_t ser_generic_srcsize (const enum pserop * __restrict desc) +{ + size_t srcoff = 0, srcalign = 0; +#define SIMPLE(basecase_, type_) do { \ + const uint32_t cnt = 1 + (uint32_t) (*desc - (basecase_)); \ + const size_t align = alignof (type_); \ + srcalign = (align > srcalign) ? align : srcalign; \ + srcoff = (srcoff + align - 1) & ~(align - 1); \ + srcoff += cnt * sizeof (type_); \ + } while (0) + while (true) + { + switch (*desc) + { + case XSTOP: return (srcoff + srcalign - 1) & ~(srcalign - 1); + case XO: SIMPLE (XO, ddsi_octetseq_t); break; + case XS: SIMPLE (XS, const char *); break; + case XE1: case XE2: case XE3: SIMPLE (*desc, unsigned); break; + case Xi: case Xix2: case Xix3: case Xix4: SIMPLE (Xi, int32_t); break; + case Xu: case Xux2: case Xux3: case Xux4: case Xux5: SIMPLE (Xu, uint32_t); break; + case XD: case XDx2: SIMPLE (XD, dds_duration_t); break; + case Xo: case Xox2: SIMPLE (Xo, unsigned char); break; + case Xb: case Xbx2: SIMPLE (Xb, unsigned char); break; + case XbCOND: SIMPLE (XbCOND, unsigned char); break; + case XG: SIMPLE (XG, ddsi_guid_t); break; + case XK: SIMPLE (XK, nn_keyhash_t); break; + case XbPROP: SIMPLE (XbPROP, unsigned char); break; + case XQ: SIMPLE (XQ, ddsi_octetseq_t); while (*++desc != XSTOP) { } break; + case Xopt: break; + } + 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 { \ type_ *x = deser_generic_dst (dst, dstoff, alignof (type_)); \ @@ -344,17 +369,29 @@ static void fini_generic_partial (void * __restrict dst, size_t * __restrict dst case XSTOP: return; case XO: COMPLEX (XO, ddsi_octetseq_t, ddsrt_free (x->value), (void) 0); break; case XS: COMPLEX (XS, char *, ddsrt_free (*x), (void) 0); break; - case XZ: COMPLEX (XZ, ddsi_stringseq_t, { for (uint32_t i = 0; i < x->n; i++) ddsrt_free (x->strs[i]); }, ddsrt_free (x->strs)); break; case XE1: case XE2: case XE3: COMPLEX (*desc, unsigned, (void) 0, (void) 0); break; case Xi: case Xix2: case Xix3: case Xix4: SIMPLE (Xi, int32_t); break; case Xu: case Xux2: case Xux3: case Xux4: case Xux5: SIMPLE (Xu, uint32_t); break; - case Xl: SIMPLE (Xl, int32_t); break; case XD: case XDx2: SIMPLE (XD, dds_duration_t); break; case Xo: case Xox2: SIMPLE (Xo, unsigned char); break; case Xb: case Xbx2: SIMPLE (Xb, unsigned char); break; case XbCOND: SIMPLE (XbCOND, unsigned char); break; case XG: SIMPLE (XG, ddsi_guid_t); break; case XK: SIMPLE (XK, nn_keyhash_t); break; + case XbPROP: SIMPLE (XbPROP, unsigned char); break; + case XQ: + /* non-nested, so never a need to deallocate only some of the entries and no complications + in locating the end of the sequence element description */ + COMPLEX (XQ, ddsi_octetseq_t, { + const size_t elem_size = ser_generic_srcsize (desc + 1); + for (uint32_t i = 0; i < x->length; i++) { + size_t elem_off = i * elem_size; + fini_generic_embeddable (x->value, &elem_off, desc + 1, desc_end, aliased); + } + }, ddsrt_free (x->value)); + while (desc + 1 != desc_end && *++desc != XSTOP) { } + break; + case Xopt: break; } desc++; } @@ -362,6 +399,26 @@ static void fini_generic_partial (void * __restrict dst, size_t * __restrict dst #undef COMPLEX } +static size_t pserop_memalign (enum pserop op) +{ + switch (op) + { + case XO: case XQ: return alignof (ddsi_octetseq_t); + case XS: return alignof (char *); + case XG: return alignof (ddsi_guid_t); + case XK: return alignof (nn_keyhash_t); + case Xb: case Xbx2: return 1; + case Xo: case Xox2: return 1; + case XbCOND: case XbPROP: return 1; + case XE1: case XE2: case XE3: return sizeof (uint32_t); + case Xi: case Xix2: case Xix3: case Xix4: return sizeof (int32_t); + case Xu: case Xux2: case Xux3: case Xux4: case Xux5: return sizeof (uint32_t); + case XD: case XDx2: return alignof (dds_duration_t); + case XSTOP: case Xopt: assert (0); + } + return 0; +} + static dds_return_t deser_generic (void * __restrict dst, size_t * __restrict dstoff, struct flagset *flagset, uint64_t flag, const struct dd * __restrict dd, size_t * __restrict srcoff, const enum pserop * __restrict desc) { enum pserop const * const desc_in = desc; @@ -376,8 +433,7 @@ static dds_return_t deser_generic (void * __restrict dst, size_t * __restrict ds switch (*desc) { case XSTOP: - *flagset->present |= flag; - return 0; + goto success; case XO: { /* octet sequence */ ddsi_octetseq_t * const x = deser_generic_dst (dst, dstoff, alignof (ddsi_octetseq_t)); if (deser_uint32 (&x->length, dd, srcoff) < 0 || dd->bufsz - *srcoff < x->length) @@ -400,22 +456,6 @@ static dds_return_t deser_generic (void * __restrict dst, size_t * __restrict ds *dstoff += sizeof (*x); break; } - case XZ: { /* string sequence: repeatedly read a string */ - ddsi_stringseq_t * const x = deser_generic_dst (dst, dstoff, alignof (ddsi_stringseq_t)); - /* sequence of string: length length ..., where each length is aligned - to a multiple of 4 bytes and the lengths are all at least 1, therefore all but the - last entry need 8 bytes and the final one at least 5; checking this protects us - against allocating large amount of memory */ - if (deser_uint32 (&x->n, dd, srcoff) < 0 || x->n > (dd->bufsz - *srcoff + 7) / 8) - goto fail; - x->strs = x->n ? ddsrt_malloc (x->n * sizeof (*x->strs)) : NULL; - size_t tmpoff = 0; - for (uint32_t i = 0; i < x->n; i++) - if (deser_generic (x->strs, &tmpoff, flagset, flag, dd, srcoff, (enum pserop []) { XS, XSTOP }) < 0) - goto fail; - *dstoff += sizeof (*x); - break; - } case XE1: case XE2: case XE3: { /* enum with max allowed value */ unsigned * const x = deser_generic_dst (dst, dstoff, alignof (int)); const uint32_t maxval = 1 + (uint32_t) (*desc - XE1); @@ -444,15 +484,6 @@ static dds_return_t deser_generic (void * __restrict dst, size_t * __restrict ds *dstoff += cnt * sizeof (*x); break; } - case Xl: { /* length(s): int32_t, -1 or >= 1 */ - int32_t * const x = deser_generic_dst (dst, dstoff, alignof (uint32_t)); - const uint32_t cnt = 1 + (uint32_t) (*desc - Xl); - for (uint32_t i = 0; i < cnt; i++) - if (deser_uint32 ((uint32_t *) &x[i], dd, srcoff) < 0 || (x[i] < 1 && x[i] != DDS_LENGTH_UNLIMITED)) - goto fail; - *dstoff += cnt * sizeof (*x); - break; - } case XD: case XDx2: { /* duration(s): int64_t <=> int32_t.uint32_t (seconds.fraction) */ dds_duration_t * const x = deser_generic_dst (dst, dstoff, alignof (dds_duration_t)); const uint32_t cnt = 1 + (uint32_t) (*desc - XD); @@ -491,6 +522,12 @@ static dds_return_t deser_generic (void * __restrict dst, size_t * __restrict ds *dstoff += cnt * sizeof (*x); break; } + case XbPROP: { /* "propagate" flag, boolean, implied in serialized representation */ + unsigned char * const x = deser_generic_dst (dst, dstoff, alignof (unsigned char)); + *x = 1; + *dstoff += sizeof (*x); + break; + } case XG: { /* GUID */ ddsi_guid_t * const x = deser_generic_dst (dst, dstoff, alignof (ddsi_guid_t)); if (dd->bufsz - *srcoff < sizeof (*x)) @@ -510,61 +547,139 @@ static dds_return_t deser_generic (void * __restrict dst, size_t * __restrict ds *dstoff += sizeof (*x); break; } + case XQ: { /* non-nested but otherwise arbitrary sequence, so no nested mallocs */ + ddsi_octetseq_t * const x = deser_generic_dst (dst, dstoff, alignof (ddsi_octetseq_t)); + if (deser_uint32 (&x->length, dd, srcoff) < 0 || x->length > dd->bufsz - *srcoff) + goto fail; + const size_t elem_size = ser_generic_srcsize (desc + 1); + x->value = x->length ? ddsrt_malloc (x->length * elem_size) : NULL; + for (uint32_t i = 0; i < x->length; i++) + { + 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) { } + break; + } + case Xopt: { /* remainder is optional; alignment is very nearly always 4 */ + bool end_of_input; + if (pserop_seralign_is_1 (desc[1])) + end_of_input = (*srcoff + 1 > dd->bufsz); + else + { + *srcoff = (*srcoff + 3) & ~(size_t)3; + end_of_input = (*srcoff + 4 > dd->bufsz); + } + if (end_of_input) + { + void * const x = deser_generic_dst (dst, dstoff, pserop_memalign (desc[1])); + size_t rem_size = ser_generic_srcsize (desc + 1); + memset (x, 0, rem_size); + goto success; + } + } } desc++; } +success: + *flagset->present |= flag; + return 0; fail: - fini_generic_partial (dst, &dstoff_in, desc_in, desc, *flagset->aliased & flag); + fini_generic_embeddable (dst, &dstoff_in, desc_in, desc, *flagset->aliased & flag); *flagset->present &= ~flag; *flagset->aliased &= ~flag; return DDS_RETCODE_BAD_PARAMETER; } -static size_t ser_generic_size (const void *src, size_t srcoff, const enum pserop * __restrict desc) +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) { - size_t dstoff = 0; #define COMPLEX(basecase_, type_, dstoff_update_) do { \ type_ const *x = deser_generic_src (src, &srcoff, alignof (type_)); \ const uint32_t cnt = 1 + (uint32_t) (*desc - (basecase_)); \ for (uint32_t xi = 0; xi < cnt; xi++, x++) { dstoff_update_; } \ srcoff += cnt * sizeof (*x); \ } while (0) -#define SIMPLE1(basecase_, type_) COMPLEX (basecase_, type_, dstoff = dstoff + sizeof (*x)) -#define SIMPLE4(basecase_, type_) COMPLEX (basecase_, type_, dstoff = align4size (dstoff) + sizeof (*x)) +#define SIMPLE1(basecase_, type_) COMPLEX (basecase_, type_, *dstoff = *dstoff + sizeof (*x)) +#define SIMPLE4(basecase_, type_) COMPLEX (basecase_, type_, *dstoff = align4size (*dstoff) + sizeof (*x)) while (true) { switch (*desc) { - case XSTOP: return dstoff; - case XO: COMPLEX (XO, ddsi_octetseq_t, dstoff = align4size (dstoff) + 4 + x->length); break; - case XS: COMPLEX (XS, const char *, dstoff = align4size (dstoff) + 4 + strlen (*x) + 1); break; - case XZ: COMPLEX (XZ, ddsi_stringseq_t, { - dstoff = align4size (dstoff) + 4; - for (uint32_t i = 0; i < x->n; i++) - dstoff = align4size (dstoff) + 4 + strlen (x->strs[i]) + 1; - }); break; - case XE1: case XE2: case XE3: COMPLEX (*desc, unsigned, dstoff = align4size (dstoff) + 4); break; + case XSTOP: return; + case XO: COMPLEX (XO, ddsi_octetseq_t, *dstoff = align4size (*dstoff) + 4 + x->length); break; + case XS: COMPLEX (XS, const char *, *dstoff = align4size (*dstoff) + 4 + strlen (*x) + 1); break; + case XE1: case XE2: case XE3: COMPLEX (*desc, unsigned, *dstoff = align4size (*dstoff) + 4); break; case Xi: case Xix2: case Xix3: case Xix4: SIMPLE4 (Xi, int32_t); break; case Xu: case Xux2: case Xux3: case Xux4: case Xux5: SIMPLE4 (Xu, uint32_t); break; - case Xl: SIMPLE4 (Xl, int32_t); break; case XD: case XDx2: SIMPLE4 (XD, dds_duration_t); break; case Xo: case Xox2: SIMPLE1 (Xo, unsigned char); break; case Xb: case Xbx2: SIMPLE1 (Xb, unsigned char); break; case XbCOND: SIMPLE1 (XbCOND, unsigned char); break; case XG: SIMPLE1 (XG, ddsi_guid_t); break; case XK: SIMPLE1 (XK, nn_keyhash_t); break; + case XbPROP: /* "propagate" boolean: when 'false'; no serialisation; no size; force early out */ + COMPLEX (XbPROP, unsigned char, if (! *x) return); break; + case XQ: COMPLEX (XQ, ddsi_octetseq_t, { + const size_t elem_size = ser_generic_srcsize (desc + 1); + *dstoff = align4size (*dstoff) + 4; + for (uint32_t i = 0; i < x->length; i++) + ser_generic_size_embeddable (dstoff, x->value, i * elem_size, desc + 1); + }); while (*++desc != XSTOP) { } break; + case Xopt: break; } desc++; } -#undef SIMPLE +#undef SIMPLE4 +#undef SIMPLE1 #undef COMPLEX } -static dds_return_t ser_generic (struct nn_xmsg *xmsg, nn_parameterid_t pid, const void *src, size_t srcoff, const enum pserop * __restrict desc) +static size_t ser_generic_size (const void *src, size_t srcoff, const enum pserop * __restrict desc) { - char * const data = nn_xmsg_addpar (xmsg, pid, ser_generic_size (src, srcoff, desc)); size_t dstoff = 0; + ser_generic_size_embeddable (&dstoff, src, srcoff, desc); + return dstoff; +} + +static uint32_t ser_generic_count (const ddsi_octetseq_t *src, size_t elem_size, const enum pserop * __restrict desc) +{ + /* This whole thing exists solely for dealing with the vile "propagate" boolean, which must come first in an + element, or one can't deserialize it at all. Therefore, if desc doesn't start with XbPROP, all "length" + elements are included in the output */ + if (*desc != XbPROP) + return src->length; + /* and if it does start with XbPROP, only those for which it is true are included */ + uint32_t count = 0; + for (uint32_t i = 0; i < src->length; i++) + if (src->value[i * elem_size]) + count++; + return count; +} + +static dds_return_t ser_generic_embeddable (char * const data, size_t *dstoff, const void *src, size_t srcoff, const enum pserop * __restrict desc) +{ while (true) { switch (*desc) @@ -573,134 +688,151 @@ static dds_return_t ser_generic (struct nn_xmsg *xmsg, nn_parameterid_t pid, con return 0; case XO: { /* octet sequence */ ddsi_octetseq_t const * const x = deser_generic_src (src, &srcoff, alignof (ddsi_octetseq_t)); - char * const p = ser_generic_align4 (data, &dstoff); + char * const p = ser_generic_align4 (data, dstoff); *((uint32_t *) p) = x->length; if (x->length) memcpy (p + 4, x->value, x->length); - dstoff += 4 + x->length; + *dstoff += 4 + x->length; srcoff += sizeof (*x); break; } case XS: { /* string */ char const * const * const x = deser_generic_src (src, &srcoff, alignof (char *)); const uint32_t size = (uint32_t) (strlen (*x) + 1); - char * const p = ser_generic_align4 (data, &dstoff); + char * const p = ser_generic_align4 (data, dstoff); *((uint32_t *) p) = size; memcpy (p + 4, *x, size); - dstoff += 4 + size; - srcoff += sizeof (*x); - break; - } - case XZ: { /* string sequence */ - ddsi_stringseq_t const * const x = deser_generic_src (src, &srcoff, alignof (ddsi_stringseq_t)); - char * const p = ser_generic_align4 (data, &dstoff); - *((uint32_t *) p) = x->n; - dstoff += 4; - for (uint32_t i = 0; i < x->n; i++) - { - char * const q = ser_generic_align4 (data, &dstoff); - const uint32_t size = (uint32_t) (strlen (x->strs[i]) + 1); - *((uint32_t *) q) = size; - memcpy (q + 4, x->strs[i], size); - dstoff += 4 + size; - } + *dstoff += 4 + size; srcoff += sizeof (*x); break; } case XE1: case XE2: case XE3: { /* enum */ unsigned const * const x = deser_generic_src (src, &srcoff, alignof (unsigned)); - uint32_t * const p = ser_generic_align4 (data, &dstoff); + uint32_t * const p = ser_generic_align4 (data, dstoff); *p = (uint32_t) *x; - dstoff += 4; + *dstoff += 4; srcoff += sizeof (*x); break; } case Xi: case Xix2: case Xix3: case Xix4: { /* int32_t(s) */ int32_t const * const x = deser_generic_src (src, &srcoff, alignof (int32_t)); const uint32_t cnt = 1 + (uint32_t) (*desc - Xi); - int32_t * const p = ser_generic_align4 (data, &dstoff); + int32_t * const p = ser_generic_align4 (data, dstoff); for (uint32_t i = 0; i < cnt; i++) p[i] = x[i]; - dstoff += cnt * sizeof (*x); + *dstoff += cnt * sizeof (*x); srcoff += cnt * sizeof (*x); break; } - case Xu: case Xux2: case Xux3: case Xux4: case Xux5: { /* uint32_t(s) */ uint32_t const * const x = deser_generic_src (src, &srcoff, alignof (uint32_t)); const uint32_t cnt = 1 + (uint32_t) (*desc - Xu); - uint32_t * const p = ser_generic_align4 (data, &dstoff); + uint32_t * const p = ser_generic_align4 (data, dstoff); for (uint32_t i = 0; i < cnt; i++) p[i] = x[i]; - dstoff += cnt * sizeof (*x); - srcoff += cnt * sizeof (*x); - break; - } - - case Xl: { /* int32_t(s) */ - int32_t const * const x = deser_generic_src (src, &srcoff, alignof (uint32_t)); - const uint32_t cnt = 1 + (uint32_t) (*desc - Xu); - int32_t * const p = ser_generic_align4 (data, &dstoff); - for (uint32_t i = 0; i < cnt; i++) - p[i] = x[i]; - dstoff += cnt * sizeof (*x); + *dstoff += cnt * sizeof (*x); srcoff += cnt * sizeof (*x); break; } case XD: case XDx2: { /* duration(s): int64_t <=> int32_t.uint32_t (seconds.fraction) */ dds_duration_t const * const x = deser_generic_src (src, &srcoff, alignof (dds_duration_t)); const uint32_t cnt = 1 + (uint32_t) (*desc - XD); - uint32_t * const p = ser_generic_align4 (data, &dstoff); + uint32_t * const p = ser_generic_align4 (data, dstoff); for (uint32_t i = 0; i < cnt; i++) { ddsi_duration_t tmp = nn_to_ddsi_duration (x[i]); p[2 * i + 0] = (uint32_t) tmp.seconds; p[2 * i + 1] = tmp.fraction; } - dstoff += 2 * cnt * sizeof (uint32_t); + *dstoff += 2 * cnt * sizeof (uint32_t); srcoff += cnt * sizeof (*x); break; } case Xo: case Xox2: { /* octet(s) */ unsigned char const * const x = deser_generic_src (src, &srcoff, alignof (unsigned char)); const uint32_t cnt = 1 + (uint32_t) (*desc - Xo); - char * const p = data + dstoff; + char * const p = data + *dstoff; memcpy (p, x, cnt); - dstoff += cnt; + *dstoff += cnt; srcoff += cnt * sizeof (*x); break; } case Xb: case Xbx2: case XbCOND: { /* boolean(s) */ unsigned char const * const x = deser_generic_src (src, &srcoff, alignof (unsigned char)); const uint32_t cnt = (*desc == Xbx2) ? 2 : 1; /* <<<< beware! */ - char * const p = data + dstoff; + char * const p = data + *dstoff; memcpy (p, x, cnt); - dstoff += cnt; + *dstoff += cnt; srcoff += cnt * sizeof (*x); break; } + case XbPROP: { /* "propagate" boolean: don't serialize, skip it and everything that follows if false */ + unsigned char const * const x = deser_generic_src (src, &srcoff, alignof (unsigned char)); + if (! *x) return 0; + srcoff++; + break; + } case XG: { /* GUID */ ddsi_guid_t const * const x = deser_generic_src (src, &srcoff, alignof (ddsi_guid_t)); const ddsi_guid_t xn = nn_hton_guid (*x); - char * const p = data + dstoff; + char * const p = data + *dstoff; memcpy (p, &xn, sizeof (xn)); - dstoff += sizeof (xn); + *dstoff += sizeof (xn); srcoff += sizeof (*x); break; } case XK: { /* keyhash */ nn_keyhash_t const * const x = deser_generic_src (src, &srcoff, alignof (nn_keyhash_t)); - char * const p = data + dstoff; + char * const p = data + *dstoff; memcpy (p, x, sizeof (*x)); - dstoff += sizeof (*x); + *dstoff += sizeof (*x); srcoff += sizeof (*x); break; } + case XQ: { + ddsi_octetseq_t const * const x = deser_generic_src (src, &srcoff, alignof (ddsi_octetseq_t)); + char * const p = ser_generic_align4 (data, dstoff); + *dstoff += 4; + if (x->length == 0) + *((uint32_t *) p) = 0; + else + { + const size_t elem_size = ser_generic_srcsize (desc + 1); + *((uint32_t *) p) = ser_generic_count (x, elem_size, desc + 1); + for (uint32_t i = 0; i < x->length; i++) + ser_generic_embeddable (data, dstoff, x->value, i * elem_size, desc + 1); + } + srcoff += sizeof (*x); + while (*++desc != XSTOP) { } + break; + } + case Xopt: + break; } desc++; } } -static dds_return_t unalias_generic (void * __restrict dst, size_t * __restrict dstoff, const enum pserop * __restrict desc) +static dds_return_t ser_generic (struct nn_xmsg *xmsg, nn_parameterid_t pid, const void *src, size_t srcoff, const enum pserop * __restrict desc) +{ + char * const data = nn_xmsg_addpar (xmsg, pid, ser_generic_size (src, srcoff, desc)); + size_t dstoff = 0; + return ser_generic_embeddable (data, &dstoff, src, srcoff, 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_)); \ @@ -717,21 +849,36 @@ static dds_return_t unalias_generic (void * __restrict dst, size_t * __restrict return 0; case XO: COMPLEX (XO, ddsi_octetseq_t, if (x->value) { x->value = ddsrt_memdup (x->value, x->length); }); break; case XS: COMPLEX (XS, char *, if (*x) { *x = ddsrt_strdup (*x); }); break; - case XZ: COMPLEX (XZ, ddsi_stringseq_t, if (x->n) { - x->strs = ddsrt_memdup (x->strs, x->n * sizeof (*x->strs)); - for (uint32_t i = 0; i < x->n; i++) - x->strs[i] = ddsrt_strdup (x->strs[i]); - }); break; case XE1: case XE2: case XE3: COMPLEX (*desc, unsigned, (void) 0); break; case Xi: case Xix2: case Xix3: case Xix4: SIMPLE (Xi, int32_t); break; case Xu: case Xux2: case Xux3: case Xux4: case Xux5: SIMPLE (Xu, uint32_t); break; - case Xl: SIMPLE (Xl, int32_t); break; case XD: case XDx2: SIMPLE (XD, dds_duration_t); break; case Xo: case Xox2: SIMPLE (Xo, unsigned char); break; case Xb: case Xbx2: SIMPLE (Xb, unsigned char); break; case XbCOND: SIMPLE (XbCOND, unsigned char); break; + case XbPROP: SIMPLE (XbPROP, unsigned char); break; case XG: SIMPLE (XG, ddsi_guid_t); break; 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); + 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, gen_seq_aliased, desc + 1); + } + }); while (*++desc != XSTOP) { } break; + case Xopt: break; } desc++; } @@ -739,13 +886,19 @@ 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) { switch (*desc++) { - case XO: case XS: case XZ: + case XO: case XS: case XQ: return true; default: break; @@ -762,10 +915,16 @@ static bool fini_generic_required (const enum pserop * __restrict desc) static dds_return_t fini_generic (void * __restrict dst, size_t * __restrict dstoff, struct flagset *flagset, uint64_t flag, const enum pserop * __restrict desc) { - fini_generic_partial (dst, dstoff, desc, NULL, *flagset->aliased & flag); + fini_generic_embeddable (dst, dstoff, desc, NULL, *flagset->aliased & flag); 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 { \ @@ -783,23 +942,29 @@ static dds_return_t valid_generic (const void *src, size_t srcoff, const enum ps case XSTOP: return 0; case XO: SIMPLE (XO, ddsi_octetseq_t, (x->length == 0) == (x->value == NULL)); break; case XS: SIMPLE (XS, const char *, *x != NULL); break; - case XZ: COMPLEX (XZ, ddsi_stringseq_t, { - if ((x->n == 0) != (x->strs == NULL)) - return DDS_RETCODE_BAD_PARAMETER; - for (uint32_t i = 0; i < x->n; i++) - if (x->strs[i] == NULL) - return DDS_RETCODE_BAD_PARAMETER; - }); break; case XE1: case XE2: case XE3: SIMPLE (*desc, unsigned, *x <= 1 + (unsigned) *desc - XE1); break; case Xi: case Xix2: case Xix3: case Xix4: TRIVIAL (Xi, int32_t); break; case Xu: case Xux2: case Xux3: case Xux4: case Xux5: TRIVIAL (Xu, uint32_t); break; - case Xl: SIMPLE (Xl, int32_t, *x == DDS_LENGTH_UNLIMITED || *x > 1); break; case XD: case XDx2: SIMPLE (XD, dds_duration_t, *x >= 0); break; case Xo: case Xox2: TRIVIAL (Xo, unsigned char); break; case Xb: case Xbx2: SIMPLE (Xb, unsigned char, *x == 0 || *x == 1); break; case XbCOND: SIMPLE (XbCOND, unsigned char, *x == 0 || *x == 1); break; + case XbPROP: SIMPLE (XbPROP, unsigned char, *x == 0 || *x == 1); break; case XG: TRIVIAL (XG, ddsi_guid_t); break; case XK: TRIVIAL (XK, nn_keyhash_t); break; + case XQ: COMPLEX (XQ, ddsi_octetseq_t, { + if ((x->length == 0) != (x->value == NULL)) + return DDS_RETCODE_BAD_PARAMETER; + if (x->length) { + const size_t elem_size = ser_generic_srcsize (desc + 1); + dds_return_t ret; + for (uint32_t i = 0; i < x->length; i++) { + if ((ret = valid_generic (x->value, i * elem_size, desc + 1)) != 0) + return ret; + } + } + }); while (*++desc != XSTOP) { } break; + case Xopt: break; } desc++; } @@ -833,19 +998,9 @@ static bool equal_generic (const void *srcx, const void *srcy, size_t srcoff, co case XS: SIMPLE (XS, const char *, strcmp (*x, *y) == 0); break; - case XZ: - COMPLEX (XZ, ddsi_stringseq_t, { - if (x->n != y->n) - return false; - for (uint32_t i = 0; i < x->n; i++) - if (strcmp (x->strs[i], y->strs[i]) != 0) - return false; - }); - break; case XE1: case XE2: case XE3: TRIVIAL (*desc, unsigned); break; case Xi: case Xix2: case Xix3: case Xix4: TRIVIAL (Xi, int32_t); break; case Xu: case Xux2: case Xux3: case Xux4: case Xux5: TRIVIAL (Xu, uint32_t); break; - case Xl: TRIVIAL (Xl, int32_t); break; case XD: case XDx2: TRIVIAL (XD, dds_duration_t); break; case Xo: case Xox2: TRIVIAL (Xo, unsigned char); break; case Xb: case Xbx2: TRIVIAL (Xb, unsigned char); break; @@ -857,8 +1012,21 @@ static bool equal_generic (const void *srcx, const void *srcy, size_t srcoff, co return true; }); 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; + if (x->length) { + const size_t elem_size = ser_generic_srcsize (desc + 1); + for (uint32_t i = 0; i < x->length; i++) { + if (!equal_generic (x->value, y->value, i * elem_size, desc + 1)) + return false; + } + } + }); while (*++desc != XSTOP) { } break; + case Xopt: break; } desc++; } @@ -867,6 +1035,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_), \ @@ -951,6 +1124,7 @@ static dds_return_t dvx_reader_favours_ssm (void * __restrict dst, const struct } #endif + /* Standardized parameters -- QoS _MUST_ come first (nn_plist_init_tables verifies this) because it allows early-out when processing a dds_qos_t instead of an nn_plist_t */ static const struct piddesc piddesc_omg[] = { @@ -966,6 +1140,9 @@ static const struct piddesc piddesc_omg[] = { QP (DEADLINE, deadline, XD), QP (LATENCY_BUDGET, latency_budget, XD), QP (LIVELINESS, liveliness, XE2, XD), + /* Property list used to be of type [(String,String]), security changed into ([String,String],Maybe [(String,[Word8])]), + the "Xopt" here is to allow both forms on input, with an assumed empty second sequence if the old form was received */ + QP (PROPERTY_LIST, property, XQ, XbPROP, XS, XS, XSTOP, Xopt, XQ, XbPROP, XS, XO, XSTOP), /* Reliability encoding does not follow the rules (best-effort/reliable map to 1/2 instead of 0/1 */ { PID_RELIABILITY, PDF_QOS | PDF_FUNCTION, QP_RELIABILITY, "RELIABILITY", offsetof (struct nn_plist, qos.reliability), membersize (struct nn_plist, qos.reliability), @@ -978,7 +1155,7 @@ static const struct piddesc piddesc_omg[] = { QP (OWNERSHIP, ownership, XE1), QP (OWNERSHIP_STRENGTH, ownership_strength, Xi), QP (PRESENTATION, presentation, XE2, Xbx2), - QP (PARTITION, partition, XZ), + QP (PARTITION, partition, XQ, XS, XSTOP), QP (TIME_BASED_FILTER, time_based_filter, XD), QP (TRANSPORT_PRIORITY, transport_priority, Xi), PP (PROTOCOL_VERSION, protocol_version, Xox2), @@ -1038,7 +1215,7 @@ static const struct piddesc piddesc_eclipse[] = { QP (PRISMTECH_READER_LIFESPAN, reader_lifespan, Xb, XD), QP (PRISMTECH_WRITER_DATA_LIFECYCLE, writer_data_lifecycle, Xb), QP (PRISMTECH_READER_DATA_LIFECYCLE, reader_data_lifecycle, XDx2), - QP (PRISMTECH_SUBSCRIPTION_KEYS, subscription_keys, XbCOND, XZ), + QP (PRISMTECH_SUBSCRIPTION_KEYS, subscription_keys, XbCOND, XQ, XS, XSTOP), { PID_PAD, PDF_QOS, QP_CYCLONE_IGNORELOCAL, "CYCLONE_IGNORELOCAL", offsetof (struct nn_plist, qos.ignorelocal), membersize (struct nn_plist, qos.ignorelocal), { .desc = { XE2, XSTOP } }, 0 }, @@ -1057,7 +1234,7 @@ static const struct piddesc piddesc_prismtech[] = { QP (PRISMTECH_READER_LIFESPAN, reader_lifespan, Xb, XD), QP (PRISMTECH_WRITER_DATA_LIFECYCLE, writer_data_lifecycle, Xb), QP (PRISMTECH_READER_DATA_LIFECYCLE, reader_data_lifecycle, XDx2), - QP (PRISMTECH_SUBSCRIPTION_KEYS, subscription_keys, XbCOND, XZ), + QP (PRISMTECH_SUBSCRIPTION_KEYS, subscription_keys, XbCOND, XQ, XS, XSTOP), PP (PRISMTECH_BUILTIN_ENDPOINT_SET, prismtech_builtin_endpoint_set, Xu), PP (PRISMTECH_PARTICIPANT_VERSION_INFO, prismtech_participant_version_info, Xux5, XS), PP (PRISMTECH_EXEC_NAME, exec_name, XS), @@ -1141,8 +1318,8 @@ static const struct piddesc_index piddesc_vendor_index[] = { /* List of entries that require unalias, fini processing; initialized by nn_plist_init_tables; will assert when table too small or too large */ -static const struct piddesc *piddesc_unalias[18]; -static const struct piddesc *piddesc_fini[18]; +static const struct piddesc *piddesc_unalias[19]; +static const struct piddesc *piddesc_fini[19]; static ddsrt_once_t table_init_control = DDSRT_ONCE_INIT; static nn_parameterid_t pid_without_flags (nn_parameterid_t pid) @@ -1331,7 +1508,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; @@ -1389,15 +1566,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); } @@ -2228,7 +2405,7 @@ const unsigned char *nn_plist_findparam_native_unchecked (const void *src, nn_pa const nn_parameter_t *par = src; while (par->parameterid != pid) { - if (pid == PID_SENTINEL) + if (par->parameterid == PID_SENTINEL) return NULL; par = (const nn_parameter_t *) ((const char *) (par + 1) + par->length); } @@ -2763,6 +2940,29 @@ void nn_log_xqos (uint32_t cat, const struct ddsrt_log_cfg *logcfg, const dds_qo }); DO (PRISMTECH_ENTITY_FACTORY, { LOGB1 ("entity_factory=%u", xqos->entity_factory.autoenable_created_entities); }); DO (CYCLONE_IGNORELOCAL, { LOGB1 ("ignorelocal=%u", xqos->ignorelocal.value); }); + DO (PROPERTY_LIST, { + LOGB0 ("property_list={"); + DDS_CLOG (cat, logcfg, "value={"); + for (uint32_t i = 0; i < xqos->property.value.n; i++) { + DDS_CLOG (cat, logcfg, "%s{%s,%s,%u}", + (i == 0) ? "" : ",", + xqos->property.value.props[i].name, + xqos->property.value.props[i].value, + xqos->property.value.props[i].propagate); + } + DDS_CLOG (cat, logcfg, "}"); + DDS_CLOG (cat, logcfg, "binary_value={"); + for (uint32_t i = 0; i < xqos->property.binary_value.n; i++) { + DDS_CLOG (cat, logcfg, "%s{%s,(%u,%p),%u}", + (i == 0) ? "" : ",", + xqos->property.binary_value.props[i].name, + xqos->property.binary_value.props[i].value.length, + xqos->property.binary_value.props[i].value.value, + xqos->property.binary_value.props[i].propagate); + } + DDS_CLOG (cat, logcfg, "}"); + DDS_CLOG (cat, logcfg, "}"); + }); #undef PRINTARG_DUR #undef FMT_DUR 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..04c91d1 --- /dev/null +++ b/src/core/ddsi/tests/plist_generic.c @@ -0,0 +1,314 @@ +/* + * 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[20]; + 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}}}}, + }, + { {Xb,XQ,XbPROP,XS,Xo,XSTOP, Xopt,XQ,XbPROP,XS,Xo,XSTOP, XSTOP}, + &(struct{char b; oseq seq, seq2;}){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}}}, + {2, (unsigned char *)(struct{char b;char *s;uint8_t o;}[]){ + {1,"oak",8}, {0,"beech",9}}} + }, + 57, (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, + 0, + SER32(1), + SER32(4), 'o','a','k',0, 8 + }, + &(struct{char b; oseq seq, seq2;}){1, + {3, (unsigned char *)(struct{char b;char *s;uint8_t o;}[]){ + {1,"orange",2}, {1,"fig",4}, {1,"prune",5}}}, + {1, (unsigned char *)(struct{char b;char *s;uint8_t o;}[]){ + {1,"oak",8}}} + } + } +}; + +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); + } +} + +CU_Test (ddsi_plist_generic, optional) +{ + union { + uint64_t u; + void *p; + char buf[256]; + } mem; + + enum pserop ser_desc[] = {Xb,XQ,XbPROP,XS,Xo,XSTOP,XSTOP}; + enum pserop deser_desc[] = {Xb,XQ,XbPROP,XS,Xo,XSTOP, Xopt,XQ,XbPROP,XS,Xo,XSTOP, XSTOP}; + const void *data = &(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}}}}; + size_t exp_sersize = 43; + const unsigned char *exp_ser = (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 + }; + const void *exp_data = &(struct{char b; oseq seq; oseq seq2;}){ + 1, {3, (unsigned char *)(struct{char b;char *s;uint8_t o;}[]){ + {1,"orange",2}, {1,"fig",4}, {1,"prune",5}}}, + {0, NULL}}; + + size_t memsize; + void *ser; + size_t sersize; + dds_return_t ret; + ret = plist_ser_generic (&ser, &sersize, data, ser_desc); + CU_ASSERT_FATAL (ret == DDS_RETCODE_OK); + CU_ASSERT (sersize == exp_sersize); + /* if sizes don't match, still check prefix */ + size_t cmpsize = (sersize < exp_sersize) ? sersize : exp_sersize; + if (memcmp (ser, exp_ser, cmpsize) != 0) + { + printf ("ddsi_plist_generic_optional: memcmp\n"); + for (size_t k = 0; k < cmpsize; k++) + printf (" %3zu %02x %02x\n", k, ((unsigned char *)ser)[k], exp_ser[k]); + CU_ASSERT (!(bool)"memcmp"); + } + /* check */ + memsize = plist_memsize_generic (deser_desc); + CU_ASSERT_FATAL (memsize <= sizeof (mem)); + memset (&mem, 0xee, sizeof (mem)); + ret = plist_deser_generic (&mem, ser, sersize, false, deser_desc); + CU_ASSERT_FATAL (ret == DDS_RETCODE_OK); + /* the compare function should be happy with it */ + CU_ASSERT (plist_equal_generic (exp_data, &mem, deser_desc)); + plist_fini_generic (&mem, deser_desc, true); + ddsrt_free (ser); +}