/* See LICENSE for licensing and NOTICE for copyright. */ package org.cryptacular.codec; import java.nio.ByteBuffer; import java.nio.CharBuffer; import java.util.Arrays; import org.cryptacular.EncodingException; /** * Stateful hexadecimal character-to-byte decoder. * * @author Middleware Services */ public class HexDecoder implements Decoder { /** Hex character decoding table. */ private static final byte[] DECODING_TABLE = new byte[128]; /** * Initializes the character decoding table. */ static { Arrays.fill(DECODING_TABLE, (byte) -1); for (int i = 0; i < 10; i++) { DECODING_TABLE[i + 48] = (byte) i; } for (int i = 0; i < 6; i++) { DECODING_TABLE[i + 65] = (byte) (10 + i); DECODING_TABLE[i + 97] = (byte) (10 + i); } } /** Number of encoded characters processed. */ private int count; @Override public void decode(final CharBuffer input, final ByteBuffer output) throws EncodingException { // Ignore leading 0x characters if present if (input.get(0) == '0' && input.get(1) == 'x') { input.position(input.position() + 2); } byte hi = 0; byte lo; char current; while (input.hasRemaining()) { current = input.get(); if (current == ':' || Character.isWhitespace(current)) { continue; } if ((count++ & 0x01) == 0) { hi = lookup(current); } else { lo = lookup(current); output.put((byte) ((hi << 4) | lo)); } } } @Override public void finalize(final ByteBuffer output) throws EncodingException { count = 0; } @Override public int outputSize(final int inputSize) { return inputSize / 2; } /** * Looks up the byte that corresponds to the given character. * * @param c Encoded character. * * @return Decoded byte. */ private static byte lookup(final char c) { final byte b = DECODING_TABLE[c & 0x7F]; if (b < 0) { throw new EncodingException("Invalid hex character " + c); } return b; } }