diff --git a/include/villas/mapping.h b/include/villas/mapping.h index 427a9de28..2f7650b1a 100644 --- a/include/villas/mapping.h +++ b/include/villas/mapping.h @@ -28,6 +28,16 @@ #include #include +#define RE_MAPPING_INDEX "[a-zA-Z0-9_]+" +#define RE_MAPPING_RANGE "(" RE_MAPPING_INDEX ")(?:-(" RE_MAPPING_INDEX "))?" + +#define RE_MAPPING_STATS "stats\\.([a-z]+)\\.([a-z]+)" +#define RE_MAPPING_HDR "hdr\\.(sequence|length)" +#define RE_MAPPING_TS "ts\\.(origin|received)" +#define RE_MAPPING_DATA1 "data\\[" RE_MAPPING_RANGE "\\]" +#define RE_MAPPING_DATA2 "(?:data\\.)?(" RE_MAPPING_INDEX ")" +#define RE_MAPPING "(?:(" RE_NODE_NAME ")\\.(?:" RE_MAPPING_STATS "|" RE_MAPPING_HDR "|" RE_MAPPING_TS "|" RE_MAPPING_DATA1 "|" RE_MAPPING_DATA2 ")|(" RE_NODE_NAME ")(?:\\[" RE_MAPPING_RANGE "\\])?)" + /* Forward declarations */ struct vnode; struct sample; @@ -52,6 +62,7 @@ enum class MappingTimestampType { }; struct mapping_entry { + const char *node_name; struct vnode *node; /**< The node to which this mapping refers. */ enum MappingType type; /**< The mapping type. Selects one of the union fields below. */ @@ -67,6 +78,9 @@ struct mapping_entry { struct { int offset; struct signal *signal; + + const char *first; + const char *last; } data; struct { @@ -84,16 +98,18 @@ struct mapping_entry { }; }; -int mapping_update(const struct mapping_entry *e, struct sample *remapped, const struct sample *original); +int mapping_entry_prepare(struct mapping_entry *me, struct vlist *nodes); -int mapping_parse(struct mapping_entry *e, json_t *cfg, struct vlist *nodes); +int mapping_entry_update(const struct mapping_entry *e, struct sample *remapped, const struct sample *original); -int mapping_parse_str(struct mapping_entry *e, const char *str, struct vlist *nodes); +int mapping_entry_parse(struct mapping_entry *e, json_t *cfg); -int mapping_to_str(const struct mapping_entry *me, unsigned index, char **str); +int mapping_entry_parse_str(struct mapping_entry *e, const std::string &str); -int mapping_list_parse(struct vlist *ml, json_t *cfg, struct vlist *nodes); +int mapping_entry_to_str(const struct mapping_entry *me, unsigned index, char **str); -int mapping_list_prepare(struct vlist *ml); +int mapping_list_parse(struct vlist *ml, json_t *cfg); + +int mapping_list_prepare(struct vlist *ml, struct vlist *nodes); int mapping_list_remap(const struct vlist *ml, struct sample *remapped, const struct sample *original); diff --git a/lib/mapping.cpp b/lib/mapping.cpp index 873617843..15da70dcc 100644 --- a/lib/mapping.cpp +++ b/lib/mapping.cpp @@ -20,7 +20,8 @@ * along with this program. If not, see . *********************************************************************************/ -#include +#include +#include #include #include @@ -33,162 +34,83 @@ using namespace villas; using namespace villas::utils; -int mapping_parse_str(struct mapping_entry *me, const char *str, struct vlist *nodes) +int mapping_entry_parse_str(struct mapping_entry *me, const std::string &str) { - char *cpy, *node, *type, *field, *end, *lasts; + std::smatch mr; + std::regex re(RE_MAPPING); - cpy = strdup(str); - if (!cpy) - return -1; + if (!std::regex_match(str, mr, re)) + goto invalid_format; - if (nodes) { - node = strtok_r(cpy, ".", &lasts); - if (!node) { - warning("Missing node name"); - goto invalid_format; - } + if (mr[1].matched) + me->node_name = strdup(mr.str(1).c_str()); + + if (mr[9].matched) + me->node_name = strdup(mr.str(9).c_str()); - me->node = vlist_lookup_name(nodes, node); - if (!me->node) { - warning("Unknown node %s", node); - goto invalid_format; - } - - type = strtok_r(nullptr, ".[", &lasts); - if (!type) - type = strf("data"); - } - else { - me->node = nullptr; - - type = strtok_r(cpy, ".[", &lasts); - if (!type) - goto invalid_format; - } - - if (!strcmp(type, "stats")) { - me->type = MappingType::STATS; - me->length = 1; - - char *metric = strtok_r(nullptr, ".", &lasts); - if (!metric) - goto invalid_format; - - type = strtok_r(nullptr, ".", &lasts); - if (!type) - goto invalid_format; - - me->stats.metric = Stats::lookupMetric(metric); - me->stats.type = Stats::lookupType(type); - } - else if (!strcmp(type, "hdr")) { - me->type = MappingType::HEADER; - me->length = 1; - - field = strtok_r(nullptr, ".", &lasts); - if (!field) { - warning("Missing header type"); - goto invalid_format; - } - - if (!strcmp(field, "sequence")) - me->header.type = MappingHeaderType::SEQUENCE; - else if (!strcmp(field, "length")) - me->header.type = MappingHeaderType::LENGTH; - else { - warning("Invalid header type"); - goto invalid_format; - } - } - else if (!strcmp(type, "ts")) { - me->type = MappingType::TIMESTAMP; - me->length = 2; - - field = strtok_r(nullptr, ".", &lasts); - if (!field) { - warning("Missing timestamp type"); - goto invalid_format; - } - - if (!strcmp(field, "origin")) - me->timestamp.type = MappingTimestampType::ORIGIN; - else if (!strcmp(field, "received")) - me->timestamp.type = MappingTimestampType::RECEIVED; - else { - warning("Invalid timestamp type"); - goto invalid_format; - } - } - else if (!strcmp(type, "data")) { - char *first_str, *last_str; - int first = -1, last = -1; + if (mr[6].matched) { + me->data.first = strdup(mr.str(6).c_str()); + + if (mr[7].matched) + me->data.last = strdup(mr.str(7).c_str()); me->type = MappingType::DATA; + } + else if (mr[10].matched) { + me->data.first = strdup(mr.str(10).c_str()); + + if (mr[11].matched) + me->data.last = strdup(mr.str(11).c_str()); - first_str = strtok_r(nullptr, "-]", &lasts); - if (first_str) { - if (me->node) - first = vlist_lookup_index(&me->node->in.signals, first_str); + me->type = MappingType::DATA; + } + else if (mr[8].matched) { + me->data.first = strdup(mr.str(8).c_str()); - if (first < 0) { - char *endptr; - first = strtoul(first_str, &endptr, 10); - if (endptr != first_str + strlen(first_str)) { - warning("Failed to parse data range"); - goto invalid_format; - } - } - } - else { - /* Map all signals */ - me->data.offset = 0; - me->length = -1; - goto end; - } + me->type = MappingType::DATA; + } + else if (mr[2].matched) { + me->stats.type = Stats::lookupType(mr.str(3)); + me->stats.metric = Stats::lookupMetric(mr.str(2)); - last_str = strtok_r(nullptr, "]", &lasts); - if (last_str) { - if (me->node) - last = vlist_lookup_index(&me->node->in.signals, last_str); - - if (last < 0) { - char *endptr; - last = strtoul(last_str, &endptr, 10); - if (endptr != last_str + strlen(last_str)) { - warning("Failed to parse data range"); - goto invalid_format; - } - } - } + me->type = MappingType::STATS; + } + else if (mr[5].matched) { + if (mr.str(5) == "origin") + me->timestamp.type = MappingTimestampType::ORIGIN; + else if (mr.str(5) == "received") + me->timestamp.type = MappingTimestampType::RECEIVED; else - last = first; /* single element: data[5] => data[5-5] */ - - if (last < first) goto invalid_format; - me->data.offset = first; - me->length = last - first + 1; + me->type = MappingType::TIMESTAMP; } - else - goto invalid_format; + else if (mr[4].matched) { + if (mr.str(4) == "sequence") + me->header.type = MappingHeaderType::SEQUENCE; + else if (mr.str(4) == "length") + me->header.type = MappingHeaderType::LENGTH; + else + goto invalid_format; -end: /* Check that there is no garbage at the end */ - end = strtok_r(nullptr, "", &lasts); - if (end) - goto invalid_format; + me->type = MappingType::HEADER; + } + /* Only node name given.. We map all data */ + else if (me->node_name) { + me->data.first = nullptr; + me->data.last = nullptr; - free(cpy); + me->type = MappingType::DATA; + } return 0; invalid_format: - - free(cpy); - - return -1; + + throw RuntimeError("Failed to parse mapping expression: {}", str); } -int mapping_parse(struct mapping_entry *me, json_t *cfg, struct vlist *nodes) +int mapping_entry_parse(struct mapping_entry *me, json_t *cfg) { const char *str; @@ -196,10 +118,10 @@ int mapping_parse(struct mapping_entry *me, json_t *cfg, struct vlist *nodes) if (!str) return -1; - return mapping_parse_str(me, str, nodes); + return mapping_entry_parse_str(me, str); } -int mapping_list_parse(struct vlist *ml, json_t *cfg, struct vlist *nodes) +int mapping_list_parse(struct vlist *ml, json_t *cfg) { int ret; @@ -221,7 +143,7 @@ int mapping_list_parse(struct vlist *ml, json_t *cfg, struct vlist *nodes) if (!me) throw MemoryAllocationError(); - ret = mapping_parse(me, json_entry, nodes); + ret = mapping_entry_parse(me, json_entry); if (ret) goto out; @@ -319,16 +241,76 @@ int mapping_list_remap(const struct vlist *ml, struct sample *remapped, const st return 0; } -int mapping_list_prepare(struct vlist *ml) +int mapping_entry_prepare(struct mapping_entry *me, struct vlist *nodes) { + if (me->node_name && me->node == nullptr) { + me->node = vlist_lookup_name(nodes, me->node_name); + if (!me->node) + throw RuntimeError("Invalid node name in mapping: {}", me->node_name); + } + + if (me->type == MappingType::DATA) { + int first = -1, last = -1; + + if (me->data.first) { + if (me->node) + first = vlist_lookup_index(&me->node->in.signals, me->data.first); + + if (first < 0) { + char *endptr; + first = strtoul(me->data.first, &endptr, 10); + if (endptr != me->data.first + strlen(me->data.first)) + throw RuntimeError("Failed to parse data index in mapping: {}", me->data.first); + } + } + else { + /* Map all signals */ + me->data.offset = 0; + me->length = -1; + goto end; + } + + if (me->data.last) { + if (me->node) + last = vlist_lookup_index(&me->node->in.signals, me->data.last); + + if (last < 0) { + char *endptr; + last = strtoul(me->data.last, &endptr, 10); + if (endptr != me->data.last + strlen(me->data.last)) + throw RuntimeError("Failed to parse data index in mapping: {}", me->data.last); + } + } + else + last = first; /* single element: data[5] => data[5-5] */ + + if (last < first) + throw RuntimeError("Invalid data range indices for mapping: {} < {}", last, first); + + me->data.offset = first; + me->length = last - first + 1; + } + +end: + if (me->length < 0) { + struct vlist *sigs = node_get_signals(me->node, NodeDir::IN); + + me->length = vlist_length(sigs); + } + + return 0; +} + +int mapping_list_prepare(struct vlist *ml, struct vlist *nodes) +{ + int ret; + for (size_t i = 0, off = 0; i < vlist_length(ml); i++) { struct mapping_entry *me = (struct mapping_entry *) vlist_at(ml, i); - if (me->length < 0) { - struct vlist *sigs = node_get_signals(me->node, NodeDir::IN); - - me->length = vlist_length(sigs); - } + ret = mapping_entry_prepare(me, nodes); + if (ret) + return ret; me->offset = off; off += me->length; @@ -337,7 +319,7 @@ int mapping_list_prepare(struct vlist *ml) return 0; } -int mapping_to_str(const struct mapping_entry *me, unsigned index, char **str) +int mapping_entry_to_str(const struct mapping_entry *me, unsigned index, char **str) { const char *type; diff --git a/lib/signal.cpp b/lib/signal.cpp index e8012e063..0d5f262bf 100644 --- a/lib/signal.cpp +++ b/lib/signal.cpp @@ -53,7 +53,7 @@ int signal_init_from_mapping(struct signal *s, const struct mapping_entry *me, u if (ret) return ret; - ret = mapping_to_str(me, index, &s->name); + ret = mapping_entry_to_str(me, index, &s->name); if (ret) return ret; diff --git a/tests/unit/mapping.cpp b/tests/unit/mapping.cpp index 6a565c9af..34c3c8b7d 100644 --- a/tests/unit/mapping.cpp +++ b/tests/unit/mapping.cpp @@ -34,155 +34,66 @@ Test(mapping, parse_nodes) { int ret; struct mapping_entry m; - struct vlist nodes; - const char *node_names[3] = { "apple", "cherry", "carrot" }; - const char *signal_names[3][4] = { - { "abra", "kadabra", "simsala", "bimm" }, - { "this", "is", "a", "test" }, - { "o", "sole", "mio", "italia" } - }; - - vlist_init(&nodes); - - for (unsigned i = 0; i < ARRAY_LEN(node_names); i++) { - struct vnode *n = new struct vnode; - cr_assert_not_null(n); - - n->name = strdup(node_names[i]); - - vlist_init(&n->in.signals); - - for (unsigned j = 0; j < ARRAY_LEN(signal_names[i]); j++) { - struct signal *sig; - - sig = signal_create(signal_names[i][j], nullptr, SignalType::FLOAT); - cr_assert_not_null(sig); - - vlist_push(&n->in.signals, sig); - } - - vlist_push(&nodes, n); - } - - ret = mapping_parse_str(&m, "apple.ts.origin", &nodes); + ret = mapping_entry_parse_str(&m, "apple.ts.origin"); cr_assert_eq(ret, 0); - cr_assert_eq(m.node, vlist_lookup_name(&nodes, "apple")); + cr_assert_str_eq(m.node_name, "apple"); cr_assert_eq(m.type, MappingType::TIMESTAMP); cr_assert_eq(m.timestamp.type, MappingTimestampType::ORIGIN); - ret = mapping_parse_str(&m, "cherry.stats.owd.mean", &nodes); + ret = mapping_entry_parse_str(&m, "cherry.stats.owd.mean"); cr_assert_eq(ret, 0); - cr_assert_eq(m.node, vlist_lookup_name(&nodes, "cherry")); + cr_assert_str_eq(m.node_name, "cherry"); cr_assert_eq(m.type, MappingType::STATS); cr_assert_eq(m.stats.metric, Stats::Metric::OWD); cr_assert_eq(m.stats.type, Stats::Type::MEAN); - ret = mapping_parse_str(&m, "carrot.data[1-2]", &nodes); + ret = mapping_entry_parse_str(&m, "carrot.data[1-2]"); cr_assert_eq(ret, 0); - cr_assert_eq(m.node, vlist_lookup_name(&nodes, "carrot")); + cr_assert_str_eq(m.node_name, "carrot"); cr_assert_eq(m.type, MappingType::DATA); - cr_assert_eq(m.data.offset, 1); - cr_assert_eq(m.length, 2); + cr_assert_str_eq(m.data.first, "1"); + cr_assert_str_eq(m.data.last, "2"); - ret = mapping_parse_str(&m, "carrot", &nodes); + ret = mapping_entry_parse_str(&m, "carrot"); cr_assert_eq(ret, 0); - cr_assert_eq(m.node, vlist_lookup_name(&nodes, "carrot")); + cr_assert_str_eq(m.node_name, "carrot"); cr_assert_eq(m.type, MappingType::DATA); - cr_assert_eq(m.data.offset, 0); - cr_assert_eq(m.length, -1); + cr_assert_eq(m.data.first, nullptr); + cr_assert_eq(m.data.last, nullptr); - ret = mapping_parse_str(&m, "carrot.data[sole]", &nodes); + ret = mapping_entry_parse_str(&m, "carrot.data[sole]"); cr_assert_eq(ret, 0); - cr_assert_eq(m.node, vlist_lookup_name(&nodes, "carrot")); + cr_assert_str_eq(m.node_name, "carrot"); cr_assert_eq(m.type, MappingType::DATA); - cr_assert_eq(m.data.offset, 1); - cr_assert_eq(m.length, 1); + cr_assert_str_eq(m.data.first, "sole"); + cr_assert_eq(m.data.last, nullptr); - ret = mapping_parse_str(&m, "carrot.data[sole-mio]", &nodes); + ret = mapping_entry_parse_str(&m, "carrot.sole"); cr_assert_eq(ret, 0); - cr_assert_eq(m.node, vlist_lookup_name(&nodes, "carrot")); + cr_assert_str_eq(m.node_name, "carrot"); cr_assert_eq(m.type, MappingType::DATA); - cr_assert_eq(m.data.offset, 1); - cr_assert_eq(m.length, 2); + cr_assert_str_eq(m.data.first, "sole"); + cr_assert_eq(m.data.last, nullptr); - ret = vlist_destroy(&nodes, nullptr, true); + ret = mapping_entry_parse_str(&m, "carrot.data.sole"); cr_assert_eq(ret, 0); -} - -Test(mapping, parse) -{ - int ret; - struct mapping_entry m; - - ret = mapping_parse_str(&m, "ts.origin", nullptr); - cr_assert_eq(ret, 0); - cr_assert_eq(m.type, MappingType::TIMESTAMP); - cr_assert_eq(m.timestamp.type, MappingTimestampType::ORIGIN); - - ret = mapping_parse_str(&m, "hdr.sequence", nullptr); - cr_assert_eq(ret, 0); - cr_assert_eq(m.type, MappingType::HEADER); - cr_assert_eq(m.header.type, MappingHeaderType::SEQUENCE); - - ret = mapping_parse_str(&m, "stats.owd.mean", nullptr); - cr_assert_eq(ret, 0); - cr_assert_eq(m.type, MappingType::STATS); - cr_assert_eq(m.stats.metric, Stats::Metric::OWD); - cr_assert_eq(m.stats.type, Stats::Type::MEAN); - - ret = mapping_parse_str(&m, "data[1-2]", nullptr); - cr_assert_eq(ret, 0); - cr_assert_eq(m.type, MappingType::DATA); - cr_assert_eq(m.data.offset, 1); - cr_assert_eq(m.length, 2); - - ret = mapping_parse_str(&m, "data[5-5]", nullptr); - cr_assert_eq(ret, 0); - cr_assert_eq(m.type, MappingType::DATA); - cr_assert_eq(m.data.offset, 5); - cr_assert_eq(m.length, 1); - - ret = mapping_parse_str(&m, "data[22]", nullptr); - cr_assert_eq(ret, 0); - cr_assert_eq(m.type, MappingType::DATA); - cr_assert_eq(m.data.offset, 22); - cr_assert_eq(m.length, 1); - - ret = mapping_parse_str(&m, "data", nullptr); - cr_assert_eq(ret, 0); - cr_assert_eq(m.type, MappingType::DATA); - cr_assert_eq(m.data.offset, 0); - cr_assert_eq(m.length, -1); - - ret = mapping_parse_str(&m, "data[]", nullptr); - cr_assert_eq(ret, 0); - cr_assert_eq(m.type, MappingType::DATA); - cr_assert_eq(m.data.offset, 0); - cr_assert_eq(m.length, -1); - - ret = mapping_parse_str(&m, "data[1.1-2f]", nullptr); - cr_assert_neq(ret, 0); - - /* Missing parts */ - ret = mapping_parse_str(&m, "stats.owd", nullptr); - cr_assert_neq(ret, 0); - - /* This a type */ - ret = mapping_parse_str(&m, "hdr.sequences", nullptr); - cr_assert_neq(ret, 0); - - /* Check for superfluous chars at the end */ - ret = mapping_parse_str(&m, "hdr.ts.origin.bla", nullptr); - cr_assert_neq(ret, 0); - - ret = mapping_parse_str(&m, "hdr.ts.origin.", nullptr); - cr_assert_neq(ret, 0); - - ret = mapping_parse_str(&m, "data[1-2]bla", nullptr); - cr_assert_neq(ret, 0); - - /* Negative length of chunk */ - ret = mapping_parse_str(&m, "data[5-3]", nullptr); - cr_assert_neq(ret, 0); + cr_assert_str_eq(m.node_name, "carrot"); + cr_assert_eq(m.type, MappingType::DATA); + cr_assert_str_eq(m.data.first, "sole"); + cr_assert_eq(m.data.last, nullptr); + + ret = mapping_entry_parse_str(&m, "carrot.data[sole-mio]"); + cr_assert_eq(ret, 0); + cr_assert_str_eq(m.node_name, "carrot"); + cr_assert_eq(m.type, MappingType::DATA); + cr_assert_str_eq(m.data.first, "sole"); + cr_assert_str_eq(m.data.last, "mio"); + + ret = mapping_entry_parse_str(&m, "carrot[sole-mio]"); + cr_assert_eq(ret, 0); + cr_assert_str_eq(m.node_name, "carrot"); + cr_assert_eq(m.type, MappingType::DATA); + cr_assert_str_eq(m.data.first, "sole"); + cr_assert_str_eq(m.data.last, "mio"); }