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.assertNotNull;
23  import static org.junit.jupiter.api.Assertions.assertThrows;
24  
25  import java.io.ByteArrayOutputStream;
26  import java.io.IOException;
27  import java.io.OutputStream;
28  import java.nio.charset.StandardCharsets;
29  import java.util.Arrays;
30  
31  import org.apache.commons.lang3.ArrayFill;
32  import org.junit.jupiter.api.Test;
33  import org.junit.jupiter.params.ParameterizedTest;
34  import org.junit.jupiter.params.provider.ValueSource;
35  
36  /**
37   * Tests {@link Base58OutputStream}.
38   */
39  class Base58OutputStreamTest extends AbstractBaseNOutputStreamTest {
40  
41      private static final byte[] CR_LF = { (byte) '\r', (byte) '\n' };
42  
43      private static final byte[] LF = { (byte) '\n' };
44  
45      @Override
46      OutputStream newOutputStream() {
47          return new Base58OutputStream(new ByteArrayOutputStream());
48      }
49  
50      private void testBase58EmptyOutputStream(final int chunkSize) throws Exception {
51          final byte[] emptyEncoded = {};
52          final byte[] emptyDecoded = {};
53          testByteByByte(emptyEncoded, emptyDecoded, chunkSize, CR_LF);
54          testByChunk(emptyEncoded, emptyDecoded, chunkSize, CR_LF);
55      }
56  
57      @Test
58      void testBase58EmptyOutputStreamMimeChunkSize() throws Exception {
59          testBase58EmptyOutputStream(BaseNCodec.MIME_CHUNK_SIZE);
60      }
61  
62      @Test
63      void testBase58EmptyOutputStreamPemChunkSize() throws Exception {
64          testBase58EmptyOutputStream(BaseNCodec.PEM_CHUNK_SIZE);
65      }
66  
67      @Test
68      void testBase58OutputStreamByChunk() throws Exception {
69          byte[] decoded = StringUtils.getBytesUtf8("Hello World");
70          byte[] encoded = new Base58().encode(decoded);
71          testByChunk(encoded, decoded, BaseNCodec.MIME_CHUNK_SIZE, CR_LF);
72          final BaseNCodec codec = new Base58();
73          for (int i = 0; i <= 150; i++) {
74              final byte[][] randomData = BaseNTestData.randomData(codec, i);
75              encoded = randomData[1];
76              decoded = randomData[0];
77              testByChunk(encoded, decoded, 0, LF);
78          }
79      }
80  
81      @Test
82      void testBase58OutputStreamByteByByte() throws Exception {
83          byte[] decoded = StringUtils.getBytesUtf8("Hello World");
84          byte[] encoded = new Base58().encode(decoded);
85          testByteByByte(encoded, decoded, 76, CR_LF);
86          final BaseNCodec codec = new Base58();
87          for (int i = 0; i <= 150; i++) {
88              final byte[][] randomData = BaseNTestData.randomData(codec, i);
89              encoded = randomData[1];
90              decoded = randomData[0];
91              testByteByByte(encoded, decoded, 0, LF);
92          }
93      }
94  
95      @Test
96      void testBuilder() {
97          assertNotNull(Base58OutputStream.builder().getBaseNCodec());
98      }
99  
100     private void testByChunk(final byte[] encoded, final byte[] decoded, final int chunkSize, final byte[] separator) throws Exception {
101         ByteArrayOutputStream byteOut = new ByteArrayOutputStream();
102         try (OutputStream out = Base58OutputStream.builder().setOutputStream(byteOut).setEncode(true).get()) {
103             out.write(decoded);
104         }
105         byte[] output = byteOut.toByteArray();
106         assertArrayEquals(encoded, output, "Streaming chunked Base58 encode");
107         byteOut = new ByteArrayOutputStream();
108         try (OutputStream out = Base58OutputStream.builder().setOutputStream(byteOut).setEncode(false).get()) {
109             out.write(encoded);
110         }
111         output = byteOut.toByteArray();
112         assertArrayEquals(decoded, output, "Streaming chunked Base58 decode");
113         byteOut = new ByteArrayOutputStream();
114         OutputStream out = byteOut;
115         for (int i = 0; i < 10; i++) {
116             out = Base58OutputStream.builder().setOutputStream(out).setEncode(false).get();
117             out = Base58OutputStream.builder().setOutputStream(out).setEncode(true).get();
118         }
119         out.write(decoded);
120         out.close();
121         output = byteOut.toByteArray();
122         assertArrayEquals(decoded, byteOut.toByteArray(), "Streaming chunked Base58 wrap-wrap-wrap!");
123     }
124 
125     private void testByteByByte(final byte[] encoded, final byte[] decoded, final int chunkSize, final byte[] separator) throws Exception {
126         ByteArrayOutputStream byteOut = new ByteArrayOutputStream();
127         try (OutputStream out = Base58OutputStream.builder().setOutputStream(byteOut).setEncode(true).get()) {
128             for (final byte element : decoded) {
129                 out.write(element);
130             }
131         }
132         final byte[] output0 = byteOut.toByteArray();
133         assertArrayEquals(encoded, output0, "Streaming byte-by-byte Base58 encode");
134         byteOut = new ByteArrayOutputStream();
135         try (OutputStream out = Base58OutputStream.builder().setOutputStream(byteOut).setEncode(false).get()) {
136             for (final byte element : encoded) {
137                 out.write(element);
138             }
139         }
140         final byte[] output1 = byteOut.toByteArray();
141         assertArrayEquals(decoded, output1,
142                 () -> String.format("Streaming byte-by-byte Base58 decode, chunkSize=%d, separator=%s, encoded=%s, decoded=%s, output=%s", chunkSize,
143                         Arrays.toString(separator), Arrays.toString(encoded), Arrays.toString(decoded), Arrays.toString(output1)));
144         byteOut = new ByteArrayOutputStream();
145         try (OutputStream out = Base58OutputStream.builder().setOutputStream(byteOut).setEncode(false).get()) {
146             for (final byte element : encoded) {
147                 out.write(element);
148                 out.flush();
149             }
150         }
151         byte[] output = byteOut.toByteArray();
152         assertArrayEquals(decoded, output, "Streaming byte-by-byte flush() Base58 decode");
153         byteOut = new ByteArrayOutputStream();
154         OutputStream out = byteOut;
155         for (int i = 0; i < 10; i++) {
156             out = Base58OutputStream.builder().setOutputStream(out).setEncode(false).get();
157             out = Base58OutputStream.builder().setOutputStream(out).setEncode(true).get();
158         }
159         for (final byte element : decoded) {
160             out.write(element);
161         }
162         out.close();
163         output = byteOut.toByteArray();
164         assertArrayEquals(decoded, output, "Streaming byte-by-byte Base58 wrap-wrap-wrap!");
165     }
166 
167     @ParameterizedTest
168     @ValueSource(ints = { 0, 1, 2, 3, 4 })
169     void testDecodeByte49(final int len) throws IOException {
170         // Sanity check, each step from scratch:
171         final byte[] zeros = new byte[len];
172         final byte[] encoded0s = ArrayFill.fill(zeros.clone(), (byte) '1');
173         assertArrayEquals(encoded0s, Base58.builder().get().encode(zeros));
174         final byte[] decoded = Base58.builder().get().decode(encoded0s);
175         assertArrayEquals(zeros, decoded, () -> String.format("zeros=%s, decoded=%s", Arrays.toString(zeros), Arrays.toString(decoded)));
176         // Version 1.21.1:
177         // AssertionFailedError: Streaming byte-by-byte Base58 decode, chunkSize=0, separator=[10], encoded=[49], decoded=[0], output=[0, 0] ==> array lengths
178         // differ, expected: <1> but was: <2>
179         ByteArrayOutputStream byteOut = new ByteArrayOutputStream();
180         final byte[] byteData = new byte[len];
181         try (OutputStream out = Base58OutputStream.builder().setOutputStream(byteOut).setEncode(true).get()) {
182             for (final byte element : byteData) {
183                 out.write(element);
184             }
185         }
186         final byte[] output0 = byteOut.toByteArray();
187         assertArrayEquals(encoded0s, output0, () -> String.format("Streaming byte-by-byte Base58 decode, encoded=%s, decoded=%s, output=%s",
188                 Arrays.toString(encoded0s), Arrays.toString(byteData), Arrays.toString(output0)));
189         byteOut = new ByteArrayOutputStream();
190         try (OutputStream out = Base58OutputStream.builder().setOutputStream(byteOut).setEncode(false).get()) {
191             for (final byte element : encoded0s) {
192                 out.write(element);
193             }
194         }
195         final byte[] output1 = byteOut.toByteArray();
196         assertArrayEquals(byteData, output1, () -> String.format("Streaming byte-by-byte Base58 decode, encoded=%s, decoded=%s, output=%s",
197                 Arrays.toString(encoded0s), Arrays.toString(byteData), Arrays.toString(output1)));
198     }
199 
200     @Test
201     void testRfcTestVector1() {
202         assertEquals("2NEpo7TZRRrLZSi2U", Base58.builder().get().encodeToString("Hello World!".getBytes(StandardCharsets.US_ASCII)));
203     }
204 
205     @Test
206     void testRfcTestVector2() {
207         assertEquals("USm3fpXnKG5EUBx2ndxBDMPVciP5hGey2Jh4NDv6gmeo1LkMeiKrLJUUBk6Z",
208                 Base58.builder().get().encodeToString("The quick brown fox jumps over the lazy dog.".getBytes(StandardCharsets.US_ASCII)));
209     }
210 
211     @Test
212     void testRfcTestVector3() {
213         assertEquals("11233QC4", Base58.builder().get().encodeToString(new byte[] { 00, 00, 0x28, 0x7f, (byte) 0xb4, (byte) 0xcd }));
214     }
215 
216     @Test
217     void testWriteOutOfBounds() throws Exception {
218         final byte[] buf = new byte[1024];
219         final ByteArrayOutputStream bout = new ByteArrayOutputStream();
220         try (Base58OutputStream out = new Base58OutputStream(bout)) {
221             assertThrows(IndexOutOfBoundsException.class, () -> out.write(buf, -1, 1), "Base58OutputStream.write(buf, -1, 1)");
222             assertThrows(IndexOutOfBoundsException.class, () -> out.write(buf, 1, -1), "Base58OutputStream.write(buf, 1, -1)");
223             assertThrows(IndexOutOfBoundsException.class, () -> out.write(buf, buf.length + 1, 0), "Base58OutputStream.write(buf, buf.length + 1, 0)");
224             assertThrows(IndexOutOfBoundsException.class, () -> out.write(buf, buf.length - 1, 2), "Base58OutputStream.write(buf, buf.length - 1, 2)");
225         }
226     }
227 
228     @Test
229     void testWriteToNullCoverage() throws Exception {
230         final ByteArrayOutputStream bout = new ByteArrayOutputStream();
231         try (Base58OutputStream out = new Base58OutputStream(bout)) {
232             assertThrows(NullPointerException.class, () -> out.write(null, 0, 0));
233         }
234     }
235 }