Pre[-]
package gitzz.codec.binary;
/**
* a radix-2 representation and radix-64 representation converter.
* <p/>
* This class is thread-safe.
*
* @author sandynz
*/
public class Radix64Converter {
/**
* radix-64 representation allowed characters.
* <p/>
* sorted by ASCII ascending, so we can compare radix-64 representation which have the same length
* by comparing ASCII.
*/
private static final char[] ALLOWED_CHARS = "+-0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
.toCharArray();
/**
* convert radix-2 representation to radix-64 representation
*
* @param r2
* radix-2 representation, i.e. 616
* @return radix-64 representation, i.e. "7c"
* @throws RuntimeException
* if there's internal error
*/
public static String r2ToR64(final long r2) {
final int distance = r2 != 0 ? 64 - Long.numberOfLeadingZeros(r2) : 1;
final int r64CharsLen = distance / 6 + (distance % 6 != 0 ? 1 : 0);
final char[] r64Chars = new char[r64CharsLen];
long l = r2;
int round = 0;
do {
r64Chars[r64CharsLen - ++round] = ALLOWED_CHARS[(int) (l & 0x3F)];
l = l >>> 6;
}
while (l != 0);
if (round != r64CharsLen)
throw new RuntimeException(String.format("internal error : round != r64CharsLen, r2=%d", r2));
return new String(r64Chars);
}
private static final String MAX_VALUE = r2ToR64(-1L);
private static final int MAX_VALUE_LEN = MAX_VALUE.length();
/** store represented value of ALLOWED_CHAR, it's index of ALLOWED_CHAR in the array */
private static final byte[] ALLOWED_CHARS_VAL = new byte[128];
static {
for (byte i = 0; i < 64; i++) {
ALLOWED_CHARS_VAL[ALLOWED_CHARS[i]] = i;
}
}
/**
* convert radix-64 representation to radix-2 representation
*
* @param r64
* radix-64 representation, i.e. "7c"
* @return radix-2 representation, i.e. 616
* @throws IllegalArgumentException
* if radix-64 representation overflow or its format is wrong
*/
public static long r64ToR2(final String r64) {
final int r64Len = r64.length();
// overflow detection must be done, otherwise, much string could be mapped to the same number,
// if the number represent userId etc, then it may cause security vulnerability.
if (r64Len > MAX_VALUE_LEN) {
throw new IllegalArgumentException(String.format("radix-64 representation %s overflow", r64));
}
// radix-64 representation pattern, regex is "^(?:[A-Za-z0-9-][A-Za-z0-9+-]*)|(?:\+)$".
// in fact, +7c is equal to 7c, since '+' represent 0, so radix-64 representation won't start
// with '+', except the related radix-2 representation is 0.
//
// cache java.util.regex.Pattern static variable, use Matcher to match r64,
// it's less efficient than manual coding, so it's replaced.
boolean isR64Invalid = false;
if (r64Len == 1) {
char c = r64.charAt(0);
isR64Invalid = !(c >= 'A' && c <= 'Z' || c >= 'a' && c <= 'z' || c >= '0' && c <= '9'
|| c == '+' || c == '-');
}
else {
char c = r64.charAt(0);
if (c >= 'A' && c <= 'Z' || c >= 'a' && c <= 'z' || c >= '0' && c <= '9' || c == '-') {
for (int i = 1; i < r64Len; i++) {
c = r64.charAt(i);
if ((c >= 'A' && c <= 'Z' || c >= 'a' && c <= 'z' || c >= '0' && c <= '9' || c == '+' || c == '-') == false) {
isR64Invalid = true;
break;
}
}
if (isR64Invalid == false && r64Len == MAX_VALUE_LEN && r64.compareTo(MAX_VALUE) > 0)
throw new IllegalArgumentException(String.format("radix-64 representation %s overflow",
r64));
}
else {
isR64Invalid = true;
}
}
if (isR64Invalid)
throw new IllegalArgumentException(
String.format("radix-64 representation %s is invalid", r64));
long r2 = 0;
for (int i = 0; i < r64Len; i++) {
r2 <<= 6;
r2 |= ALLOWED_CHARS_VAL[r64.charAt(i)];
}
return r2;
}
}
No comments:
Post a Comment