diff options
Diffstat (limited to 'core/src/main/java/org/spongycastle/util')
49 files changed, 4560 insertions, 0 deletions
diff --git a/core/src/main/java/org/spongycastle/util/Arrays.java b/core/src/main/java/org/spongycastle/util/Arrays.java new file mode 100644 index 00000000..a847f721 --- /dev/null +++ b/core/src/main/java/org/spongycastle/util/Arrays.java @@ -0,0 +1,971 @@ +package org.spongycastle.util; + +import java.math.BigInteger; + +/** + * General array utilities. + */ +public final class Arrays +{ + private Arrays() + { + // static class, hide constructor + } + + public static boolean areEqual( + boolean[] a, + boolean[] b) + { + if (a == b) + { + return true; + } + + if (a == null || b == null) + { + return false; + } + + if (a.length != b.length) + { + return false; + } + + for (int i = 0; i != a.length; i++) + { + if (a[i] != b[i]) + { + return false; + } + } + + return true; + } + + public static boolean areEqual( + char[] a, + char[] b) + { + if (a == b) + { + return true; + } + + if (a == null || b == null) + { + return false; + } + + if (a.length != b.length) + { + return false; + } + + for (int i = 0; i != a.length; i++) + { + if (a[i] != b[i]) + { + return false; + } + } + + return true; + } + + public static boolean areEqual( + byte[] a, + byte[] b) + { + if (a == b) + { + return true; + } + + if (a == null || b == null) + { + return false; + } + + if (a.length != b.length) + { + return false; + } + + for (int i = 0; i != a.length; i++) + { + if (a[i] != b[i]) + { + return false; + } + } + + return true; + } + + /** + * A constant time equals comparison - does not terminate early if + * test will fail. + * + * @param a first array + * @param b second array + * @return true if arrays equal, false otherwise. + */ + public static boolean constantTimeAreEqual( + byte[] a, + byte[] b) + { + if (a == b) + { + return true; + } + + if (a == null || b == null) + { + return false; + } + + if (a.length != b.length) + { + return false; + } + + int nonEqual = 0; + + for (int i = 0; i != a.length; i++) + { + nonEqual |= (a[i] ^ b[i]); + } + + return nonEqual == 0; + } + + public static boolean areEqual( + int[] a, + int[] b) + { + if (a == b) + { + return true; + } + + if (a == null || b == null) + { + return false; + } + + if (a.length != b.length) + { + return false; + } + + for (int i = 0; i != a.length; i++) + { + if (a[i] != b[i]) + { + return false; + } + } + + return true; + } + + public static boolean areEqual( + long[] a, + long[] b) + { + if (a == b) + { + return true; + } + + if (a == null || b == null) + { + return false; + } + + if (a.length != b.length) + { + return false; + } + + for (int i = 0; i != a.length; i++) + { + if (a[i] != b[i]) + { + return false; + } + } + + return true; + } + + public static boolean areEqual(Object[] a, Object[] b) + { + if (a == b) + { + return true; + } + if (a == null || b == null) + { + return false; + } + if (a.length != b.length) + { + return false; + } + for (int i = 0; i != a.length; i++) + { + Object objA = a[i], objB = b[i]; + if (objA == null) + { + if (objB != null) + { + return false; + } + } + else if (!objA.equals(objB)) + { + return false; + } + } + return true; + } + + public static boolean contains(short[] a, short n) + { + for (int i = 0; i < a.length; ++i) + { + if (a[i] == n) + { + return true; + } + } + return false; + } + + public static boolean contains(int[] a, int n) + { + for (int i = 0; i < a.length; ++i) + { + if (a[i] == n) + { + return true; + } + } + return false; + } + + public static void fill( + byte[] array, + byte value) + { + for (int i = 0; i < array.length; i++) + { + array[i] = value; + } + } + + public static void fill( + char[] array, + char value) + { + for (int i = 0; i < array.length; i++) + { + array[i] = value; + } + } + + public static void fill( + long[] array, + long value) + { + for (int i = 0; i < array.length; i++) + { + array[i] = value; + } + } + + public static void fill( + short[] array, + short value) + { + for (int i = 0; i < array.length; i++) + { + array[i] = value; + } + } + + public static void fill( + int[] array, + int value) + { + for (int i = 0; i < array.length; i++) + { + array[i] = value; + } + } + + public static int hashCode(byte[] data) + { + if (data == null) + { + return 0; + } + + int i = data.length; + int hc = i + 1; + + while (--i >= 0) + { + hc *= 257; + hc ^= data[i]; + } + + return hc; + } + + public static int hashCode(byte[] data, int off, int len) + { + if (data == null) + { + return 0; + } + + int i = len; + int hc = i + 1; + + while (--i >= 0) + { + hc *= 257; + hc ^= data[off + i]; + } + + return hc; + } + + public static int hashCode(char[] data) + { + if (data == null) + { + return 0; + } + + int i = data.length; + int hc = i + 1; + + while (--i >= 0) + { + hc *= 257; + hc ^= data[i]; + } + + return hc; + } + + public static int hashCode(int[][] ints) + { + int hc = 0; + + for (int i = 0; i != ints.length; i++) + { + hc = hc * 257 + hashCode(ints[i]); + } + + return hc; + } + + public static int hashCode(int[] data) + { + if (data == null) + { + return 0; + } + + int i = data.length; + int hc = i + 1; + + while (--i >= 0) + { + hc *= 257; + hc ^= data[i]; + } + + return hc; + } + + public static int hashCode(int[] data, int off, int len) + { + if (data == null) + { + return 0; + } + + int i = len; + int hc = i + 1; + + while (--i >= 0) + { + hc *= 257; + hc ^= data[off + i]; + } + + return hc; + } + + public static int hashCode(short[][][] shorts) + { + int hc = 0; + + for (int i = 0; i != shorts.length; i++) + { + hc = hc * 257 + hashCode(shorts[i]); + } + + return hc; + } + + public static int hashCode(short[][] shorts) + { + int hc = 0; + + for (int i = 0; i != shorts.length; i++) + { + hc = hc * 257 + hashCode(shorts[i]); + } + + return hc; + } + + public static int hashCode(short[] data) + { + if (data == null) + { + return 0; + } + + int i = data.length; + int hc = i + 1; + + while (--i >= 0) + { + hc *= 257; + hc ^= (data[i] & 0xff); + } + + return hc; + } + + public static int hashCode(Object[] data) + { + if (data == null) + { + return 0; + } + + int i = data.length; + int hc = i + 1; + + while (--i >= 0) + { + hc *= 257; + hc ^= data[i].hashCode(); + } + + return hc; + } + + public static byte[] clone(byte[] data) + { + if (data == null) + { + return null; + } + byte[] copy = new byte[data.length]; + + System.arraycopy(data, 0, copy, 0, data.length); + + return copy; + } + + public static byte[] clone(byte[] data, byte[] existing) + { + if (data == null) + { + return null; + } + if ((existing == null) || (existing.length != data.length)) + { + return clone(data); + } + System.arraycopy(data, 0, existing, 0, existing.length); + return existing; + } + + public static byte[][] clone(byte[][] data) + { + if (data == null) + { + return null; + } + + byte[][] copy = new byte[data.length][]; + + for (int i = 0; i != copy.length; i++) + { + copy[i] = clone(data[i]); + } + + return copy; + } + + public static byte[][][] clone(byte[][][] data) + { + if (data == null) + { + return null; + } + + byte[][][] copy = new byte[data.length][][]; + + for (int i = 0; i != copy.length; i++) + { + copy[i] = clone(data[i]); + } + + return copy; + } + + public static int[] clone(int[] data) + { + if (data == null) + { + return null; + } + int[] copy = new int[data.length]; + + System.arraycopy(data, 0, copy, 0, data.length); + + return copy; + } + + public static long[] clone(long[] data) + { + if (data == null) + { + return null; + } + long[] copy = new long[data.length]; + + System.arraycopy(data, 0, copy, 0, data.length); + + return copy; + } + + public static long[] clone(long[] data, long[] existing) + { + if (data == null) + { + return null; + } + if ((existing == null) || (existing.length != data.length)) + { + return clone(data); + } + System.arraycopy(data, 0, existing, 0, existing.length); + return existing; + } + + public static short[] clone(short[] data) + { + if (data == null) + { + return null; + } + short[] copy = new short[data.length]; + + System.arraycopy(data, 0, copy, 0, data.length); + + return copy; + } + + public static BigInteger[] clone(BigInteger[] data) + { + if (data == null) + { + return null; + } + BigInteger[] copy = new BigInteger[data.length]; + + System.arraycopy(data, 0, copy, 0, data.length); + + return copy; + } + + public static byte[] copyOf(byte[] data, int newLength) + { + byte[] tmp = new byte[newLength]; + + if (newLength < data.length) + { + System.arraycopy(data, 0, tmp, 0, newLength); + } + else + { + System.arraycopy(data, 0, tmp, 0, data.length); + } + + return tmp; + } + + public static char[] copyOf(char[] data, int newLength) + { + char[] tmp = new char[newLength]; + + if (newLength < data.length) + { + System.arraycopy(data, 0, tmp, 0, newLength); + } + else + { + System.arraycopy(data, 0, tmp, 0, data.length); + } + + return tmp; + } + + public static int[] copyOf(int[] data, int newLength) + { + int[] tmp = new int[newLength]; + + if (newLength < data.length) + { + System.arraycopy(data, 0, tmp, 0, newLength); + } + else + { + System.arraycopy(data, 0, tmp, 0, data.length); + } + + return tmp; + } + + public static long[] copyOf(long[] data, int newLength) + { + long[] tmp = new long[newLength]; + + if (newLength < data.length) + { + System.arraycopy(data, 0, tmp, 0, newLength); + } + else + { + System.arraycopy(data, 0, tmp, 0, data.length); + } + + return tmp; + } + + public static BigInteger[] copyOf(BigInteger[] data, int newLength) + { + BigInteger[] tmp = new BigInteger[newLength]; + + if (newLength < data.length) + { + System.arraycopy(data, 0, tmp, 0, newLength); + } + else + { + System.arraycopy(data, 0, tmp, 0, data.length); + } + + return tmp; + } + + /** + * Make a copy of a range of bytes from the passed in data array. The range can + * extend beyond the end of the input array, in which case the return array will + * be padded with zeroes. + * + * @param data the array from which the data is to be copied. + * @param from the start index at which the copying should take place. + * @param to the final index of the range (exclusive). + * + * @return a new byte array containing the range given. + */ + public static byte[] copyOfRange(byte[] data, int from, int to) + { + int newLength = getLength(from, to); + + byte[] tmp = new byte[newLength]; + + if (data.length - from < newLength) + { + System.arraycopy(data, from, tmp, 0, data.length - from); + } + else + { + System.arraycopy(data, from, tmp, 0, newLength); + } + + return tmp; + } + + public static int[] copyOfRange(int[] data, int from, int to) + { + int newLength = getLength(from, to); + + int[] tmp = new int[newLength]; + + if (data.length - from < newLength) + { + System.arraycopy(data, from, tmp, 0, data.length - from); + } + else + { + System.arraycopy(data, from, tmp, 0, newLength); + } + + return tmp; + } + + public static long[] copyOfRange(long[] data, int from, int to) + { + int newLength = getLength(from, to); + + long[] tmp = new long[newLength]; + + if (data.length - from < newLength) + { + System.arraycopy(data, from, tmp, 0, data.length - from); + } + else + { + System.arraycopy(data, from, tmp, 0, newLength); + } + + return tmp; + } + + public static BigInteger[] copyOfRange(BigInteger[] data, int from, int to) + { + int newLength = getLength(from, to); + + BigInteger[] tmp = new BigInteger[newLength]; + + if (data.length - from < newLength) + { + System.arraycopy(data, from, tmp, 0, data.length - from); + } + else + { + System.arraycopy(data, from, tmp, 0, newLength); + } + + return tmp; + } + + private static int getLength(int from, int to) + { + int newLength = to - from; + if (newLength < 0) + { + StringBuffer sb = new StringBuffer(from); + sb.append(" > ").append(to); + throw new IllegalArgumentException(sb.toString()); + } + return newLength; + } + + public static byte[] append(byte[] a, byte b) + { + if (a == null) + { + return new byte[]{ b }; + } + + int length = a.length; + byte[] result = new byte[length + 1]; + System.arraycopy(a, 0, result, 0, length); + result[length] = b; + return result; + } + + public static short[] append(short[] a, short b) + { + if (a == null) + { + return new short[]{ b }; + } + + int length = a.length; + short[] result = new short[length + 1]; + System.arraycopy(a, 0, result, 0, length); + result[length] = b; + return result; + } + + public static int[] append(int[] a, int b) + { + if (a == null) + { + return new int[]{ b }; + } + + int length = a.length; + int[] result = new int[length + 1]; + System.arraycopy(a, 0, result, 0, length); + result[length] = b; + return result; + } + + public static byte[] concatenate(byte[] a, byte[] b) + { + if (a != null && b != null) + { + byte[] rv = new byte[a.length + b.length]; + + System.arraycopy(a, 0, rv, 0, a.length); + System.arraycopy(b, 0, rv, a.length, b.length); + + return rv; + } + else if (b != null) + { + return clone(b); + } + else + { + return clone(a); + } + } + + public static byte[] concatenate(byte[] a, byte[] b, byte[] c) + { + if (a != null && b != null && c != null) + { + byte[] rv = new byte[a.length + b.length + c.length]; + + System.arraycopy(a, 0, rv, 0, a.length); + System.arraycopy(b, 0, rv, a.length, b.length); + System.arraycopy(c, 0, rv, a.length + b.length, c.length); + + return rv; + } + else if (b == null) + { + return concatenate(a, c); + } + else + { + return concatenate(a, b); + } + } + + public static byte[] concatenate(byte[] a, byte[] b, byte[] c, byte[] d) + { + if (a != null && b != null && c != null && d != null) + { + byte[] rv = new byte[a.length + b.length + c.length + d.length]; + + System.arraycopy(a, 0, rv, 0, a.length); + System.arraycopy(b, 0, rv, a.length, b.length); + System.arraycopy(c, 0, rv, a.length + b.length, c.length); + System.arraycopy(d, 0, rv, a.length + b.length + c.length, d.length); + + return rv; + } + else if (d == null) + { + return concatenate(a, b, c); + } + else if (c == null) + { + return concatenate(a, b, d); + } + else if (b == null) + { + return concatenate(a, c, d); + } + else + { + return concatenate(b, c, d); + } + } + + public static int[] concatenate(int[] a, int[] b) + { + if (a == null) + { + return clone(b); + } + if (b == null) + { + return clone(a); + } + + int[] c = new int[a.length + b.length]; + System.arraycopy(a, 0, c, 0, a.length); + System.arraycopy(b, 0, c, a.length, b.length); + return c; + } + + public static byte[] prepend(byte[] a, byte b) + { + if (a == null) + { + return new byte[]{ b }; + } + + int length = a.length; + byte[] result = new byte[length + 1]; + System.arraycopy(a, 0, result, 1, length); + result[0] = b; + return result; + } + + public static short[] prepend(short[] a, short b) + { + if (a == null) + { + return new short[]{ b }; + } + + int length = a.length; + short[] result = new short[length + 1]; + System.arraycopy(a, 0, result, 1, length); + result[0] = b; + return result; + } + + public static int[] prepend(int[] a, int b) + { + if (a == null) + { + return new int[]{ b }; + } + + int length = a.length; + int[] result = new int[length + 1]; + System.arraycopy(a, 0, result, 1, length); + result[0] = b; + return result; + } + + public static byte[] reverse(byte[] a) + { + if (a == null) + { + return null; + } + + int p1 = 0, p2 = a.length; + byte[] result = new byte[p2]; + + while (--p2 >= 0) + { + result[p2] = a[p1++]; + } + + return result; + } +} diff --git a/core/src/main/java/org/spongycastle/util/BigIntegers.java b/core/src/main/java/org/spongycastle/util/BigIntegers.java new file mode 100644 index 00000000..93c11347 --- /dev/null +++ b/core/src/main/java/org/spongycastle/util/BigIntegers.java @@ -0,0 +1,121 @@ +package org.spongycastle.util; + +import java.math.BigInteger; +import java.security.SecureRandom; + +/** + * BigInteger utilities. + */ +public final class BigIntegers +{ + private static final int MAX_ITERATIONS = 1000; + private static final BigInteger ZERO = BigInteger.valueOf(0); + + /** + * Return the passed in value as an unsigned byte array. + * + * @param value value to be converted. + * @return a byte array without a leading zero byte if present in the signed encoding. + */ + public static byte[] asUnsignedByteArray( + BigInteger value) + { + byte[] bytes = value.toByteArray(); + + if (bytes[0] == 0) + { + byte[] tmp = new byte[bytes.length - 1]; + + System.arraycopy(bytes, 1, tmp, 0, tmp.length); + + return tmp; + } + + return bytes; + } + + /** + * Return the passed in value as an unsigned byte array. + * + * @param value value to be converted. + * @return a byte array without a leading zero byte if present in the signed encoding. + */ + public static byte[] asUnsignedByteArray(int length, BigInteger value) + { + byte[] bytes = value.toByteArray(); + if (bytes.length == length) + { + return bytes; + } + + int start = bytes[0] == 0 ? 1 : 0; + int count = bytes.length - start; + + if (count > length) + { + throw new IllegalArgumentException("standard length exceeded for value"); + } + + byte[] tmp = new byte[length]; + System.arraycopy(bytes, start, tmp, tmp.length - count, count); + return tmp; + } + + /** + * Return a random BigInteger not less than 'min' and not greater than 'max' + * + * @param min the least value that may be generated + * @param max the greatest value that may be generated + * @param random the source of randomness + * @return a random BigInteger value in the range [min,max] + */ + public static BigInteger createRandomInRange( + BigInteger min, + BigInteger max, + SecureRandom random) + { + int cmp = min.compareTo(max); + if (cmp >= 0) + { + if (cmp > 0) + { + throw new IllegalArgumentException("'min' may not be greater than 'max'"); + } + + return min; + } + + if (min.bitLength() > max.bitLength() / 2) + { + return createRandomInRange(ZERO, max.subtract(min), random).add(min); + } + + for (int i = 0; i < MAX_ITERATIONS; ++i) + { + BigInteger x = new BigInteger(max.bitLength(), random); + if (x.compareTo(min) >= 0 && x.compareTo(max) <= 0) + { + return x; + } + } + + // fall back to a faster (restricted) method + return new BigInteger(max.subtract(min).bitLength() - 1, random).add(min); + } + + public static BigInteger fromUnsignedByteArray(byte[] buf) + { + return new BigInteger(1, buf); + } + + public static BigInteger fromUnsignedByteArray(byte[] buf, int off, int length) + { + byte[] mag = buf; + if (off != 0 || length != buf.length) + { + mag = new byte[length]; + System.arraycopy(buf, off, mag, 0, length); + } + return new BigInteger(1, mag); + } +} diff --git a/core/src/main/java/org/spongycastle/util/CollectionStore.java b/core/src/main/java/org/spongycastle/util/CollectionStore.java new file mode 100644 index 00000000..fa4c9fe5 --- /dev/null +++ b/core/src/main/java/org/spongycastle/util/CollectionStore.java @@ -0,0 +1,57 @@ +package org.spongycastle.util; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Iterator; +import java.util.List; + +/** + * A simple collection backed store. + */ +public class CollectionStore + implements Store +{ + private Collection _local; + + /** + * Basic constructor. + * + * @param collection - initial contents for the store, this is copied. + */ + public CollectionStore( + Collection collection) + { + _local = new ArrayList(collection); + } + + /** + * Return the matches in the collection for the passed in selector. + * + * @param selector the selector to match against. + * @return a possibly empty collection of matching objects. + */ + public Collection getMatches(Selector selector) + { + if (selector == null) + { + return new ArrayList(_local); + } + else + { + List col = new ArrayList(); + Iterator iter = _local.iterator(); + + while (iter.hasNext()) + { + Object obj = iter.next(); + + if (selector.match(obj)) + { + col.add(obj); + } + } + + return col; + } + } +} diff --git a/core/src/main/java/org/spongycastle/util/IPAddress.java b/core/src/main/java/org/spongycastle/util/IPAddress.java new file mode 100644 index 00000000..14c49cad --- /dev/null +++ b/core/src/main/java/org/spongycastle/util/IPAddress.java @@ -0,0 +1,188 @@ +package org.spongycastle.util; + +public class IPAddress +{ + /** + * Validate the given IPv4 or IPv6 address. + * + * @param address the IP address as a String. + * + * @return true if a valid address, false otherwise + */ + public static boolean isValid( + String address) + { + return isValidIPv4(address) || isValidIPv6(address); + } + + /** + * Validate the given IPv4 or IPv6 address and netmask. + * + * @param address the IP address as a String. + * + * @return true if a valid address with netmask, false otherwise + */ + public static boolean isValidWithNetMask( + String address) + { + return isValidIPv4WithNetmask(address) || isValidIPv6WithNetmask(address); + } + + /** + * Validate the given IPv4 address. + * + * @param address the IP address as a String. + * + * @return true if a valid IPv4 address, false otherwise + */ + public static boolean isValidIPv4( + String address) + { + if (address.length() == 0) + { + return false; + } + + int octet; + int octets = 0; + + String temp = address+"."; + + int pos; + int start = 0; + while (start < temp.length() + && (pos = temp.indexOf('.', start)) > start) + { + if (octets == 4) + { + return false; + } + try + { + octet = Integer.parseInt(temp.substring(start, pos)); + } + catch (NumberFormatException ex) + { + return false; + } + if (octet < 0 || octet > 255) + { + return false; + } + start = pos + 1; + octets++; + } + + return octets == 4; + } + + public static boolean isValidIPv4WithNetmask( + String address) + { + int index = address.indexOf("/"); + String mask = address.substring(index + 1); + + return (index > 0) && isValidIPv4(address.substring(0, index)) + && (isValidIPv4(mask) || isMaskValue(mask, 32)); + } + + public static boolean isValidIPv6WithNetmask( + String address) + { + int index = address.indexOf("/"); + String mask = address.substring(index + 1); + + return (index > 0) && (isValidIPv6(address.substring(0, index)) + && (isValidIPv6(mask) || isMaskValue(mask, 128))); + } + + private static boolean isMaskValue(String component, int size) + { + try + { + int value = Integer.parseInt(component); + + return value >= 0 && value <= size; + } + catch (NumberFormatException e) + { + return false; + } + } + + /** + * Validate the given IPv6 address. + * + * @param address the IP address as a String. + * + * @return true if a valid IPv4 address, false otherwise + */ + public static boolean isValidIPv6( + String address) + { + if (address.length() == 0) + { + return false; + } + + int octet; + int octets = 0; + + String temp = address + ":"; + boolean doubleColonFound = false; + int pos; + int start = 0; + while (start < temp.length() + && (pos = temp.indexOf(':', start)) >= start) + { + if (octets == 8) + { + return false; + } + + if (start != pos) + { + String value = temp.substring(start, pos); + + if (pos == (temp.length() - 1) && value.indexOf('.') > 0) + { + if (!isValidIPv4(value)) + { + return false; + } + + octets++; // add an extra one as address covers 2 words. + } + else + { + try + { + octet = Integer.parseInt(temp.substring(start, pos), 16); + } + catch (NumberFormatException ex) + { + return false; + } + if (octet < 0 || octet > 0xffff) + { + return false; + } + } + } + else + { + if (pos != 1 && pos != temp.length() - 1 && doubleColonFound) + { + return false; + } + doubleColonFound = true; + } + start = pos + 1; + octets++; + } + + return octets == 8 || doubleColonFound; + } +} + + diff --git a/core/src/main/java/org/spongycastle/util/Integers.java b/core/src/main/java/org/spongycastle/util/Integers.java new file mode 100644 index 00000000..e212c9fc --- /dev/null +++ b/core/src/main/java/org/spongycastle/util/Integers.java @@ -0,0 +1,19 @@ +package org.spongycastle.util; + +public class Integers +{ + public static int rotateLeft(int i, int distance) + { + return Integer.rotateLeft(i, distance); + } + + public static int rotateRight(int i, int distance) + { + return Integer.rotateRight(i, distance); + } + + public static Integer valueOf(int value) + { + return Integer.valueOf(value); + } +} diff --git a/core/src/main/java/org/spongycastle/util/Memoable.java b/core/src/main/java/org/spongycastle/util/Memoable.java new file mode 100644 index 00000000..5680c857 --- /dev/null +++ b/core/src/main/java/org/spongycastle/util/Memoable.java @@ -0,0 +1,23 @@ +package org.spongycastle.util; + +public interface Memoable +{ + /** + * Produce a copy of this object with its configuration and in its current state. + * <p> + * The returned object may be used simply to store the state, or may be used as a similar object + * starting from the copied state. + */ + public Memoable copy(); + + /** + * Restore a copied object state into this object. + * <p> + * Implementations of this method <em>should</em> try to avoid or minimise memory allocation to perform the reset. + * + * @param other an object originally {@link #copy() copied} from an object of the same type as this instance. + * @throws ClassCastException if the provided object is not of the correct type. + * @throws MemoableResetException if the <b>other</b> parameter is in some other way invalid. + */ + public void reset(Memoable other); +} diff --git a/core/src/main/java/org/spongycastle/util/MemoableResetException.java b/core/src/main/java/org/spongycastle/util/MemoableResetException.java new file mode 100644 index 00000000..6d579ed5 --- /dev/null +++ b/core/src/main/java/org/spongycastle/util/MemoableResetException.java @@ -0,0 +1,22 @@ +package org.spongycastle.util; + +/** + * Exception to be thrown on a failure to reset an object implementing Memoable. + * <p> + * The exception extends ClassCastException to enable users to have a single handling case, + * only introducing specific handling of this one if required. + * </p> + */ +public class MemoableResetException + extends ClassCastException +{ + /** + * Basic Constructor. + * + * @param msg message to be associated with this exception. + */ + public MemoableResetException(String msg) + { + super(msg); + } +} diff --git a/core/src/main/java/org/spongycastle/util/Pack.java b/core/src/main/java/org/spongycastle/util/Pack.java new file mode 100644 index 00000000..bad72850 --- /dev/null +++ b/core/src/main/java/org/spongycastle/util/Pack.java @@ -0,0 +1,201 @@ +package org.spongycastle.util; + +public abstract class Pack +{ + public static int bigEndianToInt(byte[] bs, int off) + { + int n = bs[ off] << 24; + n |= (bs[++off] & 0xff) << 16; + n |= (bs[++off] & 0xff) << 8; + n |= (bs[++off] & 0xff); + return n; + } + + public static void bigEndianToInt(byte[] bs, int off, int[] ns) + { + for (int i = 0; i < ns.length; ++i) + { + ns[i] = bigEndianToInt(bs, off); + off += 4; + } + } + + public static byte[] intToBigEndian(int n) + { + byte[] bs = new byte[4]; + intToBigEndian(n, bs, 0); + return bs; + } + + public static void intToBigEndian(int n, byte[] bs, int off) + { + bs[ off] = (byte)(n >>> 24); + bs[++off] = (byte)(n >>> 16); + bs[++off] = (byte)(n >>> 8); + bs[++off] = (byte)(n ); + } + + public static byte[] intToBigEndian(int[] ns) + { + byte[] bs = new byte[4 * ns.length]; + intToBigEndian(ns, bs, 0); + return bs; + } + + public static void intToBigEndian(int[] ns, byte[] bs, int off) + { + for (int i = 0; i < ns.length; ++i) + { + intToBigEndian(ns[i], bs, off); + off += 4; + } + } + + public static long bigEndianToLong(byte[] bs, int off) + { + int hi = bigEndianToInt(bs, off); + int lo = bigEndianToInt(bs, off + 4); + return ((long)(hi & 0xffffffffL) << 32) | (long)(lo & 0xffffffffL); + } + + public static void bigEndianToLong(byte[] bs, int off, long[] ns) + { + for (int i = 0; i < ns.length; ++i) + { + ns[i] = bigEndianToLong(bs, off); + off += 8; + } + } + + public static byte[] longToBigEndian(long n) + { + byte[] bs = new byte[8]; + longToBigEndian(n, bs, 0); + return bs; + } + + public static void longToBigEndian(long n, byte[] bs, int off) + { + intToBigEndian((int)(n >>> 32), bs, off); + intToBigEndian((int)(n & 0xffffffffL), bs, off + 4); + } + + public static byte[] longToBigEndian(long[] ns) + { + byte[] bs = new byte[8 * ns.length]; + longToBigEndian(ns, bs, 0); + return bs; + } + + public static void longToBigEndian(long[] ns, byte[] bs, int off) + { + for (int i = 0; i < ns.length; ++i) + { + longToBigEndian(ns[i], bs, off); + off += 8; + } + } + + public static int littleEndianToInt(byte[] bs, int off) + { + int n = bs[ off] & 0xff; + n |= (bs[++off] & 0xff) << 8; + n |= (bs[++off] & 0xff) << 16; + n |= bs[++off] << 24; + return n; + } + + public static void littleEndianToInt(byte[] bs, int off, int[] ns) + { + for (int i = 0; i < ns.length; ++i) + { + ns[i] = littleEndianToInt(bs, off); + off += 4; + } + } + + public static void littleEndianToInt(byte[] bs, int bOff, int[] ns, int nOff, int count) + { + for (int i = 0; i < count; ++i) + { + ns[nOff + i] = littleEndianToInt(bs, bOff); + bOff += 4; + } + } + + public static byte[] intToLittleEndian(int n) + { + byte[] bs = new byte[4]; + intToLittleEndian(n, bs, 0); + return bs; + } + + public static void intToLittleEndian(int n, byte[] bs, int off) + { + bs[ off] = (byte)(n ); + bs[++off] = (byte)(n >>> 8); + bs[++off] = (byte)(n >>> 16); + bs[++off] = (byte)(n >>> 24); + } + + public static byte[] intToLittleEndian(int[] ns) + { + byte[] bs = new byte[4 * ns.length]; + intToLittleEndian(ns, bs, 0); + return bs; + } + + public static void intToLittleEndian(int[] ns, byte[] bs, int off) + { + for (int i = 0; i < ns.length; ++i) + { + intToLittleEndian(ns[i], bs, off); + off += 4; + } + } + + public static long littleEndianToLong(byte[] bs, int off) + { + int lo = littleEndianToInt(bs, off); + int hi = littleEndianToInt(bs, off + 4); + return ((long)(hi & 0xffffffffL) << 32) | (long)(lo & 0xffffffffL); + } + + public static void littleEndianToLong(byte[] bs, int off, long[] ns) + { + for (int i = 0; i < ns.length; ++i) + { + ns[i] = littleEndianToLong(bs, off); + off += 8; + } + } + + public static byte[] longToLittleEndian(long n) + { + byte[] bs = new byte[8]; + longToLittleEndian(n, bs, 0); + return bs; + } + + public static void longToLittleEndian(long n, byte[] bs, int off) + { + intToLittleEndian((int)(n & 0xffffffffL), bs, off); + intToLittleEndian((int)(n >>> 32), bs, off + 4); + } + + public static byte[] longToLittleEndian(long[] ns) + { + byte[] bs = new byte[8 * ns.length]; + longToLittleEndian(ns, bs, 0); + return bs; + } + + public static void longToLittleEndian(long[] ns, byte[] bs, int off) + { + for (int i = 0; i < ns.length; ++i) + { + longToLittleEndian(ns[i], bs, off); + off += 8; + } + } +} diff --git a/core/src/main/java/org/spongycastle/util/Selector.java b/core/src/main/java/org/spongycastle/util/Selector.java new file mode 100644 index 00000000..6ed23f2a --- /dev/null +++ b/core/src/main/java/org/spongycastle/util/Selector.java @@ -0,0 +1,9 @@ +package org.spongycastle.util; + +public interface Selector + extends Cloneable +{ + boolean match(Object obj); + + Object clone(); +} diff --git a/core/src/main/java/org/spongycastle/util/Shorts.java b/core/src/main/java/org/spongycastle/util/Shorts.java new file mode 100644 index 00000000..0faaeb0d --- /dev/null +++ b/core/src/main/java/org/spongycastle/util/Shorts.java @@ -0,0 +1,9 @@ +package org.spongycastle.util; + +public class Shorts +{ + public static Short valueOf(short value) + { + return Short.valueOf(value); + } +} diff --git a/core/src/main/java/org/spongycastle/util/Store.java b/core/src/main/java/org/spongycastle/util/Store.java new file mode 100644 index 00000000..b19f6947 --- /dev/null +++ b/core/src/main/java/org/spongycastle/util/Store.java @@ -0,0 +1,9 @@ +package org.spongycastle.util; + +import java.util.Collection; + +public interface Store +{ + Collection getMatches(Selector selector) + throws StoreException; +} diff --git a/core/src/main/java/org/spongycastle/util/StoreException.java b/core/src/main/java/org/spongycastle/util/StoreException.java new file mode 100644 index 00000000..a02eee26 --- /dev/null +++ b/core/src/main/java/org/spongycastle/util/StoreException.java @@ -0,0 +1,18 @@ +package org.spongycastle.util; + +public class StoreException + extends RuntimeException +{ + private Throwable _e; + + public StoreException(String s, Throwable e) + { + super(s); + _e = e; + } + + public Throwable getCause() + { + return _e; + } +} diff --git a/core/src/main/java/org/spongycastle/util/StreamParser.java b/core/src/main/java/org/spongycastle/util/StreamParser.java new file mode 100644 index 00000000..ba962824 --- /dev/null +++ b/core/src/main/java/org/spongycastle/util/StreamParser.java @@ -0,0 +1,10 @@ +package org.spongycastle.util; + +import java.util.Collection; + +public interface StreamParser +{ + Object read() throws StreamParsingException; + + Collection readAll() throws StreamParsingException; +} diff --git a/core/src/main/java/org/spongycastle/util/StreamParsingException.java b/core/src/main/java/org/spongycastle/util/StreamParsingException.java new file mode 100644 index 00000000..081219d7 --- /dev/null +++ b/core/src/main/java/org/spongycastle/util/StreamParsingException.java @@ -0,0 +1,18 @@ +package org.spongycastle.util; + +public class StreamParsingException + extends Exception +{ + Throwable _e; + + public StreamParsingException(String message, Throwable e) + { + super(message); + _e = e; + } + + public Throwable getCause() + { + return _e; + } +} diff --git a/core/src/main/java/org/spongycastle/util/Strings.java b/core/src/main/java/org/spongycastle/util/Strings.java new file mode 100644 index 00000000..7e269455 --- /dev/null +++ b/core/src/main/java/org/spongycastle/util/Strings.java @@ -0,0 +1,314 @@ +package org.spongycastle.util; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.util.Vector; + +public final class Strings +{ + public static String fromUTF8ByteArray(byte[] bytes) + { + int i = 0; + int length = 0; + + while (i < bytes.length) + { + length++; + if ((bytes[i] & 0xf0) == 0xf0) + { + // surrogate pair + length++; + i += 4; + } + else if ((bytes[i] & 0xe0) == 0xe0) + { + i += 3; + } + else if ((bytes[i] & 0xc0) == 0xc0) + { + i += 2; + } + else + { + i += 1; + } + } + + char[] cs = new char[length]; + + i = 0; + length = 0; + + while (i < bytes.length) + { + char ch; + + if ((bytes[i] & 0xf0) == 0xf0) + { + int codePoint = ((bytes[i] & 0x03) << 18) | ((bytes[i+1] & 0x3F) << 12) | ((bytes[i+2] & 0x3F) << 6) | (bytes[i+3] & 0x3F); + int U = codePoint - 0x10000; + char W1 = (char)(0xD800 | (U >> 10)); + char W2 = (char)(0xDC00 | (U & 0x3FF)); + cs[length++] = W1; + ch = W2; + i += 4; + } + else if ((bytes[i] & 0xe0) == 0xe0) + { + ch = (char)(((bytes[i] & 0x0f) << 12) + | ((bytes[i + 1] & 0x3f) << 6) | (bytes[i + 2] & 0x3f)); + i += 3; + } + else if ((bytes[i] & 0xd0) == 0xd0) + { + ch = (char)(((bytes[i] & 0x1f) << 6) | (bytes[i + 1] & 0x3f)); + i += 2; + } + else if ((bytes[i] & 0xc0) == 0xc0) + { + ch = (char)(((bytes[i] & 0x1f) << 6) | (bytes[i + 1] & 0x3f)); + i += 2; + } + else + { + ch = (char)(bytes[i] & 0xff); + i += 1; + } + + cs[length++] = ch; + } + + return new String(cs); + } + + public static byte[] toUTF8ByteArray(String string) + { + return toUTF8ByteArray(string.toCharArray()); + } + + public static byte[] toUTF8ByteArray(char[] string) + { + ByteArrayOutputStream bOut = new ByteArrayOutputStream(); + + try + { + toUTF8ByteArray(string, bOut); + } + catch (IOException e) + { + throw new IllegalStateException("cannot encode string to byte array!"); + } + + return bOut.toByteArray(); + } + + public static void toUTF8ByteArray(char[] string, OutputStream sOut) + throws IOException + { + char[] c = string; + int i = 0; + + while (i < c.length) + { + char ch = c[i]; + + if (ch < 0x0080) + { + sOut.write(ch); + } + else if (ch < 0x0800) + { + sOut.write(0xc0 | (ch >> 6)); + sOut.write(0x80 | (ch & 0x3f)); + } + // surrogate pair + else if (ch >= 0xD800 && ch <= 0xDFFF) + { + // in error - can only happen, if the Java String class has a + // bug. + if (i + 1 >= c.length) + { + throw new IllegalStateException("invalid UTF-16 codepoint"); + } + char W1 = ch; + ch = c[++i]; + char W2 = ch; + // in error - can only happen, if the Java String class has a + // bug. + if (W1 > 0xDBFF) + { + throw new IllegalStateException("invalid UTF-16 codepoint"); + } + int codePoint = (((W1 & 0x03FF) << 10) | (W2 & 0x03FF)) + 0x10000; + sOut.write(0xf0 | (codePoint >> 18)); + sOut.write(0x80 | ((codePoint >> 12) & 0x3F)); + sOut.write(0x80 | ((codePoint >> 6) & 0x3F)); + sOut.write(0x80 | (codePoint & 0x3F)); + } + else + { + sOut.write(0xe0 | (ch >> 12)); + sOut.write(0x80 | ((ch >> 6) & 0x3F)); + sOut.write(0x80 | (ch & 0x3F)); + } + + i++; + } + } + + /** + * A locale independent version of toUpperCase. + * + * @param string input to be converted + * @return a US Ascii uppercase version + */ + public static String toUpperCase(String string) + { + boolean changed = false; + char[] chars = string.toCharArray(); + + for (int i = 0; i != chars.length; i++) + { + char ch = chars[i]; + if ('a' <= ch && 'z' >= ch) + { + changed = true; + chars[i] = (char)(ch - 'a' + 'A'); + } + } + + if (changed) + { + return new String(chars); + } + + return string; + } + + /** + * A locale independent version of toLowerCase. + * + * @param string input to be converted + * @return a US ASCII lowercase version + */ + public static String toLowerCase(String string) + { + boolean changed = false; + char[] chars = string.toCharArray(); + + for (int i = 0; i != chars.length; i++) + { + char ch = chars[i]; + if ('A' <= ch && 'Z' >= ch) + { + changed = true; + chars[i] = (char)(ch - 'A' + 'a'); + } + } + + if (changed) + { + return new String(chars); + } + + return string; + } + + public static byte[] toByteArray(char[] chars) + { + byte[] bytes = new byte[chars.length]; + + for (int i = 0; i != bytes.length; i++) + { + bytes[i] = (byte)chars[i]; + } + + return bytes; + } + + public static byte[] toByteArray(String string) + { + byte[] bytes = new byte[string.length()]; + + for (int i = 0; i != bytes.length; i++) + { + char ch = string.charAt(i); + + bytes[i] = (byte)ch; + } + + return bytes; + } + + public static int toByteArray(String s, byte[] buf, int off) + { + int count = s.length(); + for (int i = 0; i < count; ++i) + { + char c = s.charAt(i); + buf[off + i] = (byte)c; + } + return count; + } + + /** + * Convert an array of 8 bit characters into a string. + * + * @param bytes 8 bit characters. + * @return resulting String. + */ + public static String fromByteArray(byte[] bytes) + { + return new String(asCharArray(bytes)); + } + + /** + * Do a simple conversion of an array of 8 bit characters into a string. + * + * @param bytes 8 bit characters. + * @return resulting String. + */ + public static char[] asCharArray(byte[] bytes) + { + char[] chars = new char[bytes.length]; + + for (int i = 0; i != chars.length; i++) + { + chars[i] = (char)(bytes[i] & 0xff); + } + + return chars; + } + + public static String[] split(String input, char delimiter) + { + Vector v = new Vector(); + boolean moreTokens = true; + String subString; + + while (moreTokens) + { + int tokenLocation = input.indexOf(delimiter); + if (tokenLocation > 0) + { + subString = input.substring(0, tokenLocation); + v.addElement(subString); + input = input.substring(tokenLocation + 1); + } + else + { + moreTokens = false; + v.addElement(input); + } + } + + String[] res = new String[v.size()]; + + for (int i = 0; i != res.length; i++) + { + res[i] = (String)v.elementAt(i); + } + return res; + } +} diff --git a/core/src/main/java/org/spongycastle/util/Times.java b/core/src/main/java/org/spongycastle/util/Times.java new file mode 100644 index 00000000..71d49e31 --- /dev/null +++ b/core/src/main/java/org/spongycastle/util/Times.java @@ -0,0 +1,9 @@ +package org.spongycastle.util; + +public final class Times +{ + public static long nanoTime() + { + return System.nanoTime(); + } +} diff --git a/core/src/main/java/org/spongycastle/util/encoders/Base64.java b/core/src/main/java/org/spongycastle/util/encoders/Base64.java new file mode 100644 index 00000000..a2b5aff8 --- /dev/null +++ b/core/src/main/java/org/spongycastle/util/encoders/Base64.java @@ -0,0 +1,154 @@ +package org.spongycastle.util.encoders; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.OutputStream; + +import org.spongycastle.util.Strings; + +/** + * Utility class for converting Base64 data to bytes and back again. + */ +public class Base64 +{ + private static final Encoder encoder = new Base64Encoder(); + + public static String toBase64String( + byte[] data) + { + return toBase64String(data, 0, data.length); + } + + public static String toBase64String( + byte[] data, + int off, + int length) + { + byte[] encoded = encode(data, off, length); + return Strings.fromByteArray(encoded); + } + + /** + * encode the input data producing a base 64 encoded byte array. + * + * @return a byte array containing the base 64 encoded data. + */ + public static byte[] encode( + byte[] data) + { + return encode(data, 0, data.length); + } + + /** + * encode the input data producing a base 64 encoded byte array. + * + * @return a byte array containing the base 64 encoded data. + */ + public static byte[] encode( + byte[] data, + int off, + int length) + { + int len = (length + 2) / 3 * 4; + ByteArrayOutputStream bOut = new ByteArrayOutputStream(len); + + try + { + encoder.encode(data, off, length, bOut); + } + catch (Exception e) + { + throw new EncoderException("exception encoding base64 string: " + e.getMessage(), e); + } + + return bOut.toByteArray(); + } + + /** + * Encode the byte data to base 64 writing it to the given output stream. + * + * @return the number of bytes produced. + */ + public static int encode( + byte[] data, + OutputStream out) + throws IOException + { + return encoder.encode(data, 0, data.length, out); + } + + /** + * Encode the byte data to base 64 writing it to the given output stream. + * + * @return the number of bytes produced. + */ + public static int encode( + byte[] data, + int off, + int length, + OutputStream out) + throws IOException + { + return encoder.encode(data, off, length, out); + } + + /** + * decode the base 64 encoded input data. It is assumed the input data is valid. + * + * @return a byte array representing the decoded data. + */ + public static byte[] decode( + byte[] data) + { + int len = data.length / 4 * 3; + ByteArrayOutputStream bOut = new ByteArrayOutputStream(len); + + try + { + encoder.decode(data, 0, data.length, bOut); + } + catch (Exception e) + { + throw new DecoderException("unable to decode base64 data: " + e.getMessage(), e); + } + + return bOut.toByteArray(); + } + + /** + * decode the base 64 encoded String data - whitespace will be ignored. + * + * @return a byte array representing the decoded data. + */ + public static byte[] decode( + String data) + { + int len = data.length() / 4 * 3; + ByteArrayOutputStream bOut = new ByteArrayOutputStream(len); + + try + { + encoder.decode(data, bOut); + } + catch (Exception e) + { + throw new DecoderException("unable to decode base64 string: " + e.getMessage(), e); + } + + return bOut.toByteArray(); + } + + /** + * decode the base 64 encoded String data writing it to the given output stream, + * whitespace characters will be ignored. + * + * @return the number of bytes produced. + */ + public static int decode( + String data, + OutputStream out) + throws IOException + { + return encoder.decode(data, out); + } +} diff --git a/core/src/main/java/org/spongycastle/util/encoders/Base64Encoder.java b/core/src/main/java/org/spongycastle/util/encoders/Base64Encoder.java new file mode 100644 index 00000000..17bae1dd --- /dev/null +++ b/core/src/main/java/org/spongycastle/util/encoders/Base64Encoder.java @@ -0,0 +1,331 @@ +package org.spongycastle.util.encoders; + +import java.io.IOException; +import java.io.OutputStream; + +/** + * A streaming Base64 encoder. + */ +public class Base64Encoder + implements Encoder +{ + protected final byte[] encodingTable = + { + (byte)'A', (byte)'B', (byte)'C', (byte)'D', (byte)'E', (byte)'F', (byte)'G', + (byte)'H', (byte)'I', (byte)'J', (byte)'K', (byte)'L', (byte)'M', (byte)'N', + (byte)'O', (byte)'P', (byte)'Q', (byte)'R', (byte)'S', (byte)'T', (byte)'U', + (byte)'V', (byte)'W', (byte)'X', (byte)'Y', (byte)'Z', + (byte)'a', (byte)'b', (byte)'c', (byte)'d', (byte)'e', (byte)'f', (byte)'g', + (byte)'h', (byte)'i', (byte)'j', (byte)'k', (byte)'l', (byte)'m', (byte)'n', + (byte)'o', (byte)'p', (byte)'q', (byte)'r', (byte)'s', (byte)'t', (byte)'u', + (byte)'v', + (byte)'w', (byte)'x', (byte)'y', (byte)'z', + (byte)'0', (byte)'1', (byte)'2', (byte)'3', (byte)'4', (byte)'5', (byte)'6', + (byte)'7', (byte)'8', (byte)'9', + (byte)'+', (byte)'/' + }; + + protected byte padding = (byte)'='; + + /* + * set up the decoding table. + */ + protected final byte[] decodingTable = new byte[128]; + + protected void initialiseDecodingTable() + { + for (int i = 0; i < decodingTable.length; i++) + { + decodingTable[i] = (byte)0xff; + } + + for (int i = 0; i < encodingTable.length; i++) + { + decodingTable[encodingTable[i]] = (byte)i; + } + } + + public Base64Encoder() + { + initialiseDecodingTable(); + } + + /** + * encode the input data producing a base 64 output stream. + * + * @return the number of bytes produced. + */ + public int encode( + byte[] data, + int off, + int length, + OutputStream out) + throws IOException + { + int modulus = length % 3; + int dataLength = (length - modulus); + int a1, a2, a3; + + for (int i = off; i < off + dataLength; i += 3) + { + a1 = data[i] & 0xff; + a2 = data[i + 1] & 0xff; + a3 = data[i + 2] & 0xff; + + out.write(encodingTable[(a1 >>> 2) & 0x3f]); + out.write(encodingTable[((a1 << 4) | (a2 >>> 4)) & 0x3f]); + out.write(encodingTable[((a2 << 2) | (a3 >>> 6)) & 0x3f]); + out.write(encodingTable[a3 & 0x3f]); + } + + /* + * process the tail end. + */ + int b1, b2, b3; + int d1, d2; + + switch (modulus) + { + case 0: /* nothing left to do */ + break; + case 1: + d1 = data[off + dataLength] & 0xff; + b1 = (d1 >>> 2) & 0x3f; + b2 = (d1 << 4) & 0x3f; + + out.write(encodingTable[b1]); + out.write(encodingTable[b2]); + out.write(padding); + out.write(padding); + break; + case 2: + d1 = data[off + dataLength] & 0xff; + d2 = data[off + dataLength + 1] & 0xff; + + b1 = (d1 >>> 2) & 0x3f; + b2 = ((d1 << 4) | (d2 >>> 4)) & 0x3f; + b3 = (d2 << 2) & 0x3f; + + out.write(encodingTable[b1]); + out.write(encodingTable[b2]); + out.write(encodingTable[b3]); + out.write(padding); + break; + } + + return (dataLength / 3) * 4 + ((modulus == 0) ? 0 : 4); + } + + private boolean ignore( + char c) + { + return (c == '\n' || c =='\r' || c == '\t' || c == ' '); + } + + /** + * decode the base 64 encoded byte data writing it to the given output stream, + * whitespace characters will be ignored. + * + * @return the number of bytes produced. + */ + public int decode( + byte[] data, + int off, + int length, + OutputStream out) + throws IOException + { + byte b1, b2, b3, b4; + int outLen = 0; + + int end = off + length; + + while (end > off) + { + if (!ignore((char)data[end - 1])) + { + break; + } + + end--; + } + + int i = off; + int finish = end - 4; + + i = nextI(data, i, finish); + + while (i < finish) + { + b1 = decodingTable[data[i++]]; + + i = nextI(data, i, finish); + + b2 = decodingTable[data[i++]]; + + i = nextI(data, i, finish); + + b3 = decodingTable[data[i++]]; + + i = nextI(data, i, finish); + + b4 = decodingTable[data[i++]]; + + if ((b1 | b2 | b3 | b4) < 0) + { + throw new IOException("invalid characters encountered in base64 data"); + } + + out.write((b1 << 2) | (b2 >> 4)); + out.write((b2 << 4) | (b3 >> 2)); + out.write((b3 << 6) | b4); + + outLen += 3; + + i = nextI(data, i, finish); + } + + outLen += decodeLastBlock(out, (char)data[end - 4], (char)data[end - 3], (char)data[end - 2], (char)data[end - 1]); + + return outLen; + } + + private int nextI(byte[] data, int i, int finish) + { + while ((i < finish) && ignore((char)data[i])) + { + i++; + } + return i; + } + + /** + * decode the base 64 encoded String data writing it to the given output stream, + * whitespace characters will be ignored. + * + * @return the number of bytes produced. + */ + public int decode( + String data, + OutputStream out) + throws IOException + { + byte b1, b2, b3, b4; + int length = 0; + + int end = data.length(); + + while (end > 0) + { + if (!ignore(data.charAt(end - 1))) + { + break; + } + + end--; + } + + int i = 0; + int finish = end - 4; + + i = nextI(data, i, finish); + + while (i < finish) + { + b1 = decodingTable[data.charAt(i++)]; + + i = nextI(data, i, finish); + + b2 = decodingTable[data.charAt(i++)]; + + i = nextI(data, i, finish); + + b3 = decodingTable[data.charAt(i++)]; + + i = nextI(data, i, finish); + + b4 = decodingTable[data.charAt(i++)]; + + if ((b1 | b2 | b3 | b4) < 0) + { + throw new IOException("invalid characters encountered in base64 data"); + } + + out.write((b1 << 2) | (b2 >> 4)); + out.write((b2 << 4) | (b3 >> 2)); + out.write((b3 << 6) | b4); + + length += 3; + + i = nextI(data, i, finish); + } + + length += decodeLastBlock(out, data.charAt(end - 4), data.charAt(end - 3), data.charAt(end - 2), data.charAt(end - 1)); + + return length; + } + + private int decodeLastBlock(OutputStream out, char c1, char c2, char c3, char c4) + throws IOException + { + byte b1, b2, b3, b4; + + if (c3 == padding) + { + b1 = decodingTable[c1]; + b2 = decodingTable[c2]; + + if ((b1 | b2) < 0) + { + throw new IOException("invalid characters encountered at end of base64 data"); + } + + out.write((b1 << 2) | (b2 >> 4)); + + return 1; + } + else if (c4 == padding) + { + b1 = decodingTable[c1]; + b2 = decodingTable[c2]; + b3 = decodingTable[c3]; + + if ((b1 | b2 | b3) < 0) + { + throw new IOException("invalid characters encountered at end of base64 data"); + } + + out.write((b1 << 2) | (b2 >> 4)); + out.write((b2 << 4) | (b3 >> 2)); + + return 2; + } + else + { + b1 = decodingTable[c1]; + b2 = decodingTable[c2]; + b3 = decodingTable[c3]; + b4 = decodingTable[c4]; + + if ((b1 | b2 | b3 | b4) < 0) + { + throw new IOException("invalid characters encountered at end of base64 data"); + } + + out.write((b1 << 2) | (b2 >> 4)); + out.write((b2 << 4) | (b3 >> 2)); + out.write((b3 << 6) | b4); + + return 3; + } + } + + private int nextI(String data, int i, int finish) + { + while ((i < finish) && ignore(data.charAt(i))) + { + i++; + } + return i; + } +} diff --git a/core/src/main/java/org/spongycastle/util/encoders/BufferedDecoder.java b/core/src/main/java/org/spongycastle/util/encoders/BufferedDecoder.java new file mode 100644 index 00000000..31c702ae --- /dev/null +++ b/core/src/main/java/org/spongycastle/util/encoders/BufferedDecoder.java @@ -0,0 +1,96 @@ +package org.spongycastle.util.encoders; + + +/** + * A buffering class to allow translation from one format to another to + * be done in discrete chunks. + */ +public class BufferedDecoder +{ + protected byte[] buf; + protected int bufOff; + + protected Translator translator; + + /** + * @param translator the translator to use. + * @param bufSize amount of input to buffer for each chunk. + */ + public BufferedDecoder( + Translator translator, + int bufSize) + { + this.translator = translator; + + if ((bufSize % translator.getEncodedBlockSize()) != 0) + { + throw new IllegalArgumentException("buffer size not multiple of input block size"); + } + + buf = new byte[bufSize]; + bufOff = 0; + } + + public int processByte( + byte in, + byte[] out, + int outOff) + { + int resultLen = 0; + + buf[bufOff++] = in; + + if (bufOff == buf.length) + { + resultLen = translator.decode(buf, 0, buf.length, out, outOff); + bufOff = 0; + } + + return resultLen; + } + + public int processBytes( + byte[] in, + int inOff, + int len, + byte[] out, + int outOff) + { + if (len < 0) + { + throw new IllegalArgumentException("Can't have a negative input length!"); + } + + int resultLen = 0; + int gapLen = buf.length - bufOff; + + if (len > gapLen) + { + System.arraycopy(in, inOff, buf, bufOff, gapLen); + + resultLen += translator.decode(buf, 0, buf.length, out, outOff); + + bufOff = 0; + + len -= gapLen; + inOff += gapLen; + outOff += resultLen; + + int chunkSize = len - (len % buf.length); + + resultLen += translator.decode(in, inOff, chunkSize, out, outOff); + + len -= chunkSize; + inOff += chunkSize; + } + + if (len != 0) + { + System.arraycopy(in, inOff, buf, bufOff, len); + + bufOff += len; + } + + return resultLen; + } +} diff --git a/core/src/main/java/org/spongycastle/util/encoders/BufferedEncoder.java b/core/src/main/java/org/spongycastle/util/encoders/BufferedEncoder.java new file mode 100644 index 00000000..fbeb92b6 --- /dev/null +++ b/core/src/main/java/org/spongycastle/util/encoders/BufferedEncoder.java @@ -0,0 +1,96 @@ +package org.spongycastle.util.encoders; + + +/** + * A buffering class to allow translation from one format to another to + * be done in discrete chunks. + */ +public class BufferedEncoder +{ + protected byte[] buf; + protected int bufOff; + + protected Translator translator; + + /** + * @param translator the translator to use. + * @param bufSize amount of input to buffer for each chunk. + */ + public BufferedEncoder( + Translator translator, + int bufSize) + { + this.translator = translator; + + if ((bufSize % translator.getEncodedBlockSize()) != 0) + { + throw new IllegalArgumentException("buffer size not multiple of input block size"); + } + + buf = new byte[bufSize]; + bufOff = 0; + } + + public int processByte( + byte in, + byte[] out, + int outOff) + { + int resultLen = 0; + + buf[bufOff++] = in; + + if (bufOff == buf.length) + { + resultLen = translator.encode(buf, 0, buf.length, out, outOff); + bufOff = 0; + } + + return resultLen; + } + + public int processBytes( + byte[] in, + int inOff, + int len, + byte[] out, + int outOff) + { + if (len < 0) + { + throw new IllegalArgumentException("Can't have a negative input length!"); + } + + int resultLen = 0; + int gapLen = buf.length - bufOff; + + if (len > gapLen) + { + System.arraycopy(in, inOff, buf, bufOff, gapLen); + + resultLen += translator.encode(buf, 0, buf.length, out, outOff); + + bufOff = 0; + + len -= gapLen; + inOff += gapLen; + outOff += resultLen; + + int chunkSize = len - (len % buf.length); + + resultLen += translator.encode(in, inOff, chunkSize, out, outOff); + + len -= chunkSize; + inOff += chunkSize; + } + + if (len != 0) + { + System.arraycopy(in, inOff, buf, bufOff, len); + + bufOff += len; + } + + return resultLen; + } +} diff --git a/core/src/main/java/org/spongycastle/util/encoders/DecoderException.java b/core/src/main/java/org/spongycastle/util/encoders/DecoderException.java new file mode 100644 index 00000000..88e0b7c0 --- /dev/null +++ b/core/src/main/java/org/spongycastle/util/encoders/DecoderException.java @@ -0,0 +1,22 @@ +package org.spongycastle.util.encoders; + +/** + * Exception thrown if an attempt is made to decode invalid data, or some other failure occurs. + */ +public class DecoderException + extends IllegalStateException +{ + private Throwable cause; + + DecoderException(String msg, Throwable cause) + { + super(msg); + + this.cause = cause; + } + + public Throwable getCause() + { + return cause; + } +} diff --git a/core/src/main/java/org/spongycastle/util/encoders/Encoder.java b/core/src/main/java/org/spongycastle/util/encoders/Encoder.java new file mode 100644 index 00000000..106c36b7 --- /dev/null +++ b/core/src/main/java/org/spongycastle/util/encoders/Encoder.java @@ -0,0 +1,17 @@ +package org.spongycastle.util.encoders; + +import java.io.IOException; +import java.io.OutputStream; + +/** + * Encode and decode byte arrays (typically from binary to 7-bit ASCII + * encodings). + */ +public interface Encoder +{ + int encode(byte[] data, int off, int length, OutputStream out) throws IOException; + + int decode(byte[] data, int off, int length, OutputStream out) throws IOException; + + int decode(String data, OutputStream out) throws IOException; +} diff --git a/core/src/main/java/org/spongycastle/util/encoders/EncoderException.java b/core/src/main/java/org/spongycastle/util/encoders/EncoderException.java new file mode 100644 index 00000000..936fce02 --- /dev/null +++ b/core/src/main/java/org/spongycastle/util/encoders/EncoderException.java @@ -0,0 +1,22 @@ +package org.spongycastle.util.encoders; + +/** + * Exception thrown if an attempt is made to encode invalid data, or some other failure occurs. + */ +public class EncoderException + extends IllegalStateException +{ + private Throwable cause; + + EncoderException(String msg, Throwable cause) + { + super(msg); + + this.cause = cause; + } + + public Throwable getCause() + { + return cause; + } +} diff --git a/core/src/main/java/org/spongycastle/util/encoders/Hex.java b/core/src/main/java/org/spongycastle/util/encoders/Hex.java new file mode 100644 index 00000000..5d0efc13 --- /dev/null +++ b/core/src/main/java/org/spongycastle/util/encoders/Hex.java @@ -0,0 +1,151 @@ +package org.spongycastle.util.encoders; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.OutputStream; + +import org.spongycastle.util.Strings; + +/** + * Utility class for converting hex data to bytes and back again. + */ +public class Hex +{ + private static final Encoder encoder = new HexEncoder(); + + public static String toHexString( + byte[] data) + { + return toHexString(data, 0, data.length); + } + + public static String toHexString( + byte[] data, + int off, + int length) + { + byte[] encoded = encode(data, off, length); + return Strings.fromByteArray(encoded); + } + + /** + * encode the input data producing a Hex encoded byte array. + * + * @return a byte array containing the Hex encoded data. + */ + public static byte[] encode( + byte[] data) + { + return encode(data, 0, data.length); + } + + /** + * encode the input data producing a Hex encoded byte array. + * + * @return a byte array containing the Hex encoded data. + */ + public static byte[] encode( + byte[] data, + int off, + int length) + { + ByteArrayOutputStream bOut = new ByteArrayOutputStream(); + + try + { + encoder.encode(data, off, length, bOut); + } + catch (Exception e) + { + throw new EncoderException("exception encoding Hex string: " + e.getMessage(), e); + } + + return bOut.toByteArray(); + } + + /** + * Hex encode the byte data writing it to the given output stream. + * + * @return the number of bytes produced. + */ + public static int encode( + byte[] data, + OutputStream out) + throws IOException + { + return encoder.encode(data, 0, data.length, out); + } + + /** + * Hex encode the byte data writing it to the given output stream. + * + * @return the number of bytes produced. + */ + public static int encode( + byte[] data, + int off, + int length, + OutputStream out) + throws IOException + { + return encoder.encode(data, off, length, out); + } + + /** + * decode the Hex encoded input data. It is assumed the input data is valid. + * + * @return a byte array representing the decoded data. + */ + public static byte[] decode( + byte[] data) + { + ByteArrayOutputStream bOut = new ByteArrayOutputStream(); + + try + { + encoder.decode(data, 0, data.length, bOut); + } + catch (Exception e) + { + throw new DecoderException("exception decoding Hex data: " + e.getMessage(), e); + } + + return bOut.toByteArray(); + } + + /** + * decode the Hex encoded String data - whitespace will be ignored. + * + * @return a byte array representing the decoded data. + */ + public static byte[] decode( + String data) + { + ByteArrayOutputStream bOut = new ByteArrayOutputStream(); + + try + { + encoder.decode(data, bOut); + } + catch (Exception e) + { + throw new DecoderException("exception decoding Hex string: " + e.getMessage(), e); + } + + return bOut.toByteArray(); + } + + /** + * decode the Hex encoded String data writing it to the given output stream, + * whitespace characters will be ignored. + * + * @return the number of bytes produced. + */ + public static int decode( + String data, + OutputStream out) + throws IOException + { + return encoder.decode(data, out); + } +} diff --git a/core/src/main/java/org/spongycastle/util/encoders/HexEncoder.java b/core/src/main/java/org/spongycastle/util/encoders/HexEncoder.java new file mode 100644 index 00000000..16120129 --- /dev/null +++ b/core/src/main/java/org/spongycastle/util/encoders/HexEncoder.java @@ -0,0 +1,190 @@ +package org.spongycastle.util.encoders; + +import java.io.IOException; +import java.io.OutputStream; + +/** + * A streaming Hex encoder. + */ +public class HexEncoder + implements Encoder +{ + protected final byte[] encodingTable = + { + (byte)'0', (byte)'1', (byte)'2', (byte)'3', (byte)'4', (byte)'5', (byte)'6', (byte)'7', + (byte)'8', (byte)'9', (byte)'a', (byte)'b', (byte)'c', (byte)'d', (byte)'e', (byte)'f' + }; + + /* + * set up the decoding table. + */ + protected final byte[] decodingTable = new byte[128]; + + protected void initialiseDecodingTable() + { + for (int i = 0; i < decodingTable.length; i++) + { + decodingTable[i] = (byte)0xff; + } + + for (int i = 0; i < encodingTable.length; i++) + { + decodingTable[encodingTable[i]] = (byte)i; + } + + decodingTable['A'] = decodingTable['a']; + decodingTable['B'] = decodingTable['b']; + decodingTable['C'] = decodingTable['c']; + decodingTable['D'] = decodingTable['d']; + decodingTable['E'] = decodingTable['e']; + decodingTable['F'] = decodingTable['f']; + } + + public HexEncoder() + { + initialiseDecodingTable(); + } + + /** + * encode the input data producing a Hex output stream. + * + * @return the number of bytes produced. + */ + public int encode( + byte[] data, + int off, + int length, + OutputStream out) + throws IOException + { + for (int i = off; i < (off + length); i++) + { + int v = data[i] & 0xff; + + out.write(encodingTable[(v >>> 4)]); + out.write(encodingTable[v & 0xf]); + } + + return length * 2; + } + + private static boolean ignore( + char c) + { + return c == '\n' || c =='\r' || c == '\t' || c == ' '; + } + + /** + * decode the Hex encoded byte data writing it to the given output stream, + * whitespace characters will be ignored. + * + * @return the number of bytes produced. + */ + public int decode( + byte[] data, + int off, + int length, + OutputStream out) + throws IOException + { + byte b1, b2; + int outLen = 0; + + int end = off + length; + + while (end > off) + { + if (!ignore((char)data[end - 1])) + { + break; + } + + end--; + } + + int i = off; + while (i < end) + { + while (i < end && ignore((char)data[i])) + { + i++; + } + + b1 = decodingTable[data[i++]]; + + while (i < end && ignore((char)data[i])) + { + i++; + } + + b2 = decodingTable[data[i++]]; + + if ((b1 | b2) < 0) + { + throw new IOException("invalid characters encountered in Hex data"); + } + + out.write((b1 << 4) | b2); + + outLen++; + } + + return outLen; + } + + /** + * decode the Hex encoded String data writing it to the given output stream, + * whitespace characters will be ignored. + * + * @return the number of bytes produced. + */ + public int decode( + String data, + OutputStream out) + throws IOException + { + byte b1, b2; + int length = 0; + + int end = data.length(); + + while (end > 0) + { + if (!ignore(data.charAt(end - 1))) + { + break; + } + + end--; + } + + int i = 0; + while (i < end) + { + while (i < end && ignore(data.charAt(i))) + { + i++; + } + + b1 = decodingTable[data.charAt(i++)]; + + while (i < end && ignore(data.charAt(i))) + { + i++; + } + + b2 = decodingTable[data.charAt(i++)]; + + if ((b1 | b2) < 0) + { + throw new IOException("invalid characters encountered in Hex string"); + } + + out.write((b1 << 4) | b2); + + length++; + } + + return length; + } +} diff --git a/core/src/main/java/org/spongycastle/util/encoders/HexTranslator.java b/core/src/main/java/org/spongycastle/util/encoders/HexTranslator.java new file mode 100644 index 00000000..066f883d --- /dev/null +++ b/core/src/main/java/org/spongycastle/util/encoders/HexTranslator.java @@ -0,0 +1,87 @@ +package org.spongycastle.util.encoders; + +/** + * Converters for going from hex to binary and back. Note: this class assumes ASCII processing. + */ +public class HexTranslator + implements Translator +{ + private static final byte[] hexTable = + { + (byte)'0', (byte)'1', (byte)'2', (byte)'3', (byte)'4', (byte)'5', (byte)'6', (byte)'7', + (byte)'8', (byte)'9', (byte)'a', (byte)'b', (byte)'c', (byte)'d', (byte)'e', (byte)'f' + }; + + /** + * size of the output block on encoding produced by getDecodedBlockSize() + * bytes. + */ + public int getEncodedBlockSize() + { + return 2; + } + + public int encode( + byte[] in, + int inOff, + int length, + byte[] out, + int outOff) + { + for (int i = 0, j = 0; i < length; i++, j += 2) + { + out[outOff + j] = hexTable[(in[inOff] >> 4) & 0x0f]; + out[outOff + j + 1] = hexTable[in[inOff] & 0x0f]; + + inOff++; + } + + return length * 2; + } + + /** + * size of the output block on decoding produced by getEncodedBlockSize() + * bytes. + */ + public int getDecodedBlockSize() + { + return 1; + } + + public int decode( + byte[] in, + int inOff, + int length, + byte[] out, + int outOff) + { + int halfLength = length / 2; + byte left, right; + for (int i = 0; i < halfLength; i++) + { + left = in[inOff + i * 2]; + right = in[inOff + i * 2 + 1]; + + if (left < (byte)'a') + { + out[outOff] = (byte)((left - '0') << 4); + } + else + { + out[outOff] = (byte)((left - 'a' + 10) << 4); + } + if (right < (byte)'a') + { + out[outOff] += (byte)(right - '0'); + } + else + { + out[outOff] += (byte)(right - 'a' + 10); + } + + outOff++; + } + + return halfLength; + } +} diff --git a/core/src/main/java/org/spongycastle/util/encoders/Translator.java b/core/src/main/java/org/spongycastle/util/encoders/Translator.java new file mode 100644 index 00000000..819b761a --- /dev/null +++ b/core/src/main/java/org/spongycastle/util/encoders/Translator.java @@ -0,0 +1,23 @@ +package org.spongycastle.util.encoders; + +/** + * General interface for an translator. + */ +public interface Translator +{ + /** + * size of the output block on encoding produced by getDecodedBlockSize() + * bytes. + */ + public int getEncodedBlockSize(); + + public int encode(byte[] in, int inOff, int length, byte[] out, int outOff); + + /** + * size of the output block on decoding produced by getEncodedBlockSize() + * bytes. + */ + public int getDecodedBlockSize(); + + public int decode(byte[] in, int inOff, int length, byte[] out, int outOff); +} diff --git a/core/src/main/java/org/spongycastle/util/encoders/UrlBase64.java b/core/src/main/java/org/spongycastle/util/encoders/UrlBase64.java new file mode 100644 index 00000000..18fb0eb4 --- /dev/null +++ b/core/src/main/java/org/spongycastle/util/encoders/UrlBase64.java @@ -0,0 +1,129 @@ +package org.spongycastle.util.encoders; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.OutputStream; + +/** + * Convert binary data to and from UrlBase64 encoding. This is identical to + * Base64 encoding, except that the padding character is "." and the other + * non-alphanumeric characters are "-" and "_" instead of "+" and "/". + * <p> + * The purpose of UrlBase64 encoding is to provide a compact encoding of binary + * data that is safe for use as an URL parameter. Base64 encoding does not + * produce encoded values that are safe for use in URLs, since "/" can be + * interpreted as a path delimiter; "+" is the encoded form of a space; and + * "=" is used to separate a name from the corresponding value in an URL + * parameter. + */ +public class UrlBase64 +{ + private static final Encoder encoder = new UrlBase64Encoder(); + + /** + * Encode the input data producing a URL safe base 64 encoded byte array. + * + * @return a byte array containing the URL safe base 64 encoded data. + */ + public static byte[] encode( + byte[] data) + { + ByteArrayOutputStream bOut = new ByteArrayOutputStream(); + + try + { + encoder.encode(data, 0, data.length, bOut); + } + catch (Exception e) + { + throw new EncoderException("exception encoding URL safe base64 data: " + e.getMessage(), e); + } + + return bOut.toByteArray(); + } + + /** + * Encode the byte data writing it to the given output stream. + * + * @return the number of bytes produced. + */ + public static int encode( + byte[] data, + OutputStream out) + throws IOException + { + return encoder.encode(data, 0, data.length, out); + } + + /** + * Decode the URL safe base 64 encoded input data - white space will be ignored. + * + * @return a byte array representing the decoded data. + */ + public static byte[] decode( + byte[] data) + { + ByteArrayOutputStream bOut = new ByteArrayOutputStream(); + + try + { + encoder.decode(data, 0, data.length, bOut); + } + catch (Exception e) + { + throw new DecoderException("exception decoding URL safe base64 string: " + e.getMessage(), e); + } + + return bOut.toByteArray(); + } + + /** + * decode the URL safe base 64 encoded byte data writing it to the given output stream, + * whitespace characters will be ignored. + * + * @return the number of bytes produced. + */ + public static int decode( + byte[] data, + OutputStream out) + throws IOException + { + return encoder.decode(data, 0, data.length, out); + } + + /** + * decode the URL safe base 64 encoded String data - whitespace will be ignored. + * + * @return a byte array representing the decoded data. + */ + public static byte[] decode( + String data) + { + ByteArrayOutputStream bOut = new ByteArrayOutputStream(); + + try + { + encoder.decode(data, bOut); + } + catch (Exception e) + { + throw new DecoderException("exception decoding URL safe base64 string: " + e.getMessage(), e); + } + + return bOut.toByteArray(); + } + + /** + * Decode the URL safe base 64 encoded String data writing it to the given output stream, + * whitespace characters will be ignored. + * + * @return the number of bytes produced. + */ + public static int decode( + String data, + OutputStream out) + throws IOException + { + return encoder.decode(data, out); + } +} diff --git a/core/src/main/java/org/spongycastle/util/encoders/UrlBase64Encoder.java b/core/src/main/java/org/spongycastle/util/encoders/UrlBase64Encoder.java new file mode 100644 index 00000000..dc6f1ed7 --- /dev/null +++ b/core/src/main/java/org/spongycastle/util/encoders/UrlBase64Encoder.java @@ -0,0 +1,25 @@ +package org.spongycastle.util.encoders; + +/** + * Convert binary data to and from UrlBase64 encoding. This is identical to + * Base64 encoding, except that the padding character is "." and the other + * non-alphanumeric characters are "-" and "_" instead of "+" and "/". + * <p> + * The purpose of UrlBase64 encoding is to provide a compact encoding of binary + * data that is safe for use as an URL parameter. Base64 encoding does not + * produce encoded values that are safe for use in URLs, since "/" can be + * interpreted as a path delimiter; "+" is the encoded form of a space; and + * "=" is used to separate a name from the corresponding value in an URL + * parameter. + */ +public class UrlBase64Encoder extends Base64Encoder +{ + public UrlBase64Encoder() + { + encodingTable[encodingTable.length - 2] = (byte) '-'; + encodingTable[encodingTable.length - 1] = (byte) '_'; + padding = (byte) '.'; + // we must re-create the decoding table with the new encoded values. + initialiseDecodingTable(); + } +} diff --git a/core/src/main/java/org/spongycastle/util/io/BufferingOutputStream.java b/core/src/main/java/org/spongycastle/util/io/BufferingOutputStream.java new file mode 100644 index 00000000..ae4abec4 --- /dev/null +++ b/core/src/main/java/org/spongycastle/util/io/BufferingOutputStream.java @@ -0,0 +1,108 @@ +package org.spongycastle.util.io; + +import java.io.IOException; +import java.io.OutputStream; + +import org.spongycastle.util.Arrays; + +/** + * An output stream that buffers data to be feed into an encapsulated output stream. + * <p> + * The stream zeroes out the internal buffer on each flush. + * </p> + */ +public class BufferingOutputStream + extends OutputStream +{ + private final OutputStream other; + private final byte[] buf; + + private int bufOff; + + /** + * Create a buffering stream with the default buffer size (4096). + * + * @param other output stream to be wrapped. + */ + public BufferingOutputStream(OutputStream other) + { + this.other = other; + this.buf = new byte[4096]; + } + + /** + * Create a buffering stream with a specified buffer size. + * + * @param other output stream to be wrapped. + * @param bufferSize size in bytes for internal buffer. + */ + public BufferingOutputStream(OutputStream other, int bufferSize) + { + this.other = other; + this.buf = new byte[bufferSize]; + } + + public void write(byte[] bytes, int offset, int len) + throws IOException + { + if (len < buf.length - bufOff) + { + System.arraycopy(bytes, offset, buf, bufOff, len); + bufOff += len; + } + else + { + int gap = buf.length - bufOff; + + System.arraycopy(bytes, offset, buf, bufOff, gap); + bufOff += gap; + + flush(); + + offset += gap; + len -= gap; + while (len >= buf.length) + { + other.write(bytes, offset, buf.length); + offset += buf.length; + len -= buf.length; + } + + if (len > 0) + { + System.arraycopy(bytes, offset, buf, bufOff, len); + bufOff += len; + } + } + } + + public void write(int b) + throws IOException + { + buf[bufOff++] = (byte)b; + if (bufOff == buf.length) + { + flush(); + } + } + + /** + * Flush the internal buffer to the encapsulated output stream. Zero the buffer contents when done. + * + * @throws IOException on error. + */ + public void flush() + throws IOException + { + other.write(buf, 0, bufOff); + bufOff = 0; + Arrays.fill(buf, (byte)0); + } + + public void close() + throws IOException + { + flush(); + other.close(); + } +} diff --git a/core/src/main/java/org/spongycastle/util/io/StreamOverflowException.java b/core/src/main/java/org/spongycastle/util/io/StreamOverflowException.java new file mode 100644 index 00000000..47f85a3f --- /dev/null +++ b/core/src/main/java/org/spongycastle/util/io/StreamOverflowException.java @@ -0,0 +1,15 @@ +package org.spongycastle.util.io; + +import java.io.IOException; + +/** + * Exception thrown when too much data is written to an InputStream + */ +public class StreamOverflowException + extends IOException +{ + public StreamOverflowException(String msg) + { + super(msg); + } +} diff --git a/core/src/main/java/org/spongycastle/util/io/Streams.java b/core/src/main/java/org/spongycastle/util/io/Streams.java new file mode 100644 index 00000000..210620c4 --- /dev/null +++ b/core/src/main/java/org/spongycastle/util/io/Streams.java @@ -0,0 +1,145 @@ +package org.spongycastle.util.io; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; + +/** + * Utility methods to assist with stream processing. + */ +public final class Streams +{ + private static int BUFFER_SIZE = 512; + + /** + * Read stream till EOF is encountered. + * + * @param inStr stream to be emptied. + * @throws IOException in case of underlying IOException. + */ + public static void drain(InputStream inStr) + throws IOException + { + byte[] bs = new byte[BUFFER_SIZE]; + while (inStr.read(bs, 0, bs.length) >= 0) + { + } + } + + /** + * Read stream fully, returning contents in a byte array. + * + * @param inStr stream to be read. + * @return a byte array representing the contents of inStr. + * @throws IOException in case of underlying IOException. + */ + public static byte[] readAll(InputStream inStr) + throws IOException + { + ByteArrayOutputStream buf = new ByteArrayOutputStream(); + pipeAll(inStr, buf); + return buf.toByteArray(); + } + + /** + * Read from inStr up to a maximum number of bytes, throwing an exception if more the maximum amount + * of requested data is available. + * + * @param inStr stream to be read. + * @param limit maximum number of bytes that can be read. + * @return a byte array representing the contents of inStr. + * @throws IOException in case of underlying IOException, or if limit is reached on inStr still has data in it. + */ + public static byte[] readAllLimited(InputStream inStr, int limit) + throws IOException + { + ByteArrayOutputStream buf = new ByteArrayOutputStream(); + pipeAllLimited(inStr, limit, buf); + return buf.toByteArray(); + } + + /** + * Fully read in buf's length in data, or up to EOF, whichever occurs first, + * + * @param inStr the stream to be read. + * @param buf the buffer to be read into. + * @return the number of bytes read into the buffer. + * @throws IOException in case of underlying IOException. + */ + public static int readFully(InputStream inStr, byte[] buf) + throws IOException + { + return readFully(inStr, buf, 0, buf.length); + } + + /** + * Fully read in len's bytes of data into buf, or up to EOF, whichever occurs first, + * + * @param inStr the stream to be read. + * @param buf the buffer to be read into. + * @param off offset into buf to start putting bytes into. + * @param len the number of bytes to be read. + * @return the number of bytes read into the buffer. + * @throws IOException in case of underlying IOException. + */ + public static int readFully(InputStream inStr, byte[] buf, int off, int len) + throws IOException + { + int totalRead = 0; + while (totalRead < len) + { + int numRead = inStr.read(buf, off + totalRead, len - totalRead); + if (numRead < 0) + { + break; + } + totalRead += numRead; + } + return totalRead; + } + + /** + * Write the full contents of inStr to the destination stream outStr. + * + * @param inStr source input stream. + * @param outStr destination output stream. + * @throws IOException in case of underlying IOException. + */ + public static void pipeAll(InputStream inStr, OutputStream outStr) + throws IOException + { + byte[] bs = new byte[BUFFER_SIZE]; + int numRead; + while ((numRead = inStr.read(bs, 0, bs.length)) >= 0) + { + outStr.write(bs, 0, numRead); + } + } + + /** + * Write up to limit bytes of data from inStr to the destination stream outStr. + * + * @param inStr source input stream. + * @param limit the maximum number of bytes allowed to be read. + * @param outStr destination output stream. + * @throws IOException in case of underlying IOException, or if limit is reached on inStr still has data in it. + */ + public static long pipeAllLimited(InputStream inStr, long limit, OutputStream outStr) + throws IOException + { + long total = 0; + byte[] bs = new byte[BUFFER_SIZE]; + int numRead; + while ((numRead = inStr.read(bs, 0, bs.length)) >= 0) + { + total += numRead; + if (total > limit) + { + throw new StreamOverflowException("Data Overflow"); + } + outStr.write(bs, 0, numRead); + } + return total; + } +} diff --git a/core/src/main/java/org/spongycastle/util/io/TeeInputStream.java b/core/src/main/java/org/spongycastle/util/io/TeeInputStream.java new file mode 100644 index 00000000..9647528d --- /dev/null +++ b/core/src/main/java/org/spongycastle/util/io/TeeInputStream.java @@ -0,0 +1,71 @@ +package org.spongycastle.util.io; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; + +/** + * An input stream which copies anything read through it to another stream. + */ +public class TeeInputStream + extends InputStream +{ + private final InputStream input; + private final OutputStream output; + + /** + * Base constructor. + * + * @param input input stream to be wrapped. + * @param output output stream to copy any input read to. + */ + public TeeInputStream(InputStream input, OutputStream output) + { + this.input = input; + this.output = output; + } + + public int read(byte[] buf) + throws IOException + { + return read(buf, 0, buf.length); + } + + public int read(byte[] buf, int off, int len) + throws IOException + { + int i = input.read(buf, off, len); + + if (i > 0) + { + output.write(buf, off, i); + } + + return i; + } + + public int read() + throws IOException + { + int i = input.read(); + + if (i >= 0) + { + output.write(i); + } + + return i; + } + + public void close() + throws IOException + { + this.input.close(); + this.output.close(); + } + + public OutputStream getOutputStream() + { + return output; + } +} diff --git a/core/src/main/java/org/spongycastle/util/io/TeeOutputStream.java b/core/src/main/java/org/spongycastle/util/io/TeeOutputStream.java new file mode 100644 index 00000000..c7cdcf6e --- /dev/null +++ b/core/src/main/java/org/spongycastle/util/io/TeeOutputStream.java @@ -0,0 +1,62 @@ +package org.spongycastle.util.io; + +import java.io.IOException; +import java.io.OutputStream; + + +/** + * An output stream which copies anything written into it to another stream. + */ +public class TeeOutputStream + extends OutputStream +{ + private OutputStream output1; + private OutputStream output2; + + /** + * Base constructor. + * + * @param output1 the output stream that is wrapped. + * @param output2 a secondary stream that anything written to output1 is also written to. + */ + public TeeOutputStream(OutputStream output1, OutputStream output2) + { + this.output1 = output1; + this.output2 = output2; + } + + public void write(byte[] buf) + throws IOException + { + this.output1.write(buf); + this.output2.write(buf); + } + + public void write(byte[] buf, int off, int len) + throws IOException + { + this.output1.write(buf, off, len); + this.output2.write(buf, off, len); + } + + public void write(int b) + throws IOException + { + this.output1.write(b); + this.output2.write(b); + } + + public void flush() + throws IOException + { + this.output1.flush(); + this.output2.flush(); + } + + public void close() + throws IOException + { + this.output1.close(); + this.output2.close(); + } +}
\ No newline at end of file diff --git a/core/src/main/java/org/spongycastle/util/io/pem/PemGenerationException.java b/core/src/main/java/org/spongycastle/util/io/pem/PemGenerationException.java new file mode 100644 index 00000000..676b82d1 --- /dev/null +++ b/core/src/main/java/org/spongycastle/util/io/pem/PemGenerationException.java @@ -0,0 +1,28 @@ +package org.spongycastle.util.io.pem; + +import java.io.IOException; + +/** + * Exception thrown on failure to generate a PEM object. + */ +public class PemGenerationException + extends IOException +{ + private Throwable cause; + + public PemGenerationException(String message, Throwable cause) + { + super(message); + this.cause = cause; + } + + public PemGenerationException(String message) + { + super(message); + } + + public Throwable getCause() + { + return cause; + } +} diff --git a/core/src/main/java/org/spongycastle/util/io/pem/PemHeader.java b/core/src/main/java/org/spongycastle/util/io/pem/PemHeader.java new file mode 100644 index 00000000..14293267 --- /dev/null +++ b/core/src/main/java/org/spongycastle/util/io/pem/PemHeader.java @@ -0,0 +1,75 @@ +package org.spongycastle.util.io.pem; + +/** + * Class representing a PEM header (name, value) pair. + */ +public class PemHeader +{ + private String name; + private String value; + + /** + * Base constructor. + * + * @param name name of the header property. + * @param value value of the header property. + */ + public PemHeader(String name, String value) + { + this.name = name; + this.value = value; + } + + public String getName() + { + return name; + } + + public String getValue() + { + return value; + } + + public int hashCode() + { + return getHashCode(this.name) + 31 * getHashCode(this.value); + } + + public boolean equals(Object o) + { + if (!(o instanceof PemHeader)) + { + return false; + } + + PemHeader other = (PemHeader)o; + + return other == this || (isEqual(this.name, other.name) && isEqual(this.value, other.value)); + } + + private int getHashCode(String s) + { + if (s == null) + { + return 1; + } + + return s.hashCode(); + } + + private boolean isEqual(String s1, String s2) + { + if (s1 == s2) + { + return true; + } + + if (s1 == null || s2 == null) + { + return false; + } + + return s1.equals(s2); + } + +} diff --git a/core/src/main/java/org/spongycastle/util/io/pem/PemObject.java b/core/src/main/java/org/spongycastle/util/io/pem/PemObject.java new file mode 100644 index 00000000..16c5c4f5 --- /dev/null +++ b/core/src/main/java/org/spongycastle/util/io/pem/PemObject.java @@ -0,0 +1,64 @@ +package org.spongycastle.util.io.pem; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +/** + * A generic PEM object - type, header properties, and byte content. + */ +public class PemObject + implements PemObjectGenerator +{ + private static final List EMPTY_LIST = Collections.unmodifiableList(new ArrayList()); + + private String type; + private List headers; + private byte[] content; + + /** + * Generic constructor for object without headers. + * + * @param type pem object type. + * @param content the binary content of the object. + */ + public PemObject(String type, byte[] content) + { + this(type, EMPTY_LIST, content); + } + + /** + * Generic constructor for object with headers. + * + * @param type pem object type. + * @param headers a list of PemHeader objects. + * @param content the binary content of the object. + */ + public PemObject(String type, List headers, byte[] content) + { + this.type = type; + this.headers = Collections.unmodifiableList(headers); + this.content = content; + } + + public String getType() + { + return type; + } + + public List getHeaders() + { + return headers; + } + + public byte[] getContent() + { + return content; + } + + public PemObject generate() + throws PemGenerationException + { + return this; + } +} diff --git a/core/src/main/java/org/spongycastle/util/io/pem/PemObjectGenerator.java b/core/src/main/java/org/spongycastle/util/io/pem/PemObjectGenerator.java new file mode 100644 index 00000000..0df180ce --- /dev/null +++ b/core/src/main/java/org/spongycastle/util/io/pem/PemObjectGenerator.java @@ -0,0 +1,16 @@ +package org.spongycastle.util.io.pem; + +/** + * Base interface for generators of PEM objects. + */ +public interface PemObjectGenerator +{ + /** + * Generate a PEM object. + * + * @return the generated object. + * @throws PemGenerationException on failure. + */ + PemObject generate() + throws PemGenerationException; +} diff --git a/core/src/main/java/org/spongycastle/util/io/pem/PemObjectParser.java b/core/src/main/java/org/spongycastle/util/io/pem/PemObjectParser.java new file mode 100644 index 00000000..5192078f --- /dev/null +++ b/core/src/main/java/org/spongycastle/util/io/pem/PemObjectParser.java @@ -0,0 +1,19 @@ +package org.spongycastle.util.io.pem; + +import java.io.IOException; + +/** + * Base interface for parsers to convert PEM objects into specific objects. + */ +public interface PemObjectParser +{ + /** + * Parse an object out of the PEM object passed in. + * + * @param obj the PEM object containing the details for the specific object. + * @return a specific object represented by the PEM object. + * @throws IOException on a parsing error. + */ + Object parseObject(PemObject obj) + throws IOException; +} diff --git a/core/src/main/java/org/spongycastle/util/io/pem/PemReader.java b/core/src/main/java/org/spongycastle/util/io/pem/PemReader.java new file mode 100644 index 00000000..0b909167 --- /dev/null +++ b/core/src/main/java/org/spongycastle/util/io/pem/PemReader.java @@ -0,0 +1,87 @@ +package org.spongycastle.util.io.pem; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.Reader; +import java.util.ArrayList; +import java.util.List; + +import org.spongycastle.util.encoders.Base64; + +/** + * A generic PEM reader, based on the format outlined in RFC 1421 + */ +public class PemReader + extends BufferedReader +{ + private static final String BEGIN = "-----BEGIN "; + private static final String END = "-----END "; + + public PemReader(Reader reader) + { + super(reader); + } + + public PemObject readPemObject() + throws IOException + { + String line = readLine(); + + while (line != null && !line.startsWith(BEGIN)) + { + line = readLine(); + } + + if (line != null) + { + line = line.substring(BEGIN.length()); + int index = line.indexOf('-'); + String type = line.substring(0, index); + + if (index > 0) + { + return loadObject(type); + } + } + + return null; + } + + private PemObject loadObject(String type) + throws IOException + { + String line; + String endMarker = END + type; + StringBuffer buf = new StringBuffer(); + List headers = new ArrayList(); + + while ((line = readLine()) != null) + { + if (line.indexOf(":") >= 0) + { + int index = line.indexOf(':'); + String hdr = line.substring(0, index); + String value = line.substring(index + 1).trim(); + + headers.add(new PemHeader(hdr, value)); + + continue; + } + + if (line.indexOf(endMarker) != -1) + { + break; + } + + buf.append(line.trim()); + } + + if (line == null) + { + throw new IOException(endMarker + " not found"); + } + + return new PemObject(type, headers, Base64.decode(buf.toString())); + } + +} diff --git a/core/src/main/java/org/spongycastle/util/io/pem/PemWriter.java b/core/src/main/java/org/spongycastle/util/io/pem/PemWriter.java new file mode 100644 index 00000000..02dde96b --- /dev/null +++ b/core/src/main/java/org/spongycastle/util/io/pem/PemWriter.java @@ -0,0 +1,137 @@ +package org.spongycastle.util.io.pem; + +import java.io.BufferedWriter; +import java.io.IOException; +import java.io.Writer; +import java.util.Iterator; + +import org.spongycastle.util.encoders.Base64; + +/** + * A generic PEM writer, based on RFC 1421 + */ +public class PemWriter + extends BufferedWriter +{ + private static final int LINE_LENGTH = 64; + + private final int nlLength; + private char[] buf = new char[LINE_LENGTH]; + + /** + * Base constructor. + * + * @param out output stream to use. + */ + public PemWriter(Writer out) + { + super(out); + + String nl = System.getProperty("line.separator"); + if (nl != null) + { + nlLength = nl.length(); + } + else + { + nlLength = 2; + } + } + + /** + * Return the number of bytes or characters required to contain the + * passed in object if it is PEM encoded. + * + * @param obj pem object to be output + * @return an estimate of the number of bytes + */ + public int getOutputSize(PemObject obj) + { + // BEGIN and END boundaries. + int size = (2 * (obj.getType().length() + 10 + nlLength)) + 6 + 4; + + if (!obj.getHeaders().isEmpty()) + { + for (Iterator it = obj.getHeaders().iterator(); it.hasNext();) + { + PemHeader hdr = (PemHeader)it.next(); + + size += hdr.getName().length() + ": ".length() + hdr.getValue().length() + nlLength; + } + + size += nlLength; + } + + // base64 encoding + int dataLen = ((obj.getContent().length + 2) / 3) * 4; + + size += dataLen + (((dataLen + LINE_LENGTH - 1) / LINE_LENGTH) * nlLength); + + return size; + } + + public void writeObject(PemObjectGenerator objGen) + throws IOException + { + PemObject obj = objGen.generate(); + + writePreEncapsulationBoundary(obj.getType()); + + if (!obj.getHeaders().isEmpty()) + { + for (Iterator it = obj.getHeaders().iterator(); it.hasNext();) + { + PemHeader hdr = (PemHeader)it.next(); + + this.write(hdr.getName()); + this.write(": "); + this.write(hdr.getValue()); + this.newLine(); + } + + this.newLine(); + } + + writeEncoded(obj.getContent()); + writePostEncapsulationBoundary(obj.getType()); + } + + private void writeEncoded(byte[] bytes) + throws IOException + { + bytes = Base64.encode(bytes); + + for (int i = 0; i < bytes.length; i += buf.length) + { + int index = 0; + + while (index != buf.length) + { + if ((i + index) >= bytes.length) + { + break; + } + buf[index] = (char)bytes[i + index]; + index++; + } + this.write(buf, 0, index); + this.newLine(); + } + } + + private void writePreEncapsulationBoundary( + String type) + throws IOException + { + this.write("-----BEGIN " + type + "-----"); + this.newLine(); + } + + private void writePostEncapsulationBoundary( + String type) + throws IOException + { + this.write("-----END " + type + "-----"); + this.newLine(); + } +} diff --git a/core/src/main/java/org/spongycastle/util/test/FixedSecureRandom.java b/core/src/main/java/org/spongycastle/util/test/FixedSecureRandom.java new file mode 100644 index 00000000..a11f5e91 --- /dev/null +++ b/core/src/main/java/org/spongycastle/util/test/FixedSecureRandom.java @@ -0,0 +1,135 @@ +package org.spongycastle.util.test; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.security.SecureRandom; + +public class FixedSecureRandom + extends SecureRandom +{ + private byte[] _data; + + private int _index; + private int _intPad; + + public FixedSecureRandom(byte[] value) + { + this(false, new byte[][] { value }); + } + + public FixedSecureRandom( + byte[][] values) + { + this(false, values); + } + + /** + * Pad the data on integer boundaries. This is necessary for the classpath project's BigInteger + * implementation. + */ + public FixedSecureRandom( + boolean intPad, + byte[] value) + { + this(intPad, new byte[][] { value }); + } + + /** + * Pad the data on integer boundaries. This is necessary for the classpath project's BigInteger + * implementation. + */ + public FixedSecureRandom( + boolean intPad, + byte[][] values) + { + ByteArrayOutputStream bOut = new ByteArrayOutputStream(); + + for (int i = 0; i != values.length; i++) + { + try + { + bOut.write(values[i]); + } + catch (IOException e) + { + throw new IllegalArgumentException("can't save value array."); + } + } + + _data = bOut.toByteArray(); + + if (intPad) + { + _intPad = _data.length % 4; + } + } + + public void nextBytes(byte[] bytes) + { + System.arraycopy(_data, _index, bytes, 0, bytes.length); + + _index += bytes.length; + } + + // + // classpath's implementation of SecureRandom doesn't currently go back to nextBytes + // when next is called. We can't override next as it's a final method. + // + public int nextInt() + { + int val = 0; + + val |= nextValue() << 24; + val |= nextValue() << 16; + + if (_intPad == 2) + { + _intPad--; + } + else + { + val |= nextValue() << 8; + } + + if (_intPad == 1) + { + _intPad--; + } + else + { + val |= nextValue(); + } + + return val; + } + + // + // classpath's implementation of SecureRandom doesn't currently go back to nextBytes + // when next is called. We can't override next as it's a final method. + // + public long nextLong() + { + long val = 0; + + val |= (long)nextValue() << 56; + val |= (long)nextValue() << 48; + val |= (long)nextValue() << 40; + val |= (long)nextValue() << 32; + val |= (long)nextValue() << 24; + val |= (long)nextValue() << 16; + val |= (long)nextValue() << 8; + val |= (long)nextValue(); + + return val; + } + + public boolean isExhausted() + { + return _index == _data.length; + } + + private int nextValue() + { + return _data[_index++] & 0xff; + } +} diff --git a/core/src/main/java/org/spongycastle/util/test/NumberParsing.java b/core/src/main/java/org/spongycastle/util/test/NumberParsing.java new file mode 100644 index 00000000..2082cb71 --- /dev/null +++ b/core/src/main/java/org/spongycastle/util/test/NumberParsing.java @@ -0,0 +1,34 @@ +package org.spongycastle.util.test; + +/** + * Parsing + */ +public final class NumberParsing +{ + private NumberParsing() + { + // Hide constructor + } + + public static long decodeLongFromHex(String longAsString) + { + if ((longAsString.charAt(1) == 'x') + || (longAsString.charAt(1) == 'X')) + { + return Long.parseLong(longAsString.substring(2), 16); + } + + return Long.parseLong(longAsString, 16); + } + + public static int decodeIntFromHex(String intAsString) + { + if ((intAsString.charAt(1) == 'x') + || (intAsString.charAt(1) == 'X')) + { + return Integer.parseInt(intAsString.substring(2), 16); + } + + return Integer.parseInt(intAsString, 16); + } +} diff --git a/core/src/main/java/org/spongycastle/util/test/SimpleTest.java b/core/src/main/java/org/spongycastle/util/test/SimpleTest.java new file mode 100644 index 00000000..e5508355 --- /dev/null +++ b/core/src/main/java/org/spongycastle/util/test/SimpleTest.java @@ -0,0 +1,84 @@ +package org.spongycastle.util.test; + +import java.io.PrintStream; + +import org.spongycastle.util.Arrays; + +public abstract class SimpleTest + implements Test +{ + public abstract String getName(); + + private TestResult success() + { + return SimpleTestResult.successful(this, "Okay"); + } + + protected void fail( + String message) + { + throw new TestFailedException(SimpleTestResult.failed(this, message)); + } + + protected void fail( + String message, + Throwable throwable) + { + throw new TestFailedException(SimpleTestResult.failed(this, message, throwable)); + } + + protected void fail( + String message, + Object expected, + Object found) + { + throw new TestFailedException(SimpleTestResult.failed(this, message, expected, found)); + } + + protected boolean areEqual( + byte[] a, + byte[] b) + { + return Arrays.areEqual(a, b); + } + + public TestResult perform() + { + try + { + performTest(); + + return success(); + } + catch (TestFailedException e) + { + return e.getResult(); + } + catch (Exception e) + { + return SimpleTestResult.failed(this, "Exception: " + e, e); + } + } + + protected static void runTest( + Test test) + { + runTest(test, System.out); + } + + protected static void runTest( + Test test, + PrintStream out) + { + TestResult result = test.perform(); + + out.println(result.toString()); + if (result.getException() != null) + { + result.getException().printStackTrace(out); + } + } + + public abstract void performTest() + throws Exception; +} diff --git a/core/src/main/java/org/spongycastle/util/test/SimpleTestResult.java b/core/src/main/java/org/spongycastle/util/test/SimpleTestResult.java new file mode 100644 index 00000000..cb47c91a --- /dev/null +++ b/core/src/main/java/org/spongycastle/util/test/SimpleTestResult.java @@ -0,0 +1,80 @@ +package org.spongycastle.util.test; + +public class SimpleTestResult implements TestResult +{ + private static final String SEPARATOR = System.getProperty("line.separator"); + + private boolean success; + private String message; + private Throwable exception; + + public SimpleTestResult(boolean success, String message) + { + this.success = success; + this.message = message; + } + + public SimpleTestResult(boolean success, String message, Throwable exception) + { + this.success = success; + this.message = message; + this.exception = exception; + } + + public static TestResult successful( + Test test, + String message) + { + return new SimpleTestResult(true, test.getName() + ": " + message); + } + + public static TestResult failed( + Test test, + String message) + { + return new SimpleTestResult(false, test.getName() + ": " + message); + } + + public static TestResult failed( + Test test, + String message, + Throwable t) + { + return new SimpleTestResult(false, test.getName() + ": " + message, t); + } + + public static TestResult failed( + Test test, + String message, + Object expected, + Object found) + { + return failed(test, message + SEPARATOR + "Expected: " + expected + SEPARATOR + "Found : " + found); + } + + public static String failedMessage(String algorithm, String testName, String expected, + String actual) + { + StringBuffer sb = new StringBuffer(algorithm); + sb.append(" failing ").append(testName); + sb.append(SEPARATOR).append(" expected: ").append(expected); + sb.append(SEPARATOR).append(" got : ").append(actual); + + return sb.toString(); + } + + public boolean isSuccessful() + { + return success; + } + + public String toString() + { + return message; + } + + public Throwable getException() + { + return exception; + } +} diff --git a/core/src/main/java/org/spongycastle/util/test/Test.java b/core/src/main/java/org/spongycastle/util/test/Test.java new file mode 100644 index 00000000..50a9f2e2 --- /dev/null +++ b/core/src/main/java/org/spongycastle/util/test/Test.java @@ -0,0 +1,8 @@ +package org.spongycastle.util.test; + +public interface Test +{ + String getName(); + + TestResult perform(); +} diff --git a/core/src/main/java/org/spongycastle/util/test/TestFailedException.java b/core/src/main/java/org/spongycastle/util/test/TestFailedException.java new file mode 100644 index 00000000..e3d7180f --- /dev/null +++ b/core/src/main/java/org/spongycastle/util/test/TestFailedException.java @@ -0,0 +1,18 @@ +package org.spongycastle.util.test; + +public class TestFailedException + extends RuntimeException +{ + private TestResult _result; + + public TestFailedException( + TestResult result) + { + _result = result; + } + + public TestResult getResult() + { + return _result; + } +} diff --git a/core/src/main/java/org/spongycastle/util/test/TestResult.java b/core/src/main/java/org/spongycastle/util/test/TestResult.java new file mode 100644 index 00000000..edf3803e --- /dev/null +++ b/core/src/main/java/org/spongycastle/util/test/TestResult.java @@ -0,0 +1,10 @@ +package org.spongycastle.util.test; + +public interface TestResult +{ + public boolean isSuccessful(); + + public Throwable getException(); + + public String toString(); +} diff --git a/core/src/main/java/org/spongycastle/util/test/UncloseableOutputStream.java b/core/src/main/java/org/spongycastle/util/test/UncloseableOutputStream.java new file mode 100644 index 00000000..55e9dc2d --- /dev/null +++ b/core/src/main/java/org/spongycastle/util/test/UncloseableOutputStream.java @@ -0,0 +1,23 @@ +package org.spongycastle.util.test; + +import java.io.FilterOutputStream; +import java.io.IOException; +import java.io.OutputStream; + +public class UncloseableOutputStream extends FilterOutputStream +{ + public UncloseableOutputStream(OutputStream s) + { + super(s); + } + + public void close() + { + throw new RuntimeException("close() called on UncloseableOutputStream"); + } + + public void write(byte[] b, int off, int len) throws IOException + { + out.write(b, off, len); + } + } |