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