| /* |
| * Licensed to the Apache Software Foundation (ASF) under one or more |
| * contributor license agreements. See the NOTICE file distributed with |
| * this work for additional information regarding copyright ownership. |
| * The ASF licenses this file to You under the Apache License, Version 2.0 |
| * (the "License"); you may not use this file except in compliance with |
| * the License. You may obtain a copy of the License at |
| * |
| * https://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package org.apache.commons.codec.binary; |
| |
| import static org.junit.jupiter.api.Assertions.assertArrayEquals; |
| import static org.junit.jupiter.api.Assertions.assertEquals; |
| import static org.junit.jupiter.api.Assertions.assertFalse; |
| import static org.junit.jupiter.api.Assertions.assertNull; |
| import static org.junit.jupiter.api.Assertions.assertThrows; |
| import static org.junit.jupiter.api.Assertions.assertTrue; |
| |
| import java.io.IOException; |
| import java.nio.ByteBuffer; |
| import java.nio.charset.Charset; |
| import java.nio.charset.StandardCharsets; |
| import java.util.Arrays; |
| import java.util.Random; |
| import java.util.concurrent.atomic.AtomicInteger; |
| |
| import org.apache.commons.codec.DecoderException; |
| import org.apache.commons.codec.EncoderException; |
| import org.apache.commons.lang3.ArrayFill; |
| import org.junit.jupiter.api.Test; |
| import org.junit.jupiter.params.ParameterizedTest; |
| import org.junit.jupiter.params.provider.ValueSource; |
| |
| /** |
| * Tests {@link Base58}. |
| */ |
| public class Base58Test { |
| |
| private static final int BOUND = 10_000; |
| |
| private static final Charset CHARSET_UTF8 = StandardCharsets.UTF_8; |
| |
| private static void assertArrayEqualsAt(final byte[] data, final byte[] dec, final int i) { |
| final AtomicInteger counter = new AtomicInteger(i); |
| assertArrayEquals(data, dec, () -> String.format("Failed for length %,d: %s", counter.get(), Arrays.toString(data))); |
| } |
| |
| private final Random random = new Random(); |
| |
| @Test |
| void testBase58() { |
| final String content = "Hello World"; |
| final byte[] encodedBytes = new Base58().encode(StringUtils.getBytesUtf8(content)); |
| final String encodedContent = StringUtils.newStringUtf8(encodedBytes); |
| assertEquals("JxF12TrwUP45BMd", encodedContent, "encoding hello world"); |
| final byte[] decodedBytes = new Base58().decode(encodedBytes); |
| final String decodedContent = StringUtils.newStringUtf8(decodedBytes); |
| assertEquals(content, decodedContent, "decoding hello world"); |
| } |
| |
| @Test |
| void testEmptyBase58() { |
| byte[] empty = {}; |
| byte[] result = new Base58().encode(empty); |
| assertEquals(0, result.length, "empty Base58 encode"); |
| assertNull(new Base58().encode(null), "empty Base58 encode"); |
| empty = new byte[0]; |
| result = new Base58().decode(empty); |
| assertEquals(0, result.length, "empty Base58 decode"); |
| assertNull(new Base58().decode((byte[]) null), "empty Base58 decode"); |
| } |
| |
| @Test |
| void testEncodeDecode() { |
| for (int i = 1; i < 5; i++) { |
| final byte[] data = new byte[random.nextInt(BOUND) + 1]; |
| Arrays.fill(data, (byte) i); |
| final byte[] enc = new Base58().encode(data); |
| final byte[] dec = new Base58().decode(enc); |
| assertArrayEqualsAt(data, dec, i); |
| } |
| } |
| |
| @Test |
| void testEncodeDecodeRandom() { |
| for (int i = 1; i < 5; i++) { |
| final byte[] data = new byte[random.nextInt(BOUND) + 1]; |
| random.nextBytes(data); |
| final byte[] enc = new Base58().encode(data); |
| final byte[] dec = new Base58().decode(enc); |
| assertArrayEqualsAt(data, dec, i); |
| } |
| } |
| |
| @Test |
| void testEncodeDecodeSmall() { |
| for (int i = 0; i < 12; i++) { |
| final byte[] data = new byte[i]; |
| Arrays.fill(data, (byte) i); |
| final byte[] enc = new Base58().encode(data); |
| final byte[] dec = new Base58().decode(enc); |
| assertArrayEqualsAt(data, dec, i); |
| } |
| } |
| |
| @Test |
| void testEncodeDecodeSmallRandom() { |
| for (int i = 0; i < 12; i++) { |
| final byte[] data = new byte[i]; |
| random.nextBytes(data); |
| final byte[] enc = new Base58().encode(data); |
| final byte[] dec = new Base58().decode(enc); |
| assertArrayEqualsAt(data, dec, i); |
| } |
| } |
| |
| @Test |
| void testHexEncoding() { |
| final String hexString = "48656c6c6f20576f726c6421"; |
| final byte[] encoded = new Base58().encode(StringUtils.getBytesUtf8(hexString)); |
| final byte[] decoded = new Base58().decode(StringUtils.newStringUtf8(encoded)); |
| assertEquals("5m7UdtXCfQxGvX2K9dLrkNs7AFMS98qn8", StringUtils.newStringUtf8(encoded), "Hex encoding failed"); |
| assertEquals(hexString, StringUtils.newStringUtf8(decoded), "Hex decoding failed"); |
| } |
| |
| @Test |
| void testInvalidCharacters() { |
| // Test decoding with invalid characters (those not in Base58 alphabet) |
| final byte[] invalidChars = "0OIl".getBytes(CHARSET_UTF8); // All excluded from Base58 |
| assertThrows(IllegalArgumentException.class, () -> new Base58().decode(invalidChars)); |
| } |
| |
| @Test |
| void testIsInAlphabet() { |
| final Base58 base58 = new Base58(); |
| // Valid characters |
| for (char c = '1'; c <= '9'; c++) { |
| assertTrue(base58.isInAlphabet((byte) c), "char " + c); |
| } |
| for (char c = 'A'; c <= 'H'; c++) { |
| assertTrue(base58.isInAlphabet((byte) c), "char " + c); |
| } |
| for (char c = 'J'; c <= 'N'; c++) { |
| assertTrue(base58.isInAlphabet((byte) c), "char " + c); |
| } |
| for (char c = 'P'; c <= 'Z'; c++) { |
| assertTrue(base58.isInAlphabet((byte) c), "char " + c); |
| } |
| for (char c = 'a'; c <= 'k'; c++) { |
| assertTrue(base58.isInAlphabet((byte) c), "char " + c); |
| } |
| for (char c = 'm'; c <= 'z'; c++) { |
| assertTrue(base58.isInAlphabet((byte) c), "char " + c); |
| } |
| // Invalid characters - excluded from Base58 |
| assertFalse(base58.isInAlphabet((byte) '0'), "char 0"); |
| assertFalse(base58.isInAlphabet((byte) 'O'), "char O"); |
| assertFalse(base58.isInAlphabet((byte) 'I'), "char I"); |
| assertFalse(base58.isInAlphabet((byte) 'l'), "char l"); |
| // Out of bounds |
| assertFalse(base58.isInAlphabet((byte) -1)); |
| assertFalse(base58.isInAlphabet((byte) 0)); |
| assertFalse(base58.isInAlphabet((byte) 128)); |
| assertFalse(base58.isInAlphabet((byte) 255)); |
| } |
| |
| @Test |
| void testLeadingZeros() { |
| // Test that leading zero bytes are encoded as '1' characters |
| final byte[] input = { 0, 0, 1, 2, 3 }; |
| final byte[] encoded = new Base58().encode(input); |
| final String encodedStr = new String(encoded); |
| // Should start with "11" (two leading ones for two leading zeros) |
| assertTrue(encodedStr.startsWith("11"), "Leading zeros should encode as '1' characters"); |
| // Decode should restore the leading zeros |
| final byte[] decoded = new Base58().decode(encoded); |
| assertArrayEquals(input, decoded, "Decoded should match original including leading zeros"); |
| } |
| |
| @Test |
| void testObjectDecodeWithInvalidParameter() { |
| assertThrows(DecoderException.class, () -> new Base58().decode(Integer.valueOf(5))); |
| } |
| |
| @Test |
| void testObjectDecodeWithValidParameter() throws Exception { |
| final String original = "Hello World!"; |
| final Object o = new Base58().encode(original.getBytes(CHARSET_UTF8)); |
| final Base58 base58 = new Base58(); |
| final Object oDecoded = base58.decode(o); |
| final byte[] baDecoded = (byte[]) oDecoded; |
| final String dest = new String(baDecoded); |
| assertEquals(original, dest, "dest string does not equal original"); |
| } |
| |
| @Test |
| void testObjectEncodeWithInvalidParameter() { |
| assertThrows(EncoderException.class, () -> new Base58().encode("Yadayadayada")); |
| } |
| |
| @Test |
| void testObjectEncodeWithValidParameter() throws Exception { |
| final String original = "Hello World!"; |
| final Object origObj = original.getBytes(CHARSET_UTF8); |
| final Object oEncoded = new Base58().encode(origObj); |
| final byte[] bArray = new Base58().decode((byte[]) oEncoded); |
| final String dest = new String(bArray); |
| assertEquals(original, dest, "dest string does not equal original"); |
| } |
| |
| @Test |
| void testRoundTrip() { |
| final String[] testStrings = { "", "a", "ab", "abc", "abcd", "abcde", "abcdef", "Hello World", "The quick brown fox jumps over the lazy dog", |
| "1234567890", "!@#$%^&*()" }; |
| for (final String test : testStrings) { |
| final byte[] input = test.getBytes(CHARSET_UTF8); |
| final byte[] encoded = new Base58().encode(input); |
| final byte[] decoded = new Base58().decode(encoded); |
| assertArrayEquals(input, decoded, "Round trip failed for: " + test); |
| } |
| } |
| |
| @ParameterizedTest |
| @ValueSource(ints = { 0, 1, 2, 3, 4 }) |
| void testRoundtripByte0(final int len) throws IOException { |
| // Sanity check, each step from scratch: |
| final byte[] zeros = new byte[len]; |
| final byte[] encoded0s = ArrayFill.fill(zeros.clone(), (byte) '1'); |
| assertArrayEquals(encoded0s, Base58.builder().get().encode(zeros)); |
| final byte[] decoded = Base58.builder().get().decode(encoded0s); |
| assertArrayEquals(zeros, decoded, () -> String.format("zeros=%s, decoded=%s", Arrays.toString(zeros), Arrays.toString(decoded))); |
| } |
| |
| @Test |
| void testSingleBytes() { |
| // Test encoding of single bytes |
| for (int i = 1; i <= 255; i++) { |
| final byte[] data = { (byte) i }; |
| final byte[] enc = new Base58().encode(data); |
| final byte[] dec = new Base58().decode(enc); |
| assertArrayEquals(data, dec, "Failed for byte value: " + i); |
| } |
| } |
| |
| @Test |
| void testTestVectors() { |
| final String content = "Hello World!"; |
| final String content1 = "The quick brown fox jumps over the lazy dog."; |
| final long content2 = 0x0000287fb4cdL; // Use long to preserve the full 48-bit value |
| final byte[] encodedBytes = new Base58().encode(StringUtils.getBytesUtf8(content)); |
| final byte[] encodedBytes1 = new Base58().encode(StringUtils.getBytesUtf8(content1)); |
| final byte[] content2Bytes = ByteBuffer.allocate(8).putLong(content2).array(); |
| final byte[] content2Trimmed = new byte[6]; |
| System.arraycopy(content2Bytes, 2, content2Trimmed, 0, 6); |
| final byte[] encodedBytes2 = new Base58().encode(content2Trimmed); |
| final String encodedContent = StringUtils.newStringUtf8(encodedBytes); |
| final String encodedContent1 = StringUtils.newStringUtf8(encodedBytes1); |
| final String encodedContent2 = StringUtils.newStringUtf8(encodedBytes2); |
| assertEquals("2NEpo7TZRRrLZSi2U", encodedContent, "encoding hello world"); |
| assertEquals("USm3fpXnKG5EUBx2ndxBDMPVciP5hGey2Jh4NDv6gmeo1LkMeiKrLJUUBk6Z", encodedContent1); |
| assertEquals("11233QC4", encodedContent2, "encoding 0x0000287fb4cd"); |
| final byte[] decodedBytes = new Base58().decode(encodedBytes); |
| final byte[] decodedBytes1 = new Base58().decode(encodedBytes1); |
| final byte[] decodedBytes2 = new Base58().decode(encodedBytes2); |
| final String decodedContent = StringUtils.newStringUtf8(decodedBytes); |
| final String decodedContent1 = StringUtils.newStringUtf8(decodedBytes1); |
| assertEquals(content, decodedContent, "decoding hello world"); |
| assertEquals(content1, decodedContent1); |
| assertArrayEquals(content2Trimmed, decodedBytes2, "decoding 0x0000287fb4cd"); |
| } |
| } |