1 /*        $NetBSD: hex_code.c,v 1.4 2025/02/25 19:15:52 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 /* .PP
52 /*        hex_decode_opt() enables extended functionality as controlled
53 /*        with \fIflags\fR.
54 /* .IP HEX_DECODE_FLAG_NONE
55 /*        The default: a self-documenting flag that enables no
56 /*        functionality.
57 /* .IP HEX_DECODE_FLAG_ALLOW_COLON
58 /*        Allows, but does not require, one ":" between bytes.
59 /* DIAGNOSTICS
60 /*        hex_decode() returns a null pointer when the input contains
61 /*        characters not in the hexadecimal alphabet.
62 /* LICENSE
63 /* .ad
64 /* .fi
65 /*        The Secure Mailer license must be distributed with this software.
66 /* AUTHOR(S)
67 /*        Wietse Venema
68 /*        IBM T.J. Watson Research
69 /*        P.O. Box 704
70 /*        Yorktown Heights, NY 10598, USA
71 /*
72 /*        Wietse Venema
73 /*        Google, Inc.
74 /*        111 8th Avenue
75 /*        New York, NY 10011, USA
76 /*
77 /*        Wietse Venema
78 /*        porcupine.org
79 /*--*/
80 
81 /* System library. */
82 
83 #include <sys_defs.h>
84 #include <ctype.h>
85 #include <string.h>
86 
87 /* Utility library. */
88 
89 #include <msg.h>
90 #include <mymalloc.h>
91 #include <vstring.h>
92 #include <hex_code.h>
93 
94 /* Application-specific. */
95 
96 static const unsigned char hex_chars[] = "0123456789ABCDEF";
97 
98 #define UCHAR_PTR(x) ((const unsigned char *)(x))
99 
100 /* hex_encode - ABI compatibility */
101 
102 #undef hex_encode
103 
hex_encode(VSTRING * result,const char * in,ssize_t len)104 VSTRING *hex_encode(VSTRING *result, const char *in, ssize_t len)
105 {
106     return (hex_encode_opt(result, in, len, HEX_ENCODE_FLAG_NONE));
107 }
108 
109 /* hex_encode_opt - raw data to encoded */
110 
hex_encode_opt(VSTRING * result,const char * in,ssize_t len,int flags)111 VSTRING *hex_encode_opt(VSTRING *result, const char *in, ssize_t len, int flags)
112 {
113     const unsigned char *cp;
114     int     ch;
115     ssize_t count;
116 
117     if ((flags & HEX_ENCODE_FLAG_APPEND) == 0)
118           VSTRING_RESET(result);
119     for (cp = UCHAR_PTR(in), count = len; count > 0; count--, cp++) {
120           ch = *cp;
121           VSTRING_ADDCH(result, hex_chars[(ch >> 4) & 0xf]);
122           VSTRING_ADDCH(result, hex_chars[ch & 0xf]);
123           if ((flags & HEX_ENCODE_FLAG_USE_COLON) && count > 1)
124               VSTRING_ADDCH(result, ':');
125     }
126     VSTRING_TERMINATE(result);
127     return (result);
128 }
129 
130 /* hex_decode - ABI compatibility wrapper */
131 
132 #undef hex_decode
133 
hex_decode(VSTRING * result,const char * in,ssize_t len)134 VSTRING *hex_decode(VSTRING *result, const char *in, ssize_t len)
135 {
136     return (hex_decode_opt(result, in, len, HEX_DECODE_FLAG_NONE));
137 }
138 
139 /* hex_decode_opt - encoded data to raw */
140 
hex_decode_opt(VSTRING * result,const char * in,ssize_t len,int flags)141 VSTRING *hex_decode_opt(VSTRING *result, const char *in, ssize_t len, int flags)
142 {
143     const unsigned char *cp;
144     ssize_t count;
145     unsigned int hex;
146     unsigned int bin;
147 
148     VSTRING_RESET(result);
149     for (cp = UCHAR_PTR(in), count = len; count > 0; cp += 2, count -= 2) {
150           if (count < 2)
151               return (0);
152           hex = cp[0];
153           if (hex >= '0' && hex <= '9')
154               bin = (hex - '0') << 4;
155           else if (hex >= 'A' && hex <= 'F')
156               bin = (hex - 'A' + 10) << 4;
157           else if (hex >= 'a' && hex <= 'f')
158               bin = (hex - 'a' + 10) << 4;
159           else
160               return (0);
161           hex = cp[1];
162           if (hex >= '0' && hex <= '9')
163               bin |= (hex - '0');
164           else if (hex >= 'A' && hex <= 'F')
165               bin |= (hex - 'A' + 10);
166           else if (hex >= 'a' && hex <= 'f')
167               bin |= (hex - 'a' + 10);
168           else
169               return (0);
170           VSTRING_ADDCH(result, bin);
171 
172           /*
173            * Support *colon-separated* input (no leading or trailing colons).
174            * After decoding "xx", skip a possible ':' preceding "yy" in
175            * "xx:yy".
176            */
177           if ((flags & HEX_DECODE_FLAG_ALLOW_COLON)
178               && count > 4 && cp[2] == ':') {
179               ++cp;
180               --count;
181           }
182     }
183     VSTRING_TERMINATE(result);
184     return (result);
185 }
186 
187 #ifdef TEST
188 
189  /*
190   * Proof-of-concept test program: convert to hexadecimal and back.
191   */
192 #define STR(x)      vstring_str(x)
193 #define LEN(x)      VSTRING_LEN(x)
194 
195 typedef struct TEST_CASE {
196     const char *label;                            /* identifies test case */
197     VSTRING *(*func) (VSTRING *, const char *, ssize_t, int);
198     const char *input;                            /* input string */
199     ssize_t inlen;                      /* input size */
200     int     flags;                      /* flags */
201     const char *exp_output;             /* expected output or null */
202     ssize_t exp_outlen;                           /* expected size */
203 } TEST_CASE;
204 
205 /*
206   * The test cases.
207   */
208 #define OUTPUT_INIT "thrash:" /* output buffer initial content */
209 #define OUTPUT_INIT_SZ        (sizeof(OUTPUT_INIT) - 1)
210 
211 static const TEST_CASE test_cases[] = {
212     {"hex_encode_no_options", hex_encode_opt,
213           "this is a test",
214           sizeof("this is a test") - 1,
215           HEX_ENCODE_FLAG_NONE,
216           "7468697320697320612074657374",
217           sizeof("7468697320697320612074657374") - 1,
218     },
219     {"hex_decode_no_options", hex_decode_opt,
220           "7468697320697320612074657374",
221           sizeof("7468697320697320612074657374") - 1,
222           HEX_DECODE_FLAG_NONE,
223           "this is a test",
224           sizeof("this is a test") - 1,
225     },
226     {"hex_decode_no_colon_allow_colon", hex_decode_opt,
227           "7468697320697320612074657374",
228           sizeof("7468697320697320612074657374") - 1,
229           HEX_DECODE_FLAG_ALLOW_COLON,
230           "this is a test",
231           sizeof("this is a test") - 1,
232     },
233     {"hex_encode_appends", hex_encode_opt,
234           "this is a test",
235           sizeof("this is a test") - 1,
236           HEX_ENCODE_FLAG_APPEND,
237           OUTPUT_INIT "7468697320697320612074657374",
238           sizeof(OUTPUT_INIT "7468697320697320612074657374") - 1,
239     },
240     {"hex_encode_with_colon", hex_encode_opt,
241           "this is a test",
242           sizeof("this is a test") - 1,
243           HEX_ENCODE_FLAG_USE_COLON,
244           "74:68:69:73:20:69:73:20:61:20:74:65:73:74",
245           sizeof("74:68:69:73:20:69:73:20:61:20:74:65:73:74") - 1,
246     },
247     {"hex_encode_with_colon_and_append", hex_encode_opt,
248           "this is a test",
249           sizeof("this is a test") - 1,
250           HEX_ENCODE_FLAG_USE_COLON | HEX_ENCODE_FLAG_APPEND,
251           OUTPUT_INIT "74:68:69:73:20:69:73:20:61:20:74:65:73:74",
252           sizeof(OUTPUT_INIT "74:68:69:73:20:69:73:20:61:20:74:65:73:74") - 1,
253     },
254     {"hex_decode_error", hex_decode_opt,
255           "this is a test",
256           sizeof("this is a test") - 1,
257           HEX_DECODE_FLAG_ALLOW_COLON,
258           0,
259           0,
260     },
261     {0},
262 };
263 
main(int unused_argc,char ** unused_argv)264 int     main(int unused_argc, char **unused_argv)
265 {
266     VSTRING *buf = vstring_alloc(1);
267     int     pass = 0;
268     int     fail = 0;
269     const TEST_CASE *tp;
270 
271     for (tp = test_cases; tp->label != 0; tp++) {
272           VSTRING *out;
273           int     ok = 0;
274 
275           msg_info("RUN  %s", tp->label);
276           vstring_memcpy(buf, OUTPUT_INIT, OUTPUT_INIT_SZ);
277           out = tp->func(buf, tp->input, tp->inlen, tp->flags);
278           if (out == 0 && tp->exp_output == 0) {
279               ok = 1;
280           } else if (out != buf) {
281               msg_warn("got result '%p', want: '%p'",
282                          (void *) out, (void *) buf);
283           } else if (LEN(out) != tp->exp_outlen) {
284               msg_warn("got result length '%ld', want: '%ld'",
285                          (long) LEN(out), (long) tp->exp_outlen);
286           } else if (memcmp(STR(out), tp->exp_output, tp->exp_outlen) != 0) {
287               msg_warn("got result '%*s', want: '%*s'",
288                          (int) LEN(out), STR(out),
289                          (int) tp->exp_outlen, tp->exp_output);
290           } else {
291               ok = 1;
292           }
293           if (ok) {
294               msg_info("PASS %s", tp->label);
295               pass++;
296           } else {
297               msg_info("FAIL %s", tp->label);
298               fail++;
299           }
300     }
301     vstring_free(buf);
302     msg_info("PASS=%d FAIL=%d", pass, fail);
303     return (fail > 0);
304 }
305 
306 #endif
307