/* * Copyright(c) 2020 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 #include #include #include #include #include "dds/dds.h" #include "dds/ddsrt/misc.h" #include "dds/ddsrt/sync.h" #include "dds/ddsrt/heap.h" #include "dds/ddsrt/strtod.h" #include "dds/ddsrt/string.h" #include "dds/ddsrt/environ.h" #include "dds__types.h" #include "dds__entity.h" #include "dds__writer.h" #include "dds/ddsi/q_bswap.h" #include "dds/ddsi/q_lease.h" #include "dds/ddsi/q_xevent.h" #include "dds/ddsi/ddsi_entity_index.h" #include "test_common.h" #include "test_oneliner.h" #define MAXDOMS (sizeof (((struct oneliner_ctx){.result=0}).doms) / sizeof (((struct oneliner_ctx){.result=0}).doms[0])) static const char knownentities[] = "PRWrstwxy"; typedef struct { char n[MAXDOMS + 1]; } entname_t; #define DEFINE_STATUS_CALLBACK(name, NAME, kind) \ static void name##_cb (dds_entity_t kind, const dds_##name##_status_t status, void *arg) \ { \ struct oneliner_cb *cb = arg; \ ddsrt_mutex_lock (&cb->ctx->g_mutex); \ cb->cb_##kind = kind; \ cb->cb_##name##_status = status; \ cb->cb_called[DDS_##NAME##_STATUS_ID]++; \ ddsrt_cond_broadcast (&cb->ctx->g_cond); \ ddsrt_mutex_unlock (&cb->ctx->g_mutex); \ } DEFINE_STATUS_CALLBACK (inconsistent_topic, INCONSISTENT_TOPIC, topic) DEFINE_STATUS_CALLBACK (liveliness_changed, LIVELINESS_CHANGED, reader) DEFINE_STATUS_CALLBACK (liveliness_lost, LIVELINESS_LOST, writer) DEFINE_STATUS_CALLBACK (offered_deadline_missed, OFFERED_DEADLINE_MISSED, writer) DEFINE_STATUS_CALLBACK (offered_incompatible_qos, OFFERED_INCOMPATIBLE_QOS, writer) DEFINE_STATUS_CALLBACK (publication_matched, PUBLICATION_MATCHED, writer) DEFINE_STATUS_CALLBACK (requested_deadline_missed, REQUESTED_DEADLINE_MISSED, reader) DEFINE_STATUS_CALLBACK (requested_incompatible_qos, REQUESTED_INCOMPATIBLE_QOS, reader) DEFINE_STATUS_CALLBACK (sample_lost, SAMPLE_LOST, reader) DEFINE_STATUS_CALLBACK (sample_rejected, SAMPLE_REJECTED, reader) DEFINE_STATUS_CALLBACK (subscription_matched, SUBSCRIPTION_MATCHED, reader) static void data_on_readers_cb (dds_entity_t subscriber, void *arg) { struct oneliner_cb *cb = arg; ddsrt_mutex_lock (&cb->ctx->g_mutex); cb->cb_subscriber = subscriber; cb->cb_called[DDS_DATA_ON_READERS_STATUS_ID]++; ddsrt_cond_broadcast (&cb->ctx->g_cond); ddsrt_mutex_unlock (&cb->ctx->g_mutex); } static void data_available_cb (dds_entity_t reader, void *arg) { struct oneliner_cb *cb = arg; ddsrt_mutex_lock (&cb->ctx->g_mutex); cb->cb_reader = reader; cb->cb_called[DDS_DATA_AVAILABLE_STATUS_ID]++; ddsrt_cond_broadcast (&cb->ctx->g_cond); ddsrt_mutex_unlock (&cb->ctx->g_mutex); } static void dummy_data_on_readers_cb (dds_entity_t subscriber, void *arg) { (void)subscriber; (void)arg; } static void dummy_data_available_cb (dds_entity_t reader, void *arg) { (void)reader; (void)arg; } static void dummy_subscription_matched_cb (dds_entity_t reader, const dds_subscription_matched_status_t status, void *arg) { (void)reader; (void)status; (void)arg; } static void dummy_liveliness_changed_cb (dds_entity_t reader, const dds_liveliness_changed_status_t status, void *arg) { (void)reader; (void)status; (void)arg; } static void dummy_cb (void) { // Used as a listener function in checking merging of listeners, // and for that purpose, casting it to whatever function type is // required is ok. It is not supposed to ever be called. abort (); } #undef DEFINE_STATUS_CALLBACK // These had better match the corresponding type definitions! // n uint32_t ...count // c int32_t ...count_change // I instance handle of a data instance // P uint32_t QoS policy ID // E instance handle of an entity // R sample_rejected_status_kind static const struct { const char *name; size_t size; // size of status struct const char *desc; // description of status struct dds_status_id_t id; // status id, entry in "cb_called" size_t cb_entity_off; // which cb_... entity to look at size_t cb_status_off; // cb_..._status to look at } lldesc[] = { #define S0(abbrev, NAME, entity) \ { abbrev, 0, NULL, DDS_##NAME##_STATUS_ID, offsetof (struct oneliner_cb, cb_##entity), 0 } #define S(abbrev, name, NAME, desc, entity) \ { abbrev, sizeof (dds_##name##_status_t), desc, DDS_##NAME##_STATUS_ID, offsetof (struct oneliner_cb, cb_##entity), offsetof (struct oneliner_cb, cb_##name##_status) } S0 ("da", DATA_AVAILABLE, reader), S0 ("dor", DATA_ON_READERS, subscriber), S ("it", inconsistent_topic, INCONSISTENT_TOPIC, "nc", topic), S ("lc", liveliness_changed, LIVELINESS_CHANGED, "nnccE", reader), S ("ll", liveliness_lost, LIVELINESS_LOST, "nc", writer), S ("odm", offered_deadline_missed, OFFERED_DEADLINE_MISSED, "ncI", writer), S ("oiq", offered_incompatible_qos, OFFERED_INCOMPATIBLE_QOS, "ncP", writer), S ("pm", publication_matched, PUBLICATION_MATCHED, "ncncE", writer), S ("rdm", requested_deadline_missed, REQUESTED_DEADLINE_MISSED, "ncI", reader), S ("riq", requested_incompatible_qos, REQUESTED_INCOMPATIBLE_QOS, "ncP", reader), S ("sl", sample_lost, SAMPLE_LOST, "nc", reader), S ("sr", sample_rejected, SAMPLE_REJECTED, "ncRI", reader), S ("sm", subscription_matched, SUBSCRIPTION_MATCHED, "ncncE", reader) #undef S #undef S0 }; static const void *advance (const void *status, size_t *off, char code) { #define alignof(type_) offsetof (struct { char c; type_ d; }, d) size_t align = 1, size = 1; switch (code) { case 'n': case 'c': case 'P': align = alignof (uint32_t); size = sizeof (uint32_t); break; case 'E': case 'I': align = alignof (dds_instance_handle_t); size = sizeof (dds_instance_handle_t); break; case 'R': align = alignof (dds_sample_rejected_status_kind); size = sizeof (dds_sample_rejected_status_kind); break; default: abort (); } #undef alignof *off = (*off + align - 1) & ~(align - 1); const void *p = (const char *) status + *off; *off += size; return p; } static dds_return_t get_status (int ll, dds_entity_t ent, void *status) { dds_return_t ret; switch (ll) { case 2: ret = dds_get_inconsistent_topic_status (ent, status); break; case 3: ret = dds_get_liveliness_changed_status (ent, status); break; case 4: ret = dds_get_liveliness_lost_status (ent, status); break; case 5: ret = dds_get_offered_deadline_missed_status (ent, status); break; case 6: ret = dds_get_offered_incompatible_qos_status (ent, status); break; case 7: ret = dds_get_publication_matched_status (ent, status); break; case 8: ret = dds_get_requested_deadline_missed_status (ent, status); break; case 9: ret = dds_get_requested_incompatible_qos_status (ent, status); break; case 10: ret = dds_get_sample_lost_status (ent, status); break; case 11: ret = dds_get_sample_rejected_status (ent, status); break; case 12: ret = dds_get_subscription_matched_status (ent, status); break; default: return -1; } return (ret == 0); } static dds_return_t check_status_change_fields_are_0 (int ll, dds_entity_t ent) { if (lldesc[ll].desc) { const char *d = lldesc[ll].desc; void *status = malloc (lldesc[ll].size); dds_return_t ret; if ((ret = get_status (ll, ent, status)) <= 0) { free (status); return ret; } size_t off = 0; while (*d) { const uint32_t *p = advance (status, &off, *d); if (*d == 'c' && *p != 0) { free (status); return 0; } d++; } assert (off <= lldesc[ll].size); free (status); } return 1; } #define TOK_END -1 #define TOK_NAME -2 #define TOK_INT -3 #define TOK_DURATION -4 #define TOK_TIMESTAMP -5 #define TOK_ELLIPSIS -6 #define TOK_INVALID -7 static int setresult (struct oneliner_ctx *ctx, int result, const char *msg, ...) ddsrt_attribute_format((printf, 3, 4)); static void error (struct oneliner_ctx *ctx, const char *msg, ...) ddsrt_attribute_format((printf, 2, 3)); static void error_dds (struct oneliner_ctx *ctx, dds_return_t ret, const char *msg, ...) ddsrt_attribute_format((printf, 3, 4)); static void testfail (struct oneliner_ctx *ctx, const char *msg, ...) ddsrt_attribute_format((printf, 2, 3)); static void vsetresult (struct oneliner_ctx *ctx, int result, const char *msg, va_list ap) { assert (result <= 0); ctx->result = result; vsnprintf (ctx->msg, sizeof (ctx->msg), msg, ap); } static int setresult (struct oneliner_ctx *ctx, int result, const char *msg, ...) { va_list ap; va_start (ap, msg); vsetresult (ctx, result, msg, ap); va_end (ap); return result; } static void error (struct oneliner_ctx *ctx, const char *msg, ...) { va_list ap; va_start (ap, msg); vsetresult (ctx, -1, msg, ap); va_end (ap); longjmp (ctx->jb, 1); } static void error_dds (struct oneliner_ctx *ctx, dds_return_t ret, const char *msg, ...) { va_list ap; va_start (ap, msg); vsetresult (ctx, -1, msg, ap); va_end (ap); size_t n = strlen (ctx->msg); if (n < sizeof (ctx->msg)) snprintf (ctx->msg + n, sizeof (ctx->msg) - n, " (%s)", dds_strretcode (ret)); longjmp (ctx->jb, 1); } static void testfail (struct oneliner_ctx *ctx, const char *msg, ...) { va_list ap; va_start (ap, msg); vsetresult (ctx, 0, msg, ap); va_end (ap); longjmp (ctx->jb, 1); } static void advancetok (struct oneliner_lex *l) { while (isspace ((unsigned char) *l->inp)) l->inp++; } static int issymchar0 (char c) { return isalpha ((unsigned char) c) || c == '_'; } static int issymchar (char c) { return isalnum ((unsigned char) c) || c == '_' || c == '\''; } static bool lookingatnum (const struct oneliner_lex *l) { return (isdigit ((unsigned char) l->inp[(l->inp[0] == '-')])); } static int nexttok_dur (struct oneliner_lex *l, union oneliner_tokval *v, bool expecting_duration) { advancetok (l); if (l->inp[0] == 0) { l->tok = TOK_END; } else if (strncmp (l->inp, "...", 3) == 0) { l->inp += 3; l->tok = TOK_ELLIPSIS; } else if (!expecting_duration && lookingatnum (l)) { char *endp; // strtol: [0-9]+ ; endp = l->inp if no digits present l->v.i = (int) strtol (l->inp, &endp, 10); l->inp = endp; if (v) *v = l->v; l->tok = TOK_INT; } else if (l->inp[0] == '@' || (expecting_duration && lookingatnum (l))) { const int ists = (l->inp[0] == '@'); char *endp; if (!ists && strncmp (l->inp + ists, "inf", 3) == 0 && !issymchar (l->inp[ists + 3])) { l->inp += ists + 3; l->v.d = DDS_INFINITY; } else { double d; if (ddsrt_strtod (l->inp + ists, &endp, &d) != DDS_RETCODE_OK) return false; if (!ists && d < 0) return false; if (d >= (double) (INT64_MAX / DDS_NSECS_IN_SEC)) l->v.d = DDS_INFINITY; else if (d >= 0) l->v.d = (int64_t) (d * 1e9 + 0.5); else l->v.d = -(int64_t) (-d * 1e9 + 0.5); if (ists) l->v.d += l->tref; l->inp = endp; } if (v) *v = l->v; l->tok = ists ? TOK_TIMESTAMP : TOK_DURATION; } else if (issymchar0 (l->inp[0])) { int p = 0; while (issymchar (l->inp[p])) { if (p == (int) sizeof (l->v.n)) return TOK_INVALID; l->v.n[p] = l->inp[p]; p++; } l->v.n[p] = 0; l->inp += p; if (v) *v = l->v; l->tok = TOK_NAME; } else { l->tok = *l->inp++; } return l->tok; } static int nexttok (struct oneliner_lex *l, union oneliner_tokval *v) { return nexttok_dur (l, v, false); } static int peektok (const struct oneliner_lex *l, union oneliner_tokval *v) { struct oneliner_lex l1 = *l; return nexttok (&l1, v); } static bool nexttok_if (struct oneliner_lex *l, int tok) { if (peektok (l, NULL) != tok) return false; nexttok (l, NULL); return true; } static bool nexttok_int (struct oneliner_lex *l, int *dst) { if (peektok (l, NULL) != TOK_INT) return false; (void) nexttok (l, NULL); *dst = l->v.i; return true; } struct kvarg { const char *k; size_t klen; int v; bool (*arg) (struct oneliner_lex *l, void *dst); // *inp unchanged when false void (*def) (void *dst); }; static void def_kvarg_int0 (void *dst) { *(int *)dst = 0; } static void def_kvarg_int1 (void *dst) { *(int *)dst = 1; } static void def_kvarg_dur_inf (void *dst) { *(dds_duration_t *)dst = DDS_INFINITY; } static void def_kvarg_dur_100ms (void *dst) { *(dds_duration_t *)dst = DDS_MSECS (100); } static bool read_kvarg_int (struct oneliner_lex *l, void *dst) { return nexttok_int (l, dst); } static bool read_kvarg_posint (struct oneliner_lex *l, void *dst) { return nexttok_int (l, dst) && l->v.i > 0; } static bool read_kvarg_dur (struct oneliner_lex *l, void *dst) { dds_duration_t *x = dst; struct oneliner_lex l1 = *l; if (nexttok_dur (&l1, NULL, true) != TOK_DURATION) return false; *x = l1.v.d; *l = l1; return true; } static bool read_kvarg_3len (struct oneliner_lex *l, void *dst) { struct oneliner_lex l1 = *l; int *x = dst, i = 0; x[0] = x[1] = x[2] = DDS_LENGTH_UNLIMITED; do { if (!nexttok_int (&l1, &x[i]) || (x[i] <= 0 && x[i] != DDS_LENGTH_UNLIMITED)) return false; } while (++i < 3 && nexttok_if (&l1, '/')); *l = l1; return true; } static bool read_kvarg (const struct kvarg *ks, size_t sizeof_ks, struct oneliner_lex *l, int *v, void *arg) { // l points at name, *inp is , or ) terminated; *l unchanged when false const struct kvarg *kend = ks + sizeof_ks / sizeof (*ks); struct oneliner_lex l1 = *l; advancetok (&l1); for (const struct kvarg *k = ks; k < kend; k++) { assert (strlen (k->k) == k->klen); *v = k->v; if (k->klen == 0) { assert (k->arg != 0 && k->def == 0); struct oneliner_lex l2 = l1; if (k->arg (&l2, arg) && (peektok (&l2, NULL) == ',' || peektok (&l2, NULL) == ')')) { *l = l2; return true; } } else if (strncmp (l1.inp, k->k, k->klen) != 0) { continue; } else { /* skip symbol */ struct oneliner_lex l2 = l1; l2.inp += k->klen; if (peektok (&l2, NULL) == ',' || peektok (&l2, NULL) == ')') { if (k->arg == 0 || k->def != 0) { if (k->def) k->def (arg); *l = l2; return true; } } else if (k->arg != 0 && nexttok (&l2, NULL) == ':') { if (k->arg (&l2, arg) && (peektok (&l2, NULL) == ',' || peektok (&l2, NULL) == ')')) { *l = l2; return true; } } } } return false; } static bool qos_durability (struct oneliner_lex *l, dds_qos_t *q) { static const struct kvarg ks[] = { { "v", 1, (int) DDS_DURABILITY_VOLATILE }, { "tl", 2, (int) DDS_DURABILITY_TRANSIENT_LOCAL }, { "t", 1, (int) DDS_DURABILITY_TRANSIENT }, { "p", 1, (int) DDS_DURABILITY_PERSISTENT } }; int v; if (!read_kvarg (ks, sizeof ks, l, &v, NULL)) return false; dds_qset_durability (q, (dds_durability_kind_t) v); return true; } static const struct kvarg ks_history[] = { { "all", 3, (int) DDS_HISTORY_KEEP_ALL, .def = def_kvarg_int1 }, { "", 0, (int) DDS_HISTORY_KEEP_LAST, .arg = read_kvarg_posint } }; static bool qos_history (struct oneliner_lex *l, dds_qos_t *q) { int v, x = 1; if (!read_kvarg (ks_history, sizeof ks_history, l, &v, &x)) return false; dds_qset_history (q, (dds_history_kind_t) v, x); return true; } static bool qos_destination_order (struct oneliner_lex *l, dds_qos_t *q) { static const struct kvarg ks[] = { { "r", 1, (int) DDS_DESTINATIONORDER_BY_RECEPTION_TIMESTAMP }, { "s", 1, (int) DDS_DESTINATIONORDER_BY_SOURCE_TIMESTAMP } }; int v; if (!read_kvarg (ks, sizeof ks, l, &v, NULL)) return false; dds_qset_destination_order (q, (dds_destination_order_kind_t) v); return true; } static bool qos_ownership (struct oneliner_lex *l, dds_qos_t *q) { static const struct kvarg ks[] = { { "s", 1, (int) DDS_OWNERSHIP_SHARED, .def = def_kvarg_int0 }, { "x", 1, (int) DDS_OWNERSHIP_EXCLUSIVE, .arg = read_kvarg_int, .def = def_kvarg_int0 } }; int v, x; if (!read_kvarg (ks, sizeof ks, l, &v, &x)) return false; dds_qset_ownership (q, (dds_ownership_kind_t) v); dds_qset_ownership_strength (q, x); return true; } static bool qos_transport_priority (struct oneliner_lex *l, dds_qos_t *q) { static const struct kvarg k = { "", 0, 0, .arg = read_kvarg_int }; int v, x; if (!read_kvarg (&k, sizeof k, l, &v, &x)) return false; dds_qset_transport_priority (q, x); return true; } static bool qos_reliability (struct oneliner_lex *l, dds_qos_t *q) { static const struct kvarg ks[] = { { "be", 2, (int) DDS_RELIABILITY_BEST_EFFORT, .def = def_kvarg_dur_100ms }, { "r", 1, (int) DDS_RELIABILITY_RELIABLE, .def = def_kvarg_dur_100ms, .arg = read_kvarg_dur } }; int v; dds_duration_t x; if (!read_kvarg (ks, sizeof ks, l, &v, &x)) return false; dds_qset_reliability (q, (dds_reliability_kind_t) v, x); return true; } static bool qos_liveliness (struct oneliner_lex *l, dds_qos_t *q) { static const struct kvarg ks[] = { { "a", 1, (int) DDS_LIVELINESS_AUTOMATIC, .def = def_kvarg_dur_inf, .arg = read_kvarg_dur }, { "p", 1, (int) DDS_LIVELINESS_MANUAL_BY_PARTICIPANT, .arg = read_kvarg_dur }, { "w", 1, (int) DDS_LIVELINESS_MANUAL_BY_TOPIC, .arg = read_kvarg_dur } }; int v; dds_duration_t x; if (!read_kvarg (ks, sizeof ks, l, &v, &x)) return false; dds_qset_liveliness (q, (dds_liveliness_kind_t) v, x); return true; } static bool qos_simple_duration (struct oneliner_lex *l, dds_qos_t *q, void (*set) (dds_qos_t * __restrict q, dds_duration_t dur)) { static const struct kvarg k = { "", 0, 0, .arg = read_kvarg_dur }; int v; dds_duration_t x; if (!read_kvarg (&k, sizeof k, l, &v, &x)) return false; set (q, x); return true; } static bool qos_latency_budget (struct oneliner_lex *l, dds_qos_t *q) { return qos_simple_duration (l, q, dds_qset_latency_budget); } static bool qos_deadline (struct oneliner_lex *l, dds_qos_t *q) { return qos_simple_duration (l, q, dds_qset_deadline); } static bool qos_lifespan (struct oneliner_lex *l, dds_qos_t *q) { return qos_simple_duration (l, q, dds_qset_lifespan); } static bool qos_resource_limits (struct oneliner_lex *l, dds_qos_t *q) { int rl[3]; if (!read_kvarg_3len (l, rl)) return false; dds_qset_resource_limits (q, rl[0], rl[1], rl[2]); return true; } static bool qos_durability_service (struct oneliner_lex *l, dds_qos_t *q) { struct oneliner_lex l1 = *l; dds_duration_t scd; int hk = DDS_HISTORY_KEEP_LAST, hd = 1, rl[3]; if (!read_kvarg_dur (&l1, &scd)) return false; if (peektok (&l1, NULL) == '/') { (void) nexttok (&l1, NULL); if (!read_kvarg (ks_history, sizeof ks_history, &l1, &hk, &hd)) return false; } if (peektok (&l1, NULL) != '/') rl[0] = rl[1] = rl[2] = DDS_LENGTH_UNLIMITED; else { (void) nexttok (&l1, NULL); if (!read_kvarg_3len (&l1, rl)) return false; } dds_qset_durability_service (q, scd, (dds_history_kind_t) hk, hd, rl[0], rl[1], rl[2]); *l = l1; return true; } static bool qos_presentation (struct oneliner_lex *l, dds_qos_t *q) { static const struct kvarg ks[] = { { "i", 1, (int) DDS_PRESENTATION_INSTANCE, .def = def_kvarg_int0 }, { "t", 1, (int) DDS_PRESENTATION_TOPIC, .def = def_kvarg_int1 }, { "g", 1, (int) DDS_PRESENTATION_GROUP, .def = def_kvarg_int1 } }; int v, x; if (!read_kvarg (ks, sizeof ks, l, &v, &x)) return false; dds_qset_presentation (q, (dds_presentation_access_scope_kind_t) v, x, 0); return true; } static bool qos_autodispose_unregistered_instances (struct oneliner_lex *l, dds_qos_t *q) { static const struct kvarg ks[] = { { "y", 1, 1 }, { "n", 1, 0 } }; int v; if (!read_kvarg (ks, sizeof ks, l, &v, NULL)) return false; dds_qset_writer_data_lifecycle (q, !!v); return true; } static const struct { char *abbrev; size_t n; bool (*fn) (struct oneliner_lex *l, dds_qos_t *q); dds_qos_policy_id_t id; } qostab[] = { { "ll", 2, qos_liveliness, DDS_LIVELINESS_QOS_POLICY_ID }, { "d", 1, qos_durability, DDS_DURABILITY_QOS_POLICY_ID }, { "dl", 2, qos_deadline, DDS_DEADLINE_QOS_POLICY_ID }, { "h", 1, qos_history, DDS_HISTORY_QOS_POLICY_ID }, { "lb", 2, qos_latency_budget, DDS_LATENCYBUDGET_QOS_POLICY_ID }, { "ls", 2, qos_lifespan, DDS_LIFESPAN_QOS_POLICY_ID }, { "do", 2, qos_destination_order, DDS_DESTINATIONORDER_QOS_POLICY_ID }, { "o", 1, qos_ownership, DDS_OWNERSHIP_QOS_POLICY_ID }, { "tp", 2, qos_transport_priority, DDS_OWNERSHIPSTRENGTH_QOS_POLICY_ID }, { "p", 1, qos_presentation, DDS_PRESENTATION_QOS_POLICY_ID }, { "r", 1, qos_reliability, DDS_RELIABILITY_QOS_POLICY_ID }, { "rl", 2, qos_resource_limits, DDS_RESOURCELIMITS_QOS_POLICY_ID }, { "ds", 2, qos_durability_service, DDS_DURABILITYSERVICE_QOS_POLICY_ID }, { "ad", 2, qos_autodispose_unregistered_instances, DDS_WRITERDATALIFECYCLE_QOS_POLICY_ID } }; static bool setqos (struct oneliner_lex *l, dds_qos_t *q) { struct oneliner_lex l1 = *l; dds_reset_qos (q); // no whitespace between name & QoS if (*l1.inp != '(') return true; nexttok (&l1, NULL); // eat '(' do { size_t i; union oneliner_tokval name; if (nexttok (&l1, &name) != TOK_NAME || nexttok (&l1, NULL) != '=') return false; for (i = 0; i < sizeof (qostab) / sizeof (qostab[0]); i++) { assert (strlen (qostab[i].abbrev) == qostab[i].n); if (strcmp (name.n, qostab[i].abbrev) == 0) break; } if (i == sizeof (qostab) / sizeof (qostab[0])) return false; if (!qostab[i].fn (&l1, q)) return false; } while (nexttok_if (&l1, ',')); if (nexttok (&l1, NULL) != ')') return false; *l = l1; return true; } static int parse_entity1 (struct oneliner_lex *l, dds_qos_t *qos) { struct oneliner_lex l1 = *l; if (nexttok (&l1, NULL) != TOK_NAME) return -1; const char *p; if ((p = strchr (knownentities, l1.v.n[0])) == NULL) return -1; int ent = (int) (p - knownentities); int i; for (i = 1; l1.v.n[i] == '\''; i++) ent += (int) sizeof (knownentities) - 1; if (l1.v.n[i] != 0) return -1; if (ent / 9 >= (int) MAXDOMS) return -1; if (!setqos (&l1, qos)) return -1; *l = l1; return ent; } static int parse_entity (struct oneliner_ctx *ctx) { return parse_entity1 (&ctx->l, ctx->rwqos); } static int parse_listener1 (struct oneliner_lex *l) { struct oneliner_lex l1 = *l; size_t i; if (nexttok (&l1, NULL) != TOK_NAME) return -1; for (i = 0; i < sizeof (lldesc) / sizeof (lldesc[0]); i++) if (strcmp (l1.v.n, lldesc[i].name) == 0) break; if (i == sizeof (lldesc) / sizeof (lldesc[0])) return -1; *l = l1; return (int) i; } static int parse_listener (struct oneliner_ctx *ctx) { return parse_listener1 (&ctx->l); } static const char *getentname (entname_t *name, int ent) { DDSRT_STATIC_ASSERT (sizeof (knownentities) == 10); DDSRT_STATIC_ASSERT (MAXDOMS == 3); name->n[0] = knownentities[ent % 9]; const int dom = ent / 9; int i; for (i = 1; i <= dom; i++) name->n[i] = '\''; name->n[i] = 0; return name->n; } static void make_participant (struct oneliner_ctx *ctx, int ent, dds_listener_t *list) { const dds_domainid_t domid = (dds_domainid_t) (ent / 9); char *conf = ddsrt_expand_envvars ("${CYCLONEDDS_URI}${CYCLONEDDS_URI:+,}0", domid); entname_t name; printf ("create domain %"PRIu32, domid); fflush (stdout); if ((ctx->doms[domid] = dds_create_domain (domid, conf)) <= 0) error_dds (ctx, ctx->doms[domid], "make_participant: create domain %"PRIu32" failed", domid); ddsrt_free (conf); printf (" create participant %s", getentname (&name, ent)); fflush (stdout); if ((ctx->es[ent] = dds_create_participant (domid, NULL, list)) <= 0) error_dds (ctx, ctx->es[ent], "make_participant: create participant failed in domain %"PRIu32, domid); if ((ctx->tps[domid] = dds_create_topic (ctx->es[ent], &Space_Type1_desc, ctx->topicname, ctx->qos, NULL)) <= 0) error_dds (ctx, ctx->tps[domid], "make_participant: create topic failed in domain %"PRIu32, domid); // Create the built-in topic readers with a dummy listener to avoid any event (data available comes to mind) // from propagating to the normal data available listener, in case it has been set on the participant. // // - dummy_cb aborts when it is invoked, but all reader-related listeners that can possibly trigger are set // separately (incompatible qos, deadline missed, sample lost and sample rejected are all impossible by // construction) // - regarding data_on_readers: Cyclone handles listeners installed on an ancestor by *inheriting* them, // rather than by walking up ancestor chain. Setting data_on_readers on the reader therefore overrides the // listener set on the subscriber. It is a nice feature! dds_listener_t *dummylist = dds_create_listener (ctx); dds_lset_data_available (dummylist, dummy_data_available_cb); dds_lset_data_on_readers (dummylist, dummy_data_on_readers_cb); dds_lset_inconsistent_topic (dummylist, (dds_on_inconsistent_topic_fn) dummy_cb); dds_lset_liveliness_changed (dummylist, dummy_liveliness_changed_cb); dds_lset_liveliness_lost (dummylist, (dds_on_liveliness_lost_fn) dummy_cb); dds_lset_offered_deadline_missed (dummylist, (dds_on_offered_deadline_missed_fn) dummy_cb); dds_lset_offered_incompatible_qos (dummylist, (dds_on_offered_incompatible_qos_fn) dummy_cb); dds_lset_publication_matched (dummylist, (dds_on_publication_matched_fn) dummy_cb); dds_lset_requested_deadline_missed (dummylist, (dds_on_requested_deadline_missed_fn) dummy_cb); dds_lset_requested_incompatible_qos (dummylist, (dds_on_requested_incompatible_qos_fn) dummy_cb); dds_lset_sample_lost (dummylist, (dds_on_sample_lost_fn) dummy_cb); dds_lset_sample_rejected (dummylist, (dds_on_sample_rejected_fn) dummy_cb); dds_lset_subscription_matched (dummylist, dummy_subscription_matched_cb); if ((ctx->pubrd[domid] = dds_create_reader (ctx->es[ent], DDS_BUILTIN_TOPIC_DCPSPUBLICATION, NULL, dummylist)) <= 0) error_dds (ctx, ctx->pubrd[domid], "make_participant: create DCPSPublication reader in domain %"PRIu32, domid); if ((ctx->subrd[domid] = dds_create_reader (ctx->es[ent], DDS_BUILTIN_TOPIC_DCPSSUBSCRIPTION, NULL, dummylist)) <= 0) error_dds (ctx, ctx->subrd[domid], "make_participant: create DCPSSubscription reader in domain %"PRIu32, domid); dds_delete_listener (dummylist); //printf ("pubrd %"PRId32" subrd %"PRId32" sub %"PRId32"\n", es->pubrd[domid], es->subrd[domid], dds_get_parent (es->pubrd[domid])); } static void make_entity1 (struct oneliner_ctx *ctx, int ent, dds_listener_t *list) { entname_t wrname; dds_return_t ret; int domid = ent / 9; int ent1 = ent % 9; switch (ent1) { case 0: make_participant (ctx, ent, list); break; case 1: if (ctx->es[ent-1] == 0) { printf ("["); make_entity1 (ctx, ent-1, NULL); printf ("] "); } printf ("create subscriber %s", getentname (&wrname, ent)); fflush (stdout); ctx->es[ent] = dds_create_subscriber (ctx->es[ent-1], NULL, list); break; case 2: if (ctx->es[ent-2] == 0) { printf ("["); make_entity1 (ctx, ent-2, NULL); printf ("] "); } printf ("create publisher %s", getentname (&wrname, ent)); fflush (stdout); ctx->es[ent] = dds_create_publisher (ctx->es[ent-2], NULL, list); break; case 3: case 4: case 5: if (ctx->es[9*domid+1] == 0) { printf ("["); make_entity1 (ctx, 9*domid+1, NULL); printf ("] "); } printf ("create reader %s", getentname (&wrname, ent)); fflush (stdout); ctx->es[ent] = dds_create_reader (ctx->es[9*domid+1], ctx->tps[domid], ctx->rwqos, list); break; case 6: case 7: case 8: if (ctx->es[9*domid+2] == 0) { printf ("["); make_entity1 (ctx, 9*domid+2, NULL); printf ("] "); } printf ("create writer %s", getentname (&wrname, ent)); fflush (stdout); ctx->es[ent] = dds_create_writer (ctx->es[9*domid+2], ctx->tps[domid], ctx->rwqos, list); break; default: abort (); } printf (" = %"PRId32, ctx->es[ent]); fflush (stdout); if (ctx->es[ent] <= 0) error_dds (ctx, ctx->es[ent], "create entity %d failed", ent); if ((ret = dds_get_instance_handle (ctx->es[ent], &ctx->esi[ent])) != 0) error_dds (ctx, ret, "get instance handle for entity %"PRId32" failed", ctx->es[ent]); //printf (" %"PRIx64, es->esi[ent]); //fflush (stdout); } static void make_entity (struct oneliner_ctx *ctx, int ent, dds_listener_t *list) { make_entity1 (ctx, ent, list); printf ("\n"); } static void setlistener (struct oneliner_ctx *ctx, struct oneliner_lex *l, int ll, int ent) { printf ("set listener:"); dds_return_t ret; int dom = ent / 9; dds_listener_t *list = ctx->cb[dom].list; dds_reset_listener (list); do { printf (" %s", lldesc[ll].name); switch (ll) { case 0: dds_lset_data_available (list, data_available_cb); break; case 1: dds_lset_data_on_readers (list, data_on_readers_cb); break; case 2: dds_lset_inconsistent_topic (list, inconsistent_topic_cb); break; case 3: dds_lset_liveliness_changed (list, liveliness_changed_cb); break; case 4: dds_lset_liveliness_lost (list, liveliness_lost_cb); break; case 5: dds_lset_offered_deadline_missed (list, offered_deadline_missed_cb); break; case 6: dds_lset_offered_incompatible_qos (list, offered_incompatible_qos_cb); break; case 7: dds_lset_publication_matched (list, publication_matched_cb); break; case 8: dds_lset_requested_deadline_missed (list, requested_deadline_missed_cb); break; case 9: dds_lset_requested_incompatible_qos (list, requested_incompatible_qos_cb); break; case 10: dds_lset_sample_lost (list, sample_lost_cb); break; case 11: dds_lset_sample_rejected (list, sample_rejected_cb); break; case 12: dds_lset_subscription_matched (list, subscription_matched_cb); break; default: abort (); } } while (l && (ll = parse_listener1 (l)) >= 0); if (ctx->es[ent] == 0) { printf (" for "); make_entity (ctx, ent, list); } else { dds_listener_t *tmplist = dds_create_listener (&ctx->cb[dom]); if ((ret = dds_get_listener (ctx->es[ent], tmplist)) != 0) { dds_delete_listener (tmplist); error_dds (ctx, ret, "set listener: dds_get_listener failed on %"PRId32, ctx->es[ent]); } dds_merge_listener (list, tmplist); dds_delete_listener (tmplist); printf (" on entity %"PRId32"\n", ctx->es[ent]); if ((ret = dds_set_listener (ctx->es[ent], list)) != 0) error_dds (ctx, ret, "set listener: dds_set_listener failed on %"PRId32, ctx->es[ent]); } } static dds_instance_handle_t lookup_insthandle (const struct oneliner_ctx *ctx, int ent, int ent1) { // if both are in the same domain, it's easy if (ent / 9 == ent1 / 9) return ctx->esi[ent1]; else { // if they aren't ... find GUID from instance handle in the one domain, // then find instance handle for GUID in the other dds_entity_t rd1 = 0, rd2 = 0; switch (ent1 % 9) { case 3: case 4: case 5: rd1 = ctx->subrd[ent1/9]; rd2 = ctx->subrd[ent/9]; break; case 6: case 7: case 8: rd1 = ctx->pubrd[ent1/9]; rd2 = ctx->pubrd[ent/9]; break; default: return 0; } dds_builtintopic_endpoint_t keysample; //printf ("(in %"PRId32" %"PRIx64" -> ", rd1, es->esi[ent1]); //fflush (stdout); if (dds_instance_get_key (rd1, ctx->esi[ent1], &keysample) != 0) return 0; // In principle, only key fields are set in sample returned by get_key; // in the case of a built-in topic that is extended to the participant // key. The qos and topic/type names should not be set, and there is no // (therefore) memory allocated for the sample. assert (keysample.qos == NULL); assert (keysample.topic_name == NULL); assert (keysample.type_name == NULL); //for (size_t j = 0; j < sizeof (keysample.key.v); j++) // printf ("%s%02x", (j > 0 && j % 4 == 0) ? ":" : "", keysample.key.v[j]); const dds_instance_handle_t ih = dds_lookup_instance (rd2, &keysample); //printf (" -> %"PRIx64")", ih); //fflush (stdout); return ih; } } static void print_timestamp (struct oneliner_ctx *ctx, dds_time_t ts) { dds_time_t dt = ts - ctx->l.tref; if ((dt % DDS_NSECS_IN_SEC) == 0) printf ("@%"PRId64, dt / DDS_NSECS_IN_SEC); else { unsigned frac = (unsigned) (dt % DDS_NSECS_IN_SEC); int digs = 9; while ((frac % 10) == 0) { digs--; frac /= 10; } printf ("@%"PRId64".%0*u", dt / DDS_NSECS_IN_SEC, digs, frac); } } static bool parse_sample_value (struct oneliner_ctx *ctx, Space_Type1 *s, bool *valid_data, int def) { s->long_1 = s->long_2 = s->long_3 = def; if (nexttok (&ctx->l, NULL) == TOK_INT) // key value (invalid sample) { if (ctx->l.v.i < 0) return false; s->long_1 = ctx->l.v.i; *valid_data = false; return true; } else if (ctx->l.tok == '(') { if (nexttok (&ctx->l, NULL) != TOK_INT || ctx->l.v.i < 0) return false; s->long_1 = ctx->l.v.i; if (nexttok (&ctx->l, NULL) != ',' || nexttok (&ctx->l, NULL) != TOK_INT || ctx->l.v.i < 0) return false; s->long_2 = ctx->l.v.i; if (nexttok (&ctx->l, NULL) != ',' || nexttok (&ctx->l, NULL) != TOK_INT || ctx->l.v.i < 0) return false; s->long_3 = ctx->l.v.i; *valid_data = true; return nexttok (&ctx->l, NULL) == ')'; } else { return false; } } struct doreadlike_sample { uint32_t state; bool valid_data; dds_time_t ts; int wrent; dds_instance_handle_t wrih; Space_Type1 data; }; static bool wrname_from_pubhandle (const struct oneliner_ctx *ctx, int ent, dds_instance_handle_t pubhandle, entname_t *wrname) { dds_builtintopic_endpoint_t inf, inf1; if (dds_instance_get_key (ctx->pubrd[ent/9], pubhandle, &inf) != 0) return false; for (int j = 0; j < (int) (sizeof (ctx->doms) / sizeof (ctx->doms[0])); j++) { for (int k = 6; k < 9; k++) { if (ctx->esi[9*j+k] != 0) { if (dds_instance_get_key (ctx->pubrd[j], ctx->esi[9*j+k], &inf1) != 0) return false; if (memcmp (&inf.key, &inf1.key, sizeof (inf.key)) == 0) { getentname (wrname, 9*j+k); return true; } } } } return false; } static bool doreadlike_parse_sample (struct oneliner_ctx *ctx, struct doreadlike_sample *s) { static const char *statechars = "fsaudno"; static const uint32_t statemap[] = { DDS_NOT_READ_SAMPLE_STATE, DDS_READ_SAMPLE_STATE, DDS_ALIVE_INSTANCE_STATE, DDS_NOT_ALIVE_NO_WRITERS_INSTANCE_STATE, DDS_NOT_ALIVE_DISPOSED_INSTANCE_STATE, DDS_NEW_VIEW_STATE, DDS_NOT_NEW_VIEW_STATE }; // syntax: [state]k[pubhandle][@ts] or [state](k,l,m)[pubhandle][@ts] // the first is an invalid sample, the second a valid one, the third says anything goes // state is a combination of: sample state (F,S fresh/stale), instance state (A,U,D), view state (N,O) // unspecified: don't care s->state = 0; s->ts = -1; s->wrent = -1; s->wrih = 0; struct oneliner_lex l1 = ctx->l; if (nexttok_if (&ctx->l, TOK_NAME)) { char *inp1 = ctx->l.v.n; char *p; while (*inp1 && (p = strchr (statechars, *inp1)) != NULL) { s->state |= statemap[(int) (p - statechars)]; inp1++; } if (*inp1 == 0) ; else if (!isdigit (*inp1)) return false; else // rewind input to digit ctx->l.inp = l1.inp + (inp1 - ctx->l.v.n); } // missing states: allow everything if ((s->state & (statemap[0] | statemap[1])) == 0) s->state |= statemap[0] | statemap[1]; if ((s->state & (statemap[2] | statemap[3] | statemap[4])) == 0) s->state |= statemap[2] | statemap[3] | statemap[4]; if ((s->state & (statemap[5] | statemap[6])) == 0) s->state |= statemap[5] | statemap[6]; if (!parse_sample_value (ctx, &s->data, &s->valid_data, -1)) return false; s->wrent = parse_entity1 (&ctx->l, NULL); if (nexttok_if (&ctx->l, TOK_TIMESTAMP)) s->ts = ctx->l.v.d; return true; } static bool doreadlike_ismatch (const dds_sample_info_t *si, const Space_Type1 *s, const struct doreadlike_sample *exp) { return (si->valid_data == exp->valid_data && (si->sample_state & exp->state) != 0 && (si->instance_state & exp->state) != 0 && (si->view_state & exp->state) != 0 && (exp->data.long_1 < 0 || s->long_1 == exp->data.long_1) && (!exp->valid_data || exp->data.long_2 < 0 || s->long_2 == exp->data.long_2) && (!exp->valid_data || exp->data.long_3 < 0 || s->long_3 == exp->data.long_3) && (exp->ts < 0 || si->source_timestamp == exp->ts) && (exp->wrent < 0 || si->publication_handle == exp->wrih)); } static bool doreadlike_matchstep (const dds_sample_info_t *si, const Space_Type1 *s, const struct doreadlike_sample *exp, int nexp, bool ellipsis, unsigned *tomatch, int *cursor, dds_instance_handle_t *lastih, int *matchidx) { if (si->instance_handle != *lastih) { *lastih = si->instance_handle; *cursor = -1; for (int m = 0; m < nexp; m++) { if ((*tomatch & (1u << m)) && s->long_1 == exp[m].data.long_1) { *cursor = m; break; } } } if (*cursor < 0 || *cursor >= nexp) { *matchidx = ellipsis ? nexp : -1; return ellipsis; } else if (doreadlike_ismatch (si, s, &exp[*cursor])) { *matchidx = *cursor; *tomatch &= ~(1u << *cursor); (*cursor)++; return true; } else if (ellipsis) { *matchidx = nexp; return true; } else { *matchidx = -1; return false; } } static void doreadlike (struct oneliner_ctx *ctx, const char *name, dds_return_t (*fn) (dds_entity_t, void **buf, dds_sample_info_t *, size_t, uint32_t)) { #define MAXN 10 struct doreadlike_sample exp[MAXN]; int nexp = 0; bool ellipsis = false; int exp_nvalid = -1, exp_ninvalid = -1; int ent; switch (peektok (&ctx->l, NULL)) { default: // no expectations ellipsis = true; break; case '(': // (# valid, # invalid) nexttok (&ctx->l, NULL); if (!(nexttok_int (&ctx->l, &exp_nvalid) && nexttok_if (&ctx->l, ',') && nexttok_int (&ctx->l, &exp_ninvalid) && nexttok_if (&ctx->l, ')'))) error (ctx, "%s: expecting (NINVALID, NVALID)", name); ellipsis = true; break; case '{': nexttok (&ctx->l, NULL); if (!nexttok_if (&ctx->l, '}')) { do { if (nexttok_if (&ctx->l, TOK_ELLIPSIS)) { ellipsis = true; break; } else if (nexp == MAXN) { error (ctx, "%s: too many samples specified", name); } else if (!doreadlike_parse_sample (ctx, &exp[nexp++])) { error (ctx, "%s: expecting sample", name); } } while (nexttok_if (&ctx->l, ',')); if (!nexttok_if (&ctx->l, '}')) error (ctx, "%s: expecting '}'", name); } break; } if ((ent = parse_entity1 (&ctx->l, NULL)) < 0) error (ctx, "%s: entity required", name); for (int i = 0; i < nexp; i++) { if (exp[i].wrent >= 0 && (exp[i].wrih = lookup_insthandle (ctx, ent, exp[i].wrent)) == 0) error (ctx, "%s: instance lookup failed", name); } printf ("entity %"PRId32": %s: ", ctx->es[ent], (fn == dds_take) ? "take" : "read"); fflush (stdout); Space_Type1 data[MAXN]; void *raw[MAXN]; for (int i = 0; i < MAXN; i++) raw[i] = &data[i]; int matchidx[MAXN]; dds_sample_info_t si[MAXN]; DDSRT_STATIC_ASSERT (MAXN < CHAR_BIT * sizeof (unsigned)); const uint32_t maxs = (uint32_t) (sizeof (raw) / sizeof (raw[0])); const int32_t n = fn (ctx->es[ent], raw, si, maxs, maxs); if (n < 0) error_dds (ctx, n, "%s: failed on %"PRId32, name, ctx->es[ent]); unsigned tomatch = (1u << nexp) - 1; // used to track result entries matched by spec dds_instance_handle_t lastih = 0; int cursor = -1; int count[2] = { 0, 0 }; bool matchok = true; printf ("{"); for (int i = 0; i < n; i++) { const Space_Type1 *s = raw[i]; entname_t wrname; count[si[i].valid_data]++; printf ("%s%c%c%c", (i > 0) ? "," : "", (si[i].sample_state == DDS_NOT_READ_SAMPLE_STATE) ? 'f' : 's', (si[i].instance_state == DDS_ALIVE_INSTANCE_STATE) ? 'a' : (si[i].instance_state == DDS_NOT_ALIVE_NO_WRITERS_INSTANCE_STATE) ? 'u' : 'd', (si[i].view_state == DDS_NEW_VIEW_STATE) ? 'n' : 'o'); if (si[i].valid_data) printf ("(%"PRId32",%"PRId32",%"PRId32")", s->long_1, s->long_2, s->long_3); else printf ("%"PRId32, s->long_1); if (!wrname_from_pubhandle (ctx, ent, si[i].publication_handle, &wrname)) error (ctx, "%s: unknown publication handle received", name); printf ("%s", wrname.n); print_timestamp (ctx, si[i].source_timestamp); if (!doreadlike_matchstep (&si[i], s, exp, nexp, ellipsis, &tomatch, &cursor, &lastih, &matchidx[i])) matchok = false; } printf ("}:"); for (int i = 0; i < n; i++) printf (" %d", matchidx[i]); if (tomatch != 0) { printf (" (samples missing)"); matchok = false; } printf (" valid %d %d invalid %d %d", count[1], exp_nvalid, count[0], exp_ninvalid); if (exp_nvalid >= 0 && (count[1] != exp_nvalid)) matchok = false; if (exp_ninvalid >= 0 && (count[0] != exp_ninvalid)) matchok = false; printf ("\n"); fflush (stdout); if (!matchok) testfail (ctx, "%s: mismatch between actual and expected set\n", name); #undef MAXN } static void dotake (struct oneliner_ctx *ctx) { doreadlike (ctx, "take", dds_take); } static void doread (struct oneliner_ctx *ctx) { doreadlike (ctx, "read", dds_read); } static void dowritelike (struct oneliner_ctx *ctx, const char *name, bool fail, dds_return_t (*fn) (dds_entity_t wr, const void *sample, dds_time_t ts)) { dds_return_t ret; dds_time_t ts = dds_time (); bool valid_data; int ent; Space_Type1 sample; if ((ent = parse_entity (ctx)) < 0) error (ctx, "%s: expecting entity", name); if (ctx->es[ent] == 0) make_entity (ctx, ent, NULL); if (!parse_sample_value (ctx, &sample, &valid_data, 0)) error (ctx, "%s: expecting sample value", name); if (nexttok_if (&ctx->l, TOK_TIMESTAMP)) ts = ctx->l.v.d; printf ("entity %"PRId32": %s (%"PRId32",%"PRId32",%"PRId32")", ctx->es[ent], name, sample.long_1, sample.long_2, sample.long_3); print_timestamp (ctx, ts); printf ("\n"); ret = fn (ctx->es[ent], &sample, ts); if (!fail) { if (ret != 0) error_dds (ctx, ret, "%s: failed", name); } else { if (ret == 0) testfail (ctx, "%s: succeeded unexpectedly", name); else if (ret != DDS_RETCODE_TIMEOUT) error_dds (ctx, ret, "%s: failed", name); } } static void dowr (struct oneliner_ctx *ctx) { dowritelike (ctx, "wr", false, dds_write_ts); } static void dowrfail (struct oneliner_ctx *ctx) { dowritelike (ctx, "wrfail", true, dds_write_ts); } static void dowrdisp (struct oneliner_ctx *ctx) { dowritelike (ctx, "wrdisp", false, dds_writedispose_ts); } static void dowrdispfail (struct oneliner_ctx *ctx) { dowritelike (ctx, "wrdispfail", true, dds_writedispose_ts); } static void dodisp (struct oneliner_ctx *ctx) { dowritelike (ctx, "disp", false, dds_dispose_ts); } static void dodispfail (struct oneliner_ctx *ctx) { dowritelike (ctx, "dispfail", true, dds_dispose_ts); } static void dounreg (struct oneliner_ctx *ctx) { dowritelike (ctx, "unreg", false, dds_unregister_instance_ts); } static void dounregfail (struct oneliner_ctx *ctx) { dowritelike (ctx, "unregfail", true, dds_unregister_instance_ts); } static int checkstatus (struct oneliner_ctx *ctx, int ll, int ent, struct oneliner_lex *argl, const void *status) { assert (lldesc[ll].desc != NULL); const char *d = lldesc[ll].desc; int field = 0; const char *sep = "("; size_t off = 0; if (nexttok (argl, NULL) != '(') abort (); while (*d) { const void *p = advance (status, &off, *d); int i; switch (*d) { case 'n': if (!nexttok_int (argl, &i) || i < 0) return setresult (ctx, -1, "checkstatus: field %d expecting non-negative integer", field); printf ("%s%"PRIu32" %d", sep, *(uint32_t *)p, i); fflush (stdout); if (*(uint32_t *)p != (uint32_t)i) return setresult (ctx, 0, "checkstatus: field %d has actual %"PRIu32" expected %d", field, *(uint32_t *)p, i); break; case 'c': if (!nexttok_int (argl, &i)) return setresult (ctx, -1, "checkstatus: field %d expecting integer", field); printf ("%s%"PRId32" %d", sep, *(int32_t *)p, i); fflush (stdout); if (*(int32_t *)p != i) return setresult (ctx, 0, "checkstatus: field %d has actual %"PRId32" expected %d", field, *(int32_t *)p, i); break; case 'P': if (nexttok (argl, NULL) != TOK_NAME) return setresult (ctx, -1, "checkstatus: field %d expecting policy name", field); size_t polidx; for (polidx = 0; polidx < sizeof (qostab) / sizeof (qostab[0]); polidx++) if (strcmp (argl->v.n, qostab[polidx].abbrev) == 0) break; if (polidx == sizeof (qostab) / sizeof (qostab[0])) return setresult (ctx, -1, "checkstatus: field %d expecting policy name", field); printf ("%s%"PRIu32" %"PRIu32, sep, *(uint32_t *)p, (uint32_t) qostab[polidx].id); fflush (stdout); if (*(uint32_t *)p != (uint32_t) qostab[polidx].id) return setresult (ctx, 0, "checkstatus: field %d has actual %"PRIu32" expected %d", field, *(uint32_t *)p, (int) qostab[polidx].id); break; case 'R': if (nexttok (argl, NULL) != TOK_NAME) return setresult (ctx, -1, "checkstatus: field %d expecting reason", field); if (strcmp (argl->v.n, "i") == 0) i = (int) DDS_REJECTED_BY_INSTANCES_LIMIT; else if (strcmp (argl->v.n, "s") == 0) i = (int) DDS_REJECTED_BY_SAMPLES_LIMIT; else if (strcmp (argl->v.n, "spi") == 0) i = (int) DDS_REJECTED_BY_SAMPLES_PER_INSTANCE_LIMIT; else return setresult (ctx, -1, "checkstatus: field %d expecting reason", field); printf ("%s%d %d", sep, (int) *(dds_sample_rejected_status_kind *)p, i); fflush (stdout); if (*(dds_sample_rejected_status_kind *)p != (dds_sample_rejected_status_kind) i) return setresult (ctx, 0, "checkstatus: field %d has actual %d expected %d", field, (int) (*(dds_sample_rejected_status_kind *)p), i); break; case 'I': // instance handle is too complicated break; case 'E': { int ent1 = -1; dds_instance_handle_t esi1 = 0; if (nexttok_if (argl, '*')) ent1 = -1; else if ((ent1 = parse_entity1 (argl, NULL)) < 0) return setresult (ctx, -1, "checkstatus: field %d expecting * or entity name", field); else if ((esi1 = lookup_insthandle (ctx, ent, ent1)) == 0) return setresult (ctx, -1, "checkstatus: field %d instance handle lookup failed", field); printf ("%s%"PRIx64" %"PRIx64, sep, *(dds_instance_handle_t *)p, esi1); fflush (stdout); if (ent1 >= 0 && *(dds_instance_handle_t *)p != esi1) return setresult (ctx, 0, "checkstatus: field %d has actual %"PRIx64" expected %"PRIx64, field, *(dds_instance_handle_t *)p, esi1); break; } default: return DDS_RETCODE_BAD_PARAMETER; } sep = ", "; if (*d != 'I') field++; ++d; if (*d && *d != 'I' && !nexttok_if (argl, ',')) return setresult (ctx, -1, "checkstatus: field %d expecting ','", field); } printf (")"); if (!nexttok_if (argl, ')')) return setresult (ctx, -1, "checkstatus: field %d expecting ')'", field); assert (off <= lldesc[ll].size); return 1; } static void checklistener (struct oneliner_ctx *ctx, int ll, int ent, struct oneliner_lex *argl) { bool signalled = true; uint32_t min_cnt = 1, max_cnt = UINT32_MAX; uint32_t status; const int dom = ent / 9; dds_return_t ret; printf ("listener %s: check called for entity %"PRId32, lldesc[ll].name, ctx->es[ent]); fflush (stdout); if (argl && lldesc[ll].cb_status_off == 0) { // those that don't have a status can check the number of invocations int cnt = -1; if (!(nexttok_if (argl, '(') && nexttok_int (argl, &cnt) && nexttok_if (argl, ')'))) error (ctx, "listener %s: expecting (COUNT)", lldesc[ll].name); if (cnt < 0) error (ctx, "listener %s: invocation count must be at least 0", lldesc[ll].name); min_cnt = max_cnt = (uint32_t) cnt; } ddsrt_mutex_lock (&ctx->g_mutex); bool cnt_ok = (ctx->cb[dom].cb_called[lldesc[ll].id] >= min_cnt && ctx->cb[dom].cb_called[lldesc[ll].id] <= max_cnt); while (ctx->cb[dom].cb_called[lldesc[ll].id] < min_cnt && signalled) { signalled = ddsrt_cond_waitfor (&ctx->g_cond, &ctx->g_mutex, DDS_SECS (5)); cnt_ok = (ctx->cb[dom].cb_called[lldesc[ll].id] >= min_cnt && ctx->cb[dom].cb_called[lldesc[ll].id] <= max_cnt); } printf (" cb_called %"PRIu32" (%s)", ctx->cb[dom].cb_called[lldesc[ll].id], cnt_ok ? "ok" : "fail"); fflush (stdout); if (!cnt_ok) { ddsrt_mutex_unlock (&ctx->g_mutex); testfail (ctx, "listener %s: not invoked [%"PRIu32",%"PRIu32"] times", lldesc[ll].name, min_cnt, max_cnt); } dds_entity_t * const cb_entity = (dds_entity_t *) ((char *) &ctx->cb[dom] + lldesc[ll].cb_entity_off); printf (" cb_entity %"PRId32" %"PRId32" (%s)", *cb_entity, ctx->es[ent], (*cb_entity == ctx->es[ent]) ? "ok" : "fail"); fflush (stdout); if (*cb_entity != ctx->es[ent]) { ddsrt_mutex_unlock (&ctx->g_mutex); testfail (ctx, "listener %s: invoked on %"PRId32" instead of %"PRId32, lldesc[ll].name, *cb_entity, ctx->es[ent]); } if (!(ctx->doms[0] && ctx->doms[1])) { // FIXME: two domains: listener invocation happens on another thread and we can observe non-0 "change" fields // they get updated, listener gets invoked, then they get reset -- pretty sure it is allowed by the spec, but // not quite elegant if ((ret = check_status_change_fields_are_0 (ll, ctx->es[ent])) <= 0) { ddsrt_mutex_unlock (&ctx->g_mutex); if (ret == 0) testfail (ctx, "listener %s: status contains non-zero change fields", lldesc[ll].name); else if (ret < 0) error_dds (ctx, ret, "listener %s: get entity status failed", lldesc[ll].name); } } if (argl && lldesc[ll].cb_status_off != 0) { void *cb_status = (char *) &ctx->cb[dom] + lldesc[ll].cb_status_off; if (checkstatus (ctx, ll, ent, argl, cb_status) <= 0) { ddsrt_mutex_unlock (&ctx->g_mutex); longjmp (ctx->jb, 1); } } printf ("\n"); ctx->cb[dom].cb_called[lldesc[ll].id] = 0; ddsrt_mutex_unlock (&ctx->g_mutex); if ((ret = dds_get_status_changes (ctx->es[ent], &status)) != 0) error_dds (ctx, ret, "listener %s: dds_get_status_change on %"PRId32, lldesc[ll].name, ctx->es[ent]); if ((status & (1u << lldesc[ll].id)) != 0) testfail (ctx, "listener %s: status mask not cleared", lldesc[ll].name); } static void dowaitforack (struct oneliner_ctx *ctx) { dds_return_t ret; int ent, ent1 = -1; union { dds_guid_t x; ddsi_guid_t i; } rdguid; if (*ctx->l.inp == '(') // reader present { nexttok (&ctx->l, NULL); if ((ent1 = parse_entity (ctx)) < 0) error (ctx, "wait for ack: expecting entity"); if ((ent1 % 9) < 3 || (ent1 % 9) > 5 || ctx->es[ent1] == 0) error (ctx, "wait for ack: expecting existing reader as argument"); if ((ret = dds_get_guid (ctx->es[ent1], &rdguid.x)) != 0) error_dds (ctx, ret, "wait for ack: failed to get GUID for reader %"PRId32, ctx->es[ent1]); rdguid.i = nn_ntoh_guid (rdguid.i); if (!nexttok_if (&ctx->l, ')')) error (ctx, "wait for ack: expecting ')'"); } if ((ent = parse_entity (ctx)) < 0) error (ctx, "wait for ack: expecting writer"); if (ent1 >= 0 && ent / 9 == ent1 / 9) error (ctx, "wait for ack: reader and writer must be in different domains"); if (ctx->es[ent] == 0) make_entity (ctx, ent, NULL); printf ("wait for ack %"PRId32" reader %"PRId32"\n", ctx->es[ent], ent1 < 0 ? 0 : ctx->es[ent1]); // without a reader argument a simple dds_wait_for_acks (ctx->es[ent], DDS_SECS (5)) suffices struct dds_entity *x; if ((ret = dds_entity_pin (ctx->es[ent], &x)) < 0) error_dds (ctx, ret, "wait for ack: pin entity failed %"PRId32, ctx->es[ent]); if (dds_entity_kind (x) != DDS_KIND_WRITER) error_dds (ctx, ret, "wait for ack: %"PRId32" is not a writer", ctx->es[ent]); else ret = dds__writer_wait_for_acks ((struct dds_writer *) x, (ent1 < 0) ? NULL : &rdguid.i, dds_time () + DDS_SECS (5)); dds_entity_unpin (x); if (ret != 0) { if (ret == DDS_RETCODE_TIMEOUT) testfail (ctx, "wait for acks timed out on entity %"PRId32, ctx->es[ent]); else error_dds (ctx, ret, "wait for acks failed on entity %"PRId32, ctx->es[ent]); } } static void dowaitfornolistener (struct oneliner_ctx *ctx, int ll) { printf ("listener %s: check not called", lldesc[ll].name); fflush (stdout); ddsrt_mutex_lock (&ctx->g_mutex); bool ret = true; for (int i = 0; i < (int) (sizeof (ctx->doms) / sizeof (ctx->doms[0])); i++) { printf (" %"PRIu32, ctx->cb[i].cb_called[lldesc[ll].id]); if (ctx->cb[i].cb_called[lldesc[ll].id] != 0) ret = false; } printf (" (%s)\n", ret ? "ok" : "fail"); ddsrt_mutex_unlock (&ctx->g_mutex); if (!ret) testfail (ctx, "callback %s invoked unexpectedly", lldesc[ll].name); } static void dowaitforlistener (struct oneliner_ctx *ctx, int ll) { struct oneliner_lex l1 = ctx->l; // no whitespace between name and args const bool have_args = (*ctx->l.inp == '('); if (have_args) { // skip args: we need the entity before we can interpret them int tok; while ((tok = nexttok (&ctx->l, NULL)) != EOF && tok != ')') ; } const int ent = parse_entity (ctx); if (ent < 0) error (ctx, "check listener: requires an entity"); if (ctx->es[ent] == 0) setlistener (ctx, NULL, ll, ent); checklistener (ctx, ll, ent, have_args ? &l1 : NULL); } static void dowait (struct oneliner_ctx *ctx) { union oneliner_tokval tokval; if (peektok (&ctx->l, &tokval) == TOK_NAME && strcmp (tokval.n, "ack") == 0) { nexttok (&ctx->l, NULL); dowaitforack (ctx); } else { const bool expectclear = nexttok_if (&ctx->l, '!'); const int ll = parse_listener (ctx); if (ll < 0) error (ctx, "check listener: requires listener name"); if (expectclear) dowaitfornolistener (ctx, ll); else dowaitforlistener (ctx, ll); } } static void dodelete (struct oneliner_ctx *ctx) { dds_return_t ret; int ent; if ((ent = parse_entity (ctx)) < 0) error (ctx, "delete: requires entity"); if ((ret = dds_delete (ctx->es[ent])) != 0) error_dds (ctx, ret, "delete: failed on %"PRId32, ctx->es[ent]); ctx->es[ent] = 0; } static void dodeaf (struct oneliner_ctx *ctx) { dds_return_t ret; entname_t name; int ent; if ((ent = parse_entity (ctx)) < 0 || (ent % 9) != 0) error (ctx, "deaf: requires participant"); printf ("deaf: %s\n", getentname (&name, ent)); if ((ret = dds_domain_set_deafmute (ctx->es[ent], true, false, DDS_INFINITY)) != 0) error_dds (ctx, ret, "deaf: dds_domain_set_deafmute failed on %"PRId32, ctx->es[ent]); // speed up the process by forcing lease expiry dds_entity *x, *xprime; if ((ret = dds_entity_pin (ctx->es[ent], &x)) < 0) error_dds (ctx, ret, "deaf: pin participant failed %"PRId32, ctx->es[ent]); for (int i = 0; i < (int) (sizeof (ctx->doms) / sizeof (ctx->doms[0])); i++) { if (i == ent / 9 || ctx->es[9*i] == 0) continue; if ((ret = dds_entity_pin (ctx->es[9*i], &xprime)) < 0) { dds_entity_unpin (x); error_dds (ctx, ret, "deaf: pin counterpart participant failed %"PRId32, ctx->es[9*i]); } thread_state_awake (lookup_thread_state (), &x->m_domain->gv); delete_proxy_participant_by_guid (&x->m_domain->gv, &xprime->m_guid, ddsrt_time_wallclock (), true); thread_state_asleep (lookup_thread_state ()); dds_entity_unpin (xprime); } dds_entity_unpin (x); } static void dohearing (struct oneliner_ctx *ctx) { dds_return_t ret; entname_t name; int ent; if ((ent = parse_entity (ctx)) < 0 || (ent % 9) != 0) error (ctx, "hearing: requires participant"); printf ("hearing: %s\n", getentname (&name, ent)); if ((ret = dds_domain_set_deafmute (ctx->es[ent], false, false, DDS_INFINITY)) != 0) error_dds (ctx, ret, "hearing: dds_domain_set_deafmute failed %"PRId32, ctx->es[ent]); // speed up the process by forcing SPDP publication on the remote for (int i = 0; i < (int) (sizeof (ctx->doms) / sizeof (ctx->doms[0])); i++) { if (i == ent / 9 || ctx->es[9*i] == 0) continue; dds_entity *xprime; struct participant *pp; if ((ret = dds_entity_pin (ctx->es[9*i], &xprime)) < 0) error_dds (ctx, ret, "hearing: pin counterpart participant failed %"PRId32, ctx->es[9*i]); thread_state_awake (lookup_thread_state (), &xprime->m_domain->gv); if ((pp = entidx_lookup_participant_guid (xprime->m_domain->gv.entity_index, &xprime->m_guid)) != NULL) resched_xevent_if_earlier (pp->spdp_xevent, ddsrt_mtime_add_duration (ddsrt_time_monotonic (), DDS_MSECS (100))); thread_state_asleep (lookup_thread_state ()); dds_entity_unpin (xprime); } } static void dosleep (struct oneliner_ctx *ctx) { if (nexttok_dur (&ctx->l, NULL, true) != TOK_DURATION) error (ctx, "sleep: invalid duration"); dds_sleepfor (ctx->l.v.d); } static void dispatchcmd (struct oneliner_ctx *ctx) { static const struct { const char *name; void (*fn) (struct oneliner_ctx *ct); } cs[] = { { "-", dodelete }, { "?", dowait }, { "wr", dowr }, { "wrdisp", dowrdisp }, { "disp", dodisp }, { "unreg", dounreg }, { "wrfail", dowrfail }, { "wrdispfail", dowrdispfail }, { "dispfail", dodispfail }, { "unregfail", dounregfail }, { "take", dotake }, { "read", doread }, { "deaf", dodeaf }, { "hearing", dohearing }, { "sleep", dosleep } }; size_t i; if (ctx->l.tok > 0) { // convert single-character token to string ctx->l.v.n[0] = (char) ctx->l.tok; ctx->l.v.n[1] = 0; } for (i = 0; i < sizeof (cs) / sizeof (cs[0]); i++) if (strcmp (ctx->l.v.n, cs[i].name) == 0) break; if (i == sizeof (cs) / sizeof (cs[0])) error (ctx, "%s: unknown command", ctx->l.v.n); cs[i].fn (ctx); } static void dosetlistener (struct oneliner_ctx *ctx, int ll) { int ent; struct oneliner_lex l1 = ctx->l; // scan past listener names to get at the entity, which we need // to get the right listener object (and hence argument) while (parse_listener1 (&ctx->l) >= 0) ; if ((ent = parse_entity (ctx)) < 0) error (ctx, "set listener: entity required"); setlistener (ctx, &l1, ll, ent); } static void test_oneliner_step1 (struct oneliner_ctx *ctx) { while (peektok (&ctx->l, NULL) != TOK_END) { int ent, ll; if (nexttok_if (&ctx->l, ';')) ; // skip ;s else if ((ent = parse_entity (ctx)) >= 0) make_entity (ctx, ent, NULL); else if ((ll = parse_listener (ctx)) >= 0) dosetlistener (ctx, ll); else if (nexttok (&ctx->l, NULL) == TOK_NAME || ctx->l.tok > 0) dispatchcmd (ctx); else error (ctx, "unexpected token %d", ctx->l.tok); } } void test_oneliner_init (struct oneliner_ctx *ctx) { dds_qos_t *qos = dds_create_qos (); dds_qset_reliability (qos, DDS_RELIABILITY_RELIABLE, DDS_MSECS (100)); dds_qset_destination_order (qos, DDS_DESTINATIONORDER_BY_SOURCE_TIMESTAMP); dds_qset_history (qos, DDS_HISTORY_KEEP_ALL, 0); *ctx = (struct oneliner_ctx) { .l = { .tref = dds_time () }, .qos = qos, .rwqos = dds_create_qos (), .result = 1, .cb = { [0] = { .ctx = ctx, .list = dds_create_listener (&ctx->cb[0]) }, [1] = { .ctx = ctx, .list = dds_create_listener (&ctx->cb[1]) }, [2] = { .ctx = ctx, .list = dds_create_listener (&ctx->cb[2]) } } }; ddsrt_mutex_init (&ctx->g_mutex); ddsrt_cond_init (&ctx->g_cond); create_unique_topic_name ("ddsc_listener_test", ctx->topicname, sizeof (ctx->topicname)); } int test_oneliner_step (struct oneliner_ctx *ctx, const char *ops) { if (ctx->result > 0 && setjmp (ctx->jb) == 0) { ctx->l.inp = ops; test_oneliner_step1 (ctx); } return ctx->result; } const char *test_oneliner_message (const struct oneliner_ctx *ctx) { return ctx->msg; } int test_oneliner_fini (struct oneliner_ctx *ctx) { for (size_t i = 0; i < sizeof (ctx->cb) / sizeof (ctx->cb[0]); i++) dds_delete_listener (ctx->cb[i].list); dds_delete_qos (ctx->rwqos); dds_delete_qos ((dds_qos_t *) ctx->qos); // prevent any listeners from being invoked so we can safely delete the // mutex and the condition variable -- must do this going down the // hierarchy, or listeners may remain set through inheritance dds_return_t ret; for (size_t i = 0; i < sizeof (ctx->es) / sizeof (ctx->es[0]); i++) if (ctx->es[i] && (ret = dds_set_listener (ctx->es[i], NULL)) != 0) setresult (ctx, ret, "terminate: reset listener failed on %"PRId32, ctx->es[i]); if (ctx->result == 0) { printf ("\n-- dumping content of readers after failure --\n"); for (int i = 0; i < (int) (sizeof (ctx->doms) / sizeof (ctx->doms[0])); i++) { for (int j = 3; j <= 5; j++) { if (ctx->es[9*i + j]) { const char *inp_orig = ctx->l.inp; entname_t n; ctx->l.inp = getentname (&n, 9*i + j); doreadlike (ctx, "read", dds_read); ctx->l.inp = inp_orig; } } } } ddsrt_mutex_destroy (&ctx->g_mutex); ddsrt_cond_destroy (&ctx->g_cond); for (size_t i = 0; i < sizeof (ctx->doms) / sizeof (ctx->doms[0]); i++) if (ctx->doms[i] && (ret = dds_delete (ctx->doms[i])) != 0) setresult (ctx, ret, "terminate: delete domain on %"PRId32, ctx->doms[i]); return ctx->result; } int test_oneliner (const char *ops) { struct oneliner_ctx ctx; printf ("dotest: %s\n", ops); test_oneliner_init (&ctx); test_oneliner_step (&ctx, ops); if (test_oneliner_fini (&ctx) <= 0) fprintf (stderr, "FAIL: %s\n", test_oneliner_message (&ctx)); return ctx.result; }