Home | History | Annotate | Download | only in cls
      1 /*
      2  * lib/route/cls/ematch.c	Extended Matches
      3  *
      4  *	This library is free software; you can redistribute it and/or
      5  *	modify it under the terms of the GNU Lesser General Public
      6  *	License as published by the Free Software Foundation version 2.1
      7  *	of the License.
      8  *
      9  * Copyright (c) 2008-2013 Thomas Graf <tgraf (at) suug.ch>
     10  */
     11 
     12 /**
     13  * @ingroup cls
     14  * @defgroup ematch Extended Match
     15  *
     16  * @{
     17  */
     18 
     19 #include <netlink-private/netlink.h>
     20 #include <netlink-private/tc.h>
     21 #include <netlink/netlink.h>
     22 #include <netlink/route/classifier.h>
     23 #include <netlink/route/cls/ematch.h>
     24 #include <netlink/route/cls/ematch/cmp.h>
     25 
     26 #include "ematch_syntax.h"
     27 #include "ematch_grammar.h"
     28 
     29 /**
     30  * @name Module API
     31  * @{
     32  */
     33 
     34 static NL_LIST_HEAD(ematch_ops_list);
     35 
     36 /**
     37  * Register ematch module
     38  * @arg ops		Module operations.
     39  *
     40  * This function must be called by each ematch module at initialization
     41  * time. It registers the calling module as available module.
     42  *
     43  * @return 0 on success or a negative error code.
     44  */
     45 int rtnl_ematch_register(struct rtnl_ematch_ops *ops)
     46 {
     47 	if (rtnl_ematch_lookup_ops(ops->eo_kind))
     48 		return -NLE_EXIST;
     49 
     50 	NL_DBG(1, "ematch module \"%s\" registered\n", ops->eo_name);
     51 
     52 	nl_list_add_tail(&ops->eo_list, &ematch_ops_list);
     53 
     54 	return 0;
     55 }
     56 
     57 /**
     58  * Lookup ematch module by identification number.
     59  * @arg kind		Module kind.
     60  *
     61  * Searches the list of registered ematch modules for match and returns it.
     62  *
     63  * @return Module operations or NULL if not found.
     64  */
     65 struct rtnl_ematch_ops *rtnl_ematch_lookup_ops(int kind)
     66 {
     67 	struct rtnl_ematch_ops *ops;
     68 
     69 	nl_list_for_each_entry(ops, &ematch_ops_list, eo_list)
     70 		if (ops->eo_kind == kind)
     71 			return ops;
     72 
     73 	return NULL;
     74 }
     75 
     76 /**
     77  * Lookup ematch module by name
     78  * @arg name		Name of ematch module.
     79  *
     80  * Searches the list of registered ematch modules for a match and returns it.
     81  *
     82  * @return Module operations or NULL if not fuond.
     83  */
     84 struct rtnl_ematch_ops *rtnl_ematch_lookup_ops_by_name(const char *name)
     85 {
     86 	struct rtnl_ematch_ops *ops;
     87 
     88 	nl_list_for_each_entry(ops, &ematch_ops_list, eo_list)
     89 		if (!strcasecmp(ops->eo_name, name))
     90 			return ops;
     91 
     92 	return NULL;
     93 }
     94 
     95 /** @} */
     96 
     97 /**
     98  * @name Match
     99  */
    100 
    101 /**
    102  * Allocate ematch object.
    103  *
    104  * Allocates and initializes an ematch object.
    105  *
    106  * @return New ematch object or NULL.
    107  */
    108 struct rtnl_ematch *rtnl_ematch_alloc(void)
    109 {
    110 	struct rtnl_ematch *e;
    111 
    112 	if (!(e = calloc(1, sizeof(*e))))
    113 		return NULL;
    114 
    115 	NL_DBG(2, "allocated ematch %p\n", e);
    116 
    117 	NL_INIT_LIST_HEAD(&e->e_list);
    118 	NL_INIT_LIST_HEAD(&e->e_childs);
    119 
    120 	return e;
    121 }
    122 
    123 /**
    124  * Add ematch to the end of the parent's list of children.
    125  * @arg parent		parent ematch object
    126  * @arg child		ematch object to be added to parent
    127  *
    128  * The parent must be a container ematch.
    129  */
    130 int rtnl_ematch_add_child(struct rtnl_ematch *parent,
    131 			   struct rtnl_ematch *child)
    132 {
    133 	if (parent->e_kind != TCF_EM_CONTAINER)
    134 		return -NLE_OPNOTSUPP;
    135 
    136 	NL_DBG(2, "added ematch %p \"%s\" to container %p\n",
    137 		  child, child->e_ops->eo_name, parent);
    138 
    139 	nl_list_add_tail(&child->e_list, &parent->e_childs);
    140 
    141 	return 0;
    142 }
    143 
    144 /**
    145  * Remove ematch from the list of ematches it is linked to.
    146  * @arg ematch		ematch object
    147  */
    148 void rtnl_ematch_unlink(struct rtnl_ematch *ematch)
    149 {
    150 	NL_DBG(2, "unlinked ematch %p from any lists\n", ematch);
    151 
    152 	if (!nl_list_empty(&ematch->e_childs))
    153 		NL_DBG(1, "warning: ematch %p with childs was unlinked\n",
    154 			  ematch);
    155 
    156 	nl_list_del(&ematch->e_list);
    157 	nl_init_list_head(&ematch->e_list);
    158 }
    159 
    160 void rtnl_ematch_free(struct rtnl_ematch *ematch)
    161 {
    162 	NL_DBG(2, "freed ematch %p\n", ematch);
    163 	rtnl_ematch_unlink(ematch);
    164 	free(ematch->e_data);
    165 	free(ematch);
    166 }
    167 
    168 int rtnl_ematch_set_ops(struct rtnl_ematch *ematch, struct rtnl_ematch_ops *ops)
    169 {
    170 	if (ematch->e_ops)
    171 		return -NLE_EXIST;
    172 
    173 	ematch->e_ops = ops;
    174 	ematch->e_kind = ops->eo_kind;
    175 
    176 	if (ops->eo_datalen) {
    177 		ematch->e_data = calloc(1, ops->eo_datalen);
    178 		if (!ematch->e_data)
    179 			return -NLE_NOMEM;
    180 
    181 		ematch->e_datalen = ops->eo_datalen;
    182 	}
    183 
    184 	return 0;
    185 }
    186 
    187 int rtnl_ematch_set_kind(struct rtnl_ematch *ematch, uint16_t kind)
    188 {
    189 	struct rtnl_ematch_ops *ops;
    190 
    191 	if (ematch->e_kind)
    192 		return -NLE_EXIST;
    193 
    194 	ematch->e_kind = kind;
    195 
    196 	if ((ops = rtnl_ematch_lookup_ops(kind)))
    197 		rtnl_ematch_set_ops(ematch, ops);
    198 
    199 	return 0;
    200 }
    201 
    202 int rtnl_ematch_set_name(struct rtnl_ematch *ematch, const char *name)
    203 {
    204 	struct rtnl_ematch_ops *ops;
    205 
    206 	if (ematch->e_kind)
    207 		return -NLE_EXIST;
    208 
    209 	if (!(ops = rtnl_ematch_lookup_ops_by_name(name)))
    210 		return -NLE_OPNOTSUPP;
    211 
    212 	rtnl_ematch_set_ops(ematch, ops);
    213 
    214 	return 0;
    215 }
    216 
    217 void rtnl_ematch_set_flags(struct rtnl_ematch *ematch, uint16_t flags)
    218 {
    219 	ematch->e_flags |= flags;
    220 }
    221 
    222 void rtnl_ematch_unset_flags(struct rtnl_ematch *ematch, uint16_t flags)
    223 {
    224 	ematch->e_flags &= ~flags;
    225 }
    226 
    227 uint16_t rtnl_ematch_get_flags(struct rtnl_ematch *ematch)
    228 {
    229 	return ematch->e_flags;
    230 }
    231 
    232 void *rtnl_ematch_data(struct rtnl_ematch *ematch)
    233 {
    234 	return ematch->e_data;
    235 }
    236 
    237 /** @} */
    238 
    239 /**
    240  * @name Tree
    241  */
    242 
    243 /**
    244  * Allocate ematch tree object
    245  * @arg progid		program id
    246  */
    247 struct rtnl_ematch_tree *rtnl_ematch_tree_alloc(uint16_t progid)
    248 {
    249 	struct rtnl_ematch_tree *tree;
    250 
    251 	if (!(tree = calloc(1, sizeof(*tree))))
    252 		return NULL;
    253 
    254 	NL_INIT_LIST_HEAD(&tree->et_list);
    255 	tree->et_progid = progid;
    256 
    257 	NL_DBG(2, "allocated new ematch tree %p, progid=%u\n", tree, progid);
    258 
    259 	return tree;
    260 }
    261 
    262 static void free_ematch_list(struct nl_list_head *head)
    263 {
    264 	struct rtnl_ematch *pos, *next;
    265 
    266 	nl_list_for_each_entry_safe(pos, next, head, e_list) {
    267 		if (!nl_list_empty(&pos->e_childs))
    268 			free_ematch_list(&pos->e_childs);
    269 		rtnl_ematch_free(pos);
    270 	}
    271 }
    272 
    273 /**
    274  * Free ematch tree object
    275  * @arg tree		ematch tree object
    276  *
    277  * This function frees the ematch tree and all ematches attached to it.
    278  */
    279 void rtnl_ematch_tree_free(struct rtnl_ematch_tree *tree)
    280 {
    281 	if (!tree)
    282 		return;
    283 
    284 	free_ematch_list(&tree->et_list);
    285 
    286 	NL_DBG(2, "Freed ematch tree %p\n", tree);
    287 
    288 	free(tree);
    289 }
    290 
    291 /**
    292  * Add ematch object to the end of the ematch tree
    293  * @arg tree		ematch tree object
    294  * @arg ematch		ematch object to add
    295  */
    296 void rtnl_ematch_tree_add(struct rtnl_ematch_tree *tree,
    297 			  struct rtnl_ematch *ematch)
    298 {
    299 	nl_list_add_tail(&ematch->e_list, &tree->et_list);
    300 }
    301 
    302 static inline uint32_t container_ref(struct rtnl_ematch *ematch)
    303 {
    304 	return *((uint32_t *) rtnl_ematch_data(ematch));
    305 }
    306 
    307 static int link_tree(struct rtnl_ematch *index[], int nmatches, int pos,
    308 		     struct nl_list_head *root)
    309 {
    310 	struct rtnl_ematch *ematch;
    311 	int i;
    312 
    313 	for (i = pos; i < nmatches; i++) {
    314 		ematch = index[i];
    315 
    316 		nl_list_add_tail(&ematch->e_list, root);
    317 
    318 		if (ematch->e_kind == TCF_EM_CONTAINER)
    319 			link_tree(index, nmatches, container_ref(ematch),
    320 				  &ematch->e_childs);
    321 
    322 		if (!(ematch->e_flags & TCF_EM_REL_MASK))
    323 			return 0;
    324 	}
    325 
    326 	/* Last entry in chain can't possibly have no relation */
    327 	return -NLE_INVAL;
    328 }
    329 
    330 static struct nla_policy tree_policy[TCA_EMATCH_TREE_MAX+1] = {
    331 	[TCA_EMATCH_TREE_HDR]  = { .minlen=sizeof(struct tcf_ematch_tree_hdr) },
    332 	[TCA_EMATCH_TREE_LIST] = { .type = NLA_NESTED },
    333 };
    334 
    335 /**
    336  * Parse ematch netlink attributes
    337  *
    338  * @return 0 on success or a negative error code.
    339  */
    340 int rtnl_ematch_parse_attr(struct nlattr *attr, struct rtnl_ematch_tree **result)
    341 {
    342 	struct nlattr *a, *tb[TCA_EMATCH_TREE_MAX+1];
    343 	struct tcf_ematch_tree_hdr *thdr;
    344 	struct rtnl_ematch_tree *tree;
    345 	struct rtnl_ematch **index;
    346 	int nmatches = 0, err, remaining;
    347 
    348 	NL_DBG(2, "Parsing attribute %p as ematch tree\n", attr);
    349 
    350 	err = nla_parse_nested(tb, TCA_EMATCH_TREE_MAX, attr, tree_policy);
    351 	if (err < 0)
    352 		return err;
    353 
    354 	if (!tb[TCA_EMATCH_TREE_HDR])
    355 		return -NLE_MISSING_ATTR;
    356 
    357 	thdr = nla_data(tb[TCA_EMATCH_TREE_HDR]);
    358 
    359 	/* Ignore empty trees */
    360 	if (thdr->nmatches == 0) {
    361 		NL_DBG(2, "Ignoring empty ematch configuration\n");
    362 		return 0;
    363 	}
    364 
    365 	if (!tb[TCA_EMATCH_TREE_LIST])
    366 		return -NLE_MISSING_ATTR;
    367 
    368 	NL_DBG(2, "ematch tree found with nmatches=%u, progid=%u\n",
    369 		  thdr->nmatches, thdr->progid);
    370 
    371 	/*
    372 	 * Do some basic sanity checking since we will allocate
    373 	 * index[thdr->nmatches]. Calculate how many ematch headers fit into
    374 	 * the provided data and make sure nmatches does not exceed it.
    375 	 */
    376 	if (thdr->nmatches > (nla_len(tb[TCA_EMATCH_TREE_LIST]) /
    377 			      nla_total_size(sizeof(struct tcf_ematch_hdr))))
    378 		return -NLE_INVAL;
    379 
    380 	if (!(index = calloc(thdr->nmatches, sizeof(struct rtnl_ematch *))))
    381 		return -NLE_NOMEM;
    382 
    383 	if (!(tree = rtnl_ematch_tree_alloc(thdr->progid))) {
    384 		err = -NLE_NOMEM;
    385 		goto errout;
    386 	}
    387 
    388 	nla_for_each_nested(a, tb[TCA_EMATCH_TREE_LIST], remaining) {
    389 		struct rtnl_ematch_ops *ops;
    390 		struct tcf_ematch_hdr *hdr;
    391 		struct rtnl_ematch *ematch;
    392 		void *data;
    393 		size_t len;
    394 
    395 		NL_DBG(3, "parsing ematch attribute %d, len=%u\n",
    396 			  nmatches+1, nla_len(a));
    397 
    398 		if (nla_len(a) < sizeof(*hdr)) {
    399 			err = -NLE_INVAL;
    400 			goto errout;
    401 		}
    402 
    403 		/* Quit as soon as we've parsed more matches than expected */
    404 		if (nmatches >= thdr->nmatches) {
    405 			err = -NLE_RANGE;
    406 			goto errout;
    407 		}
    408 
    409 		hdr = nla_data(a);
    410 		data = nla_data(a) + NLA_ALIGN(sizeof(*hdr));
    411 		len = nla_len(a) - NLA_ALIGN(sizeof(*hdr));
    412 
    413 		NL_DBG(3, "ematch attribute matchid=%u, kind=%u, flags=%u\n",
    414 			  hdr->matchid, hdr->kind, hdr->flags);
    415 
    416 		/*
    417 		 * Container matches contain a reference to another sequence
    418 		 * of matches. Ensure that the reference is within boundries.
    419 		 */
    420 		if (hdr->kind == TCF_EM_CONTAINER &&
    421 		    *((uint32_t *) data) >= thdr->nmatches) {
    422 			err = -NLE_INVAL;
    423 			goto errout;
    424 		}
    425 
    426 		if (!(ematch = rtnl_ematch_alloc())) {
    427 			err = -NLE_NOMEM;
    428 			goto errout;
    429 		}
    430 
    431 		ematch->e_id = hdr->matchid;
    432 		ematch->e_kind = hdr->kind;
    433 		ematch->e_flags = hdr->flags;
    434 
    435 		if ((ops = rtnl_ematch_lookup_ops(hdr->kind))) {
    436 			if (ops->eo_minlen && len < ops->eo_minlen) {
    437 				rtnl_ematch_free(ematch);
    438 				err = -NLE_INVAL;
    439 				goto errout;
    440 			}
    441 
    442 			rtnl_ematch_set_ops(ematch, ops);
    443 
    444 			if (ops->eo_parse &&
    445 			    (err = ops->eo_parse(ematch, data, len)) < 0) {
    446 				rtnl_ematch_free(ematch);
    447 				goto errout;
    448 			}
    449 		}
    450 
    451 		NL_DBG(3, "index[%d] = %p\n", nmatches, ematch);
    452 		index[nmatches++] = ematch;
    453 	}
    454 
    455 	if (nmatches != thdr->nmatches) {
    456 		err = -NLE_INVAL;
    457 		goto errout;
    458 	}
    459 
    460 	err = link_tree(index, nmatches, 0, &tree->et_list);
    461 	if (err < 0)
    462 		goto errout;
    463 
    464 	free(index);
    465 	*result = tree;
    466 
    467 	return 0;
    468 
    469 errout:
    470 	rtnl_ematch_tree_free(tree);
    471 	free(index);
    472 	return err;
    473 }
    474 
    475 static void dump_ematch_sequence(struct nl_list_head *head,
    476 				 struct nl_dump_params *p)
    477 {
    478 	struct rtnl_ematch *match;
    479 
    480 	nl_list_for_each_entry(match, head, e_list) {
    481 		if (match->e_flags & TCF_EM_INVERT)
    482 			nl_dump(p, "!");
    483 
    484 		if (match->e_kind == TCF_EM_CONTAINER) {
    485 			nl_dump(p, "(");
    486 			dump_ematch_sequence(&match->e_childs, p);
    487 			nl_dump(p, ")");
    488 		} else if (!match->e_ops) {
    489 			nl_dump(p, "[unknown ematch %d]", match->e_kind);
    490 		} else {
    491 			if (match->e_ops->eo_dump)
    492 				match->e_ops->eo_dump(match, p);
    493 			else
    494 				nl_dump(p, "[data]");
    495 		}
    496 
    497 		switch (match->e_flags & TCF_EM_REL_MASK) {
    498 		case TCF_EM_REL_AND:
    499 			nl_dump(p, " AND ");
    500 			break;
    501 		case TCF_EM_REL_OR:
    502 			nl_dump(p, " OR ");
    503 			break;
    504 		default:
    505 			/* end of first level ematch sequence */
    506 			return;
    507 		}
    508 	}
    509 }
    510 
    511 void rtnl_ematch_tree_dump(struct rtnl_ematch_tree *tree,
    512 			   struct nl_dump_params *p)
    513 {
    514 	if (!tree)
    515 		BUG();
    516 
    517 	dump_ematch_sequence(&tree->et_list, p);
    518 	nl_dump(p, "\n");
    519 }
    520 
    521 static int update_container_index(struct nl_list_head *list, int *index)
    522 {
    523 	struct rtnl_ematch *e;
    524 
    525 	nl_list_for_each_entry(e, list, e_list)
    526 		e->e_index = (*index)++;
    527 
    528 	nl_list_for_each_entry(e, list, e_list) {
    529 		if (e->e_kind == TCF_EM_CONTAINER) {
    530 			int err;
    531 
    532 			if (nl_list_empty(&e->e_childs))
    533 				return -NLE_OBJ_NOTFOUND;
    534 
    535 			*((uint32_t *) e->e_data) = *index;
    536 
    537 			err = update_container_index(&e->e_childs, index);
    538 			if (err < 0)
    539 				return err;
    540 		}
    541 	}
    542 
    543 	return 0;
    544 }
    545 
    546 static int fill_ematch_sequence(struct nl_msg *msg, struct nl_list_head *list)
    547 {
    548 	struct rtnl_ematch *e;
    549 
    550 	nl_list_for_each_entry(e, list, e_list) {
    551 		struct tcf_ematch_hdr match = {
    552 			.matchid = e->e_id,
    553 			.kind = e->e_kind,
    554 			.flags = e->e_flags,
    555 		};
    556 		struct nlattr *attr;
    557 		int err = 0;
    558 
    559 		if (!(attr = nla_nest_start(msg, e->e_index + 1)))
    560 			return -NLE_NOMEM;
    561 
    562 		if (nlmsg_append(msg, &match, sizeof(match), 0) < 0)
    563 			return -NLE_NOMEM;
    564 
    565 		if (e->e_ops->eo_fill)
    566 			err = e->e_ops->eo_fill(e, msg);
    567 		else if (e->e_flags & TCF_EM_SIMPLE)
    568 			err = nlmsg_append(msg, e->e_data, 4, 0);
    569 		else if (e->e_datalen > 0)
    570 			err = nlmsg_append(msg, e->e_data, e->e_datalen, 0);
    571 
    572 		NL_DBG(3, "msg %p: added ematch [%d] id=%d kind=%d flags=%d\n",
    573 			  msg, e->e_index, match.matchid, match.kind, match.flags);
    574 
    575 		if (err < 0)
    576 			return -NLE_NOMEM;
    577 
    578 		nla_nest_end(msg, attr);
    579 	}
    580 
    581 	nl_list_for_each_entry(e, list, e_list) {
    582 		if (e->e_kind == TCF_EM_CONTAINER &&
    583 		    fill_ematch_sequence(msg, &e->e_childs) < 0)
    584 			return -NLE_NOMEM;
    585 	}
    586 
    587 	return 0;
    588 }
    589 
    590 int rtnl_ematch_fill_attr(struct nl_msg *msg, int attrid,
    591 			  struct rtnl_ematch_tree *tree)
    592 {
    593 	struct tcf_ematch_tree_hdr thdr = {
    594 		.progid = tree->et_progid,
    595 	};
    596 	struct nlattr *list, *topattr;
    597 	int err, index = 0;
    598 
    599 	/* Assign index number to each ematch to allow for references
    600 	 * to be made while constructing the sequence of matches. */
    601 	err = update_container_index(&tree->et_list, &index);
    602 	if (err < 0)
    603 		return err;
    604 
    605 	if (!(topattr = nla_nest_start(msg, attrid)))
    606 		goto nla_put_failure;
    607 
    608 	thdr.nmatches = index;
    609 	NLA_PUT(msg, TCA_EMATCH_TREE_HDR, sizeof(thdr), &thdr);
    610 
    611 	if (!(list = nla_nest_start(msg, TCA_EMATCH_TREE_LIST)))
    612 		goto nla_put_failure;
    613 
    614 	if (fill_ematch_sequence(msg, &tree->et_list) < 0)
    615 		goto nla_put_failure;
    616 
    617 	nla_nest_end(msg, list);
    618 
    619 	nla_nest_end(msg, topattr);
    620 
    621 	return 0;
    622 
    623 nla_put_failure:
    624 	return -NLE_NOMEM;
    625 }
    626 
    627 /** @} */
    628 
    629 extern int ematch_parse(void *, char **, struct nl_list_head *);
    630 
    631 int rtnl_ematch_parse_expr(const char *expr, char **errp,
    632 			   struct rtnl_ematch_tree **result)
    633 {
    634 	struct rtnl_ematch_tree *tree;
    635 	YY_BUFFER_STATE buf = NULL;
    636 	yyscan_t scanner = NULL;
    637 	int err;
    638 
    639 	NL_DBG(2, "Parsing ematch expression \"%s\"\n", expr);
    640 
    641 	if (!(tree = rtnl_ematch_tree_alloc(RTNL_EMATCH_PROGID)))
    642 		return -NLE_FAILURE;
    643 
    644 	if ((err = ematch_lex_init(&scanner)) < 0) {
    645 		err = -NLE_FAILURE;
    646 		goto errout;
    647 	}
    648 
    649 	buf = ematch__scan_string(expr, scanner);
    650 
    651 	if ((err = ematch_parse(scanner, errp, &tree->et_list)) != 0) {
    652 		ematch__delete_buffer(buf, scanner);
    653 		err = -NLE_PARSE_ERR;
    654 		goto errout;
    655 	}
    656 
    657 	ematch_lex_destroy(scanner);
    658 	*result = tree;
    659 
    660 	return 0;
    661 
    662 errout:
    663 	if (scanner)
    664 		ematch_lex_destroy(scanner);
    665 
    666 	rtnl_ematch_tree_free(tree);
    667 
    668 	return err;
    669 }
    670 
    671 static const char *layer_txt[] = {
    672 	[TCF_LAYER_LINK]	= "eth",
    673 	[TCF_LAYER_NETWORK]	= "ip",
    674 	[TCF_LAYER_TRANSPORT]	= "tcp",
    675 };
    676 
    677 char *rtnl_ematch_offset2txt(uint8_t layer, uint16_t offset, char *buf, size_t len)
    678 {
    679 	snprintf(buf, len, "%s+%u",
    680 		 (layer <= TCF_LAYER_MAX) ? layer_txt[layer] : "?",
    681 		 offset);
    682 
    683 	return buf;
    684 }
    685 
    686 static const char *operand_txt[] = {
    687 	[TCF_EM_OPND_EQ] = "=",
    688 	[TCF_EM_OPND_LT] = "<",
    689 	[TCF_EM_OPND_GT] = ">",
    690 };
    691 
    692 char *rtnl_ematch_opnd2txt(uint8_t opnd, char *buf, size_t len)
    693 {
    694 	snprintf(buf, len, "%s",
    695 		opnd < ARRAY_SIZE(operand_txt) ? operand_txt[opnd] : "?");
    696 
    697 	return buf;
    698 }
    699 
    700 /** @} */
    701