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