Fixes for table-driven plist ser/deser

* GUID, keyhash compare (currently no reliance on this comparison, so
  not causing trouble in Cyclone for applications)

* comparing "propagate" boolean in plist (newly added for security,
  not yet used)

* fix memory leak in plist_unalias (currently only used in duplicating
  them, in which case the memory leak doesn't occur)

* add unit tests for plist handling

Signed-off-by: Erik Boasson <eb@ilities.com>
This commit is contained in:
Erik Boasson 2019-09-25 20:14:36 +02:00
parent 8f46889f74
commit bf8bc87a87
6 changed files with 571 additions and 31 deletions

View file

@ -70,6 +70,7 @@ PREPEND(hdrs_private_ddsi "${CMAKE_CURRENT_LIST_DIR}/include/dds/ddsi"
ddsi_raweth.h ddsi_raweth.h
ddsi_ipaddr.h ddsi_ipaddr.h
ddsi_mcgroup.h ddsi_mcgroup.h
ddsi_plist_generic.h
ddsi_serdata.h ddsi_serdata.h
ddsi_sertopic.h ddsi_sertopic.h
ddsi_serdata_default.h ddsi_serdata_default.h
@ -129,3 +130,8 @@ install(
DIRECTORY "${CMAKE_CURRENT_LIST_DIR}/include/dds" DIRECTORY "${CMAKE_CURRENT_LIST_DIR}/include/dds"
DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}" DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}"
COMPONENT dev) 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()

View file

@ -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 <stddef.h>
#include <stdbool.h>
#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

View file

@ -38,6 +38,8 @@
#include "dds/ddsrt/avl.h" #include "dds/ddsrt/avl.h"
#include "dds/ddsi/q_misc.h" /* for vendor_is_... */ #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 /* I am tempted to change LENGTH_UNLIMITED to 0 in the API (with -1
supported for backwards compatibility) ... on the wire however supported for backwards compatibility) ... on the wire however
it must be -1 */ it must be -1 */
@ -88,25 +90,6 @@ struct flagset {
uint64_t wanted; 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 { struct piddesc {
nn_parameterid_t pid; /* parameter id or PID_PAD if strictly local */ nn_parameterid_t pid; /* parameter id or PID_PAD if strictly local */
uint16_t flags; /* see PDF_xxx flags */ uint16_t flags; /* see PDF_xxx flags */
@ -359,6 +342,11 @@ static size_t ser_generic_srcsize (const enum pserop * __restrict desc)
#undef SIMPLE #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) 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 { \ #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; size_t elem_off = i * elem_size;
if (deser_generic (x->value, &elem_off, flagset, flag, dd, srcoff, desc + 1) < 0) if (deser_generic (x->value, &elem_off, flagset, flag, dd, srcoff, desc + 1) < 0)
{
ddsrt_free (x->value);
goto fail; goto fail;
}
} }
*dstoff += sizeof (*x); *dstoff += sizeof (*x);
while (*++desc != XSTOP) { } while (*++desc != XSTOP) { }
@ -563,6 +554,22 @@ fail:
return DDS_RETCODE_BAD_PARAMETER; 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) 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 { \ #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); 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 { \ #define COMPLEX(basecase_, type_, ...) do { \
type_ *x = deser_generic_dst (dst, dstoff, alignof (type_)); \ 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 XK: SIMPLE (XK, nn_keyhash_t); break;
case XQ: COMPLEX (XQ, ddsi_octetseq_t, if (x->length) { case XQ: COMPLEX (XQ, ddsi_octetseq_t, if (x->length) {
const size_t elem_size = ser_generic_srcsize (desc + 1); 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++) { for (uint32_t i = 0; i < x->length; i++) {
size_t elem_off = i * elem_size; 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; }); while (*++desc != XSTOP) { } break;
} }
@ -808,6 +839,12 @@ static dds_return_t unalias_generic (void * __restrict dst, size_t * __restrict
#undef COMPLEX #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) static bool unalias_generic_required (const enum pserop * __restrict desc)
{ {
while (*desc != XSTOP) while (*desc != XSTOP)
@ -835,6 +872,12 @@ static dds_return_t fini_generic (void * __restrict dst, size_t * __restrict dst
return 0; 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) static dds_return_t valid_generic (const void *src, size_t srcoff, const enum pserop * __restrict desc)
{ {
#define COMPLEX(basecase_, type_, cond_stmts_) do { \ #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; return true;
}); });
break; break;
case XbPROP: TRIVIAL (Xb, unsigned char); break; case XbPROP: TRIVIAL (XbPROP, unsigned char); break;
case XG: SIMPLE (XG, ddsi_guid_t, memcmp (x, y, sizeof (*x))); 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))); break; case XK: SIMPLE (XK, nn_keyhash_t, memcmp (x, y, sizeof (*x)) == 0); break;
case XQ: COMPLEX (XQ, ddsi_octetseq_t, { case XQ: COMPLEX (XQ, ddsi_octetseq_t, {
if (x->length != y->length) if (x->length != y->length)
return false; return false;
@ -943,6 +986,11 @@ static bool equal_generic (const void *srcx, const void *srcy, size_t srcoff, co
#undef COMPLEX #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 membersize(type, member) sizeof (((type *) 0)->member)
#define ENTRY(PFX_, NAME_, member_, flag_, validate_, ...) \ #define ENTRY(PFX_, NAME_, member_, flag_, validate_, ...) \
{ PID_##NAME_, flag_, PFX_##_##NAME_, #NAME_, offsetof (struct nn_plist, member_), \ { 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 ((*fs->present & entry->present_flag) && (*fs->aliased & entry->present_flag))
{ {
if (!(entry->flags & PDF_FUNCTION)) 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) else if (entry->op.f.unalias)
entry->op.f.unalias (dst, &dstoff); entry->op.f.unalias (dst, &dstoff);
*fs->aliased &= ~entry->present_flag; *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)) 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 /* 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 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. 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: dst & src have the same type, so offset in src is the same;
Note: unalias may have to look at */ Note: unalias may have to look at */
memcpy ((char *) dst + dstoff, (const char *) src + dstoff, entry->size); memcpy ((char *) dst + dstoff, (const char *) src + dstoff, entry->size);
*fs_dst->present |= entry->present_flag; *fs_dst->present |= entry->present_flag;
if (!(entry->flags & PDF_FUNCTION)) 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) else if (entry->op.f.unalias)
entry->op.f.unalias (dst, &dstoff); entry->op.f.unalias (dst, &dstoff);
} }

View file

@ -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
"$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/../../ddsi/include/>")
target_link_libraries(cunit_ddsi PRIVATE ddsc)

170
src/core/ddsi/tests/plist.c Normal file
View file

@ -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);
}

View file

@ -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);
}
}