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