Home | History | Annotate | Line # | Download | only in util
      1 /*	$NetBSD: hex_code.c,v 1.5 2026/05/09 18:49:22 christos Exp $	*/
      2 
      3 /*++
      4 /* NAME
      5 /*	hex_code 3
      6 /* SUMMARY
      7 /*	encode/decode data, hexadecimal style
      8 /* SYNOPSIS
      9 /*	#include <hex_code.h>
     10 /*
     11 /*	VSTRING	*hex_encode(result, in, len)
     12 /*	VSTRING	*result;
     13 /*	const char *in;
     14 /*	ssize_t	len;
     15 /*
     16 /*	VSTRING	*hex_decode(result, in, len)
     17 /*	VSTRING	*result;
     18 /*	const char *in;
     19 /*	ssize_t	len;
     20 /*
     21 /*	VSTRING	*hex_encode_opt(result, in, len, flags)
     22 /*	VSTRING	*result;
     23 /*	const char *in;
     24 /*	ssize_t	len;
     25 /*	int	flags;
     26 /*
     27 /*	VSTRING	*hex_decode_opt(result, in, len, flags)
     28 /*	VSTRING	*result;
     29 /*	const char *in;
     30 /*	ssize_t	len;
     31 /*	int	flags;
     32 /* DESCRIPTION
     33 /*	hex_encode() takes a block of len bytes and encodes it as one
     34 /*	upper-case null-terminated string.  The result value is
     35 /*	the result argument.
     36 /*
     37 /*	hex_decode() performs the opposite transformation on
     38 /*	lower-case, upper-case or mixed-case input. The result
     39 /*	value is the result argument. The result is null terminated,
     40 /*	whether or not that makes sense.
     41 /*
     42 /*	hex_encode_opt() enables extended functionality as controlled
     43 /*	with \fIflags\fR.
     44 /* .IP HEX_ENCODE_FLAG_NONE
     45 /*	The default: a self-documenting flag that enables no
     46 /*	functionality.
     47 /* .IP HEX_ENCODE_FLAG_USE_COLON
     48 /*	Inserts one ":" between bytes.
     49 /* .IP HEX_ENCODE_FLAG_APPEND
     50 /*	Append output to the buffer.
     51 /* .IP HEX_ENCODE_FLAG_LOWERCASE
     52 /*	Output lowercase characters.
     53 /* .PP
     54 /*	hex_decode_opt() enables extended functionality as controlled
     55 /*	with \fIflags\fR.
     56 /* .IP HEX_DECODE_FLAG_NONE
     57 /*	The default: a self-documenting flag that enables no
     58 /*	functionality.
     59 /* .IP HEX_DECODE_FLAG_ALLOW_COLON
     60 /*	Allows, but does not require, one ":" between bytes.
     61 /* DIAGNOSTICS
     62 /*	hex_decode() returns a null pointer when the input contains
     63 /*	characters not in the hexadecimal alphabet.
     64 /* LICENSE
     65 /* .ad
     66 /* .fi
     67 /*	The Secure Mailer license must be distributed with this software.
     68 /* AUTHOR(S)
     69 /*	Wietse Venema
     70 /*	IBM T.J. Watson Research
     71 /*	P.O. Box 704
     72 /*	Yorktown Heights, NY 10598, USA
     73 /*
     74 /*	Wietse Venema
     75 /*	Google, Inc.
     76 /*	111 8th Avenue
     77 /*	New York, NY 10011, USA
     78 /*
     79 /*	Wietse Venema
     80 /*	porcupine.org
     81 /*--*/
     82 
     83 /* System library. */
     84 
     85 #include <sys_defs.h>
     86 #include <ctype.h>
     87 #include <string.h>
     88 
     89 /* Utility library. */
     90 
     91 #include <msg.h>
     92 #include <mymalloc.h>
     93 #include <vstring.h>
     94 #include <hex_code.h>
     95 
     96 /* Application-specific. */
     97 
     98 static const unsigned char lower_hex_chars[] = "0123456789abcdef";
     99 static const unsigned char upper_hex_chars[] = "0123456789ABCDEF";
    100 
    101 #define UCHAR_PTR(x) ((const unsigned char *)(x))
    102 
    103 /* hex_encode - ABI compatibility */
    104 
    105 #undef hex_encode
    106 
    107 VSTRING *hex_encode(VSTRING *result, const char *in, ssize_t len)
    108 {
    109     return (hex_encode_opt(result, in, len, HEX_ENCODE_FLAG_NONE));
    110 }
    111 
    112 /* hex_encode_opt - raw data to encoded */
    113 
    114 VSTRING *hex_encode_opt(VSTRING *result, const char *in, ssize_t len, int flags)
    115 {
    116     const unsigned char *hex_chars;
    117     const unsigned char *cp;
    118     int     ch;
    119     ssize_t count;
    120 
    121     if ((flags & HEX_ENCODE_FLAG_APPEND) == 0)
    122 	VSTRING_RESET(result);
    123     if ((flags & HEX_ENCODE_FLAG_LOWERCASE) != 0)
    124 	hex_chars = lower_hex_chars;
    125     else
    126 	hex_chars = upper_hex_chars;
    127     for (cp = UCHAR_PTR(in), count = len; count > 0; count--, cp++) {
    128 	ch = *cp;
    129 	VSTRING_ADDCH(result, hex_chars[(ch >> 4) & 0xf]);
    130 	VSTRING_ADDCH(result, hex_chars[ch & 0xf]);
    131 	if ((flags & HEX_ENCODE_FLAG_USE_COLON) && count > 1)
    132 	    VSTRING_ADDCH(result, ':');
    133     }
    134     VSTRING_TERMINATE(result);
    135     return (result);
    136 }
    137 
    138 /* hex_decode - ABI compatibility wrapper */
    139 
    140 #undef hex_decode
    141 
    142 VSTRING *hex_decode(VSTRING *result, const char *in, ssize_t len)
    143 {
    144     return (hex_decode_opt(result, in, len, HEX_DECODE_FLAG_NONE));
    145 }
    146 
    147 /* hex_decode_opt - encoded data to raw */
    148 
    149 VSTRING *hex_decode_opt(VSTRING *result, const char *in, ssize_t len, int flags)
    150 {
    151     const unsigned char *cp;
    152     ssize_t count;
    153     unsigned int hex;
    154     unsigned int bin;
    155 
    156     VSTRING_RESET(result);
    157     for (cp = UCHAR_PTR(in), count = len; count > 0; cp += 2, count -= 2) {
    158 	if (count < 2)
    159 	    return (0);
    160 	hex = cp[0];
    161 	if (hex >= '0' && hex <= '9')
    162 	    bin = (hex - '0') << 4;
    163 	else if (hex >= 'A' && hex <= 'F')
    164 	    bin = (hex - 'A' + 10) << 4;
    165 	else if (hex >= 'a' && hex <= 'f')
    166 	    bin = (hex - 'a' + 10) << 4;
    167 	else
    168 	    return (0);
    169 	hex = cp[1];
    170 	if (hex >= '0' && hex <= '9')
    171 	    bin |= (hex - '0');
    172 	else if (hex >= 'A' && hex <= 'F')
    173 	    bin |= (hex - 'A' + 10);
    174 	else if (hex >= 'a' && hex <= 'f')
    175 	    bin |= (hex - 'a' + 10);
    176 	else
    177 	    return (0);
    178 	VSTRING_ADDCH(result, bin);
    179 
    180 	/*
    181 	 * Support *colon-separated* input (no leading or trailing colons).
    182 	 * After decoding "xx", skip a possible ':' preceding "yy" in
    183 	 * "xx:yy".
    184 	 */
    185 	if ((flags & HEX_DECODE_FLAG_ALLOW_COLON)
    186 	    && count > 4 && cp[2] == ':') {
    187 	    ++cp;
    188 	    --count;
    189 	}
    190     }
    191     VSTRING_TERMINATE(result);
    192     return (result);
    193 }
    194 
    195 #ifdef TEST
    196 
    197  /*
    198   * Proof-of-concept test program: convert to hexadecimal and back.
    199   */
    200 #define STR(x)	vstring_str(x)
    201 #define LEN(x)	VSTRING_LEN(x)
    202 
    203 typedef struct TEST_CASE {
    204     const char *label;			/* identifies test case */
    205     VSTRING *(*func) (VSTRING *, const char *, ssize_t, int);
    206     const char *input;			/* input string */
    207     ssize_t inlen;			/* input size */
    208     int     flags;			/* flags */
    209     const char *exp_output;		/* expected output or null */
    210     ssize_t exp_outlen;			/* expected size */
    211 } TEST_CASE;
    212 
    213 /*
    214   * The test cases.
    215   */
    216 #define OUTPUT_INIT	"thrash:"	/* output buffer initial content */
    217 #define OUTPUT_INIT_SZ	(sizeof(OUTPUT_INIT) - 1)
    218 
    219 static const TEST_CASE test_cases[] = {
    220     {"hex_encode_no_options", hex_encode_opt,
    221 	"this is a test",
    222 	sizeof("this is a test") - 1,
    223 	HEX_ENCODE_FLAG_NONE,
    224 	"7468697320697320612074657374",
    225 	sizeof("7468697320697320612074657374") - 1,
    226     },
    227     {"hex_decode_no_options", hex_decode_opt,
    228 	"7468697320697320612074657374",
    229 	sizeof("7468697320697320612074657374") - 1,
    230 	HEX_DECODE_FLAG_NONE,
    231 	"this is a test",
    232 	sizeof("this is a test") - 1,
    233     },
    234     {"hex_decode_no_colon_allow_colon", hex_decode_opt,
    235 	"7468697320697320612074657374",
    236 	sizeof("7468697320697320612074657374") - 1,
    237 	HEX_DECODE_FLAG_ALLOW_COLON,
    238 	"this is a test",
    239 	sizeof("this is a test") - 1,
    240     },
    241     {"hex_encode_appends", hex_encode_opt,
    242 	"this is a test",
    243 	sizeof("this is a test") - 1,
    244 	HEX_ENCODE_FLAG_APPEND,
    245 	OUTPUT_INIT "7468697320697320612074657374",
    246 	sizeof(OUTPUT_INIT "7468697320697320612074657374") - 1,
    247     },
    248     {"hex_encode_with_colon", hex_encode_opt,
    249 	"this is a test",
    250 	sizeof("this is a test") - 1,
    251 	HEX_ENCODE_FLAG_USE_COLON,
    252 	"74:68:69:73:20:69:73:20:61:20:74:65:73:74",
    253 	sizeof("74:68:69:73:20:69:73:20:61:20:74:65:73:74") - 1,
    254     },
    255     {"hex_encode_with_colon_and_append", hex_encode_opt,
    256 	"this is a test",
    257 	sizeof("this is a test") - 1,
    258 	HEX_ENCODE_FLAG_USE_COLON | HEX_ENCODE_FLAG_APPEND,
    259 	OUTPUT_INIT "74:68:69:73:20:69:73:20:61:20:74:65:73:74",
    260 	sizeof(OUTPUT_INIT "74:68:69:73:20:69:73:20:61:20:74:65:73:74") - 1,
    261     },
    262     {"hex_decode_error", hex_decode_opt,
    263 	"this is a test",
    264 	sizeof("this is a test") - 1,
    265 	HEX_DECODE_FLAG_ALLOW_COLON,
    266 	0,
    267 	0,
    268     },
    269     {"hex_encode_to_lowercase", hex_encode_opt,
    270 	"\377\376\375\374\373\372",
    271 	sizeof("\377\376\375\374\373\372") - 1,
    272 	HEX_ENCODE_FLAG_LOWERCASE,
    273 	"fffefdfcfbfa",
    274 	sizeof("fffefdfcfbfa") - 1,
    275     },
    276     {"hex_decode_from_lowercase", hex_decode_opt,
    277 	"fffefdfcfbfa",
    278 	sizeof("fffefdfcfbfa") - 1,
    279 	0,
    280 	"\377\376\375\374\373\372",
    281 	sizeof("\377\376\375\374\373\372") - 1,
    282     },
    283     {"hex_encode_to_uppercase", hex_encode_opt,
    284 	"\377\376\375\374\373\372",
    285 	sizeof("\377\376\375\374\373\372") - 1,
    286 	0,
    287 	"FFFEFDFCFBFA",
    288 	sizeof("FFFEFDFCFBFA") - 1,
    289     },
    290     {"hex_decode_from_uppercase", hex_decode_opt,
    291 	"FFFEFDFCFBFA",
    292 	sizeof("FFFEFDFCFBFA") - 1,
    293 	0,
    294 	"\377\376\375\374\373\372",
    295 	sizeof("\377\376\375\374\373\372") - 1,
    296     },
    297     {0},
    298 };
    299 
    300 int     main(int unused_argc, char **unused_argv)
    301 {
    302     VSTRING *buf = vstring_alloc(1);
    303     int     pass = 0;
    304     int     fail = 0;
    305     const TEST_CASE *tp;
    306 
    307     for (tp = test_cases; tp->label != 0; tp++) {
    308 	VSTRING *out;
    309 	int     ok = 0;
    310 
    311 	msg_info("RUN  %s", tp->label);
    312 	vstring_memcpy(buf, OUTPUT_INIT, OUTPUT_INIT_SZ);
    313 	out = tp->func(buf, tp->input, tp->inlen, tp->flags);
    314 	if (out == 0 && tp->exp_output == 0) {
    315 	    ok = 1;
    316 	} else if (out != buf) {
    317 	    msg_warn("got result '%p', want: '%p'",
    318 		     (void *) out, (void *) buf);
    319 	} else if (LEN(out) != tp->exp_outlen) {
    320 	    msg_warn("got result length '%ld', want: '%ld'",
    321 		     (long) LEN(out), (long) tp->exp_outlen);
    322 	} else if (memcmp(STR(out), tp->exp_output, tp->exp_outlen) != 0) {
    323 	    msg_warn("got result '%*s', want: '%*s'",
    324 		     (int) LEN(out), STR(out),
    325 		     (int) tp->exp_outlen, tp->exp_output);
    326 	} else {
    327 	    ok = 1;
    328 	}
    329 	if (ok) {
    330 	    msg_info("PASS %s", tp->label);
    331 	    pass++;
    332 	} else {
    333 	    msg_info("FAIL %s", tp->label);
    334 	    fail++;
    335 	}
    336     }
    337     vstring_free(buf);
    338     msg_info("PASS=%d FAIL=%d", pass, fail);
    339     return (fail > 0);
    340 }
    341 
    342 #endif
    343