On Mon, Jun 26, 2023 at 5:54 PM Alexander Zubkov <[email protected]> wrote:
>
> On Mon, Jun 26, 2023 at 5:43 PM Ondrej Zajicek <[email protected]> wrote:
> >
> > On Mon, Jun 26, 2023 at 03:24:47AM +0200, Alexander Zubkov wrote:
> > > On Sat, Jun 24, 2023 at 3:16 PM Ondrej Zajicek <[email protected]>
> > > wrote:
> > > >
> > > > On Sat, Jun 24, 2023 at 02:20:03AM +0200, Alexander Zubkov wrote:
> > > > > > Yes, the original idea there was to add bytestring as a data type,
> > > > > > make
> > > > > > hex() a regular (filter) function instead of special function-like
> > > > > > syntax, and add equivalent of 'expr' grammar term for other data
> > > > > > types.
> > > > > >
> > > > >
> > > > > I see. I think I can look into preparing a patch for that too.
> > > > > But for such variant I would suggest using function names like
> > > > > "from_hex/base64" instead of "hex/base64", or something including
> > > > > bytestring reference: "bs_hex". Because the simple variants could be
> > > > > misleading when used not only in the limited set of scopes.
> > > > > they can be thought of converting to hex/base64 representation too.
> > > > > Or they
> > > > > could collide with "hex" function to convert from string to int, which
> > > > > someone would need to implement in the future.
> > > >
> > > > Yes, that is true.
> > > >
> > > > You can try it if you are brave enough to add new f_val type.
> > >
> > > Take a look at the patch, please. Waiting for the critics and
> > > improvement suggestions.
> >
> > Hi
> >
> > It looks pretty good. First, could you split it to at least four patches?
>
> Sure. I'll provide split patches later.
>
> >
> > 1) unrelated changes, like the newline-in-string-constant
> > 2) preparatory changes (functions in lib/bytestring.c, change to BYTESTRING
> > lexer)
> > 3) adding bytestring type to filter code (including FI_FROM_HEX inst)
Added patches up to this point. There are also some fixes and
modification. For example, I noted that 'bytestring' symbol for the
type name conflicts with lexer's BYTESTRING id. So I had to rename
lexer's BYTESTRING to BYTETEXT (like it is done for strings).
For the following patches it is better to decide the structure of the
new *eval* functions.
> > 4) change to parser related to f_eval_val(), bytestring nonterminal and so
> > on.
> >
> > Some more comments:
> >
> > > It was needed to add another function like f_eval_int(), so I decided
> > > to do some more generic approach and replaced all occurences of
> > > f_eval_int() with it.
> >
> > That is good approach, although it would be probably better to call this
> > function like cf_eval(), associated macro as cf_eval_val, and keep some
> > inline functions like cf_eval_int(), cf_eval_bs() and so on.
> >
> > Or perhaps cf_eval() could return f_val as return value, and have
> > shorthand functions like:
> >
> > static inline cf_eval_int(..) { return cf_eval(.., T_INT).i; }
I actually tried first to return the struct instead of modifying it by
a reference. But for that we need to have "struct f_val" known in
filter/filter.h, which is defined in filter/data.h. But that causes
some circular dependencies problem. I didn't dig deep into it, but
maybe it is possible to solve the conflict in a clean way.
> >
> > I will give more comments later.
> >
> > --
> > Elen sila lumenn' omentielvo
> >
> > Ondrej 'Santiago' Zajicek (email: [email protected])
> > OpenPGP encrypted e-mails preferred (KeyID 0x11DEADC3, wwwkeys.pgp.net)
> > "To err is human -- to blame it on a computer is even more so."
From 41542bbffebe351b1df96210849a1a22b3c74474 Mon Sep 17 00:00:00 2001
From: Alexander Zubkov <[email protected]>
Date: Tue, 27 Jun 2023 00:53:05 +0200
Subject: [PATCH] Use more proper pointers to constant bytestrings
---
conf/confbase.Y | 2 +-
proto/radv/config.Y | 2 +-
proto/radv/radv.h | 2 +-
3 files changed, 3 insertions(+), 3 deletions(-)
diff --git a/conf/confbase.Y b/conf/confbase.Y
index 3e8f5807..3dd5fed7 100644
--- a/conf/confbase.Y
+++ b/conf/confbase.Y
@@ -94,7 +94,7 @@ CF_DECLS
struct channel_limit cl;
struct timeformat *tf;
mpls_label_stack *mls;
- struct bytestring *bs;
+ const struct bytestring *bs;
}
%token END CLI_MARKER INVALID_TOKEN ELSECOL DDOT
diff --git a/proto/radv/config.Y b/proto/radv/config.Y
index db683194..eeafe6f4 100644
--- a/proto/radv/config.Y
+++ b/proto/radv/config.Y
@@ -26,7 +26,7 @@ static list radv_dns_list; /* Used by radv_rdnss and radv_dnssl */
static u8 radv_mult_val; /* Used by radv_mult for second return value */
static inline void
-radv_add_to_custom_list(list *l, int type, struct bytestring *payload)
+radv_add_to_custom_list(list *l, int type, const struct bytestring *payload)
{
if (type < 0 || type > 255) cf_error("RA cusom type must be in range 0-255");
struct radv_custom_config *cf = cfg_allocz(sizeof(struct radv_custom_config));
diff --git a/proto/radv/radv.h b/proto/radv/radv.h
index 8c716158..2baf0bad 100644
--- a/proto/radv/radv.h
+++ b/proto/radv/radv.h
@@ -129,7 +129,7 @@ struct radv_custom_config
{
node n;
u8 type; /* Identifier of the type of option */
- struct bytestring *payload; /* Payload of the option */
+ const struct bytestring *payload; /* Payload of the option */
};
/*
--
2.41.0
From ad9cf04a003d9af3b632fb90ba3c81fba5518ffb Mon Sep 17 00:00:00 2001
From: Alexander Zubkov <[email protected]>
Date: Tue, 27 Jun 2023 01:45:00 +0200
Subject: [PATCH] Conf: add bytestring type
* rename BYTESTRING lexem to BYTETEXT, not to collide with 'bytestring' symbol
* add bytestring type with id T_BYTESTRING (0x2c)
* add from_hex() filter function to create bytestring from string
* add filer test cases for bytestring type
---
bird-gdb.py | 1 +
conf/cf-lex.l | 2 +-
conf/confbase.Y | 2 +-
filter/config.Y | 28 +++++++++++++++++++---------
filter/data.c | 4 ++++
filter/data.h | 2 ++
filter/f-inst.c | 21 +++++++++++++++++++++
filter/f-inst.h | 3 +++
filter/test.conf | 24 ++++++++++++++++++++++++
nest/config.Y | 2 +-
proto/radv/config.Y | 4 ++--
11 files changed, 79 insertions(+), 14 deletions(-)
diff --git a/bird-gdb.py b/bird-gdb.py
index 3cf65a9c..262035dc 100644
--- a/bird-gdb.py
+++ b/bird-gdb.py
@@ -34,6 +34,7 @@ class BIRDFValPrinter(BIRDPrinter):
"T_IP": "ip",
"T_NET": "net",
"T_STRING": "s",
+ "T_BYTESTRING": "bs",
"T_PATH_MASK": "path_mask",
"T_PATH": "ad",
"T_CLIST": "ad",
diff --git a/conf/cf-lex.l b/conf/cf-lex.l
index 59b88bd5..cd150dc7 100644
--- a/conf/cf-lex.l
+++ b/conf/cf-lex.l
@@ -277,7 +277,7 @@ WHITE [ \t]
cf_error("Invalid hex string");
cf_lval.bs = bytes;
- return BYTESTRING;
+ return BYTETEXT;
}
({XIGIT}*::|({XIGIT}*:){3,})({XIGIT}*|{DIGIT}+\.{DIGIT}+\.{DIGIT}+\.{DIGIT}+) {
diff --git a/conf/confbase.Y b/conf/confbase.Y
index 3dd5fed7..da750d38 100644
--- a/conf/confbase.Y
+++ b/conf/confbase.Y
@@ -106,7 +106,7 @@ CF_DECLS
%token <i64> VPN_RD
%token <s> CF_SYM_KNOWN CF_SYM_UNDEFINED
%token <t> TEXT
-%token <bs> BYTESTRING
+%token <bs> BYTETEXT
%type <iface> ipa_scope
%type <i> expr bool pxlen4
diff --git a/filter/config.Y b/filter/config.Y
index a1e5e9f1..7c52847b 100644
--- a/filter/config.Y
+++ b/filter/config.Y
@@ -301,7 +301,7 @@ CF_DECLS
CF_KEYWORDS(FUNCTION, PRINT, PRINTN, UNSET, RETURN,
ACCEPT, REJECT, ERROR,
INT, BOOL, IP, TYPE, PREFIX, RD, PAIR, QUAD, EC, LC,
- SET, STRING, BGPMASK, BGPPATH, CLIST, ECLIST, LCLIST,
+ SET, STRING, BYTESTRING, BGPMASK, BGPPATH, CLIST, ECLIST, LCLIST,
IF, THEN, ELSE, CASE,
FOR, IN, DO,
TRUE, FALSE, RT, RO, UNKNOWN, GENERIC,
@@ -317,13 +317,14 @@ CF_KEYWORDS(FUNCTION, PRINT, PRINTN, UNSET, RETURN,
MIN, MAX,
EMPTY,
FILTER, WHERE, EVAL, ATTRIBUTE,
+ FROM_HEX,
BT_ASSERT, BT_TEST_SUITE, BT_CHECK_ASSIGN, BT_TEST_SAME, FORMAT)
%nonassoc THEN
%nonassoc ELSE
%type <xp> cmds_int cmd_prep
-%type <x> term cmd cmd_var cmds cmds_scoped constant constructor print_list var var_init var_list function_call symbol_value bgp_path_expr bgp_path bgp_path_tail
+%type <x> term term_bs cmd cmd_var cmds cmds_scoped constant constructor print_list var var_init var_list function_call symbol_value bgp_path_expr bgp_path bgp_path_tail
%type <fda> dynamic_attr
%type <fsa> static_attr
%type <f> filter where_filter
@@ -343,6 +344,8 @@ CF_KEYWORDS(FUNCTION, PRINT, PRINTN, UNSET, RETURN,
CF_GRAMMAR
+kw_sym: FROM_HEX ;
+
conf: filter_def ;
filter_def:
FILTER symbol { $2 = cf_define_symbol($2, SYM_FILTER, filter, NULL); cf_push_scope( $2 ); }
@@ -404,6 +407,7 @@ type:
| EC { $$ = T_EC; }
| LC { $$ = T_LC; }
| STRING { $$ = T_STRING; }
+ | BYTESTRING { $$ = T_BYTESTRING; }
| BGPMASK { $$ = T_PATH_MASK; }
| BGPPATH { $$ = T_PATH; }
| CLIST { $$ = T_CLIST; }
@@ -714,13 +718,14 @@ bgp_path_tail:
;
constant:
- NUM { $$ = f_new_inst(FI_CONSTANT, (struct f_val) { .type = T_INT, .val.i = $1, }); }
- | TRUE { $$ = f_new_inst(FI_CONSTANT, (struct f_val) { .type = T_BOOL, .val.i = 1, }); }
- | FALSE { $$ = f_new_inst(FI_CONSTANT, (struct f_val) { .type = T_BOOL, .val.i = 0, }); }
- | TEXT { $$ = f_new_inst(FI_CONSTANT, (struct f_val) { .type = T_STRING, .val.s = $1, }); }
- | fipa { $$ = f_new_inst(FI_CONSTANT, $1); }
- | VPN_RD { $$ = f_new_inst(FI_CONSTANT, (struct f_val) { .type = T_RD, .val.ec = $1, }); }
- | net_ { $$ = f_new_inst(FI_CONSTANT, (struct f_val) { .type = T_NET, .val.net = $1, }); }
+ NUM { $$ = f_new_inst(FI_CONSTANT, (struct f_val) { .type = T_INT, .val.i = $1, }); }
+ | TRUE { $$ = f_new_inst(FI_CONSTANT, (struct f_val) { .type = T_BOOL, .val.i = 1, }); }
+ | FALSE { $$ = f_new_inst(FI_CONSTANT, (struct f_val) { .type = T_BOOL, .val.i = 0, }); }
+ | TEXT { $$ = f_new_inst(FI_CONSTANT, (struct f_val) { .type = T_STRING, .val.s = $1, }); }
+ | BYTETEXT { $$ = f_new_inst(FI_CONSTANT, (struct f_val) { .type = T_BYTESTRING, .val.bs = $1, }); }
+ | fipa { $$ = f_new_inst(FI_CONSTANT, $1); }
+ | VPN_RD { $$ = f_new_inst(FI_CONSTANT, (struct f_val) { .type = T_RD, .val.ec = $1, }); }
+ | net_ { $$ = f_new_inst(FI_CONSTANT, (struct f_val) { .type = T_NET, .val.net = $1, }); }
| '[' ']' { $$ = f_new_inst(FI_CONSTANT, (struct f_val) { .type = T_SET, .val.t = NULL, }); }
| '[' set_items ']' {
DBG( "We've got a set here..." );
@@ -868,9 +873,14 @@ term:
/* | term '.' LEN { $$->code = P('P','l'); } */
+ | term_bs
| function_call
;
+term_bs:
+ FROM_HEX '(' term ')' { $$ = f_new_inst(FI_FROM_HEX, $3); }
+ ;
+
break_command:
ACCEPT { $$ = F_ACCEPT; }
| REJECT { $$ = F_REJECT; }
diff --git a/filter/data.c b/filter/data.c
index 56d746fd..62573f4e 100644
--- a/filter/data.c
+++ b/filter/data.c
@@ -46,6 +46,7 @@ static const char * const f_type_str[] = {
[T_IP] = "ip",
[T_NET] = "prefix",
[T_STRING] = "string",
+ [T_BYTESTRING] = "bytestring",
[T_PATH_MASK] = "bgpmask",
[T_PATH] = "bgppath",
[T_CLIST] = "clist",
@@ -286,6 +287,8 @@ val_same(const struct f_val *v1, const struct f_val *v2)
return 0;
switch (v1->type) {
+ case T_BYTESTRING:
+ return v1->val.bs->length == v2->val.bs->length && !memcmp(v1->val.bs->data, v2->val.bs->data, v1->val.bs->length);
case T_PATH_MASK:
return pm_same(v1->val.path_mask, v2->val.path_mask);
case T_PATH_MASK_ITEM:
@@ -585,6 +588,7 @@ val_format(const struct f_val *v, buffer *buf)
case T_BOOL: buffer_puts(buf, v->val.i ? "TRUE" : "FALSE"); return;
case T_INT: buffer_print(buf, "%u", v->val.i); return;
case T_STRING: buffer_print(buf, "%s", v->val.s); return;
+ case T_BYTESTRING: bstrbintohex(v->val.bs, buf2, 1000); buffer_print(buf, "%s", buf2); return;
case T_IP: buffer_print(buf, "%I", v->val.ip); return;
case T_NET: buffer_print(buf, "%N", v->val.net); return;
case T_PAIR: buffer_print(buf, "(%u,%u)", v->val.i >> 16, v->val.i & 0xffff); return;
diff --git a/filter/data.h b/filter/data.h
index b3767f7b..f44e08e1 100644
--- a/filter/data.h
+++ b/filter/data.h
@@ -58,6 +58,7 @@ enum f_type {
T_LCLIST = 0x29, /* Large community list */
T_RD = 0x2a, /* Route distinguisher for VPN addresses */
T_PATH_MASK_ITEM = 0x2b, /* Path mask item for path mask constructors */
+ T_BYTESTRING = 0x2c,
T_SET = 0x80,
T_PREFIX_SET = 0x81,
@@ -73,6 +74,7 @@ struct f_val {
ip_addr ip;
const net_addr *net;
const char *s;
+ const struct bytestring *bs;
const struct f_tree *t;
const struct f_trie *ti;
const struct adata *ad;
diff --git a/filter/f-inst.c b/filter/f-inst.c
index 33436853..565aa2a9 100644
--- a/filter/f-inst.c
+++ b/filter/f-inst.c
@@ -1555,6 +1555,27 @@
}
+ INST(FI_FROM_HEX, 1, 1) { /* Convert hex text to bytestring */
+ ARG(1, T_STRING);
+
+ size_t len;
+ struct bytestring *bs;
+
+ errno = 0;
+ len = bstrhextobin(v1.val.s, 0);
+ if (errno || len == (size_t)-1)
+ runtime("Invalid hex string");
+
+ bs = falloc(sizeof(*bs) + len);
+ bs->length = len;
+
+ bstrhextobin(v1.val.s, bs->data);
+ if (errno)
+ runtime("Invalid hex string");
+
+ RESULT(T_BYTESTRING, bs, bs);
+ }
+
INST(FI_FORMAT, 1, 1) { /* Format */
ARG_ANY(1);
RESULT(T_STRING, s, val_format_str(fpool, &v1));
diff --git a/filter/f-inst.h b/filter/f-inst.h
index 72b080f8..185c01ac 100644
--- a/filter/f-inst.h
+++ b/filter/f-inst.h
@@ -13,12 +13,15 @@
#ifndef _BIRD_F_INST_H_
#define _BIRD_F_INST_H_
+#include <errno.h>
+
#include "nest/bird.h"
#include "conf/conf.h"
#include "filter/filter.h"
#include "filter/data.h"
#include "lib/buffer.h"
#include "lib/flowspec.h"
+#include "lib/string.h"
/* Flags for instructions */
enum f_instruction_flags {
diff --git a/filter/test.conf b/filter/test.conf
index e9e3af89..a118ddd9 100644
--- a/filter/test.conf
+++ b/filter/test.conf
@@ -237,6 +237,30 @@ bt_test_suite(t_string, "Testing string matching");
+/*
+ * Testing bytestings
+ * ------------------
+ */
+
+function t_bytestring()
+{
+ bytestring bs1 = hex:;
+ bytestring bs2 = hex:00112233445566778899aabbccddeeff;
+
+ bt_assert(format(bs1) = "hex:");
+ bt_assert(hex:00:11:22:33:44:55:66:77:88:99:aa:bb:cc:dd:ee:ff = bs2);
+ bt_assert(00:11:22:33:44:55:66:77:88:99:aa:bb:cc:dd:ee:ff = bs2);
+ bt_assert(00112233445566778899aabbccddeeff = bs2);
+ bt_assert(format(hex:0123456789abcdef) = "hex:01:23:45:67:89:ab:cd:ef");
+ bt_assert(from_hex(" ") = bs1);
+ bt_assert(from_hex(" 0011:2233-44556677 88-99 - aa-bb cc:dd : ee:ff ") = bs2);
+}
+
+bt_test_suite(t_bytestring, "Testing bytestrings");
+
+
+
+
/*
* Testing pairs
* -------------
diff --git a/nest/config.Y b/nest/config.Y
index c83c715b..9197f985 100644
--- a/nest/config.Y
+++ b/nest/config.Y
@@ -548,7 +548,7 @@ pass_key: PASSWORD | KEY;
password_item_begin:
pass_key text { init_password_list(); init_password($2, strlen($2), password_id++); }
- | pass_key BYTESTRING { init_password_list(); init_password($2->data, $2->length, password_id++); }
+ | pass_key BYTETEXT { init_password_list(); init_password($2->data, $2->length, password_id++); }
;
password_item_params:
diff --git a/proto/radv/config.Y b/proto/radv/config.Y
index eeafe6f4..9653cd7b 100644
--- a/proto/radv/config.Y
+++ b/proto/radv/config.Y
@@ -73,7 +73,7 @@ radv_proto_item:
| PREFIX radv_prefix { add_tail(&RADV_CFG->pref_list, NODE this_radv_prefix); }
| RDNSS { init_list(&radv_dns_list); } radv_rdnss { add_tail_list(&RADV_CFG->rdnss_list, &radv_dns_list); }
| DNSSL { init_list(&radv_dns_list); } radv_dnssl { add_tail_list(&RADV_CFG->dnssl_list, &radv_dns_list); }
- | CUSTOM OPTION TYPE expr VALUE BYTESTRING { radv_add_to_custom_list(&RADV_CFG->custom_list, $4, $6); }
+ | CUSTOM OPTION TYPE expr VALUE BYTETEXT { radv_add_to_custom_list(&RADV_CFG->custom_list, $4, $6); }
| TRIGGER net_ip6 { RADV_CFG->trigger = $2; }
| PROPAGATE ROUTES bool { RADV_CFG->propagate_routes = $3; }
;
@@ -138,7 +138,7 @@ radv_iface_item:
| PREFIX radv_prefix { add_tail(&RADV_IFACE->pref_list, NODE this_radv_prefix); }
| RDNSS { init_list(&radv_dns_list); } radv_rdnss { add_tail_list(&RADV_IFACE->rdnss_list, &radv_dns_list); }
| DNSSL { init_list(&radv_dns_list); } radv_dnssl { add_tail_list(&RADV_IFACE->dnssl_list, &radv_dns_list); }
- | CUSTOM OPTION TYPE expr VALUE BYTESTRING { radv_add_to_custom_list(&RADV_IFACE->custom_list, $4, $6); }
+ | CUSTOM OPTION TYPE expr VALUE BYTETEXT { radv_add_to_custom_list(&RADV_IFACE->custom_list, $4, $6); }
| RDNSS LOCAL bool { RADV_IFACE->rdnss_local = $3; }
| DNSSL LOCAL bool { RADV_IFACE->dnssl_local = $3; }
| CUSTOM OPTION LOCAL bool { RADV_IFACE->custom_local = $4; }
--
2.41.0
From a3f85402790f960ab225e9f35872d15738904665 Mon Sep 17 00:00:00 2001
From: Alexander Zubkov <[email protected]>
Date: Tue, 27 Jun 2023 00:47:56 +0200
Subject: [PATCH] Conf: allow multiline string literals
---
conf/cf-lex.l | 3 +--
1 file changed, 1 insertion(+), 2 deletions(-)
diff --git a/conf/cf-lex.l b/conf/cf-lex.l
index 9025a84d..3ebf1a9a 100644
--- a/conf/cf-lex.l
+++ b/conf/cf-lex.l
@@ -361,7 +361,6 @@ else: {
quoted_buffer_init();
}
-<QUOTED>\n cf_error("Unterminated string");
<QUOTED><<EOF>> cf_error("Unterminated string");
<QUOTED>["] {
BEGIN(INITIAL);
@@ -370,7 +369,7 @@ else: {
return TEXT;
}
-<QUOTED>. BUFFER_PUSH(quoted_buffer) = yytext[0];
+<QUOTED>(.|\n) BUFFER_PUSH(quoted_buffer) = yytext[0];
<INITIAL,COMMENT><<EOF>> { if (check_eof()) return END; }
--
2.41.0
From 7ece395fe83ea03dae3e655c33318bac58aed099 Mon Sep 17 00:00:00 2001
From: Alexander Zubkov <[email protected]>
Date: Tue, 27 Jun 2023 01:03:56 +0200
Subject: [PATCH] Add library to process bytestrings, use hex parser from
library in lexer
---
conf/cf-lex.l | 33 ++++++-----------
lib/Makefile | 2 +-
lib/bytestring.c | 93 ++++++++++++++++++++++++++++++++++++++++++++++++
lib/string.h | 4 +++
4 files changed, 109 insertions(+), 23 deletions(-)
create mode 100644 lib/bytestring.c
diff --git a/conf/cf-lex.l b/conf/cf-lex.l
index 3ebf1a9a..59b88bd5 100644
--- a/conf/cf-lex.l
+++ b/conf/cf-lex.l
@@ -256,37 +256,26 @@ WHITE [ \t]
}
({XIGIT}{2}){16,}|{XIGIT}{2}(:{XIGIT}{2}){15,}|hex:({XIGIT}{2}(:?{XIGIT}{2})*)? {
- char *s, *sb = yytext;
- size_t len = 0, i;
+ char *sb = yytext;
+ size_t len = 0;
struct bytestring *bytes;
- byte *b;
/* skip 'hex:' prefix */
if (sb[0] == 'h' && sb[1] == 'e' && sb[2] == 'x' && sb[3] == ':')
sb += 4;
- s = sb;
- while (*s) {
- len++;
- s += 2;
- if (*s == ':')
- s++;
- }
+ errno = 0;
+ len = bstrhextobin(sb, 0);
+ if (errno || len == (size_t)-1)
+ cf_error("Invalid hex string");
+
bytes = cfg_allocz(sizeof(*bytes) + len);
bytes->length = len;
- b = &bytes->data[0];
- s = sb;
- errno = 0;
- for (i = 0; i < len; i++) {
- *b = bstrtobyte16(s);
- if (errno == ERANGE)
- cf_error("Invalid hex string");
- b++;
- s += 2;
- if (*s == ':')
- s++;
- }
+ bstrhextobin(sb, bytes->data);
+ if (errno)
+ cf_error("Invalid hex string");
+
cf_lval.bs = bytes;
return BYTESTRING;
}
diff --git a/lib/Makefile b/lib/Makefile
index 812f721c..296152ff 100644
--- a/lib/Makefile
+++ b/lib/Makefile
@@ -1,4 +1,4 @@
-src := bitmap.c bitops.c blake2s.c blake2b.c checksum.c event.c flowspec.c idm.c ip.c lists.c mac.c md5.c mempool.c net.c patmatch.c printf.c resource.c sha1.c sha256.c sha512.c slab.c slists.c strtoul.c tbf.c timer.c xmalloc.c
+src := bitmap.c bitops.c blake2s.c blake2b.c bytestring.c checksum.c event.c flowspec.c idm.c ip.c lists.c mac.c md5.c mempool.c net.c patmatch.c printf.c resource.c sha1.c sha256.c sha512.c slab.c slists.c strtoul.c tbf.c timer.c xmalloc.c
obj := $(src-o-files)
$(all-daemon)
diff --git a/lib/bytestring.c b/lib/bytestring.c
new file mode 100644
index 00000000..ff9ef257
--- /dev/null
+++ b/lib/bytestring.c
@@ -0,0 +1,93 @@
+/*
+ * BIRD Library -- Work with binary sequences
+ *
+ * (c) 2023 TODO
+ *
+ * Can be freely distributed and used under the terms of the GNU GPL.
+ */
+
+#include <ctype.h>
+#include <errno.h>
+
+#include "nest/bird.h"
+#include "lib/string.h"
+#include "conf/conf.h"
+
+char
+toxdigit(int b)
+{
+ if (b >= 0 && b <= 9)
+ return ('0' + b);
+ else if (b >= 10 && b < 16)
+ return ('a' + b - 10);
+ else
+ return 0;
+}
+
+int
+bstrhextobin(const char *s, byte *d)
+{
+ size_t len = 0;
+
+ while (*s) {
+ if (!isalnum(s[0])) {
+ s++;
+ continue;
+ }
+
+ if (!isalnum(s[1])) {
+ errno = ERANGE;
+ return -1;
+ }
+
+ if (d) {
+ errno = 0;
+ *d = bstrtobyte16(s);
+ if (errno)
+ return -1;
+ d++;
+ }
+
+ s += 2;
+ len++;
+ }
+
+ return len;
+}
+
+int
+bstrbintohex(const struct bytestring *bs, byte *buf, uint size)
+{
+ if (size < 8)
+ return -1;
+
+ size_t len = bs->length;
+ const byte *b = bs->data;
+
+ byte *end = buf + size - 4;
+
+ memcpy(buf, "hex", 3);
+ buf += 3;
+
+ if (!len) {
+ strcpy(buf, ":");
+ return 0;
+ }
+
+ size_t i;
+ for (i = 0; i < len && buf + 3 <= end; ++i, buf += 3) {
+ byte x = b[i];
+ buf[0] = ':';
+ buf[1] = toxdigit(x >> 4);
+ buf[2] = toxdigit(x & 0xF);
+ }
+
+ if (i < len) {
+ strcpy(buf, "...");
+ return -1;
+ }
+
+ *buf = 0;
+
+ return 0;
+}
diff --git a/lib/string.h b/lib/string.h
index 2829943d..62f03eb6 100644
--- a/lib/string.h
+++ b/lib/string.h
@@ -33,6 +33,10 @@ u64 bstrtoul10(const char *str, char **end);
u64 bstrtoul16(const char *str, char **end);
byte bstrtobyte16(const char *str);
+struct bytestring;
+int bstrhextobin(const char *s, byte *d);
+int bstrbintohex(const struct bytestring *bs, byte *buf, uint size);
+
int patmatch(const byte *pat, const byte *str);
static inline char *xbasename(const char *str)
--
2.41.0