fixed and enhanced Base64 (en)coder (again)

pull/1/head
Michael Peter Christen 11 years ago
parent 2415e3db43
commit e132689818

@ -21,10 +21,13 @@
package net.yacy.cora.order;
import java.io.Serializable;
import java.lang.management.ManagementFactory;
import java.lang.reflect.Method;
import java.util.Comparator;
import java.util.Random;
import java.util.regex.Pattern;
import net.yacy.cora.document.encoding.ASCII;
import net.yacy.cora.document.encoding.UTF8;
//ATTENTION! THIS CLASS SHALL NOT IMPORT FROM OTHER PACKAGES THAN CORA AND JRE
@ -161,24 +164,6 @@ public class Base64Order extends AbstractOrder<byte[]> implements ByteOrder, Com
return s;
}
public final char[] encodeLongCH(long c, int length) {
final char[] s = new char[length];
while (length > 0) {
s[--length] = (char) this.alpha[(byte) (c & 0x3F)];
c >>= 6;
}
return s;
}
public final byte[] encodeLongSubstr(long c, int length) {
final byte[] s = new byte[length];
while (length > 0) {
s[--length] = this.alpha[(byte) (c & 0x3F)];
c >>= 6;
}
return s;
}
public final void encodeLong(long c, final byte[] b, final int offset, int length) {
assert offset + length <= b.length;
while (length > 0) {
@ -213,62 +198,57 @@ public class Base64Order extends AbstractOrder<byte[]> implements ByteOrder, Com
return encode(UTF8.getBytes(in));
}
// we will use this encoding to encode strings with 2^8 values to
// b64-Strings
// we will do that by grouping each three input bytes to four output bytes.
/**
* encode arbitrary 2^8 bit-values to b64 strings. we will do that by grouping each three input bytes to four output bytes.
* @param in
* @return a base64-encoding of the input if rfc1521compliant = true, otherwise a b64-encoding of the input.
*/
public final String encode(final byte[] in) {
if (in == null || in.length == 0) return "";
final int lene = in.length * 4 / 3 + 3;
final StringBuilder out = new StringBuilder(lene);
int pos = 0;
long l;
while (in.length - pos >= 3) {
l = ((((0XffL & in[pos]) << 8) + (0XffL & in[pos + 1])) << 8) + (0XffL & in[pos + 2]);
pos += 3;
out.append(encodeLongCH(l, 4));
}
// now there may be remaining bytes
if (in.length % 3 != 0) out.append((in.length % 3 == 2) ? encodeLongSB((((0XffL & in[pos]) << 8) + (0XffL & in[pos + 1])) << 8, 4).substring(0, 3) : encodeLongSB((((0XffL & in[pos])) << 8) << 8, 4).substring(0, 2));
if (this.rfc1521compliant) while (out.length() % 4 > 0) out.append("=");
// return result
assert (!this.rfc1521compliant || lene == out.length()) && lene >= out.length() : "lene = " + lene + ", out.len = " + out.length();
return out.toString();
int rfc1521compliantLength = ((in.length + 2) / 3) * 4; // (bytes/bits/chars) = {(0/0/0), (1/8/4), (2/16/4), (3/24/4), (4/32/8), (5/40/8), (6/48/8), (7/56/12), (8/64/12), (9/72/12), ..}
if (!this.rfc1521compliant) rfc1521compliantLength -= in.length % 3 == 2 ? 1 : in.length % 3 == 1 ? 2 : 0; // non-compliant are shorter (!)
return ASCII.String(encodeSubstring(in, rfc1521compliantLength));
}
public final byte[] encodeSubstring(final byte[] in, final int sublen) {
if (in.length == 0) return null;
if (in.length == 0) return new byte[0];
assert sublen <= ((in.length + 2) / 3) * 4 : "sublen = " + sublen + ", expected: " + ((in.length + 2) / 3) * 4;
final byte[] out = new byte[sublen];
int writepos = 0;
int pos = 0;
long l;
while (in.length - pos >= 3 && writepos < sublen) {
l = ((((0XffL & in[pos]) << 8) + (0XffL & in[pos + 1])) << 8) + (0XffL & in[pos + 2]);
pos += 3;
System.arraycopy(encodeLongSubstr(l, 4), 0, out, writepos, 4);
l = ((((0XffL & in[pos++]) << 8) | (0XffL & in[pos++])) << 8) | (0XffL & in[pos++]);
encodeLong(l, out, writepos, 4);
writepos += 4;
}
// now there may be remaining bytes
if (in.length % 3 != 0 && writepos < sublen) {
if (in.length % 3 == 2) {
System.arraycopy(encodeLongBA((((0XffL & in[pos]) << 8) + (0XffL & in[pos + 1])) << 8, 4), 0, out, writepos, 3);
long c = (((0XffL & in[pos]) << 8) + (0XffL & in[pos + 1])) << 2;
out[writepos + 2] = this.alpha[(byte) (c & 0x3F)]; c >>= 6;
out[writepos + 1] = this.alpha[(byte) (c & 0x3F)]; c >>= 6;
out[writepos ] = this.alpha[(byte) (c & 0x3F)]; c >>= 6;
writepos += 3;
if (this.rfc1521compliant && writepos < sublen) out[writepos++] = '=';
} else {
System.arraycopy(encodeLongBA((((0XffL & in[pos])) << 8) << 8, 4), 0, out, writepos, 2);
long c = (0XffL & in[pos]) << 4;
out[writepos + 1] = this.alpha[(byte) (c & 0x3F)]; c >>= 6;
out[writepos ] = this.alpha[(byte) (c & 0x3F)]; c >>= 6;
writepos += 2;
if (this.rfc1521compliant) {if (writepos < sublen) out[writepos++] = '='; if (writepos < sublen) out[writepos++] = '=';}
}
}
if (this.rfc1521compliant) while (writepos % 4 > 0 && writepos < sublen) out[writepos] = '=';
//assert encode(in).substring(0, sublen).equals(ASCII.String(out));
return out;
}
public final String decodeString(final String in) {
return UTF8.String(decode(in));
}
final static Pattern cr = Pattern.compile("\n");
public final byte[] decode(String in) {
if ((in == null) || (in.isEmpty())) return new byte[0];
in = cr.matcher(in).replaceAll("");
try {
int posIn = 0;
int posOut = 0;
@ -607,43 +587,73 @@ public class Base64Order extends AbstractOrder<byte[]> implements ByteOrder, Com
System.out.println(((double) Base64Order.enhancedCoder.cardinal(s[1].getBytes())) / ((double) Long.MAX_VALUE));
}
if ("-test".equals(s[0])) {
System.out.println("Pid: " + ManagementFactory.getRuntimeMXBean().getName());
// do some checks
Random r = new Random(System.currentTimeMillis());
Random r = new Random(0); // not real random to be able to reproduce the test
try {
// use the class loader to call sun.misc.BASE64Encoder, the sun base64 encoder
// we do not instantiate that class here directly since that provokes a
// "warning: sun.misc.BASE64Encoder is internal proprietary API and may be removed in a future release"
// encode with enhancedCoder, decode with standard RFC 1521 base64
Class<?> rfc1521Decoder_class = Class.forName("sun.misc.BASE64Decoder");
Object rfc1521Decoder = rfc1521Decoder_class.newInstance();
Method rfc1521Decoder_decodeBuffer = rfc1521Decoder_class.getMethod("decodeBuffer", String.class);
for (int i = 0; i < 100000; i++) {
String challenge = Long.toString(r.nextLong());
String eb64 = enhancedCoder.encode(UTF8.getBytes(challenge));
String rfc1521 = new String(eb64);
while (rfc1521.length() % 4 != 0) rfc1521 += "=";
rfc1521 = rfc1521.replace('-', '+').replace('_', '/');
System.out.println("Encode enhancedB64 + Decode RFC1521: Challenge=" + challenge + ", eb64=" + eb64 + ", rfc1521=" + rfc1521);
if (!UTF8.String((byte[]) rfc1521Decoder_decodeBuffer.invoke(rfc1521Decoder, rfc1521)).equals(challenge)) System.out.println("Encode Fail for " + challenge);
}
// encode with enhancedCoder, decode with standard RFC 1521 base64
Class<?> rfc1521Encoder_class = Class.forName("sun.misc.BASE64Encoder");
Object rfc1521Encoder = rfc1521Encoder_class.newInstance();
Method rfc1521Encoder_encode = rfc1521Encoder_class.getMethod("encode", byte[].class);
// sun.misc.BASE64Encoder rfc1521Encoder = new sun.misc.BASE64Encoder();
for (int i = 0; i < 100000; i++) {
System.out.println("preparing tests..");
// prepare challenges and results with rfc1521Encoder
int count = 100000;
String[] challenges = new String[count];
String[] rfc1521Encoded = new String[count];
for (int i = 0; i < count; i++) {
int len = r.nextInt(10000);
StringBuilder challenge = new StringBuilder(len);
for (int j = 0; j < len; j++) challenge.append((char) (32 + r.nextInt(64)));
challenges[i] = challenge.toString();
rfc1521Encoded[i] = (String) rfc1521Encoder_encode.invoke(rfc1521Encoder, UTF8.getBytes(challenges[i]));
}
// starting tests
long start = System.currentTimeMillis();
for (boolean rfc1521Compliant: new boolean[]{false, true}) {
System.out.println("starting tests, rfc1521Compliant = " + rfc1521Compliant + " ...");
String eb64, rfc1521;
// encode with enhancedCoder, decode with standard RFC 1521 base64
for (int i = 0; i < count; i++) {
if (rfc1521Compliant) {
rfc1521 = Base64Order.standardCoder.encode(UTF8.getBytes(challenges[i]));
} else {
eb64 = Base64Order.enhancedCoder.encode(UTF8.getBytes(challenges[i]));
rfc1521 = new String(eb64);
while (rfc1521.length() % 4 != 0) rfc1521 += "=";
rfc1521 = rfc1521.replace('-', '+').replace('_', '/');
}
String rfc1521Decoded = UTF8.String((byte[]) rfc1521Decoder_decodeBuffer.invoke(rfc1521Decoder, rfc1521));
if (!rfc1521Decoded.equals(challenges[i])) System.out.println("Encode enhancedB64 + Decode RFC1521: Fail for " + challenges[i]);
}
// encode with enhancedCoder, decode with standard RFC 1521 base64
String challenge = Long.toString(r.nextLong());
String rfc1521 = (String) rfc1521Encoder_encode.invoke(rfc1521Encoder, UTF8.getBytes(challenge));
String eb64 = new String(rfc1521);
while (eb64.endsWith("=")) eb64 = eb64.substring(0, eb64.length() - 1);
eb64 = eb64.replace('+', '-').replace('/', '_');
System.out.println("Encode RFC1521 + Decode enhancedB64: Challenge=" + challenge + ", rfc1521=" + rfc1521 + ", eb64=" + eb64);
if (!UTF8.String(enhancedCoder.decode(eb64)).equals(challenge)) System.out.println("Encode Fail for " + challenge);
// sun.misc.BASE64Encoder rfc1521Encoder = new sun.misc.BASE64Encoder();
for (int i = 0; i < count; i++) {
// encode with enhancedCoder, decode with standard RFC 1521 base64
rfc1521 = new String(rfc1521Encoded[i]);
if (rfc1521Compliant) {
String standardCoderDecoded = UTF8.String(Base64Order.standardCoder.decode(rfc1521));
if (!standardCoderDecoded.equals(challenges[i])) System.out.println("Encode RFC1521 + Decode enhancedB64: Fail for " + rfc1521);
} else {
eb64 = new String(rfc1521);
while (eb64.endsWith("=")) eb64 = eb64.substring(0, eb64.length() - 1);
eb64 = eb64.replace('+', '-').replace('/', '_');
String enhancedCoderDecoded = UTF8.String(Base64Order.enhancedCoder.decode(eb64));
if (!enhancedCoderDecoded.equals(challenges[i])) System.out.println("Encode RFC1521 + Decode enhancedB64: Fail for " + eb64);
}
}
}
long time = System.currentTimeMillis() - start;
System.out.println("time: " + (time / 1000) + " seconds, " + (1000 * time / count) + " ms / 1000 steps");
} catch (Throwable e) {
e.printStackTrace();
}

Loading…
Cancel
Save