View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  
18  package org.apache.commons.codec.binary;
19  
20  import static org.junit.jupiter.api.Assertions.assertArrayEquals;
21  import static org.junit.jupiter.api.Assertions.assertEquals;
22  import static org.junit.jupiter.api.Assertions.assertFalse;
23  import static org.junit.jupiter.api.Assertions.assertThrows;
24  
25  import java.io.ByteArrayInputStream;
26  import java.io.IOException;
27  import java.io.InputStream;
28  
29  import org.junit.jupiter.api.Test;
30  
31  /**
32   */
33  public class Base16InputStreamTest {
34  
35      /**
36       * Decodes to {202, 254, 186, 190, 255, 255}
37       */
38      private static final String ENCODED_B16 = "CAFEBABEFFFF";
39  
40      private static final String STRING_FIXTURE = "Hello World";
41  
42      /**
43       * Tests skipping past the end of a stream.
44       *
45       * @throws IOException for some failure scenarios.
46       */
47      @Test
48      public void testAvailable() throws IOException {
49          final InputStream ins = new ByteArrayInputStream(StringUtils.getBytesIso8859_1(ENCODED_B16));
50          try (final Base16InputStream b16Stream = new Base16InputStream(ins)) {
51              assertEquals(1, b16Stream.available());
52              assertEquals(6, b16Stream.skip(10));
53              // End of stream reached
54              assertEquals(0, b16Stream.available());
55              assertEquals(-1, b16Stream.read());
56              assertEquals(-1, b16Stream.read());
57              assertEquals(0, b16Stream.available());
58          }
59      }
60  
61      /**
62       * Tests the Base16InputStream implementation against empty input.
63       *
64       * @throws IOException for some failure scenarios.
65       */
66      @Test
67      public void testBase16EmptyInputStream() throws IOException {
68          final byte[] emptyEncoded = {};
69          final byte[] emptyDecoded = {};
70          testByteByByte(emptyEncoded, emptyDecoded);
71          testByChunk(emptyEncoded, emptyDecoded);
72      }
73  
74      /**
75       * Tests the Base16InputStream implementation.
76       *
77       * @throws IOException for some failure scenarios.
78       */
79      @Test
80      public void testBase16InputStreamByChunk() throws IOException {
81          // Hello World test.
82          byte[] encoded = StringUtils.getBytesUtf8("48656C6C6F20576F726C64");
83          byte[] decoded = StringUtils.getBytesUtf8(STRING_FIXTURE);
84          testByChunk(encoded, decoded);
85  
86          // Single Byte test.
87          encoded = StringUtils.getBytesUtf8("41");
88          decoded = new byte[] { (byte) 0x41 };
89          testByChunk(encoded, decoded);
90  
91          // OpenSSL interop test.
92          encoded = StringUtils.getBytesUtf8(Base16TestData.ENCODED_UTF8_UPPERCASE);
93          decoded = BaseNTestData.DECODED;
94          testByChunk(encoded, decoded);
95  
96          // test random data of sizes 0 through 150
97          final BaseNCodec codec = new Base16(true);
98          for (int i = 0; i <= 150; i++) {
99              final byte[][] randomData = BaseNTestData.randomData(codec, i);
100             encoded = randomData[1];
101             decoded = randomData[0];
102             testByChunk(encoded, decoded, true);
103         }
104     }
105 
106     /**
107      * Tests the Base16InputStream implementation.
108      *
109      * @throws IOException for some failure scenarios.
110      */
111     @Test
112     public void testBase16InputStreamByteByByte() throws IOException {
113         // Hello World test.
114         byte[] encoded = StringUtils.getBytesUtf8("48656C6C6F20576F726C64");
115         byte[] decoded = StringUtils.getBytesUtf8(STRING_FIXTURE);
116         testByteByByte(encoded, decoded);
117 
118         // Single Byte test.
119         encoded = StringUtils.getBytesUtf8("41");
120         decoded = new byte[] { (byte) 0x41 };
121         testByteByByte(encoded, decoded);
122 
123         // OpenSSL interop test.
124         encoded = StringUtils.getBytesUtf8(Base16TestData.ENCODED_UTF8_UPPERCASE);
125         decoded = BaseNTestData.DECODED;
126         testByteByByte(encoded, decoded);
127 
128         // test random data of sizes 0 through 150
129         final BaseNCodec codec = new Base16(true);
130         for (int i = 0; i <= 150; i++) {
131             final byte[][] randomData = BaseNTestData.randomData(codec, i);
132             encoded = randomData[1];
133             decoded = randomData[0];
134             testByteByByte(encoded, decoded, true);
135         }
136     }
137 
138     /**
139      * Tests method does three tests on the supplied data: 1. encoded ---[DECODE]--> decoded 2. decoded ---[ENCODE]--> encoded 3. decoded
140      * ---[WRAP-WRAP-WRAP-etc...] --> decoded
141      * <p/>
142      * By "[WRAP-WRAP-WRAP-etc...]" we mean situation where the Base16InputStream wraps itself in encode and decode mode over and over again.
143      *
144      * @param encoded Base16 encoded data
145      * @param decoded the data from above, but decoded
146      * @throws IOException Usually signifies a bug in the Base16 commons-codec implementation.
147      */
148     private void testByChunk(final byte[] encoded, final byte[] decoded) throws IOException {
149         testByChunk(encoded, decoded, false);
150     }
151 
152     /**
153      * Tests method does three tests on the supplied data: 1. encoded ---[DECODE]--> decoded 2. decoded ---[ENCODE]--> encoded 3. decoded
154      * ---[WRAP-WRAP-WRAP-etc...] --> decoded
155      * <p/>
156      * By "[WRAP-WRAP-WRAP-etc...]" we mean situation where the Base16InputStream wraps itself in encode and decode mode over and over again.
157      *
158      * @param encoded   Base16 encoded data
159      * @param decoded   the data from above, but decoded
160      * @param lowerCase if {@code true} then use a lower-case Base16 alphabet
161      * @throws IOException Usually signifies a bug in the Base16 commons-codec implementation.
162      */
163     private void testByChunk(final byte[] encoded, final byte[] decoded, final boolean lowerCase) throws IOException {
164 
165         // Start with encode.
166         try (final InputStream in = new Base16InputStream(new ByteArrayInputStream(decoded), true, lowerCase)) {
167             final byte[] output = BaseNTestData.streamToBytes(in);
168 
169             assertEquals(-1, in.read(), "EOF");
170             assertEquals(-1, in.read(), "Still EOF");
171             assertArrayEquals(encoded, output, "Streaming Base16 encode");
172         }
173 
174         // Now let's try to decode.
175         try (final InputStream in = new Base16InputStream(new ByteArrayInputStream(encoded), false, lowerCase)) {
176             final byte[] output = BaseNTestData.streamToBytes(in);
177 
178             assertEquals(-1, in.read(), "EOF");
179             assertEquals(-1, in.read(), "Still EOF");
180             assertArrayEquals(decoded, output, "Streaming Base16 decode");
181         }
182 
183         // wrap encoder with decoder
184         try (final InputStream in = new ByteArrayInputStream(decoded);
185                 final InputStream inEncode = new Base16InputStream(in, true, lowerCase);
186                 final InputStream inDecode = new Base16InputStream(inEncode, false, lowerCase)) {
187 
188             final byte[] output = BaseNTestData.streamToBytes(inDecode);
189 
190             assertEquals(-1, inDecode.read(), "EOF");
191             assertEquals(-1, inDecode.read(), "Still EOF");
192             assertArrayEquals(decoded, output, "Streaming Base16 wrap-wrap!");
193         }
194     }
195 
196     /**
197      * Tests method does three tests on the supplied data: 1. encoded ---[DECODE]--> decoded 2. decoded ---[ENCODE]--> encoded 3. decoded
198      * ---[WRAP-WRAP-WRAP-etc...] --> decoded
199      * <p/>
200      * By "[WRAP-WRAP-WRAP-etc...]" we mean situation where the Base16InputStream wraps itself in encode and decode mode over and over again.
201      *
202      * @param encoded Base16 encoded data
203      * @param decoded the data from above, but decoded
204      * @throws IOException Usually signifies a bug in the Base16 commons-codec implementation.
205      */
206     private void testByteByByte(final byte[] encoded, final byte[] decoded) throws IOException {
207         testByteByByte(encoded, decoded, false);
208     }
209 
210     /**
211      * Tests method does three tests on the supplied data: 1. encoded ---[DECODE]--> decoded 2. decoded ---[ENCODE]--> encoded 3. decoded
212      * ---[WRAP-WRAP-WRAP-etc...] --> decoded
213      * <p/>
214      * By "[WRAP-WRAP-WRAP-etc...]" we mean situation where the Base16InputStream wraps itself in encode and decode mode over and over again.
215      *
216      * @param encoded   Base16 encoded data
217      * @param decoded   the data from above, but decoded
218      * @param lowerCase if {@code true} then use a lower-case Base16 alphabet
219      * @throws IOException Usually signifies a bug in the Base16 commons-codec implementation.
220      */
221     private void testByteByByte(final byte[] encoded, final byte[] decoded, final boolean lowerCase) throws IOException {
222 
223         // Start with encode.
224         try (final InputStream in = new Base16InputStream(new ByteArrayInputStream(decoded), true, lowerCase)) {
225             final byte[] output = new byte[encoded.length];
226             for (int i = 0; i < output.length; i++) {
227                 output[i] = (byte) in.read();
228             }
229 
230             assertEquals(-1, in.read(), "EOF");
231             assertEquals(-1, in.read(), "Still EOF");
232             assertArrayEquals(encoded, output, "Streaming Base16 encode");
233         }
234 
235         // Now let's try to decode.
236         try (final InputStream in = new Base16InputStream(new ByteArrayInputStream(encoded), false, lowerCase)) {
237             final byte[] output = new byte[decoded.length];
238             for (int i = 0; i < output.length; i++) {
239                 output[i] = (byte) in.read();
240             }
241 
242             assertEquals(-1, in.read(), "EOF");
243             assertEquals(-1, in.read(), "Still EOF");
244             assertArrayEquals(decoded, output, "Streaming Base16 decode");
245         }
246 
247         // wrap encoder with decoder
248         try (final InputStream in = new ByteArrayInputStream(decoded);
249                 final InputStream inEncode = new Base16InputStream(in, true, lowerCase);
250                 final InputStream inDecode = new Base16InputStream(inEncode, false, lowerCase)) {
251 
252             final byte[] output = new byte[decoded.length];
253             for (int i = 0; i < output.length; i++) {
254                 output[i] = (byte) inDecode.read();
255             }
256 
257             assertEquals(-1, inDecode.read(), "EOF");
258             assertEquals(-1, inDecode.read(), "Still EOF");
259             assertArrayEquals(decoded, output, "Streaming Base16 wrap-wrap!");
260         }
261     }
262 
263     /**
264      * Tests markSupported.
265      *
266      * @throws IOException for some failure scenarios.
267      */
268     @Test
269     public void testMarkSupported() throws IOException {
270         final byte[] decoded = StringUtils.getBytesUtf8(STRING_FIXTURE);
271         final ByteArrayInputStream bin = new ByteArrayInputStream(decoded);
272         try (final Base16InputStream in = new Base16InputStream(bin, true)) {
273             // Always returns false for now.
274             assertFalse(in.markSupported(), "Base16InputStream.markSupported() is false");
275         }
276     }
277 
278     /**
279      * Tests read returning 0
280      *
281      * @throws IOException for some failure scenarios.
282      */
283     @Test
284     public void testRead0() throws IOException {
285         final byte[] decoded = StringUtils.getBytesUtf8(STRING_FIXTURE);
286         final byte[] buf = new byte[1024];
287         int bytesRead = 0;
288         final ByteArrayInputStream bin = new ByteArrayInputStream(decoded);
289         try (final Base16InputStream in = new Base16InputStream(bin, true)) {
290             bytesRead = in.read(buf, 0, 0);
291             assertEquals(0, bytesRead, "Base16InputStream.read(buf, 0, 0) returns 0");
292         }
293     }
294 
295     /**
296      * Tests read with null.
297      *
298      * @throws IOException for some failure scenarios.
299      */
300     @Test
301     public void testReadNull() throws IOException {
302         final byte[] decoded = StringUtils.getBytesUtf8(STRING_FIXTURE);
303         final ByteArrayInputStream bin = new ByteArrayInputStream(decoded);
304         try (final Base16InputStream in = new Base16InputStream(bin, true)) {
305             assertThrows(NullPointerException.class, () -> in.read(null, 0, 0), "Base16InputStream.read(null, 0, 0)");
306         }
307     }
308 
309     /**
310      * Tests read throwing IndexOutOfBoundsException
311      *
312      * @throws IOException for some failure scenarios.
313      */
314     @Test
315     public void testReadOutOfBounds() throws IOException {
316         final byte[] decoded = StringUtils.getBytesUtf8(STRING_FIXTURE);
317         final byte[] buf = new byte[1024];
318         final ByteArrayInputStream bin = new ByteArrayInputStream(decoded);
319         try (final Base16InputStream in = new Base16InputStream(bin, true)) {
320             assertThrows(IndexOutOfBoundsException.class, () -> in.read(buf, -1, 0), "Base16InputStream.read(buf, -1, 0)");
321             assertThrows(IndexOutOfBoundsException.class, () -> in.read(buf, 0, -1), "Base16InputStream.read(buf, 0, -1)");
322             assertThrows(IndexOutOfBoundsException.class, () -> in.read(buf, buf.length + 1, 0), "Base16InputStream.read(buf, buf.length + 1, 0)");
323             assertThrows(IndexOutOfBoundsException.class, () -> in.read(buf, buf.length - 1, 2), "Base16InputStream.read(buf, buf.length - 1, 2)");
324         }
325     }
326 
327     /**
328      * Tests skipping number of characters larger than the internal buffer.
329      *
330      * @throws IOException for some failure scenarios.
331      */
332     @Test
333     public void testSkipBig() throws IOException {
334         final InputStream ins = new ByteArrayInputStream(StringUtils.getBytesIso8859_1(ENCODED_B16));
335         try (final Base16InputStream b16Stream = new Base16InputStream(ins)) {
336             assertEquals(6, b16Stream.skip(Integer.MAX_VALUE));
337             // End of stream reached
338             assertEquals(-1, b16Stream.read());
339             assertEquals(-1, b16Stream.read());
340         }
341     }
342 
343     /**
344      * Tests skipping as a noop
345      *
346      * @throws IOException for some failure scenarios.
347      */
348     @Test
349     public void testSkipNone() throws IOException {
350         final InputStream ins = new ByteArrayInputStream(StringUtils.getBytesIso8859_1(ENCODED_B16));
351         try (final Base16InputStream b16Stream = new Base16InputStream(ins)) {
352             final byte[] actualBytes = new byte[6];
353             assertEquals(0, b16Stream.skip(0));
354             b16Stream.read(actualBytes, 0, actualBytes.length);
355             assertArrayEquals(actualBytes, new byte[] { (byte) 202, (byte) 254, (byte) 186, (byte) 190, (byte) 255, (byte) 255 });
356             // End of stream reached
357             assertEquals(-1, b16Stream.read());
358         }
359     }
360 
361     /**
362      * Tests skipping past the end of a stream.
363      *
364      * @throws IOException for some failure scenarios.
365      */
366     @Test
367     public void testSkipPastEnd() throws IOException {
368         final InputStream ins = new ByteArrayInputStream(StringUtils.getBytesIso8859_1(ENCODED_B16));
369         try (final Base16InputStream b16Stream = new Base16InputStream(ins)) {
370             // due to CODEC-130, skip now skips correctly decoded characters rather than encoded
371             assertEquals(6, b16Stream.skip(10));
372             // End of stream reached
373             assertEquals(-1, b16Stream.read());
374             assertEquals(-1, b16Stream.read());
375         }
376     }
377 
378     /**
379      * Tests skipping to the end of a stream.
380      *
381      * @throws IOException for some failure scenarios.
382      */
383     @Test
384     public void testSkipToEnd() throws IOException {
385         final InputStream ins = new ByteArrayInputStream(StringUtils.getBytesIso8859_1(ENCODED_B16));
386         try (final Base16InputStream b16Stream = new Base16InputStream(ins)) {
387             // due to CODEC-130, skip now skips correctly decoded characters rather than encoded
388             assertEquals(6, b16Stream.skip(6));
389             // End of stream reached
390             assertEquals(-1, b16Stream.read());
391             assertEquals(-1, b16Stream.read());
392         }
393     }
394 
395     /**
396      * Tests if negative arguments to skip are handled correctly.
397      *
398      * @throws IOException for some failure scenarios.
399      */
400     @Test
401     public void testSkipWrongArgument() throws IOException {
402         final InputStream ins = new ByteArrayInputStream(StringUtils.getBytesIso8859_1(ENCODED_B16));
403         try (final Base16InputStream b16Stream = new Base16InputStream(ins)) {
404             assertThrows(IllegalArgumentException.class, () -> b16Stream.skip(-10));
405         }
406     }
407 }