some util classes stolen from webfunds
[cacert-birdshack.git] / Business Logic / trunk / src / org / cacert / birdshack / util / Hex.java
1 /*
2 * Was webfunds.util.Hex, contributed.
3 */
4 package org.cacert.birdshack.util;
5
6 import java.io.ByteArrayOutputStream;
7
8 /**
9 * <p>Common hex-conversion and digest routines.</p>
10 *
11 * <p>These functions returns lower-case characters when converting to
12 * hexadecimal. Functions parsing hexadecimal can accept either case.</p>.
13 *
14 * <p>byte[] methods must not change the bytes as many Immutable
15 * classes rely on no changes being made.</p>
16 */
17 public class Hex
18 {
19 private static final char[] HEX_DIGITS = {
20 '0','1','2','3','4','5','6','7','8','9','a','b','c','d','e','f'
21 };
22
23
24 /** Static methods only. */
25 private Hex() {}
26
27
28 /**
29 * <p>
30 * Checks whether a String is made of Hex digits.
31 * </p><p>
32 * A null or empty argument is ok and returns false,
33 * as the intention is that all hex strings should
34 * be populated.
35 * </p>
36 */
37 public static boolean isHexAscii(String s)
38 {
39 if (null == s) // not logically correct, but is the intention
40 return false ;
41
42 if (s.length() == 0)
43 return false ;
44
45 /*
46 * For Hex, both upper and lower are ok.
47 */
48 for (int i = 0; i < s.length(); i++)
49 {
50 if (toDataNibble(s.charAt(i)) < 0)
51 return false;
52 }
53 return true ;
54 }
55
56
57 /**
58 * Must not change the bytes, many Immutable classes rely on this.
59 * Note that null is returned as the string NULL == "<NULL>".
60 */
61 public static String data2hex(byte[] data)
62 {
63 if (data == null)
64 return NULL;
65
66 int len = data.length;
67 StringBuffer buf = new StringBuffer(len*2);
68 for (int pos = 0; pos < len; pos++) {
69 buf.append(toHexDigit((data[pos]>>>4)&0x0F));
70 buf.append(toHexDigit((data[pos] )&0x0F));
71 }
72 return buf.toString();
73 }
74
75 public static String data2spacedhex(byte[] data)
76 {
77 return data2spacedhex(data, 1, " ");
78 }
79
80 /**
81 * Create a blocked hexadecimal string suitable for diagnostics
82 * bytes=2 and space=" " will give OpenPGP fingerprint style.
83 *
84 * @data some binary data to turn into a printable hex string
85 * @param bytes How many bytes in each block (1 gives 2 hex chars)
86 * @param space is the string between each block
87 */
88 public static String data2spacedhex(byte[] data, int bytes, String space)
89 {
90 if (data == null)
91 return NULL;
92 if (data.length == 0)
93 return "";
94 if (bytes < 1)
95 bytes = 1;
96 if (space == null)
97 space = " ";
98
99 int len = data.length;
100 StringBuffer buf = new StringBuffer(len * (space.length()+bytes*2) - 1);
101 int pos = 0;
102 while (pos < len)
103 {
104 if (pos != 0) buf.append(space);
105
106 int i = 0;
107 while (i < bytes && pos < len)
108 {
109 buf.append(toHexDigit((data[pos]>>>4)&0x0F));
110 buf.append(toHexDigit((data[pos] )&0x0F));
111 pos++; i++;
112 }
113 }
114 return buf.toString();
115 }
116
117 /*
118 * <p>
119 * Create a binary byte array with the hex characters converted.
120 * </p><p>
121 * Unchecked, GIGO.
122 * </p>
123 *
124 * @return always returns a byte array with the binary conversion,
125 * never returns null (empty on null argument)
126 * @see hex2dataOrNull for a checked method
127 */
128 public static byte[] hex2data(String str)
129 {
130 if (str == null)
131 return new byte[0] ;
132
133 int len = str.length(); // probably should check length
134 char hex[] = str.toCharArray();
135 byte[] buf = new byte[len/2];
136
137 for (int pos = 0; pos < len / 2; pos++)
138 {
139 byte hi = toDataNibble(hex[2*pos]);
140 byte lo = toDataNibble(hex[2*pos + 1]);
141
142 buf[pos] = (byte)( ((hi << 4) & 0xF0) | ( lo & 0x0F) );
143 }
144
145 return buf;
146 }
147
148 /**
149 * <p>
150 * Create a binary byte array with the hex characters converted.
151 * </p><p>
152 * Will fail (return null) if any non-hex chars are encountered,
153 * or if the string is empty or null.
154 * </p>
155 *
156 * @return the binary converted from the hex, else null if any bads found
157 * @see hex2dataOrNull for a unchecked method
158 */
159 public static byte[] hex2dataOrNull(String str)
160 {
161 if (str == null || str.length() == 0)
162 return null ;
163
164 int len = str.length();
165 char hex[] = str.toCharArray();
166 int buflen = len/2;
167 if (buflen * 2 != len) // whoops, need to check odd-length strings
168 return null;
169 byte[] buf = new byte[buflen];
170
171 for (int pos = 0; pos < buflen; pos++)
172 {
173 byte hi = toDataNibble(hex[2*pos]);
174 if (hi < 0) return null;
175 byte lo = toDataNibble(hex[2*pos + 1]);
176 if (lo < 0) return null;
177
178 buf[pos] = (byte)( ((hi << 4) & 0xF0) | ( lo & 0x0F) );
179 }
180
181 return buf;
182 }
183
184 /**
185 * Returns the the hex digit corresponding to the argument.
186 *
187 * @throws ArrayIndexOutOfBoundsException
188 * If the argument is not in range [0,15];
189 */
190 private static char toHexDigit(int i)
191 {
192 return HEX_DIGITS[i];
193 }
194
195 private static byte toDataNibble(char c)
196 {
197 if (('0' <= c) && (c <= '9' ))
198 return (byte)((byte)c - (byte)'0');
199 else if (('a' <= c) && (c <= 'f' ))
200 return (byte)((byte)c - (byte)'a' + 10);
201 else if (('A' <= c) && (c <= 'F' ))
202 return (byte)((byte)c - (byte)'A' + 10);
203 else
204 return -1;
205 }
206
207 static final String NULL = "<null>";
208
209 /**
210 * @return a byte array converted to hex, and truncated beyond some
211 * reasonable length, long enough to avoid debugging collisions.
212 */
213 public static String quick(byte[] b, int len)
214 {
215 if (b == null)
216 return NULL;
217 String middle = "..";
218 byte[] end = new byte[3];
219
220 if (b.length < (len + (middle.length() / 2) + end.length))
221 return data2hex(b);
222
223 byte[] start = new byte[len];
224 int i, j;
225 for (j = 0; j < start.length; j++)
226 start[j] = b[j];
227
228 for (j = 0, i = b.length - end.length; i < b.length; j++, i++)
229 end[j] = b[i];
230
231 return data2hex(start) + middle + data2hex(end);
232 }
233
234 /**
235 * @return a byte array converted to hex, and truncated beyond some
236 * reasonable length, long enough to avoid debugging collisions.
237 */
238 public static String quick(byte[] b)
239 {
240 return quick(b, 8);
241 }
242
243
244
245 /**
246 * Test if this is basic Ascii and printable.
247 *
248 * @return true if the string is printable
249 */
250 public static boolean isPrintable(byte[] data)
251 {
252 if (data == null)
253 return true;
254
255 int len = data.length;
256 for (int pos = 0; pos < len; pos++)
257 {
258 if (0x20 <= data[pos] && data[pos] <= 0xEF)
259 continue ;
260 if (data[pos] == '\r' || data[pos] == '\n' || data[pos] == '\t')
261 continue ;
262 return false;
263 }
264 return true;
265 }
266
267 /**
268 * Useful for printing descriptions, which are generally messages
269 * to the user. Sometimes they could be binary.
270 *
271 * @return a string that is printable, either the original, or a hex
272 * conversion of the original if funny chars found.
273 */
274 public static String description(byte[] data)
275 {
276 if (data == null)
277 return NULL;
278
279 int len = data.length;
280 if (isPrintable(data))
281 return new String(data);
282 else
283 return "[" + len + "] <<" + data2hex(data) + ">>";
284 }
285
286
287 /**
288 * Useful for printing printable strings, which may be binary,
289 * but most likely are not.
290 *
291 * @return a string that is printable, either the original, or a hex
292 * conversion of the original if funny chars found.
293 */
294 public static String printable(byte[] data)
295 {
296 if (data == null)
297 return NULL;
298
299 int len = data.length;
300 if (isPrintable(data))
301 return "{" + len + "} \"" + (new String(data)) + "\"";
302 else
303 return "[" + len + "] <<" + data2hex(data) + ">>";
304 }
305
306 /**
307 * Useful for printing descriptions, which can include binary.
308 * Does not change data ... but it used to !
309 * @return a string that is printable, with binaries dotted out.
310 */
311 public static String dotable(byte[] data)
312 {
313 if (data == null)
314 return NULL;
315
316 int len = data.length;
317 byte[] copy = new byte[len];
318 for (int pos = 0; pos < len; pos++)
319 {
320 if (0x20 <= data[pos] && data[pos] <= 0xEF)
321 copy[pos] = data[pos];
322 else if (data[pos] == '\n' || data[pos] == '\t')
323 copy[pos] = data[pos];
324 else if (data[pos] == '\r')
325 copy[pos] = (byte)'\\';
326 else
327 copy[pos] = (byte)'.';
328 }
329 return new String(copy);
330 }
331
332 private static final byte[] dots = "...".getBytes();
333
334 /**
335 * Useful for printing errors, which can include binary.
336 * Turns any wierdness into hex numbers, leaves US-ASCII
337 * printable.
338 * Kind to estimated callers, corrects bounds on from, to.
339 *
340 * @param data the ascii text with some dodgy chars
341 * @param from the start position, made 0 if negative
342 * @param to the end position, made the end if greater than length
343 * @return a string that is printable, with binary hexed.
344 */
345 public static String hexable(byte[] data, int from, int to)
346 {
347 if (data == null)
348 return NULL;
349 if (from < 0) from = 0;
350 if (to > data.length) to = data.length;
351
352 ByteArrayOutputStream bais = new ByteArrayOutputStream();
353
354 try {
355 if (from > 0) bais.write(dots);
356
357 int len = to;
358 for (int pos = from; pos < len; pos++)
359 {
360 int b = data[pos] & 0xFF;
361 if (0x20 <= b && b < 0x7F)
362 bais.write(b) ;
363 else if (b == '\n')
364 bais.write("\\n\n".getBytes());
365 else if (b == '\r')
366 bais.write("\\r\n".getBytes());
367 else if (b < 0x20)
368 {
369 String s = "\0x" + toString((byte)b);
370 bais.write(s.getBytes());
371 }
372 else
373 {
374 String s = "<" + toString((byte)b) + ">";
375 bais.write(s.getBytes());
376 }
377 }
378 if (to < data.length) bais.write(dots);
379 } catch (Exception ex) {
380 throw new Error(ex);
381 }
382 return new String( bais.toByteArray() );
383 }
384
385 /**
386 * Useful for printing errors, which can include binary.
387 * Turns any wierdness into hex numbers, leaves US-ASCII
388 * printable.
389 *
390 * @return a string that is printable, with binary hexed.
391 */
392 public static String hexable(byte[] data)
393 {
394 if (data == null)
395 return NULL;
396
397 return hexable(data, 0, data.length);
398 }
399
400 /**
401 * Useful for printing errors, which can include binary.
402 * Turns any wierdness into hex numbers, leaves US-ASCII
403 * printable.
404 *
405 * @return a string that is printable, with binary hexed.
406 */
407 public static String hexable(String s)
408 {
409 if (s == null)
410 return NULL;
411
412 try {
413 byte[] data = s.getBytes("UTF-8");
414 return hexable(data);
415 } catch (Exception ex) {
416 throw new Error(ex);
417 }
418 }
419
420
421 /**
422 * Converts the given byte to an unsigned 2-character hex string.
423 */
424 public static String toString(byte x) {
425 char[] cs = new char[2];
426 cs[0] = toHexDigit((x >>> 4) & 0xF);
427 cs[1] = toHexDigit((x ) & 0xF);
428 return new String(cs);
429 }
430
431
432 /**
433 * Converts the given int to an unsigned 8-character hex string.
434 */
435 public static String toString(int x) {
436 char[] cs = new char[8];
437 for(int i=7; i>=0; i--, x>>>=4)
438 cs[i] = toHexDigit(x & 0xF);
439 return new String(cs);
440 }
441
442
443 /**
444 * Converts the given long to an unsigned 16-character hex string.
445 */
446 public static String toString(long x) {
447 char[] cs = new char[16];
448 for(int i=15; i>=0; i--, x>>>=4)
449 cs[i] = toHexDigit((int)x & 0xF);
450 return new String(cs);
451 }
452
453
454
455 /**
456 * Make a copy of the raw bytes, for an immutable Id get method.
457 * (This is similar to clone(), but it handles null, and there
458 * is some change/bug where byte[].clone() has become protected.
459 * Also, byte[].clone() doesn't work for 1.4.1 ?)
460 *
461 * @param the original bytes (can be null)
462 * @return the bytes copied (null is returned if null passed)
463 */
464 public static byte[] copy(byte[] buf)
465 {
466 if (buf == null)
467 return null;
468 int len = buf.length;
469 byte[] temp = new byte[len];
470 System.arraycopy(buf, 0, temp, 0, len);
471 return temp;
472 }
473
474
475 /**
476 * <p>
477 * Convert this string representation into the bytes.
478 * Strips any hint strings that might be present in
479 * debug outputs, etc. The hint strings are of the
480 * the form "SHA1#abc123..." or perhaps "contractid:123abc..."
481 * </p><p>
482 * The resultant bytes should be the underlying message digest
483 * binary bytes, and suitable for reconstruction into another
484 * form of Id. This is essential for crossing package boundaries
485 * where the packages themselves cannot agree on dependencies.
486 * </p><p>
487 * The strings come from many different higher level applications
488 * that use strings (Lucaya, servers, etc). Users tend to
489 * pass this format across to things like payment dialogs.
490 * </p>
491 *
492 * @return a byte array of indeterminate length, or null if no
493 * hex string was found, or there were garbage chars in it
494 */
495 public static byte[] getBytesFromIdString(String s)
496 {
497 if (s == null)
498 return null;
499 int start = s.indexOf('#');
500 if (start == -1)
501 start = s.indexOf(':');
502 if (start >= 0)
503 s = s.substring(start + 1);
504 byte[] bytes = hex2dataOrNull(s);
505 return bytes;
506 }
507
508 /**
509 * Boring utility that saves writing it all the time.
510 * Turn 4 bytes into a hashcode, no pertubations.
511 * @throws Boom if not 4 bytes available in b or null
512 */
513 public static int bytes2hashCode(byte[] b)
514 {
515 return (b[0] << 24) | (b[1] << 16) | (b[2] << 8) | b[3];
516 }
517
518 /**
519 * A quick function for diags, long enough to
520 * avoid collisions, but short enough to print out.
521 * Does not change the bytes.
522 *
523 * @param buf is the bytes to convert to hex
524 * @param number is minimum of bytes (chars is 2*(bytes+1))
525 */
526 public static String diag(byte[] buf, int number)
527 {
528 if (buf.length <= (number+1))
529 return Hex.data2hex(buf);
530
531 byte[] bbb = new byte[number];
532 for (int j = 0; j < bbb.length; j++)
533 bbb[j] = buf[j];
534
535 return Hex.data2hex(bbb) + "..";
536 }
537
538
539
540
541
542
543 ////////////////////////////////
544 // //
545 // T E S T C O D E //
546 // //
547 ////////////////////////////////
548
549
550
551 public static void main(String args[])
552 throws Exception
553 {
554 args = null;
555 madeUpTest();
556 dataTest();
557 System.err.println("Tests Passed.");
558 System.exit(0);
559 }
560
561 /**
562 * Test some made up strings for explicit conversion to bytes.
563 */
564 public static void madeUpTest()
565 throws Exception
566 {
567 String s = "c001d00d";
568 byte b[] = { (byte)0xC0, (byte)0x01, (byte)0xD0, (byte)0x0D };
569
570 System.err.println("bytes: " + data2spacedhex(b));
571 String test = data2hex(b).toLowerCase();
572 if (!test.equals(s))
573 throw new Exception("data2hex " + s + " failed: " + test);
574 System.err.println("string: " + s);
575
576 byte ttt[] = hex2data(s);
577 byte uuu[] = hex2dataOrNull(s);
578 if (uuu == null)
579 throw new Exception("hex2dataOrNull " + s + " failed: null");
580 // ttt[2] = (byte)0xFA; // test
581 for (int i = 0; i < b.length; i++)
582 {
583 if (ttt[i] != b[i])
584 throw new Exception("hex2data " + s + " failed: "
585 + data2hex(ttt) + " (byte " + i + ")");
586 if (uuu[i] != b[i])
587 throw new Exception("hex2dataOrNull " + s + " failed: "
588 + data2hex(ttt) + " (byte " + i + ")");
589 }
590
591 s += "-0";
592 if (hex2dataOrNull(s) != null)
593 throw new Exception("hex2dataOrNull did not return null on " + s);
594 if (hex2data(s) == null)
595 throw new Exception("hex2data returned null on " + s + "???");
596
597 System.err.println("made up data tested.");
598 }
599
600 public static void dataTest()
601 {
602 for (int i = 0; i < 10; i++)
603 {
604 String s = Junk.string();
605 System.err.println(i + ": " + s);
606 System.err.println(data2spacedhex(s.getBytes(), i, ".."));
607 cycle(s);
608 }
609 }
610
611 public static void cycle(String s)
612 {
613 byte[] b = s.getBytes();
614 String test = data2hex(b).toLowerCase();
615 byte[] back = hex2data(test);
616 String result = new String(back);
617 if (!result.equals(s))
618 throw new RuntimeException("Failed!" +
619 "\nstart: " + s +
620 "\n hex : " + test +
621 "\n back: " + result);
622 }
623
624 }