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    *      https://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.assertNotNull;
22  import static org.junit.jupiter.api.Assertions.assertThrows;
23  
24  import java.io.ByteArrayOutputStream;
25  import java.io.IOException;
26  import java.io.OutputStream;
27  
28  import org.junit.jupiter.api.Test;
29  
30  /**
31   * Tests {@link Base16OutputStream}.
32   */
33  class Base16OutputStreamTest {
34  
35      private static final String STRING_FIXTURE = "Hello World";
36  
37      /**
38       * Test the Base16OutputStream implementation against empty input.
39       *
40       * @throws IOException for some failure scenarios.
41       */
42      @Test
43      void testBase16EmptyOutputStream() throws IOException {
44          final byte[] emptyEncoded = {};
45          final byte[] emptyDecoded = {};
46          testByteByByte(emptyEncoded, emptyDecoded);
47          testByChunk(emptyEncoded, emptyDecoded);
48      }
49  
50      /**
51       * Test the Base16OutputStream implementation
52       *
53       * @throws IOException for some failure scenarios.
54       */
55      @Test
56      void testBase16OutputStreamByChunk() throws IOException {
57          // Hello World test.
58          byte[] encoded = StringUtils.getBytesUtf8("48656C6C6F20576F726C64");
59          byte[] decoded = StringUtils.getBytesUtf8(STRING_FIXTURE);
60          testByChunk(encoded, decoded);
61  
62          // Single Byte test.
63          encoded = StringUtils.getBytesUtf8("41");
64          decoded = new byte[]{(byte) 0x41};
65          testByChunk(encoded, decoded);
66  
67          // test random data of sizes 0 through 150
68          final BaseNCodec codec = Base16.builder().setLowerCase(true).get();
69          for (int i = 0; i <= 150; i++) {
70              final byte[][] randomData = BaseNTestData.randomData(codec, i);
71              encoded = randomData[1];
72              decoded = randomData[0];
73              testByChunk(encoded, decoded, true);
74          }
75      }
76  
77      /**
78       * Test the Base16OutputStream implementation
79       *
80       * @throws IOException for some failure scenarios.
81       */
82      @Test
83      void testBase16OutputStreamByteByByte() throws IOException {
84          // Hello World test.
85          byte[] encoded = StringUtils.getBytesUtf8("48656C6C6F20576F726C64");
86          byte[] decoded = StringUtils.getBytesUtf8(STRING_FIXTURE);
87          testByteByByte(encoded, decoded);
88          // Single Byte test.
89          encoded = StringUtils.getBytesUtf8("41");
90          decoded = new byte[] { (byte) 0x41 };
91          testByteByByte(encoded, decoded);
92          // test random data of sizes 0 through 150
93          final BaseNCodec codec = Base16.builder().setLowerCase(true).get();
94          for (int i = 0; i <= 150; i++) {
95              final byte[][] randomData = BaseNTestData.randomData(codec, i);
96              encoded = randomData[1];
97              decoded = randomData[0];
98              testByteByByte(encoded, decoded, true);
99          }
100     }
101 
102     @Test
103     void testBuilder() {
104         assertNotNull(Base16OutputStream.builder().getBaseNCodec());
105     }
106 
107     /**
108      * Test method does three tests on the supplied data: 1. encoded ---[DECODE]--> decoded 2. decoded ---[ENCODE]-->
109      * encoded 3. decoded ---[WRAP-WRAP-WRAP-etc...] --> decoded
110      * <p/>
111      * By "[WRAP-WRAP-WRAP-etc...]" we mean situation where the Base16OutputStream wraps itself in encode and decode
112      * mode over and over again.
113      *
114      * @param encoded base16 encoded data
115      * @param decoded the data from above, but decoded
116      * @throws IOException Usually signifies a bug in the Base16 commons-codec implementation.
117      */
118     private void testByChunk(final byte[] encoded, final byte[] decoded) throws IOException {
119         testByChunk(encoded, decoded, false);
120     }
121 
122     /**
123      * Test method does three tests on the supplied data: 1. encoded ---[DECODE]--> decoded 2. decoded ---[ENCODE]-->
124      * encoded 3. decoded ---[WRAP-WRAP-WRAP-etc...] --> decoded
125      * <p/>
126      * By "[WRAP-WRAP-WRAP-etc...]" we mean situation where the Base16OutputStream wraps itself in encode and decode
127      * mode over and over again.
128      *
129      * @param encoded base16 encoded data
130      * @param decoded the data from above, but decoded
131      * @param lowerCase if {@code true} then use a lower-case Base16 alphabet
132      * @throws IOException Usually signifies a bug in the Base16 commons-codec implementation.
133      */
134     private void testByChunk(final byte[] encoded, final byte[] decoded, final boolean lowerCase) throws IOException {
135 
136         // Start with encode.
137         try (ByteArrayOutputStream byteOut = new ByteArrayOutputStream();
138                 OutputStream out = new Base16OutputStream(byteOut, true, lowerCase)) {
139             out.write(decoded);
140             final byte[] output = byteOut.toByteArray();
141             assertArrayEquals(encoded, output, "Streaming chunked base16 encode");
142         }
143 
144         // Now let's try to decode.
145         try (ByteArrayOutputStream byteOut = new ByteArrayOutputStream();
146                 OutputStream out = new Base16OutputStream(byteOut, false, lowerCase)) {
147             out.write(encoded);
148             final byte[] output = byteOut.toByteArray();
149             assertArrayEquals(decoded, output, "Streaming chunked base16 decode");
150         }
151 
152         // wrap encoder with decoder
153         try (ByteArrayOutputStream byteOut = new ByteArrayOutputStream();
154              OutputStream decoderOut = new Base16OutputStream(byteOut, false, lowerCase);
155              OutputStream encoderOut = new Base16OutputStream(decoderOut, true, lowerCase)) {
156 
157             encoderOut.write(decoded);
158             final byte[] output = byteOut.toByteArray();
159 
160             assertArrayEquals(decoded, output, "Streaming chunked base16 wrap-wrap!");
161         }
162     }
163 
164     /**
165      * Test method does three tests on the supplied data: 1. encoded ---[DECODE]--> decoded 2. decoded ---[ENCODE]-->
166      * encoded 3. decoded ---[WRAP-WRAP-WRAP-etc...] --> decoded
167      * <p/>
168      * By "[WRAP-WRAP-WRAP-etc...]" we mean situation where the Base16OutputStream wraps itself in encode and decode
169      * mode over and over again.
170      *
171      * @param encoded base16 encoded data
172      * @param decoded the data from above, but decoded
173      * @throws IOException Usually signifies a bug in the Base16 commons-codec implementation.
174      */
175     private void testByteByByte(final byte[] encoded, final byte[] decoded) throws IOException {
176         testByteByByte(encoded, decoded, false);
177     }
178 
179     /**
180      * Test method does three tests on the supplied data: 1. encoded ---[DECODE]--> decoded 2. decoded ---[ENCODE]-->
181      * encoded 3. decoded ---[WRAP-WRAP-WRAP-etc...] --> decoded
182      * <p/>
183      * By "[WRAP-WRAP-WRAP-etc...]" we mean situation where the Base16OutputStream wraps itself in encode and decode
184      * mode over and over again.
185      *
186      * @param encoded base16 encoded data
187      * @param decoded the data from above, but decoded
188      * @throws IOException Usually signifies a bug in the Base16 commons-codec implementation.
189      */
190     private void testByteByByte(final byte[] encoded, final byte[] decoded, final boolean lowerCase) throws IOException {
191         // Start with encode.
192         try (ByteArrayOutputStream byteOut = new ByteArrayOutputStream();
193                 OutputStream out = new Base16OutputStream(byteOut, true, lowerCase)) {
194             for (final byte element : decoded) {
195                 out.write(element);
196             }
197             final byte[] output = byteOut.toByteArray();
198             assertArrayEquals(encoded, output, "Streaming byte-by-byte base16 encode");
199         }
200         try (ByteArrayOutputStream byteOut = new ByteArrayOutputStream();
201                 OutputStream out = Base16OutputStream.builder()
202                         .setOutputStream(byteOut).setEncode(true).setBaseNCodec(Base16.builder().setLowerCase(lowerCase).get())
203                         .get()) {
204             for (final byte element : decoded) {
205                 out.write(element);
206             }
207             final byte[] output = byteOut.toByteArray();
208             assertArrayEquals(encoded, output, "Streaming byte-by-byte base16 encode");
209         }
210         // Now let's try to decode.
211         try (ByteArrayOutputStream byteOut = new ByteArrayOutputStream();
212                 OutputStream out = new Base16OutputStream(byteOut, false, lowerCase)) {
213             for (final byte element : encoded) {
214                 out.write(element);
215             }
216             final byte[] output = byteOut.toByteArray();
217             assertArrayEquals(decoded, output, "Streaming byte-by-byte base16 decode");
218         }
219         // Now let's try to decode with tonnes of flushes.
220         try (ByteArrayOutputStream byteOut = new ByteArrayOutputStream();
221                 OutputStream out = new Base16OutputStream(byteOut, false, lowerCase)) {
222             for (final byte element : encoded) {
223                 out.write(element);
224                 out.flush();
225             }
226             final byte[] output = byteOut.toByteArray();
227             assertArrayEquals(decoded, output, "Streaming byte-by-byte flush() base16 decode");
228         }
229         // wrap encoder with decoder
230         try (ByteArrayOutputStream byteOut = new ByteArrayOutputStream();
231                 OutputStream decoderOut = new Base16OutputStream(byteOut, false, lowerCase);
232                 OutputStream encoderOut = new Base16OutputStream(decoderOut, true, lowerCase)) {
233             for (final byte element : decoded) {
234                 encoderOut.write(element);
235             }
236             final byte[] output = byteOut.toByteArray();
237             assertArrayEquals(decoded, output, "Streaming byte-by-byte base16 wrap-wrap!");
238         }
239     }
240 
241     /**
242      * Tests Base16OutputStream.write for expected IndexOutOfBoundsException conditions.
243      *
244      * @throws IOException for some failure scenarios.
245      */
246     @Test
247     void testWriteOutOfBounds() throws IOException {
248         final byte[] buf = new byte[1024];
249         final ByteArrayOutputStream bout = new ByteArrayOutputStream();
250         try (Base16OutputStream out = new Base16OutputStream(bout)) {
251             assertThrows(IndexOutOfBoundsException.class, () -> out.write(buf, -1, 1), "Base16InputStream.write(buf, -1, 0)");
252             assertThrows(IndexOutOfBoundsException.class, () -> out.write(buf, 1, -1), "Base16InputStream.write(buf, 1, -1)");
253             assertThrows(IndexOutOfBoundsException.class, () -> out.write(buf, buf.length + 1, 0), "Base16InputStream.write(buf, buf.length + 1, 0)");
254             assertThrows(IndexOutOfBoundsException.class, () -> out.write(buf, buf.length - 1, 2), "Base16InputStream.write(buf, buf.length - 1, 2)");
255         }
256     }
257 
258     /**
259      * Tests Base16OutputStream.write(null).
260      *
261      * @throws IOException for some failure scenarios.
262      */
263     @Test
264     void testWriteToNullCoverage() throws IOException {
265         final ByteArrayOutputStream bout = new ByteArrayOutputStream();
266         try (Base16OutputStream out = new Base16OutputStream(bout)) {
267             assertThrows(NullPointerException.class, () -> out.write(null, 0, 0));
268         }
269     }
270 }