001/*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  You may obtain a copy of the License at
008 *
009 *      http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017
018package org.apache.commons.codec.binary;
019
020import static org.junit.Assert.assertEquals;
021import static org.junit.Assert.assertFalse;
022import static org.junit.Assert.assertTrue;
023import static org.junit.Assert.fail;
024
025import java.io.UnsupportedEncodingException;
026import java.nio.charset.Charset;
027import java.nio.charset.UnsupportedCharsetException;
028import java.util.Arrays;
029import java.util.Random;
030
031import org.junit.Assert;
032
033import org.apache.commons.codec.DecoderException;
034import org.apache.commons.codec.EncoderException;
035import org.junit.Test;
036
037/**
038 * Tests {@link org.apache.commons.codec.binary.Hex}.
039 *
040 * @version $Id: HexTest.html 891688 2013-12-24 20:49:46Z ggregory $
041 */
042public class HexTest {
043
044    private static final String BAD_ENCODING_NAME = "UNKNOWN";
045
046    private final static boolean LOG = false;
047
048    private boolean charsetSanityCheck(final String name) {
049        final String source = "the quick brown dog jumped over the lazy fox";
050        try {
051            final byte[] bytes = source.getBytes(name);
052            final String str = new String(bytes, name);
053            final boolean equals = source.equals(str);
054            if (equals == false) {
055                // Here with:
056                //
057                // Java Sun 1.4.2_19 x86 32-bits on Windows XP
058                // JIS_X0212-1990
059                // x-JIS0208
060                //
061                // Java Sun 1.5.0_17 x86 32-bits on Windows XP
062                // JIS_X0212-1990
063                // x-IBM834
064                // x-JIS0208
065                // x-MacDingbat
066                // x-MacSymbol
067                //
068                // Java Sun 1.6.0_14 x86 32-bits
069                // JIS_X0212-1990
070                // x-IBM834
071                // x-JIS0208
072                // x-MacDingbat
073                // x-MacSymbol
074                //
075                log("FAILED charsetSanityCheck=Interesting Java charset oddity: Roundtrip failed for " + name);
076            }
077            return equals;
078        } catch (final UnsupportedEncodingException e) {
079            // Should NEVER happen since we are getting the name from the Charset class.
080            if (LOG) {
081                log("FAILED charsetSanityCheck=" + name + ", e=" + e);
082                log(e);
083            }
084            return false;
085        } catch (final UnsupportedOperationException e) {
086            // Caught here with:
087            // x-JISAutoDetect on Windows XP and Java Sun 1.4.2_19 x86 32-bits
088            // x-JISAutoDetect on Windows XP and Java Sun 1.5.0_17 x86 32-bits
089            // x-JISAutoDetect on Windows XP and Java Sun 1.6.0_14 x86 32-bits
090            if (LOG) {
091                log("FAILED charsetSanityCheck=" + name + ", e=" + e);
092                log(e);
093            }
094            return false;
095        }
096    }
097
098    /**
099     * @param data
100     */
101    private void checkDecodeHexOddCharacters(final char[] data) {
102        try {
103            Hex.decodeHex(data);
104            fail("An exception wasn't thrown when trying to decode an odd number of characters");
105        } catch (final DecoderException e) {
106            // Expected exception
107        }
108    }
109
110    private void log(final String s) {
111        if (LOG) {
112            System.out.println(s);
113            System.out.flush();
114        }
115    }
116
117    private void log(final Throwable t) {
118        if (LOG) {
119            t.printStackTrace(System.out);
120            System.out.flush();
121        }
122    }
123
124    @Test
125    public void testCustomCharset() throws UnsupportedEncodingException, DecoderException {
126        for (final String name : Charset.availableCharsets().keySet()) {
127            testCustomCharset(name, "testCustomCharset");
128        }
129    }
130
131    /**
132     * @param name
133     * @param parent
134     * @throws UnsupportedEncodingException
135     * @throws DecoderException
136     */
137    private void testCustomCharset(final String name, final String parent) throws UnsupportedEncodingException, DecoderException {
138        if (charsetSanityCheck(name) == false) {
139            return;
140        }
141        log(parent + "=" + name);
142        final Hex customCodec = new Hex(name);
143        // source data
144        final String sourceString = "Hello World";
145        final byte[] sourceBytes = sourceString.getBytes(name);
146        // test 1
147        // encode source to hex string to bytes with charset
148        final byte[] actualEncodedBytes = customCodec.encode(sourceBytes);
149        // encode source to hex string...
150        String expectedHexString = Hex.encodeHexString(sourceBytes);
151        // ... and get the bytes in the expected charset
152        final byte[] expectedHexStringBytes = expectedHexString.getBytes(name);
153        Assert.assertTrue(Arrays.equals(expectedHexStringBytes, actualEncodedBytes));
154        // test 2
155        String actualStringFromBytes = new String(actualEncodedBytes, name);
156        assertEquals(name + ", expectedHexString=" + expectedHexString + ", actualStringFromBytes=" + actualStringFromBytes,
157                expectedHexString, actualStringFromBytes);
158        // second test:
159        final Hex utf8Codec = new Hex();
160        expectedHexString = "48656c6c6f20576f726c64";
161        final byte[] decodedUtf8Bytes = (byte[]) utf8Codec.decode(expectedHexString);
162        actualStringFromBytes = new String(decodedUtf8Bytes, utf8Codec.getCharset());
163        // sanity check:
164        assertEquals(name, sourceString, actualStringFromBytes);
165        // actual check:
166        final byte[] decodedCustomBytes = customCodec.decode(actualEncodedBytes);
167        actualStringFromBytes = new String(decodedCustomBytes, name);
168        assertEquals(name, sourceString, actualStringFromBytes);
169    }
170
171    @Test(expected=UnsupportedCharsetException.class)
172    public void testCustomCharsetBadName() {
173        new Hex(BAD_ENCODING_NAME);
174    }
175
176    @Test
177    public void testCustomCharsetToString() {
178        assertTrue(new Hex().toString().indexOf(Hex.DEFAULT_CHARSET_NAME) >= 0);
179    }
180
181    @Test
182    public void testDecodeArrayOddCharacters() {
183        try {
184            new Hex().decode(new byte[]{65});
185            fail("An exception wasn't thrown when trying to decode an odd number of characters");
186        } catch (final DecoderException e) {
187            // Expected exception
188        }
189    }
190
191    @Test
192    public void testDecodeBadCharacterPos0() {
193        try {
194            new Hex().decode("q0");
195            fail("An exception wasn't thrown when trying to decode an illegal character");
196        } catch (final DecoderException e) {
197            // Expected exception
198        }
199    }
200
201    @Test
202    public void testDecodeBadCharacterPos1() {
203        try {
204            new Hex().decode("0q");
205            fail("An exception wasn't thrown when trying to decode an illegal character");
206        } catch (final DecoderException e) {
207            // Expected exception
208        }
209    }
210
211    @Test
212    public void testDecodeClassCastException() {
213        try {
214            new Hex().decode(new int[]{65});
215            fail("An exception wasn't thrown when trying to decode.");
216        } catch (final DecoderException e) {
217            // Expected exception
218        }
219    }
220
221    @Test
222    public void testDecodeHexOddCharacters1() {
223        checkDecodeHexOddCharacters(new char[]{'A'});
224    }
225
226    @Test
227    public void testDecodeHexOddCharacters3() {
228        checkDecodeHexOddCharacters(new char[]{'A', 'B', 'C'});
229    }
230
231    @Test
232    public void testDecodeHexOddCharacters5() {
233        checkDecodeHexOddCharacters(new char[]{'A', 'B', 'C', 'D', 'E'});
234    }
235
236    @Test
237    public void testDecodeStringOddCharacters() {
238        try {
239            new Hex().decode("6");
240            fail("An exception wasn't thrown when trying to decode an odd number of characters");
241        } catch (final DecoderException e) {
242            // Expected exception
243        }
244    }
245
246    @Test
247    public void testDencodeEmpty() throws DecoderException {
248        assertTrue(Arrays.equals(new byte[0], Hex.decodeHex(new char[0])));
249        assertTrue(Arrays.equals(new byte[0], new Hex().decode(new byte[0])));
250        assertTrue(Arrays.equals(new byte[0], (byte[]) new Hex().decode("")));
251    }
252
253    @Test
254    public void testEncodeClassCastException() {
255        try {
256            new Hex().encode(new int[]{65});
257            fail("An exception wasn't thrown when trying to encode.");
258        } catch (final EncoderException e) {
259            // Expected exception
260        }
261    }
262
263    @Test
264    public void testEncodeDecodeRandom() throws DecoderException, EncoderException {
265        final Random random = new Random();
266
267        final Hex hex = new Hex();
268        for (int i = 5; i > 0; i--) {
269            final byte[] data = new byte[random.nextInt(10000) + 1];
270            random.nextBytes(data);
271
272            // static API
273            final char[] encodedChars = Hex.encodeHex(data);
274            byte[] decodedBytes = Hex.decodeHex(encodedChars);
275            assertTrue(Arrays.equals(data, decodedBytes));
276
277            // instance API with array parameter
278            final byte[] encodedStringBytes = hex.encode(data);
279            decodedBytes = hex.decode(encodedStringBytes);
280            assertTrue(Arrays.equals(data, decodedBytes));
281
282            // instance API with char[] (Object) parameter
283            String dataString = new String(encodedChars);
284            char[] encodedStringChars = (char[]) hex.encode(dataString);
285            decodedBytes = (byte[]) hex.decode(encodedStringChars);
286            assertTrue(Arrays.equals(StringUtils.getBytesUtf8(dataString), decodedBytes));
287
288            // instance API with String (Object) parameter
289            dataString = new String(encodedChars);
290            encodedStringChars = (char[]) hex.encode(dataString);
291            decodedBytes = (byte[]) hex.decode(new String(encodedStringChars));
292            assertTrue(Arrays.equals(StringUtils.getBytesUtf8(dataString), decodedBytes));
293        }
294    }
295
296    @Test
297    public void testEncodeEmpty() throws EncoderException {
298        assertTrue(Arrays.equals(new char[0], Hex.encodeHex(new byte[0])));
299        assertTrue(Arrays.equals(new byte[0], new Hex().encode(new byte[0])));
300        assertTrue(Arrays.equals(new char[0], (char[]) new Hex().encode("")));
301    }
302
303    @Test
304    public void testEncodeZeroes() {
305        final char[] c = Hex.encodeHex(new byte[36]);
306        assertEquals("000000000000000000000000000000000000000000000000000000000000000000000000", new String(c));
307    }
308
309    @Test
310    public void testHelloWorldLowerCaseHex() {
311        final byte[] b = StringUtils.getBytesUtf8("Hello World");
312        final String expected = "48656c6c6f20576f726c64";
313        char[] actual;
314        actual = Hex.encodeHex(b);
315        assertEquals(expected, new String(actual));
316        actual = Hex.encodeHex(b, true);
317        assertEquals(expected, new String(actual));
318        actual = Hex.encodeHex(b, false);
319        assertFalse(expected.equals(new String(actual)));
320    }
321
322    @Test
323    public void testHelloWorldUpperCaseHex() {
324        final byte[] b = StringUtils.getBytesUtf8("Hello World");
325        final String expected = "48656C6C6F20576F726C64";
326        char[] actual;
327        actual = Hex.encodeHex(b);
328        assertFalse(expected.equals(new String(actual)));
329        actual = Hex.encodeHex(b, true);
330        assertFalse(expected.equals(new String(actual)));
331        actual = Hex.encodeHex(b, false);
332        assertTrue(expected.equals(new String(actual)));
333    }
334
335    @Test
336    public void testRequiredCharset() throws UnsupportedEncodingException, DecoderException {
337        testCustomCharset("UTF-8", "testRequiredCharset");
338        testCustomCharset("UTF-16", "testRequiredCharset");
339        testCustomCharset("UTF-16BE", "testRequiredCharset");
340        testCustomCharset("UTF-16LE", "testRequiredCharset");
341        testCustomCharset("US-ASCII", "testRequiredCharset");
342        testCustomCharset("ISO8859_1", "testRequiredCharset");
343    }
344}