Skip to content

Commit f61099a

Browse files
authored
Merge pull request #5 from sic2/master
Added tests for invalid Base16 input + made Base enum immutable
2 parents 4a9ef51 + 3afcfe4 commit f61099a

File tree

6 files changed

+143
-70
lines changed

6 files changed

+143
-70
lines changed
Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,20 @@
11
package io.ipfs.multibase;
22

33
public class Base16 {
4-
public static byte[] decode(String hex)
5-
{
4+
5+
public static byte[] decode(String hex) {
66
byte[] res = new byte[hex.length()/2];
7-
for (int i=0; i < res.length; i++)
8-
res[i] = (byte) Integer.parseInt(hex.substring(2*i, 2*i+2), 16);
7+
for (int i=0; i < res.length; i++) {
8+
res[i] = (byte) Integer.parseInt(hex.substring(2 * i, 2 * i + 2), 16);
9+
}
910
return res;
1011
}
1112

12-
public static String encode(byte[] data)
13-
{
13+
public static String encode(byte[] data) {
1414
StringBuilder s = new StringBuilder();
15-
for (byte b : data)
15+
for (byte b : data) {
1616
s.append(String.format("%02x", b & 0xFF));
17+
}
1718
return s.toString();
1819
}
1920
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
package io.ipfs.multibase;
2+
3+
import java.math.BigInteger;
4+
5+
/**
6+
* Based on RFC 4648
7+
* No padding
8+
*/
9+
public class Base32 {
10+
private static final String ALPHABET = "abcdefghijklmnopqrstuvwxyz234567";
11+
private static final BigInteger BASE = BigInteger.valueOf(32);
12+
13+
public static String encode(final byte[] input) {
14+
return BaseN.encode(ALPHABET, BASE, input);
15+
}
16+
17+
public static byte[] decode(final String input) {
18+
return BaseN.decode(ALPHABET, BASE, input);
19+
}
20+
}

src/main/java/io/ipfs/multibase/Base58.java

Lines changed: 4 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -36,54 +36,11 @@ public class Base58 {
3636
private static final String ALPHABET = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz";
3737
private static final BigInteger BASE = BigInteger.valueOf(58);
3838

39-
public static String encode(byte[] input) {
40-
// TODO: This could be a lot more efficient.
41-
BigInteger bi = new BigInteger(1, input);
42-
StringBuffer s = new StringBuffer();
43-
while (bi.compareTo(BASE) >= 0) {
44-
BigInteger mod = bi.mod(BASE);
45-
s.insert(0, ALPHABET.charAt(mod.intValue()));
46-
bi = bi.subtract(mod).divide(BASE);
47-
}
48-
s.insert(0, ALPHABET.charAt(bi.intValue()));
49-
// Convert leading zeros too.
50-
for (byte anInput : input) {
51-
if (anInput == 0)
52-
s.insert(0, ALPHABET.charAt(0));
53-
else
54-
break;
55-
}
56-
return s.toString();
39+
public static String encode(final byte[] input) {
40+
return BaseN.encode(ALPHABET, BASE, input);
5741
}
5842

59-
public static byte[] decode(String input) {
60-
byte[] bytes = decodeToBigInteger(input).toByteArray();
61-
// We may have got one more byte than we wanted, if the high bit of the next-to-last byte was not zero. This
62-
// is because BigIntegers are represented with twos-compliment notation, thus if the high bit of the last
63-
// byte happens to be 1 another 8 zero bits will be added to ensure the number parses as positive. Detect
64-
// that case here and chop it off.
65-
boolean stripSignByte = bytes.length > 1 && bytes[0] == 0 && bytes[1] < 0;
66-
// Count the leading zeros, if any.
67-
int leadingZeros = 0;
68-
for (int i = 0; input.charAt(i) == ALPHABET.charAt(0); i++) {
69-
leadingZeros++;
70-
}
71-
// Now cut/pad correctly. Java 6 has a convenience for this, but Android can't use it.
72-
byte[] tmp = new byte[bytes.length - (stripSignByte ? 1 : 0) + leadingZeros];
73-
System.arraycopy(bytes, stripSignByte ? 1 : 0, tmp, leadingZeros, tmp.length - leadingZeros);
74-
return tmp;
75-
}
76-
77-
public static BigInteger decodeToBigInteger(String input) {
78-
BigInteger bi = BigInteger.valueOf(0);
79-
// Work backwards through the string.
80-
for (int i = input.length() - 1; i >= 0; i--) {
81-
int alphaIndex = ALPHABET.indexOf(input.charAt(i));
82-
if (alphaIndex == -1) {
83-
throw new IllegalStateException("Illegal character " + input.charAt(i) + " at " + i);
84-
}
85-
bi = bi.add(BigInteger.valueOf(alphaIndex).multiply(BASE.pow(input.length() - 1 - i)));
86-
}
87-
return bi;
43+
public static byte[] decode(final String input) {
44+
return BaseN.decode(ALPHABET, BASE, input);
8845
}
8946
}
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
package io.ipfs.multibase;
2+
3+
import java.math.BigInteger;
4+
5+
public class BaseN {
6+
7+
static String encode(final String alphabet, final BigInteger base, final byte[] input) {
8+
// TODO: This could be a lot more efficient.
9+
BigInteger bi = new BigInteger(1, input);
10+
StringBuffer s = new StringBuffer();
11+
while (bi.compareTo(base) >= 0) {
12+
BigInteger mod = bi.mod(base);
13+
s.insert(0, alphabet.charAt(mod.intValue()));
14+
bi = bi.subtract(mod).divide(base);
15+
}
16+
s.insert(0, alphabet.charAt(bi.intValue()));
17+
// Convert leading zeros too.
18+
for (byte anInput : input) {
19+
if (anInput == 0)
20+
s.insert(0, alphabet.charAt(0));
21+
else
22+
break;
23+
}
24+
return s.toString();
25+
}
26+
27+
static byte[] decode(final String alphabet, final BigInteger base, final String input) {
28+
byte[] bytes = decodeToBigInteger(alphabet, base, input).toByteArray();
29+
// We may have got one more byte than we wanted, if the high bit of the next-to-last byte was not zero. This
30+
// is because BigIntegers are represented with twos-compliment notation, thus if the high bit of the last
31+
// byte happens to be 1 another 8 zero bits will be added to ensure the number parses as positive. Detect
32+
// that case here and chop it off.
33+
boolean stripSignByte = bytes.length > 1 && bytes[0] == 0 && bytes[1] < 0;
34+
// Count the leading zeros, if any.
35+
int leadingZeros = 0;
36+
for (int i = 0; input.charAt(i) == alphabet.charAt(0); i++) {
37+
leadingZeros++;
38+
}
39+
// Now cut/pad correctly. Java 6 has a convenience for this, but Android can't use it.
40+
byte[] tmp = new byte[bytes.length - (stripSignByte ? 1 : 0) + leadingZeros];
41+
System.arraycopy(bytes, stripSignByte ? 1 : 0, tmp, leadingZeros, tmp.length - leadingZeros);
42+
return tmp;
43+
}
44+
45+
private static BigInteger decodeToBigInteger(final String alphabet, final BigInteger base, final String input) {
46+
BigInteger bi = BigInteger.valueOf(0);
47+
// Work backwards through the string.
48+
for (int i = input.length() - 1; i >= 0; i--) {
49+
int alphaIndex = alphabet.indexOf(input.charAt(i));
50+
if (alphaIndex == -1) {
51+
throw new IllegalStateException("Illegal character " + input.charAt(i) + " at " + i);
52+
}
53+
bi = bi.add(BigInteger.valueOf(alphaIndex).multiply(base.pow(input.length() - 1 - i)));
54+
}
55+
return bi;
56+
}
57+
}

src/main/java/io/ipfs/multibase/Multibase.java

Lines changed: 16 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,22 @@
11
package io.ipfs.multibase;
22

3-
import java.util.*;
3+
import java.util.Map;
4+
import java.util.TreeMap;
45

56
public class Multibase {
67

78
public enum Base {
8-
Base1('1'),
9-
Base2('0'),
10-
Base8('7'),
11-
Base10('9'),
12-
Base16('f'),
13-
Base58Flickr('Z'),
14-
Base58BTC('z');
9+
// encoding(code)
10+
Base1('1'), // unary tends to be 11111
11+
Base2('0'), // binary has 1 and 0
12+
Base8('7'), // highest char in octal
13+
Base10('9'), // highest char in decimal
14+
Base16('f'), // highest char in hex
15+
Base32('b'), // rfc4648 no padding
16+
Base58Flickr('Z'), // highest char
17+
Base58BTC('z'); // highest char
1518

16-
public char prefix;
19+
private final char prefix;
1720

1821
Base(char prefix) {
1922
this.prefix = prefix;
@@ -38,6 +41,8 @@ public static String encode(Base b, byte[] data) {
3841
return b.prefix + Base58.encode(data);
3942
case Base16:
4043
return b.prefix + Base16.encode(data);
44+
case Base32:
45+
return b.prefix + Base32.encode(data);
4146
default:
4247
throw new IllegalStateException("Unsupported base encoding: " + b.name());
4348
}
@@ -55,6 +60,8 @@ public static byte[] decode(String data) {
5560
return Base58.decode(rest);
5661
case Base16:
5762
return Base16.decode(rest);
63+
case Base32:
64+
return Base32.decode(rest);
5865
default:
5966
throw new IllegalStateException("Unsupported base encoding: " + b.name());
6067
}
Lines changed: 38 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,12 @@
11
package io.ipfs.multibase;
22

3-
import org.junit.*;
3+
import org.junit.Test;
44

5-
import java.util.*;
5+
import java.util.Arrays;
6+
import java.util.List;
7+
8+
import static org.junit.Assert.assertEquals;
9+
import static org.junit.Assert.assertNotEquals;
610

711
public class MultibaseTest {
812

@@ -13,20 +17,47 @@ public void base58Test() {
1317
for (String example: examples) {
1418
byte[] output = Multibase.decode(example);
1519
String encoded = Multibase.encode(Multibase.Base.Base58BTC, output);
16-
if (!encoded.equals(encoded))
17-
throw new IllegalStateException("Incorrect base58! " + example + " => " + encoded);
20+
assertEquals(example, encoded);
1821
}
1922
}
2023

2124
@Test
2225
public void base16Test() {
2326
List<String> examples = Arrays.asList("f234abed8debede",
24-
"f87ad873defc2b288a");
27+
"f87ad873defc2b288",
28+
"f",
29+
"f01",
30+
"f0123456789abcdef");
2531
for (String example: examples) {
2632
byte[] output = Multibase.decode(example);
2733
String encoded = Multibase.encode(Multibase.Base.Base16, output);
28-
if (!encoded.equals(encoded))
29-
throw new IllegalStateException("Incorrect base16! " + example + " => " + encoded);
34+
assertEquals(example, encoded);
35+
}
36+
}
37+
38+
@Test
39+
public void base32Test() {
40+
List<String> examples = Arrays.asList("bnbswy3dpeb3w64tmmq");
41+
for (String example: examples) {
42+
byte[] output = Multibase.decode(example);
43+
String encoded = Multibase.encode(Multibase.Base.Base32, output);
44+
assertEquals(example, encoded);
3045
}
3146
}
47+
48+
@Test
49+
public void invalidBase16Test() {
50+
String example = "f012"; // hex string of odd length
51+
byte[] output = Multibase.decode(example);
52+
String encoded = Multibase.encode(Multibase.Base.Base16, output);
53+
assertNotEquals(example, encoded);
54+
55+
}
56+
57+
@Test (expected = NumberFormatException.class)
58+
public void invalidWithExceptionBase16Test() {
59+
String example = "f0g"; // g char is not allowed in hex
60+
Multibase.decode(example);
61+
}
62+
3263
}

0 commit comments

Comments
 (0)