1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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.assertThrows;
23 import static org.junit.jupiter.api.Assertions.assertTrue;
24
25 import java.io.ByteArrayInputStream;
26 import java.io.ByteArrayOutputStream;
27 import java.io.IOException;
28 import java.util.Arrays;
29
30 import org.apache.commons.io.IOUtils;
31 import org.junit.jupiter.api.Test;
32 import org.junit.jupiter.params.ParameterizedTest;
33 import org.junit.jupiter.params.provider.ValueSource;
34
35
36
37
38 class Base58MaxDecodeLengthTest {
39
40 private static byte[] ones(final int length) {
41 final byte[] bytes = new byte[length];
42
43 Arrays.fill(bytes, (byte) '1');
44 return bytes;
45 }
46
47 @Test
48 void testConfiguredLimit() {
49 final Base58 small = Base58.builder().setMaxDecodeLength(10).get();
50 assertThrows(IllegalArgumentException.class, () -> small.decode(ones(11)));
51 assertEquals(10, small.decode(ones(10)).length);
52 final Base58 unlimited = Base58.builder().setMaxDecodeLength(Integer.MAX_VALUE).get();
53 assertEquals(Base58.DEFAULT_MAX_DECODE_LENGTH + 1, unlimited.decode(ones(Base58.DEFAULT_MAX_DECODE_LENGTH + 1)).length);
54 }
55
56 @Test
57 void testDecodeAtLimitAccepted() {
58 assertEquals(Base58.DEFAULT_MAX_DECODE_LENGTH, new Base58().decode(ones(Base58.DEFAULT_MAX_DECODE_LENGTH)).length);
59 }
60
61 @Test
62 void testDecodeOverLimitRejected() {
63 assertThrows(IllegalArgumentException.class, () -> new Base58().decode(ones(Base58.DEFAULT_MAX_DECODE_LENGTH + 1)));
64 }
65
66 @Test
67 void testDefaultStreamsRejectOversizedInput() throws IOException {
68 final byte[] encoded = ones(Base58.DEFAULT_MAX_DECODE_LENGTH + 1);
69 try (Base58InputStream stream = new Base58InputStream(new ByteArrayInputStream(encoded))) {
70 assertThrows(IOException.class, stream::read);
71 }
72 try (Base58OutputStream stream = Base58OutputStream.builder().setOutputStream(new ByteArrayOutputStream()).setEncode(false).get()) {
73 assertThrows(IOException.class, () -> stream.write(encoded));
74 }
75 }
76
77 @Test
78 void testEncodingMayExceedDecodeLimit() throws IOException {
79 final byte[] input = new byte[Base58.DEFAULT_MAX_DECODE_LENGTH + 1];
80 final Base58 codec = Base58.builder().setMaxEncodeLength(input.length).get();
81 final byte[] encoded = codec.encode(input);
82 assertEquals(input.length, encoded.length);
83 assertThrows(IllegalArgumentException.class, () -> codec.decode(encoded));
84 final ByteArrayOutputStream sink = new ByteArrayOutputStream();
85 try (Base58OutputStream stream = Base58OutputStream.builder().setOutputStream(sink).setBaseNCodec(codec).get()) {
86 stream.write(input);
87 }
88 assertArrayEquals(encoded, sink.toByteArray());
89 try (Base58InputStream stream = Base58InputStream.builder().setByteArray(input).setEncode(true).setBaseNCodec(codec).get()) {
90 assertArrayEquals(encoded, IOUtils.toByteArray(stream));
91 }
92 final ByteArrayOutputStream decoded = new ByteArrayOutputStream();
93 try (Base58OutputStream stream = Base58OutputStream.builder().setOutputStream(decoded).setEncode(false)
94 .setBaseNCodec(Base58.builder().setMaxDecodeLength(encoded.length).get()).get()) {
95 stream.write(encoded);
96 }
97 assertArrayEquals(input, decoded.toByteArray());
98 }
99
100 @ParameterizedTest
101 @ValueSource(ints = { 10, Base58.DEFAULT_MAX_DECODE_LENGTH, Base58.DEFAULT_MAX_DECODE_LENGTH + 1 })
102 void testInputStreamAtConfiguredLimit(final int limit) throws IOException {
103 try (Base58InputStream stream = Base58InputStream.builder().setByteArray(ones(limit)).setBaseNCodec(Base58.builder().setMaxDecodeLength(limit).get())
104 .get()) {
105 assertArrayEquals(new byte[limit], IOUtils.toByteArray(stream));
106 }
107 }
108
109 @Test
110 void testInputStreamRejectsBeforeEof() throws IOException {
111 final ByteArrayInputStream source = new ByteArrayInputStream(ones(100)) {
112
113 @Override
114 public synchronized int read(final byte[] bytes, final int offset, final int length) {
115 return super.read(bytes, offset, Math.min(length, 3));
116 }
117 };
118 try (Base58InputStream stream = Base58InputStream.builder().setInputStream(source).setBaseNCodec(Base58.builder().setMaxDecodeLength(10).get()).get()) {
119 final IOException exception = assertThrows(IOException.class, stream::read);
120 assertTrue(exception.getCause() instanceof IllegalArgumentException);
121 assertEquals(88, source.available());
122 }
123 }
124
125 @Test
126 void testInvalidLimitRejected() {
127 assertThrows(IllegalArgumentException.class, () -> Base58.builder().setMaxDecodeLength(0));
128 assertThrows(IllegalArgumentException.class, () -> Base58.builder().setMaxDecodeLength(-1));
129 }
130
131 @Test
132 void testNonzeroDigitsAtLimit() {
133 final Base58 codec = new Base58();
134 final byte[] encoded = new byte[Base58.DEFAULT_MAX_DECODE_LENGTH];
135 Arrays.fill(encoded, (byte) 'z');
136 assertArrayEquals(encoded, codec.encode(codec.decode(encoded)));
137 final byte[] over = Arrays.copyOf(encoded, encoded.length + 1);
138 over[encoded.length] = 'z';
139 assertThrows(IllegalArgumentException.class, () -> codec.decode(over));
140 assertThrows(IllegalArgumentException.class, () -> codec.decode(StringUtils.newStringUsAscii(over)));
141 assertThrows(IllegalArgumentException.class, () -> codec.decode((Object) over));
142 }
143
144 @ParameterizedTest
145 @ValueSource(ints = { 1, 3, 10 })
146 void testOutputStreamRejectsCrossingChunk(final int chunkSize) throws IOException {
147 final ByteArrayOutputStream sink = new ByteArrayOutputStream();
148 try (Base58OutputStream stream = Base58OutputStream.builder().setOutputStream(sink).setEncode(false)
149 .setBaseNCodec(Base58.builder().setMaxDecodeLength(10).get()).get()) {
150 int written = 0;
151 while (written < 10) {
152 final int length = Math.min(chunkSize, 10 - written);
153 stream.write(ones(length));
154 written += length;
155 }
156 final IOException exception = assertThrows(IOException.class, () -> stream.write('1'));
157 assertTrue(exception.getCause() instanceof IllegalArgumentException);
158 assertEquals(0, sink.size());
159 }
160 assertArrayEquals(new byte[10], sink.toByteArray());
161 }
162
163 @Test
164 void testRejectsBeforeBuffering() {
165 final Base58 codec = Base58.builder().setMaxDecodeLength(10).get();
166 final BaseNCodec.Context context = new BaseNCodec.Context();
167 codec.decode(ones(8), 0, 8, context);
168 assertThrows(IllegalArgumentException.class, () -> codec.decode(ones(3), 0, 3, context));
169 assertArrayEquals(ones(8), context.buffer);
170
171 assertThrows(IllegalArgumentException.class, () -> codec.decode(ones(1), 0, Integer.MAX_VALUE, context));
172 assertArrayEquals(ones(8), context.buffer);
173 }
174
175 @Test
176 void testRoundTripUnaffected() {
177 final Base58 codec = new Base58();
178 final byte[] data = { 0, 0, 1, 2, 3, 4, 5, -1, 127 };
179 assertArrayEquals(data, codec.decode(codec.encode(data)));
180 }
181 }