package net.gcdc.asn1.uper;
import java.lang.annotation.Annotation;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import logger.Logger;
import logger.LoggerFactory;
import net.gcdc.asn1.datatypes.Asn1Default;
import net.gcdc.asn1.datatypes.Asn1Optional;
import net.gcdc.asn1.datatypes.HasExtensionMarker;
import net.gcdc.asn1.datatypes.IntRange;
import net.gcdc.asn1.datatypes.IsExtension;
import net.gcdc.asn1.datatypes.NoAsn1Field;
import net.gcdc.asn1.datatypes.SizeRange;
/** A "quick-and-dirty" implementation of ASN.1 encoder for UPER (Unaligned Packed Encoding Rules).
*
* @see ITU-T Recommendation X.691
*
* TODO: Cover the rest of (useful) ASN.1 datatypes and PER-visible constraints,
* write unit tests for them. Clean-up, do more refactoring.
**/
public final class UperEncoder {
public final static Logger logger = LoggerFactory.getLogger("asnLogger");
private final static int NUM_16K = 16384;
@SuppressWarnings("unused")
private final static int NUM_32K = 32768;
@SuppressWarnings("unused")
private final static int NUM_48K = 49152;
@SuppressWarnings("unused")
private final static int NUM_64K = 65536;
private UperEncoder(){}
public static byte[] encode(T obj)
throws IllegalArgumentException, UnsupportedOperationException {
try {
BitBuffer bitbuffer = ByteBitBuffer.createInfinite();
encode2(bitbuffer, obj, new Annotation[] {});
bitbuffer.flip();
byte[] result = Arrays.copyOf(bitbuffer.array(), (bitbuffer.limit() + 7) / 8);
return result;
} catch (IllegalArgumentException e) {
throw new IllegalArgumentException("Can't encode " + obj.getClass().getName() + ": " + e, e);
} catch (Asn1EncodingException e) {
throw new IllegalArgumentException("Can't encode " + obj.getClass().getName() + ":" + e.getMessage(), e);
}
}
public static T decode(byte[] bytes, Class classOfT) throws IllegalArgumentException,
UnsupportedOperationException {
BitBuffer bitQueue = bitBufferFromBinaryString(binaryStringFromBytes(bytes));
T result = decodeAny(bitQueue, classOfT,null, new Annotation[] {});
if (bitQueue.remaining() > 7) {
throw new IllegalArgumentException("Can't fully decode "
+ classOfT.getName() + ", got (" + result.getClass().getName() + "): " + result
+ "; remaining " + bitQueue.remaining() + " bits: " + bitQueue);
}
return result;
}
public static T decode(byte[] bytes, Class classOfT, Field f) throws IllegalArgumentException,
UnsupportedOperationException {
BitBuffer bitQueue = bitBufferFromBinaryString(binaryStringFromBytes(bytes));
T result = decodeAny(bitQueue, classOfT, f, new Annotation[] {});
if (bitQueue.remaining() > 7) {
throw new IllegalArgumentException("Can't fully decode "
+ classOfT.getName() + ", got (" + result.getClass().getName() + "): " + result
+ "; remaining " + bitQueue.remaining() + " bits: " + bitQueue);
}
return result;
}
static void encode2(BitBuffer bitbuffer, T obj, Annotation[] extraAnnotations) throws Asn1EncodingException {
for (Encoder e : encoders) {
if (e.canEncode(obj, extraAnnotations)) {
e.encode(bitbuffer, obj, extraAnnotations);
return;
}
}
logger.debug(String.format("Can't find encoder for %s",obj.getClass().getSimpleName()));
throw new IllegalArgumentException("Can't find encoder for " + obj.getClass().getName()
+ " with extra annotations " + Arrays.asList(extraAnnotations));
}
static T decodeAny(BitBuffer bitbuffer,Class classOfT, Field f, Annotation[] extraAnnotations) {
logger.debug(String.format(String.format("Decoding classOfT : %s",classOfT.getCanonicalName())));
for (Decoder e : decoders) {
if (e.canDecode(classOfT, extraAnnotations)) {
return e.decode(bitbuffer, classOfT,f, extraAnnotations);
}
}
logger.debug(String.format("Can't find decoder for %s",classOfT.getSimpleName()));
throw new IllegalArgumentException("Can't find decoder for " + classOfT.getName()
+ " with extra annotations " + Arrays.asList(extraAnnotations));
}
static T getDefault(Class classOfT, Annotation[] annotations) {
AnnotationStore annots = new AnnotationStore(classOfT.getAnnotations(), annotations);
Asn1Default defaultAnnotation = annots.getAnnotation(Asn1Default.class);
if (defaultAnnotation == null){
return null;
}
Annotation[] defaultAnnots = new Annotation[] {defaultAnnotation};
for (Decoder e : decoders) {
if (e.canDecode(classOfT, defaultAnnots)) {
return e.getDefault(classOfT, defaultAnnots);
}
}
logger.debug(String.format("Can't find decoder for %s",classOfT.getSimpleName()));
throw new IllegalArgumentException("Can't find default for " + classOfT.getName()
+ " with extra annotations " + defaultAnnotation.toString());
}
static IntRange newRange(
final long minValue,
final long maxValue,
final boolean hasExtensionMarker) {
return new IntRange() {
@Override public Class extends Annotation> annotationType() {
return IntRange.class;
}
@Override public long minValue() { return minValue; }
@Override public long maxValue() { return maxValue; }
@Override public boolean hasExtensionMarker() { return hasExtensionMarker; }
};
}
static IntRange intRangeFromSizeRange(SizeRange sizeRange) {
return newRange(sizeRange.minValue(), sizeRange.maxValue(), sizeRange.hasExtensionMarker());
}
private static List encoders = new ArrayList<>();
private static List decoders = new ArrayList<>();
static {
encoders.add(new IntCoder());
//encoders.add(new BigIntCoder());
encoders.add(new ByteCoder());
encoders.add(new BooleanCoder());
encoders.add(new SequenceCoder());
encoders.add(new ChoiceCoder());
encoders.add(new EnumCoder());
encoders.add(new BitStringCoder());
encoders.add(new SeqOfCoder());
encoders.add(new StringCoder());
decoders.add(new IntCoder());
//decoders.add(new BigIntCoder());
decoders.add(new ByteCoder());
decoders.add(new BooleanCoder());
decoders.add(new SequenceCoder());
decoders.add(new ChoiceCoder());
decoders.add(new EnumCoder());
decoders.add(new BitStringCoder());
decoders.add(new SeqOfCoder());
decoders.add(new StringCoder());
}
static void encodeAsOpenType(
BitBuffer bitbuffer, T obj, Annotation[] extraAnnotations)
throws IllegalArgumentException, IllegalAccessException, Asn1EncodingException {
logger.debug(String.format("OPEN TYPE for {%s}. Encoding preceedes length determinant" ,obj != null ? obj.getClass().getSimpleName() : "null"));
BitBuffer tmpbuffer = ByteBitBuffer.createInfinite();
encode2(tmpbuffer, obj, extraAnnotations);
int numBytes = (tmpbuffer.position() + 7) / 8;
logger.debug(String.format("Encoding open type length determinant (%d) for %s (will be inserted before the open type content)", numBytes, obj != null ? obj.getClass().getName() : "null" ));
try {
encodeLengthDeterminant(bitbuffer, numBytes);
} catch (Asn1EncodingException e) {
throw new Asn1EncodingException(" length of open type ", e);
}
tmpbuffer.flip();
for (int i = 0; i < tmpbuffer.limit(); i++) {
bitbuffer.put(tmpbuffer.get());
}
//CG padding bits to fill the byte: Open Types are wrapped in an OCTET STRING
int paddingBits = numBytes*8 - tmpbuffer.limit();
for (int i = 0; i < paddingBits; i++) {
bitbuffer.put(false);
}
}
static T decodeAsOpenType(BitBuffer bitbuffer, Class classOfT,Field f, Annotation[] extraAnnotations) {
logger.debug(String.format("OPEN TYPE for %s. Encoding preceedes length determinant", classOfT != null ? classOfT.getName() : "null"));
long numBytes = decodeLengthDeterminant(bitbuffer);
BitBuffer openTypeBitBuffer = ByteBitBuffer.allocate((int)numBytes * 8);
for (int i = 0; i < numBytes * 8; i++) {
openTypeBitBuffer.put(bitbuffer.get());
}
openTypeBitBuffer.flip();
if (classOfT != null) {
T result = decodeAny(openTypeBitBuffer, classOfT, f, extraAnnotations);
// Assert that padding bits are all 0.
logger.debug(String.format("open type had padding bits"));
for (int i = 0; i < openTypeBitBuffer.remaining(); i++) {
boolean paddingBit = openTypeBitBuffer.get();
logger.debug(String.format("padding bit %d was <%s>", i, paddingBit ? "1" : "0"));
if (paddingBit) {
throw new IllegalArgumentException("non-zero padding bit " + i + " for open type " + classOfT.getSimpleName()); }
}
return result;
} else {
return null;
}
}
/*
* skip an unknown extension element
* - decode length
* - skip the bytes according to the length
*/
static void decodeSkipUnknownElement(BitBuffer bitbuffer, String name) {
logger.debug(String.format("Skip unknown extension in %s. Encoding preceedes length determinant", name));
long numBytes = decodeLengthDeterminant(bitbuffer);
for (int i = 0; i < numBytes * 8; i++) {
bitbuffer.get();
}
logger.debug(String.format(String.format("Skiped %d bytes", numBytes)));
}
static boolean hasNonNullExtensions(
T obj, Asn1ContainerFieldSorter sorter)
throws IllegalArgumentException, IllegalAccessException {
for (Field f : sorter.extensionFields) {
//CG elements with default value will not be not included
if (f.get(obj) != null && !isDefault(f,f.get(obj)) ) {
return true;
}
}
return false;
}
private static Constructor findConsturctor(Class classOfT, Object... parameters) {
@SuppressWarnings("unchecked")
Constructor[] declaredConstructors = (Constructor[]) classOfT
.getDeclaredConstructors();
for (Constructor c : declaredConstructors) {
Class>[] parameterTypes = c.getParameterTypes();
if (parameterTypes.length == parameters.length) {
boolean constructorIsOk = true;
for (int i = 0; i < parameters.length; i++) {
if (!parameterTypes[i].isAssignableFrom(parameters[i].getClass())) {
constructorIsOk = false;
break;
}
}
if (constructorIsOk) { return c; }
}
}
Class>[] parameterTypes = new Class>[parameters.length];
for (int i = 0; i < parameters.length; i++) {
parameterTypes[i] = parameters[i].getClass();
}
throw new IllegalArgumentException("Can't get the " + parameters.length +
"-argument constructor for parameter(s) "
+ Arrays.asList(parameters) +
" of type(s) " + Arrays.asList(parameterTypes) + " for class "
+ classOfT.getName() + " (" + classOfT.getClass().getName() + " or " + Arrays.asList(classOfT.getClasses()) + ")" +
", all constructors: " + Arrays.asList(classOfT.getDeclaredConstructors()));
}
/** Instantiate a given class T using given parameters. */
static T instantiate(Class classOfT, Object... parameters) {
Class>[] parameterTypes = new Class>[parameters.length];
for (int i = 0; i < parameters.length; i++) {
parameterTypes[i] = parameters[i].getClass();
}
Constructor constructor = findConsturctor(classOfT, parameters);
boolean constructorIsAccessible = constructor.isAccessible();
constructor.setAccessible(true);
T result;
try {
result = constructor.newInstance(parameters);
} catch (IllegalArgumentException | InvocationTargetException | InstantiationException | IllegalAccessException e) {
throw new IllegalArgumentException("Can't instantiate " + classOfT.getName(), e);
}
constructor.setAccessible(constructorIsAccessible);
return result;
}
static long decodeConstrainedInt(BitBuffer bitqueue, IntRange intRange) {
long lowerBound = intRange.minValue();
long upperBound = intRange.maxValue();
boolean hasExtensionMarker = intRange.hasExtensionMarker();
if (upperBound < lowerBound) {
throw new IllegalArgumentException("Lower bound " + lowerBound + " is larger that upper bound " + upperBound);
}
if (hasExtensionMarker) {
boolean extensionIsActive = bitqueue.get();
if (extensionIsActive) {
//in extensions are encoded as uncontraint integers, thius an Asn1BigInteger type should be used(a lower range bound might be applied).
throw new UnsupportedOperationException("int extension are not supported yet");
}
}
final Long range = upperBound - lowerBound + 1;
if (range == 1) {
return lowerBound;
}
int bitlength = BigInteger.valueOf(range - 1).bitLength();
logger.debug(String.format("This int will require %d bits, available %d" , bitlength, bitqueue.remaining()));
BitBuffer relevantBits = ByteBitBuffer.allocate( ((bitlength + 7) / 8) * 8); // Full bytes.
int numPaddingBits = (8 - (bitlength % 8)) % 8; // Leading padding 0-bits.
for (int i = 0; i < numPaddingBits; i++) {
relevantBits.put(false);
}
for (int i = 0; i < bitlength; i++) {
relevantBits.put(bitqueue.get());
}
relevantBits.flip();
final BigInteger big = new BigInteger(+1, relevantBits.array());
final Long result = lowerBound + big.longValue();
logger.debug(String.format("bits %s decoded as %d plus lower bound %d give %d",
relevantBits.toBooleanStringFromPosition(0), big.longValue(), lowerBound, result));
if ((result < intRange.minValue() || intRange.maxValue() < result)
&& !intRange.hasExtensionMarker()) {
throw new AssertionError("Decoded value "
+ result + " is outside of range (" + intRange.minValue() + ".."
+ intRange.maxValue() + ")");
}
return result;
}
//CG Begin
static boolean isDefault(Field f, Object obj) {
if (f.getAnnotation(Asn1Default.class) != null) {
String value = f.getAnnotation(Asn1Default.class).value();
if (obj.toString().equals(value)){
return true;
}
}
return false;
}
public static boolean isNotAsn1(Field f) {
return (f.getAnnotation(NoAsn1Field.class) != null);
}
//CG End
static boolean hasExtensionMarker(AnnotationStore annotations) {
return annotations.getAnnotation(HasExtensionMarker.class) != null;
}
private static boolean isExtension(Field f) {
return f.getAnnotation(IsExtension.class) != null;
}
static boolean isMandatory(Field f) {
return !isOptional(f);
}
static boolean isOptional(Field f) {
//CG elements with default value are treated as optional as they are not encoded in case of the default value
if (f.getAnnotation(Asn1Optional.class) != null ||
f.getAnnotation(Asn1Default.class) != null) {
return true;
}
return false;
}
static class Asn1ContainerFieldSorter {
/** "Outside extension root" */
List extensionFields = new ArrayList<>();
List optionalExtensionFields = new ArrayList<>();
List mandatoryExtensionField = new ArrayList<>();
/** "Within extension root" */
List ordinaryFields = new ArrayList<>();
List mandatoryOrdinaryFields = new ArrayList<>();
List optionalOrdinaryFields = new ArrayList<>();
List allFields = new ArrayList<>(); // Excluding test instrumentation.
Map originalAccess = new HashMap<>();
Asn1ContainerFieldSorter(Class> type) {
for (Field f : type.getDeclaredFields()) {
if (isTestInstrumentation(f) || isNonAsn1Field(f) ) {
continue;
}
originalAccess.put(f, f.isAccessible());
f.setAccessible(true);
if (isExtension(f)) {
extensionFields.add(f);
if (isOptional(f)) {
optionalExtensionFields.add(f);
} else {
mandatoryExtensionField.add(f);
}
}
else {
ordinaryFields.add(f);
}
allFields.add(f);
}
for (Field f : ordinaryFields) {
if (isMandatory(f)) {
mandatoryOrdinaryFields.add(f);
} else {
optionalOrdinaryFields.add(f);
}
}
}
public void revertAccess() {
for (Entry entry : originalAccess.entrySet()) {
entry.getKey().setAccessible(entry.getValue());
}
}
}
static boolean isTestInstrumentation(Field f) {
return f.getName().startsWith("$");
}
static boolean isNonAsn1Field(Field f) {
if (f.getAnnotation(NoAsn1Field.class) != null) {
return true;
}
return false;
}
static void encodeLengthOfBitmask(BitBuffer bitbuffer, int n) throws Asn1EncodingException {
try {
if (n <= 63) {
logger.debug(String.format("normally small length of bitmask, length %d <= 63 indicated as bit <0>", n));
bitbuffer.put(false);
encodeConstrainedInt(bitbuffer, n, 1, 63);
return;
} else {
logger.debug(String.format("normally small length of bitmask, length %s > 63 indicated as bit <1>", n));
bitbuffer.put(true);
encodeLengthDeterminant(bitbuffer, n);
return;
}
} catch (Asn1EncodingException e) {
throw new Asn1EncodingException(" length of bitmask ", e);
}
}
static void encodeSmallInt(BitBuffer bitbuffer, int n) throws Asn1EncodingException {
try {
if (n <= 63) {
logger.debug(String.format("normally small length of bitmask, length %d <= 63 indicated as bit <0>", n));
bitbuffer.put(false);
encodeConstrainedInt(bitbuffer, n, 0, 63);
return;
} else {
logger.debug(String.format("normally small length of bitmask, length %s > 63 indicated as bit <1>", n));
bitbuffer.put(true);
encodeLengthDeterminant(bitbuffer, n);
return;
}
} catch (Asn1EncodingException e) {
throw new Asn1EncodingException(" length of bitmask ", e);
}
}
static void encodeLengthDeterminant(BitBuffer bitbuffer, int n) throws Asn1EncodingException {
try {
int position = bitbuffer.position();
if (n < 128) {
bitbuffer.put(false);
encodeConstrainedInt(bitbuffer, n, 0, 127);
logger.debug(String.format("Length determinant %d, encoded as <%s>", n, bitbuffer.toBooleanStringFromPosition(position)));
if (bitbuffer.position() - position != 8) {
throw new AssertionError("length determinant encoded not as 8 bits");
}
return;
} else if (n < NUM_16K) {
bitbuffer.put(true);
bitbuffer.put(false);
encodeConstrainedInt(bitbuffer, n, 0, NUM_16K - 1);
logger.debug(String.format("Length determinant %d, encoded as 2bits+14bits: <%s>", n,bitbuffer.toBooleanStringFromPosition(position)));
if (bitbuffer.position() - position != 16) {
throw new AssertionError("length determinant encoded not as 16 bits");
}
return;
} else {
throw new UnsupportedOperationException("Length greater than 16K is not supported yet.");
}
} catch (Asn1EncodingException e) {
throw new Asn1EncodingException(" length determinant ", e);
}
}
static long decodeLengthOfBitmask(BitBuffer bitbuffer) {
logger.debug("decoding length of bitmask");
boolean isGreaterThan63 = bitbuffer.get();
logger.debug(String.format("length determinant extension preamble size flag: preamble size > 63 is %s", isGreaterThan63));
if (!isGreaterThan63) {
Long result = decodeConstrainedInt(bitbuffer, newRange(1, 63, false));
logger.debug(String.format("normally small length of bitmask, length <= 63, decoded as %d", result));
return result;
} else {
logger.debug(String.format("normally small length of bitmask, length > 63, decoding as ordinary length determinant..."));
return decodeLengthDeterminant(bitbuffer);
}
}
static long decodeSmallInt(BitBuffer bitbuffer) {
logger.debug("decoding small int");
boolean isGreaterThan63 = bitbuffer.get();
logger.debug(String.format("length determinant extension preamble size flag: preamble size > 63 is %s", isGreaterThan63));
if (!isGreaterThan63) {
Long result = decodeConstrainedInt(bitbuffer, newRange(0, 63, false));
logger.debug(String.format("normally small length of bitmask, length <= 63, decoded as %d", result));
return result;
} else {
logger.debug(String.format("normally small length of bitmask, length > 63, decoding as ordinary length determinant..."));
return decodeLengthDeterminant(bitbuffer);
}
}
static long decodeLengthDeterminant(BitBuffer bitbuffer) {
boolean bit8 = bitbuffer.get();
if (!bit8) { // then value is less than 128
Long result = decodeConstrainedInt(bitbuffer, newRange(0, 127, false));
logger.debug(String.format("length determinant, decoded as %d", result));
return result;
} else {
boolean bit7 = bitbuffer.get();
if (!bit7) { // then value is less than 16K
Long result = decodeConstrainedInt(bitbuffer, newRange(0, NUM_16K - 1, false));
logger.debug(String.format("length determinant, decoded as %d", result));
return result;
} else { // "Large" n
logger.debug("lengthes longer than 16K are not supported yet.");
throw new UnsupportedOperationException("lengthes longer than 16K are not supported yet.");
}
}
}
static void encodeConstrainedInt(
final BitBuffer bitbuffer,
final long value,
final long lowerBound,
final long upperBound) throws Asn1EncodingException {
encodeConstrainedInt(bitbuffer, value, lowerBound, upperBound, false);
}
static void encodeConstrainedInt(
final BitBuffer bitbuffer,
final long value,
final long lowerBound,
final long upperBound,
final boolean hasExtensionMarker
) throws Asn1EncodingException {
if (upperBound < lowerBound) {
throw new IllegalArgumentException("Lower bound "
+ lowerBound + " is larger than upper bound " + upperBound);
}
if (!hasExtensionMarker && (value < lowerBound || value > upperBound)) {
throw new Asn1EncodingException(
" Value " + value + " is outside of fixed range " +
lowerBound + ".." + upperBound);
}
final Long range = upperBound - lowerBound + 1;
final int position = bitbuffer.position();
if (hasExtensionMarker) {
boolean outsideOfRange = value < lowerBound || value > upperBound;
logger.debug(String.format("constrained int with extension marker, %s extension range",outsideOfRange ? "outside" : "within", outsideOfRange ? "1" : "0"));
bitbuffer.put(outsideOfRange);
if (outsideOfRange) {
throw new UnsupportedOperationException(
"INT extensions are not supported yet");
}
}
if (range == 1) {
logger.debug("constrained int of empty range, resulting in empty encoding <>");
return;
}
final BigInteger big = BigInteger.valueOf(value - lowerBound);
final int numPaddingBits = BigInteger.valueOf(range - 1).bitLength() - big.bitLength();
for (int i = 0; i < numPaddingBits; i++) {
bitbuffer.put(false);
}
for (int i = big.bitLength() - 1; i >= 0; i--) {
bitbuffer.put(big.testBit(i));
}
logger.debug(String.format("constrained int %d encoded as <%s>", value, bitbuffer.toBooleanStringFromPosition(position)));
return;
}
public static byte[] bytesFromCollection(List bitlist) {
int sizeBytes = (bitlist.size() + 7) / 8;
byte[] result = new byte[sizeBytes];
int byteId = 0;
byte bitId = 7;
logger.debug(String.format("byte: < %s >", bitlist));
for (Boolean b : bitlist) {
//logger.debug(String.format("bitId: %s, byteId: %s, value: %s", bitId, byteId, b));
result[byteId] |= (b ? 1 : 0) << bitId;
bitId--;
if (bitId < 0) {
bitId = 7;
byteId++;
}
}
int nZeros = sizeBytes * 8 - bitlist.size();
String zeros = nZeros > 0 ? String.format("%0" + nZeros + "d", 0) : "";
logger.debug(String.format("Padding bits (%d): <%s>", nZeros, zeros));
return result;
}
final protected static char[] hexArray = "0123456789ABCDEF".toCharArray();
public static String hexStringFromBytes(byte[] bytes) {
char[] hexChars = new char[bytes.length * 2];
for (int j = 0; j < bytes.length; j++) {
int v = bytes[j] & 0xFF;
hexChars[j * 2] = hexArray[v >>> 4];
hexChars[j * 2 + 1] = hexArray[v & 0x0F];
}
return new String(hexChars);
}
public static byte[] bytesFromHexString(String s) {
int len = s.length();
byte[] data = new byte[len / 2];
for (int i = 0; i < len; i += 2) {
data[i / 2] = (byte) ((Character.digit(s.charAt(i), 16) << 4)
+ Character.digit(s.charAt(i + 1), 16));
}
return data;
}
public static String binaryStringFromBytes(byte[] bytes) {
StringBuilder sb = new StringBuilder(bytes.length * Byte.SIZE);
for (int i = 0; i < Byte.SIZE * bytes.length; i++)
sb.append((bytes[i / Byte.SIZE] << i % Byte.SIZE & 0x80) == 0 ? '0' : '1');
return sb.toString();
}
public static byte[] bytesFromBinaryString(String s) {
int len = s.length();
byte[] result = new byte[(len + Byte.SIZE - 1) / Byte.SIZE];
char c;
for (int i = 0; i < len; i++)
if ((c = s.charAt(i)) == '1') result[i / Byte.SIZE] = (byte) (result[i / Byte.SIZE] | (0x80 >>> (i % Byte.SIZE)));
else if (c != '0')
throw new IllegalArgumentException();
return result;
}
private static BitBuffer bitBufferFromBinaryString(String s) {
ByteBitBuffer result = ByteBitBuffer.allocate(s.length());
for (int i = 0; i < s.length(); i++) {
if (s.charAt(i) != '1' && s.charAt(i) != '0') {
throw new IllegalArgumentException("bad character in 'binary' string " + s.charAt(i));
}
result.put(s.charAt(i) == '1');
}
result.flip();
return result;
}
}