package net.i2p.data;
/*
* Released into the public domain
* with no warranty of any kind, either expressed or implied.
*/
import java.io.ByteArrayOutputStream;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Locale;
/**
* Encodes and decodes to and from Base32 notation.
* Ref: RFC 3548
*
* Don't bother with '=' padding characters on encode or
* accept them on decode (i.e. don't require 5-character groups).
* No whitespace allowed.
*
* Decode accepts upper or lower case.
* @author zzz
* @since 0.7
*/
public class Base32 {
//private final static Log _log = new Log(Base32.class);
/** The 32 valid Base32 values. */
private final static char[] ALPHABET = {'a', 'b', 'c', 'd',
'e', 'f', 'g', 'h', 'i', 'j',
'k', 'l', 'm', 'n', 'o', 'p',
'q', 'r', 's', 't', 'u', 'v',
'w', 'x', 'y', 'z',
'2', '3', '4', '5', '6', '7'};
/**
* Translates a Base32 value to either its 5-bit reconstruction value
* or a negative number indicating some other meaning.
* Allow upper or lower case.
**/
private final static byte[] DECODABET = {
26, 27, 28, 29, 30, 31, -9, -9, // Numbers two through nine
-9, -9, -9, // Decimal 58 - 60
-1, // Equals sign at decimal 61
-9, -9, -9, // Decimal 62 - 64
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, // Letters 'A' through 'M'
13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, // Letters 'N' through 'Z'
-9, -9, -9, -9, -9, -9, // Decimal 91 - 96
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, // Letters 'a' through 'm'
13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, // Letters 'n' through 'z'
-9, -9, -9, -9, -9 // Decimal 123 - 127
};
private final static byte BAD_ENCODING = -9; // Indicates error in encoding
/** Defeats instantiation. */
private Base32() { // nop
}
public static void main(String[] args) {
if (args.length == 0) {
help();
return;
}
runApp(args);
}
private static void runApp(String args[]) {
String cmd = args[0].toLowerCase(Locale.US);
if ("encodestring".equals(cmd)) {
System.out.println(encode(DataHelper.getUTF8(args[1])));
return;
}
InputStream in = System.in;
OutputStream out = System.out;
try {
if (args.length >= 3) {
out = new FileOutputStream(args[2]);
}
if (args.length >= 2) {
in = new FileInputStream(args[1]);
}
if ("encode".equals(cmd)) {
encode(in, out);
return;
}
if ("decode".equals(cmd)) {
decode(in, out);
return;
}
} catch (IOException ioe) {
ioe.printStackTrace(System.err);
} finally {
try { in.close(); } catch (IOException e) {}
try { out.close(); } catch (IOException e) {}
}
}
private static byte[] read(InputStream in) throws IOException {
ByteArrayOutputStream baos = new ByteArrayOutputStream(64);
DataHelper.copy(in, baos);
return baos.toByteArray();
}
private static void encode(InputStream in, OutputStream out) throws IOException {
String encoded = encode(read(in));
for (int i = 0; i < encoded.length(); i++)
out.write((byte)(encoded.charAt(i) & 0xFF));
}
private static void decode(InputStream in, OutputStream out) throws IOException {
byte decoded[] = decode(DataHelper.getUTF8(read(in)));
if (decoded == null) {
System.out.println("FAIL");
return;
}
out.write(decoded);
}
private static void help() {
System.out.println("Syntax: Base32 encode <inFile> <outFile>");
System.out.println("or : Base32 encode <inFile>");
System.out.println("or : Base32 encodestring <string>");
System.out.println("or : Base32 encode");
System.out.println("or : Base32 decode <inFile> <outFile>");
System.out.println("or : Base32 decode <inFile>");
System.out.println("or : Base32 decode");
}
/**
* Returns lower case.
* Does not add trailing '='.
*
* @param source if null will return ""
*/
public static String encode(String source) {
return (source != null ? encode(DataHelper.getUTF8(source)) : "");
}
/**
* Returns lower case.
* Does not add trailing '='.
*
* @param source The data to convert non-null
*/
public static String encode(byte[] source) {
StringBuilder buf = new StringBuilder((source.length + 7) * 8 / 5);
encodeBytes(source, buf);
return buf.toString();
}
private final static byte[] emask = { (byte) 0x1f,
(byte) 0x01, (byte) 0x03, (byte) 0x07, (byte) 0x0f };
/**
* Encodes a byte array into Base32 notation.
*
* @param source The data to convert non-null
*/
private static void encodeBytes(byte[] source, StringBuilder out) {
int usedbits = 0;
for (int i = 0; i < source.length; ) {
int fivebits;
if (usedbits < 3) {
fivebits = (source[i] >> (3 - usedbits)) & 0x1f;
usedbits += 5;
} else if (usedbits == 3) {
fivebits = source[i++] & 0x1f;
usedbits = 0;
} else {
fivebits = (source[i++] << (usedbits - 3)) & 0x1f;
if (i < source.length) {
usedbits -= 3;
fivebits |= (source[i] >> (8 - usedbits)) & emask[usedbits];
}
}
out.append(ALPHABET[fivebits]);
}
}
/**
* Decodes data from Base32 notation and
* returns it as a string.
* Case-insensitive.
* Does not allow trailing '='.
*
* @param s the string to decode, if null returns null
* @return The data as a string or null on failure
*/
public static String decodeToString(String s) {
byte[] b = decode(s);
if (b == null)
return null;
return DataHelper.getUTF8(b);
}
/**
* Case-insensitive.
* Does not allow trailing '='.
*
* @param s non-null
* @return decoded data, null on error
*/
public static byte[] decode(String s) {
return decode(DataHelper.getASCII(s));
}
private final static byte[] dmask = { (byte) 0xf8, (byte) 0x7c, (byte) 0x3e, (byte) 0x1f,
(byte) 0x0f, (byte) 0x07, (byte) 0x03, (byte) 0x01 };
/**
* Decodes Base32 content in byte array format and returns
* the decoded byte array.
*
* @param source The Base32 encoded data non-null
* @return decoded data, null on error
*/
private static byte[] decode(byte[] source) {
int len58;
if (source.length <= 1)
len58 = source.length;
else
len58 = source.length * 5 / 8;
byte[] outBuff = new byte[len58];
int outBuffPosn = 0;
int usedbits = 0;
for (int i = 0; i < source.length; i++) {
int fivebits;
if ((source[i] & 0x80) != 0 || source[i] < '2' || source[i] > 'z')
fivebits = BAD_ENCODING;
else
fivebits = DECODABET[source[i] - '2'];
if (fivebits >= 0) {
if (usedbits == 0) {
outBuff[outBuffPosn] = (byte) ((fivebits << 3) & 0xf8);
usedbits = 5;
} else if (usedbits < 3) {
outBuff[outBuffPosn] |= (fivebits << (3 - usedbits)) & dmask[usedbits];
usedbits += 5;
} else if (usedbits == 3) {
outBuff[outBuffPosn++] |= fivebits;
usedbits = 0;
} else {
outBuff[outBuffPosn++] |= (fivebits >> (usedbits - 3)) & dmask[usedbits];
byte next = (byte) (fivebits << (11 - usedbits));
if (outBuffPosn < len58) {
outBuff[outBuffPosn] = next;
usedbits -= 3;
} else if (next != 0) {
//_log.warn("Extra data at the end: " + next + "(decimal)");
return null;
}
}
} else {
//_log.warn("Bad Base32 input character at " + i + ": " + source[i] + "(decimal)");
return null;
}
}
return outBuff;
}
}