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.assertEquals;
22  import static org.junit.jupiter.api.Assertions.assertFalse;
23  import static org.junit.jupiter.api.Assertions.assertNotNull;
24  import static org.junit.jupiter.api.Assertions.assertThrows;
25  import static org.junit.jupiter.api.Assertions.assertTrue;
26  
27  import java.io.ByteArrayInputStream;
28  import java.io.InputStream;
29  
30  import org.apache.commons.io.IOUtils;
31  import org.junit.jupiter.api.Test;
32  
33  /**
34   * Tests {@link Base58InputStream}.
35   */
36  class Base58InputStreamTest {
37  
38      private static final byte[] CRLF = { (byte) '\r', (byte) '\n' };
39  
40      private static final byte[] LF = { (byte) '\n' };
41  
42      private static final String STRING_FIXTURE = "Hello World";
43  
44      @Test
45      void testAvailable() throws Throwable {
46          final String encoded = new String(new Base58().encode(StringUtils.getBytesUtf8("foo")));
47          final InputStream ins = new ByteArrayInputStream(StringUtils.getBytesIso8859_1(encoded));
48          try (Base58InputStream b58stream = new Base58InputStream(ins)) {
49              final int initialAvailable = b58stream.available();
50              assertTrue(initialAvailable > 0, "Initial available should be greater than 0");
51              assertEquals(3, b58stream.skip(10), "Skip should return 3 (decoded bytes)");
52              assertEquals(0, b58stream.available());
53              assertEquals(-1, b58stream.read());
54              assertEquals(-1, b58stream.read());
55          }
56      }
57  
58      private void testBase58EmptyInputStream(final int chuckSize) throws Exception {
59          final byte[] emptyEncoded = {};
60          final byte[] emptyDecoded = {};
61          testByChunk(emptyEncoded, emptyDecoded, chuckSize, CRLF);
62          testByteByByte(emptyEncoded, emptyDecoded, chuckSize, CRLF);
63      }
64  
65      /**
66       * Tests the Base58InputStream implementation against empty input.
67       *
68       * @throws Exception for some failure scenarios.
69       */
70      @Test
71      void testBase58EmptyInputStreamMimeChuckSize() throws Exception {
72          testBase58EmptyInputStream(BaseNCodec.MIME_CHUNK_SIZE);
73      }
74  
75      /**
76       * Tests the Base58InputStream implementation against empty input.
77       *
78       * @throws Exception for some failure scenarios.
79       */
80      @Test
81      void testBase58EmptyInputStreamPemChuckSize() throws Exception {
82          testBase58EmptyInputStream(BaseNCodec.PEM_CHUNK_SIZE);
83      }
84  
85      @Test
86      void testBase58InputStreamByChunk() throws Exception {
87          // Hello World test.
88          byte[] decoded = StringUtils.getBytesUtf8(STRING_FIXTURE);
89          byte[] encoded = new Base58().encode(decoded);
90          testByChunk(encoded, decoded, BaseNCodec.MIME_CHUNK_SIZE, CRLF);
91          // test random data of sizes 0 through 150
92          final BaseNCodec codec = new Base58();
93          for (int i = 0; i <= 150; i++) {
94              final byte[][] randomData = BaseNTestData.randomData(codec, i);
95              encoded = randomData[1];
96              decoded = randomData[0];
97              testByChunk(encoded, decoded, 0, LF);
98          }
99      }
100 
101     @Test
102     void testBase58InputStreamByteByByte() throws Exception {
103         // Hello World test.
104         byte[] decoded = StringUtils.getBytesUtf8(STRING_FIXTURE);
105         byte[] encoded = new Base58().encode(decoded);
106         testByteByByte(encoded, decoded, BaseNCodec.MIME_CHUNK_SIZE, CRLF);
107         // test random data of sizes 0 through 150
108         final BaseNCodec codec = new Base58();
109         for (int i = 0; i <= 150; i++) {
110             final byte[][] randomData = BaseNTestData.randomData(codec, i);
111             encoded = randomData[1];
112             decoded = randomData[0];
113             testByteByByte(encoded, decoded, 0, LF);
114         }
115     }
116 
117     @Test
118     void testBuilder() {
119         assertNotNull(Base58InputStream.builder().getBaseNCodec());
120     }
121 
122     /**
123      * Tests method does three tests on the supplied data: 1. encoded ---[DECODE]--> decoded 2. decoded ---[ENCODE]--> encoded 3. decoded
124      * ---[WRAP-WRAP-WRAP-etc...] --> decoded
125      * <p/>
126      * By "[WRAP-WRAP-WRAP-etc...]" we mean situation where the Base58InputStream wraps itself in encode and decode mode over and over again.
127      *
128      * @param encoded   base58 encoded data
129      * @param decoded   the data from above, but decoded
130      * @param chunkSize chunk size (line-length) of the base58 encoded data.
131      * @param separator Line separator in the base58 encoded data.
132      * @throws Exception Usually signifies a bug in the Base58 commons-codec implementation.
133      */
134     private void testByChunk(final byte[] encoded, final byte[] decoded, final int chunkSize, final byte[] separator) throws Exception {
135         try (InputStream in = Base58InputStream.builder().setByteArray(decoded).setEncode(true).get()) {
136             final byte[] output = IOUtils.toByteArray(in);
137             assertEquals(-1, in.read(), "EOF");
138             assertEquals(-1, in.read(), "Still EOF");
139             assertArrayEquals(encoded, output, "Streaming base58 encode");
140         }
141         try (InputStream in = new Base58InputStream(new ByteArrayInputStream(encoded))) {
142             final byte[] output = IOUtils.toByteArray(in);
143             assertEquals(-1, in.read(), "EOF");
144             assertEquals(-1, in.read(), "Still EOF");
145             assertArrayEquals(decoded, output, "Streaming base58 decode");
146         }
147         InputStream in = new ByteArrayInputStream(decoded);
148         for (int i = 0; i < 10; i++) {
149             in = Base58InputStream.builder().setInputStream(in).setEncode(true).get();
150             in = Base58InputStream.builder().setInputStream(in).setEncode(false).get();
151         }
152         final InputStream in1 = in;
153         final byte[] output = IOUtils.toByteArray(in1);
154         assertEquals(-1, in.read(), "EOF");
155         assertEquals(-1, in.read(), "Still EOF");
156         assertArrayEquals(decoded, output, "Streaming base58 wrap-wrap-wrap!");
157         in.close();
158     }
159 
160     /**
161      * Tests method does three tests on the supplied data: 1. encoded ---[DECODE]--> decoded 2. decoded ---[ENCODE]--> encoded 3. decoded
162      * ---[WRAP-WRAP-WRAP-etc...] --> decoded
163      * <p/>
164      * By "[WRAP-WRAP-WRAP-etc...]" we mean situation where the Base58InputStream wraps itself in encode and decode mode over and over again.
165      *
166      * @param encoded   base58 encoded data
167      * @param decoded   the data from above, but decoded
168      * @param chunkSize chunk size (line-length) of the base58 encoded data.
169      * @param separator Line separator in the base58 encoded data.
170      * @throws Exception Usually signifies a bug in the Base58 commons-codec implementation.
171      */
172     private void testByteByByte(final byte[] encoded, final byte[] decoded, final int chunkSize, final byte[] separator) throws Exception {
173         InputStream in;
174         in = Base58InputStream.builder().setByteArray(decoded).setEncode(true).get();
175         final InputStream in1 = in;
176         byte[] output = IOUtils.toByteArray(in1);
177         assertEquals(-1, in.read(), "EOF");
178         assertEquals(-1, in.read(), "Still EOF");
179         assertArrayEquals(encoded, output, "Streaming base58 encode");
180         in.close();
181         in = new Base58InputStream(new ByteArrayInputStream(encoded));
182         final InputStream in2 = in;
183         output = IOUtils.toByteArray(in2);
184         assertEquals(-1, in.read(), "EOF");
185         assertEquals(-1, in.read(), "Still EOF");
186         assertArrayEquals(decoded, output, "Streaming base58 decode");
187         in.close();
188         in = new ByteArrayInputStream(decoded);
189         for (int i = 0; i < 10; i++) {
190             in = Base58InputStream.builder().setInputStream(in).setEncode(true).get();
191             in = Base58InputStream.builder().setInputStream(in).setEncode(false).get();
192         }
193         final InputStream in3 = in;
194         output = IOUtils.toByteArray(in3);
195         assertEquals(-1, in.read(), "EOF");
196         assertEquals(-1, in.read(), "Still EOF");
197         assertArrayEquals(decoded, output, "Streaming base58 wrap-wrap-wrap!");
198     }
199 
200     /**
201      * Tests markSupported.
202      *
203      * @throws Exception for some failure scenarios.
204      */
205     @Test
206     void testMarkSupported() throws Exception {
207         final byte[] decoded = StringUtils.getBytesUtf8(STRING_FIXTURE);
208         try (Base58InputStream in = Base58InputStream.builder().setByteArray(decoded).setEncode(true).get()) {
209             // Always returns false for now.
210             assertFalse(in.markSupported(), "Base58InputStream.markSupported() is false");
211         }
212     }
213 
214     /**
215      * Tests read returning 0
216      *
217      * @throws Exception for some failure scenarios.
218      */
219     @Test
220     void testRead0() throws Exception {
221         final byte[] decoded = StringUtils.getBytesUtf8(STRING_FIXTURE);
222         final byte[] buf = new byte[1024];
223         int bytesRead = 0;
224         try (Base58InputStream in = Base58InputStream.builder().setByteArray(decoded).setEncode(true).get()) {
225             bytesRead = in.read(buf, 0, 0);
226             assertEquals(0, bytesRead, "Base58InputStream.read(buf, 0, 0) returns 0");
227         }
228     }
229 
230     /**
231      * Tests read with null.
232      *
233      * @throws Exception for some failure scenarios.
234      */
235     @Test
236     void testReadNull() throws Exception {
237         final byte[] decoded = StringUtils.getBytesUtf8(STRING_FIXTURE);
238         try (Base58InputStream in = Base58InputStream.builder().setByteArray(decoded).setEncode(true).get()) {
239             assertThrows(NullPointerException.class, () -> in.read(null, 0, 0));
240         }
241     }
242 
243     /**
244      * Tests read throwing IndexOutOfBoundsException
245      *
246      * @throws Exception for some failure scenarios.
247      */
248     @Test
249     void testReadOutOfBounds() throws Exception {
250         final byte[] decoded = StringUtils.getBytesUtf8(STRING_FIXTURE);
251         final byte[] buf = new byte[1024];
252         try (Base58InputStream in = Base58InputStream.builder().setByteArray(decoded).setEncode(true).get()) {
253             assertThrows(IndexOutOfBoundsException.class, () -> in.read(buf, -1, 0), "Base58InputStream.read(buf, -1, 0)");
254             assertThrows(IndexOutOfBoundsException.class, () -> in.read(buf, 0, -1), "Base58InputStream.read(buf, 0, -1)");
255             assertThrows(IndexOutOfBoundsException.class, () -> in.read(buf, buf.length + 1, 0), "Base58InputStream.read(buf, buf.length + 1, 0)");
256             assertThrows(IndexOutOfBoundsException.class, () -> in.read(buf, buf.length - 1, 2), "Base58InputStream.read(buf, buf.length - 1, 2)");
257         }
258     }
259 
260     /**
261      * Tests skipping number of characters larger than the internal buffer.
262      *
263      * @throws Throwable for some failure scenarios.
264      */
265     @Test
266     void testSkipBig() throws Throwable {
267         final String encoded = new String(new Base58().encode(StringUtils.getBytesUtf8("foo")));
268         final InputStream ins = new ByteArrayInputStream(StringUtils.getBytesIso8859_1(encoded));
269         try (Base58InputStream b58stream = new Base58InputStream(ins)) {
270             assertEquals(3, b58stream.skip(1024));
271             // End of stream reached
272             assertEquals(-1, b58stream.read());
273             assertEquals(-1, b58stream.read());
274         }
275     }
276 
277     /**
278      * Tests skipping as a noop
279      *
280      * @throws Throwable for some failure scenarios.
281      */
282     @Test
283     void testSkipNone() throws Throwable {
284         final String encoded = new String(new Base58().encode(StringUtils.getBytesUtf8("foo")));
285         final InputStream ins = new ByteArrayInputStream(StringUtils.getBytesIso8859_1(encoded));
286         try (Base58InputStream b58stream = new Base58InputStream(ins)) {
287             final byte[] actualBytes = new byte[3];
288             assertEquals(0, b58stream.skip(0));
289             b58stream.read(actualBytes, 0, actualBytes.length);
290             assertArrayEquals(actualBytes, new byte[] { 102, 111, 111 });
291             // End of stream reached
292             assertEquals(-1, b58stream.read());
293         }
294     }
295 
296     /**
297      * Tests skipping past the end of a stream.
298      *
299      * @throws Throwable for some failure scenarios.
300      */
301     @Test
302     void testSkipPastEnd() throws Throwable {
303         final String encoded = new String(new Base58().encode(StringUtils.getBytesUtf8("foo")));
304         final InputStream ins = new ByteArrayInputStream(StringUtils.getBytesIso8859_1(encoded));
305         try (Base58InputStream b58stream = new Base58InputStream(ins)) {
306             // skip correctly decoded characters
307             assertEquals(3, b58stream.skip(10));
308             // End of stream reached
309             assertEquals(-1, b58stream.read());
310             assertEquals(-1, b58stream.read());
311         }
312     }
313 
314     /**
315      * Tests skipping to the end of a stream.
316      *
317      * @throws Throwable for some failure scenarios.
318      */
319     @Test
320     void testSkipToEnd() throws Throwable {
321         final String encoded = new String(new Base58().encode(StringUtils.getBytesUtf8("foo")));
322         final InputStream ins = new ByteArrayInputStream(StringUtils.getBytesIso8859_1(encoded));
323         try (Base58InputStream b58stream = new Base58InputStream(ins)) {
324             // skip correctly decoded characters
325             assertEquals(3, b58stream.skip(3));
326             assertEquals(-1, b58stream.read());
327         }
328     }
329 
330     /**
331      * Tests if negative arguments to skip are handled correctly.
332      *
333      * @throws Throwable for some failure scenarios.
334      */
335     @Test
336     void testSkipWrongArgument() throws Throwable {
337         final String encoded = new String(new Base58().encode(StringUtils.getBytesUtf8("foo")));
338         final InputStream ins = new ByteArrayInputStream(StringUtils.getBytesIso8859_1(encoded));
339         try (Base58InputStream b58stream = new Base58InputStream(ins)) {
340             assertThrows(IllegalArgumentException.class, () -> b58stream.skip(-1));
341         }
342     }
343 }