Base32.java
/**
*
* Copyright the original author or authors
*
* Licensed 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
*
* http://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.jivesoftware.smack.util.stringencoder;
import java.io.ByteArrayOutputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
/**
* Base32 string encoding is useful for when filenames case-insensitive filesystems are encoded.
* Base32 representation takes roughly 20% more space then Base64.
*
* @author Florian Schmaus
* Based on code by Brian Wellington (bwelling@xbill.org)
* @see <a href="http://en.wikipedia.org/wiki/Base32">Base32 Wikipedia entry</a>
*
*/
public class Base32 {
private static final StringEncoder<String> base32Stringencoder = new StringEncoder<String>() {
@Override
public String encode(String string) {
return Base32.encode(string);
}
@Override
public String decode(String string) {
return Base32.decode(string);
}
};
private static final String ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567";
public static StringEncoder<String> getStringEncoder() {
return base32Stringencoder;
}
public static String decode(String str) {
ByteArrayOutputStream bs = new ByteArrayOutputStream();
byte[] raw = str.getBytes(StandardCharsets.UTF_8);
for (int i = 0; i < raw.length; i++) {
char c = (char) raw[i];
if (!Character.isWhitespace(c)) {
c = Character.toUpperCase(c);
bs.write((byte) c);
}
}
while (bs.size() % 8 != 0)
bs.write('8');
byte[] in = bs.toByteArray();
bs.reset();
DataOutputStream ds = new DataOutputStream(bs);
for (int i = 0; i < in.length / 8; i++) {
short[] s = new short[8];
int[] t = new int[5];
int padlen = 8;
for (int j = 0; j < 8; j++) {
char c = (char) in[i * 8 + j];
if (c == '8')
break;
s[j] = (short) ALPHABET.indexOf(in[i * 8 + j]);
if (s[j] < 0)
return null;
padlen--;
}
int blocklen = paddingToLen(padlen);
if (blocklen < 0)
return null;
// all 5 bits of 1st, high 3 (of 5) of 2nd
t[0] = (s[0] << 3) | s[1] >> 2;
// lower 2 of 2nd, all 5 of 3rd, high 1 of 4th
t[1] = ((s[1] & 0x03) << 6) | (s[2] << 1) | (s[3] >> 4);
// lower 4 of 4th, high 4 of 5th
t[2] = ((s[3] & 0x0F) << 4) | ((s[4] >> 1) & 0x0F);
// lower 1 of 5th, all 5 of 6th, high 2 of 7th
t[3] = (s[4] << 7) | (s[5] << 2) | (s[6] >> 3);
// lower 3 of 7th, all of 8th
t[4] = ((s[6] & 0x07) << 5) | s[7];
try {
for (int j = 0; j < blocklen; j++)
ds.writeByte((byte) (t[j] & 0xFF));
} catch (IOException e) {
}
}
String res = new String(bs.toByteArray(), StandardCharsets.UTF_8);
return res;
}
public static String encode(String str) {
byte[] b = str.getBytes(StandardCharsets.UTF_8);
ByteArrayOutputStream os = new ByteArrayOutputStream();
for (int i = 0; i < (b.length + 4) / 5; i++) {
short[] s = new short[5];
int[] t = new int[8];
int blocklen = 5;
for (int j = 0; j < 5; j++) {
if ((i * 5 + j) < b.length)
s[j] = (short) (b[i * 5 + j] & 0xFF);
else {
s[j] = 0;
blocklen--;
}
}
int padlen = lenToPadding(blocklen);
// convert the 5 byte block into 8 characters (values 0-31).
// upper 5 bits from first byte
t[0] = (byte) ((s[0] >> 3) & 0x1F);
// lower 3 bits from 1st byte, upper 2 bits from 2nd.
t[1] = (byte) (((s[0] & 0x07) << 2) | ((s[1] >> 6) & 0x03));
// bits 5-1 from 2nd.
t[2] = (byte) ((s[1] >> 1) & 0x1F);
// lower 1 bit from 2nd, upper 4 from 3rd
t[3] = (byte) (((s[1] & 0x01) << 4) | ((s[2] >> 4) & 0x0F));
// lower 4 from 3rd, upper 1 from 4th.
t[4] = (byte) (((s[2] & 0x0F) << 1) | ((s[3] >> 7) & 0x01));
// bits 6-2 from 4th
t[5] = (byte) ((s[3] >> 2) & 0x1F);
// lower 2 from 4th, upper 3 from 5th;
t[6] = (byte) (((s[3] & 0x03) << 3) | ((s[4] >> 5) & 0x07));
// lower 5 from 5th;
t[7] = (byte) (s[4] & 0x1F);
// write out the actual characters.
for (int j = 0; j < t.length - padlen; j++) {
char c = ALPHABET.charAt(t[j]);
os.write(c);
}
}
String res = new String(os.toByteArray(), StandardCharsets.UTF_8);
return res;
}
private static int lenToPadding(int blocklen) {
switch (blocklen) {
case 1:
return 6;
case 2:
return 4;
case 3:
return 3;
case 4:
return 1;
case 5:
return 0;
default:
return -1;
}
}
private static int paddingToLen(int padlen) {
switch (padlen) {
case 6:
return 1;
case 4:
return 2;
case 3:
return 3;
case 1:
return 4;
case 0:
return 5;
default:
return -1;
}
}
}