From 46039355e87825a9dce12ebaad0305d20eea3f43 Mon Sep 17 00:00:00 2001 From: CGantert345 <57003061+CGantert345@users.noreply.github.com> Date: Fri, 3 Jan 2020 10:37:01 +0100 Subject: basic asn.1 library --- src/net/gcdc/asn1/uper/AnnotationStore.java | 31 + src/net/gcdc/asn1/uper/Asn1EncodingException.java | 18 + src/net/gcdc/asn1/uper/BigIntCoder.java | 96 +++ src/net/gcdc/asn1/uper/BitBuffer.java | 32 + src/net/gcdc/asn1/uper/BitStringCoder.java | 165 +++++ src/net/gcdc/asn1/uper/BooleanCoder.java | 35 ++ src/net/gcdc/asn1/uper/ByteBitBuffer.java | 271 +++++++++ src/net/gcdc/asn1/uper/ByteCoder.java | 34 ++ src/net/gcdc/asn1/uper/ChoiceCoder.java | 161 +++++ src/net/gcdc/asn1/uper/Decoder.java | 10 + src/net/gcdc/asn1/uper/Document2.txt | 34 ++ src/net/gcdc/asn1/uper/Encoder.java | 8 + src/net/gcdc/asn1/uper/EnumCoder.java | 156 +++++ src/net/gcdc/asn1/uper/IntCoder.java | 266 +++++++++ src/net/gcdc/asn1/uper/SeqOfCoder.java | 156 +++++ src/net/gcdc/asn1/uper/SeqOfFixedSize.java | 18 + src/net/gcdc/asn1/uper/SequenceCoder.java | 269 +++++++++ src/net/gcdc/asn1/uper/SimpleTypeResolver.java | 515 ++++++++++++++++ src/net/gcdc/asn1/uper/StringCoder.java | 299 ++++++++++ src/net/gcdc/asn1/uper/UperEncoder.java | 694 ++++++++++++++++++++++ 20 files changed, 3268 insertions(+) create mode 100644 src/net/gcdc/asn1/uper/AnnotationStore.java create mode 100644 src/net/gcdc/asn1/uper/Asn1EncodingException.java create mode 100644 src/net/gcdc/asn1/uper/BigIntCoder.java create mode 100644 src/net/gcdc/asn1/uper/BitBuffer.java create mode 100644 src/net/gcdc/asn1/uper/BitStringCoder.java create mode 100644 src/net/gcdc/asn1/uper/BooleanCoder.java create mode 100644 src/net/gcdc/asn1/uper/ByteBitBuffer.java create mode 100644 src/net/gcdc/asn1/uper/ByteCoder.java create mode 100644 src/net/gcdc/asn1/uper/ChoiceCoder.java create mode 100644 src/net/gcdc/asn1/uper/Decoder.java create mode 100644 src/net/gcdc/asn1/uper/Document2.txt create mode 100644 src/net/gcdc/asn1/uper/Encoder.java create mode 100644 src/net/gcdc/asn1/uper/EnumCoder.java create mode 100644 src/net/gcdc/asn1/uper/IntCoder.java create mode 100644 src/net/gcdc/asn1/uper/SeqOfCoder.java create mode 100644 src/net/gcdc/asn1/uper/SeqOfFixedSize.java create mode 100644 src/net/gcdc/asn1/uper/SequenceCoder.java create mode 100644 src/net/gcdc/asn1/uper/SimpleTypeResolver.java create mode 100644 src/net/gcdc/asn1/uper/StringCoder.java create mode 100644 src/net/gcdc/asn1/uper/UperEncoder.java (limited to 'src/net/gcdc/asn1/uper') diff --git a/src/net/gcdc/asn1/uper/AnnotationStore.java b/src/net/gcdc/asn1/uper/AnnotationStore.java new file mode 100644 index 0000000..325ade8 --- /dev/null +++ b/src/net/gcdc/asn1/uper/AnnotationStore.java @@ -0,0 +1,31 @@ +package net.gcdc.asn1.uper; + +import java.lang.annotation.Annotation; +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; + +class AnnotationStore { + + private Map, Annotation> annotations = new HashMap<>(); + + public AnnotationStore(Annotation[] classAnnot, Annotation[] fieldAnnot) { + for (Annotation a : classAnnot) { + annotations.put(a.annotationType(), a); + } + for (Annotation a : fieldAnnot) { + annotations.put(a.annotationType(), a); + } + } + + public T getAnnotation(Class classOfT) { + @SuppressWarnings("unchecked") + // Annotations were added with value T for key classOfT. + T result = (T) annotations.get(classOfT); + return result; + } + + public Collection getAnnotations() { + return annotations.values(); + } +} \ No newline at end of file diff --git a/src/net/gcdc/asn1/uper/Asn1EncodingException.java b/src/net/gcdc/asn1/uper/Asn1EncodingException.java new file mode 100644 index 0000000..da3681b --- /dev/null +++ b/src/net/gcdc/asn1/uper/Asn1EncodingException.java @@ -0,0 +1,18 @@ +package net.gcdc.asn1.uper; + +public class Asn1EncodingException extends Exception { + + /** + * + */ + private static final long serialVersionUID = -8719453936776248228L; + + public Asn1EncodingException(String message) { + super(message); + } + + public Asn1EncodingException(String extraMessage, Asn1EncodingException cause) { + super(extraMessage + cause.getMessage(), cause); + } + +} diff --git a/src/net/gcdc/asn1/uper/BigIntCoder.java b/src/net/gcdc/asn1/uper/BigIntCoder.java new file mode 100644 index 0000000..8c24eb7 --- /dev/null +++ b/src/net/gcdc/asn1/uper/BigIntCoder.java @@ -0,0 +1,96 @@ +package net.gcdc.asn1.uper; + +import java.lang.annotation.Annotation; +import java.lang.reflect.Field; +import java.math.BigInteger; +import net.gcdc.asn1.datatypes.Asn1BigInteger; +import net.gcdc.asn1.datatypes.Asn1Default; +import net.gcdc.asn1.datatypes.IntRange; + +class BigIntCoder implements Encoder, Decoder { + + @Override public boolean canDecode(Class classOfT, Annotation[] extraAnnotations) { + return Asn1BigInteger.class.isAssignableFrom(classOfT); + } + + @Override public T decode(BitBuffer bitbuffer, + Class classOfT, Field f, + Annotation[] extraAnnotations) { + AnnotationStore annotations = new AnnotationStore(classOfT.getAnnotations(), + extraAnnotations); + + String pos = String.format("%d.%d", bitbuffer.position()/8 , bitbuffer.position() % 8); + UperEncoder.logger.debug(String.format("Position %s BIG INT",pos)); + IntRange intRange = annotations.getAnnotation(IntRange.class); + if (intRange != null && intRange.maxValue() > 0) { + throw new UnsupportedOperationException("Big int with upper range is not supported yet"); + } + + int lengthInOctets = (int) UperEncoder.decodeLengthDeterminant(bitbuffer); + BitBuffer valueBits = ByteBitBuffer.allocate(lengthInOctets * 8); + for (int i = 0; i < lengthInOctets * 8; i++) { + valueBits.put(bitbuffer.get()); + } + valueBits.flip(); + BigInteger resultValue = new BigInteger(+1, valueBits.array()); + UperEncoder.logger.debug(String.format("big int Decoded as %s", resultValue)); + + + //CG support for int range + if (intRange != null){ + resultValue.add(BigInteger.valueOf(intRange.minValue())); + } + + + return UperEncoder.instantiate(classOfT, resultValue); + } + + @Override public boolean canEncode(T obj, Annotation[] extraAnnotations) { + return obj instanceof Asn1BigInteger; + } + + @Override public void encode(BitBuffer bitbuffer, T obj, Annotation[] extraAnnotations) throws Asn1EncodingException { + Class type = obj.getClass(); + AnnotationStore annotations = new AnnotationStore(type.getAnnotations(), extraAnnotations); + IntRange range = annotations.getAnnotation(IntRange.class); + + //CG implementation with lower range limit added + BigInteger bint = ((Asn1BigInteger) obj).toBigInteger(); + if (range != null) { + throw new UnsupportedOperationException("Asn1 BigInteger with range is not supported"); + } + byte[] array = bint.toByteArray(); + int lengthInOctets = array.length; + int position1 = bitbuffer.position(); + try { + UperEncoder.encodeLengthDeterminant(bitbuffer, lengthInOctets); + } catch (Asn1EncodingException e) { + throw new Asn1EncodingException(" length determinant of " + type.getName(), e); + } + int position2 = bitbuffer.position(); + for (byte b : array) { + bitbuffer.putByte(b); + } + UperEncoder.logger.debug(String.format("Big Int(%s): len %s, val %s", obj, + bitbuffer.toBooleanString(position1, position2 - position1), + bitbuffer.toBooleanStringFromPosition(position2))); + return; + } + + @Override + public T getDefault(Class classOfT, Annotation[] extraAnnotations) { + AnnotationStore annotations = new AnnotationStore(classOfT.getAnnotations(), extraAnnotations); + Asn1Default defaultAnnotation = annotations.getAnnotation(Asn1Default.class); + if (defaultAnnotation == null) return null; + //check whether the class has a constructor for numeric types + String valueString = defaultAnnotation.value(); + long value = Long.parseLong(valueString); + UperEncoder.logger.debug(String.format("Default INTEGER: %d",value )); + + @SuppressWarnings("unchecked") + T t = (T) new Asn1BigInteger(value); + return t; + + } + +} \ No newline at end of file diff --git a/src/net/gcdc/asn1/uper/BitBuffer.java b/src/net/gcdc/asn1/uper/BitBuffer.java new file mode 100644 index 0000000..948dda5 --- /dev/null +++ b/src/net/gcdc/asn1/uper/BitBuffer.java @@ -0,0 +1,32 @@ +package net.gcdc.asn1.uper; + +/** + * An interface for convenient storage of bits, similar to Java's ByteBuffer. + * + * This interface and its implementation are very useful for UPER, since UPER operates on bits + * regardless of byte boundaries. + * + */ +public interface BitBuffer { + boolean get(); + boolean get(int index); + BitBuffer put(boolean element); + BitBuffer put(int index, boolean element); + int limit(); + int capacity(); + int position(); + int remaining(); + BitBuffer flip(); + String toBooleanString(int startIndex, int length); + String toBooleanStringFromPosition(int startIndex); + byte[] array(); + BitBuffer putByte(byte element); + byte getByte(); + void putInteger(int index, int length,int number); + void putChar6String(int index, int length, String s); + int getInteger(int index, int length); + String getChar6String(int position, int length); + void putChar5String(int index, int length, String s); + String getChar5String(int inxed, int length); + BitBuffer putByteArray(int index, byte[] data); +} diff --git a/src/net/gcdc/asn1/uper/BitStringCoder.java b/src/net/gcdc/asn1/uper/BitStringCoder.java new file mode 100644 index 0000000..e60c68e --- /dev/null +++ b/src/net/gcdc/asn1/uper/BitStringCoder.java @@ -0,0 +1,165 @@ +package net.gcdc.asn1.uper; + +import java.lang.annotation.Annotation; +import java.lang.reflect.Field; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; + +import net.gcdc.asn1.datatypes.Asn1VarSizeBitstring; +import net.gcdc.asn1.datatypes.Bitstring; +import net.gcdc.asn1.datatypes.FixedSize; +import net.gcdc.asn1.datatypes.SizeRange; +import net.gcdc.asn1.uper.UperEncoder.Asn1ContainerFieldSorter; + +class BitStringCoder implements Decoder, Encoder { + + @Override public boolean canEncode(T obj, Annotation[] extraAnnotations) { + Class type = obj.getClass(); + AnnotationStore annotations = new AnnotationStore(type.getAnnotations(), + extraAnnotations); + return annotations.getAnnotation(Bitstring.class) != null; + } + + @Override public void encode(BitBuffer bitbuffer, T obj, Annotation[] extraAnnotations) throws Asn1EncodingException { + Class type = obj.getClass(); + AnnotationStore annotations = new AnnotationStore(type.getAnnotations(), + extraAnnotations); + if (!(obj instanceof Asn1VarSizeBitstring)) { + if (UperEncoder.hasExtensionMarker(annotations)) { + throw new UnsupportedOperationException( + "Bitstring with extensions is not implemented yet"); + } + FixedSize size = type.getAnnotation(FixedSize.class); + int position = bitbuffer.position(); + if (size != null) { + Asn1ContainerFieldSorter sorter = new Asn1ContainerFieldSorter(type); + if (sorter.ordinaryFields.size() != size.value()) { throw new AssertionError( + "Declared size (" + size.value() + + ") and number of fields (" + sorter.ordinaryFields.size() + + ") do not match!"); } + for (Field f : sorter.ordinaryFields) { + try { + bitbuffer.put(f.getBoolean(obj)); + } catch (IllegalArgumentException | IllegalAccessException e) { + throw new IllegalArgumentException("can't encode" + obj, e); + } + } + UperEncoder.logger.debug(String.format("BITSTRING %s, encoded as <%s>", obj.getClass().getName(), + bitbuffer.toBooleanStringFromPosition(position))); + return; + } else { + throw new UnsupportedOperationException( + "Bitstrings of variable size are not implemented yet"); + } + } else if (obj instanceof Asn1VarSizeBitstring) { + int position = bitbuffer.position(); + if (UperEncoder.hasExtensionMarker(annotations)) { throw new UnsupportedOperationException( + "Bitstring with extensions is not implemented yet"); } + Asn1VarSizeBitstring bitstring = (Asn1VarSizeBitstring) obj; + FixedSize fixedSize = annotations.getAnnotation(FixedSize.class); + SizeRange sizeRange = annotations.getAnnotation(SizeRange.class); + if (fixedSize != null) { + for (int i = 0; i < fixedSize.value(); i++) { + bitbuffer.put(bitstring.getBit(i)); + } + UperEncoder.logger.debug(String.format("BITSTRING %s: %s", obj.getClass().getName(), + bitbuffer.toBooleanStringFromPosition(position))); + return; + } else if (sizeRange != null) { + int position1 = bitbuffer.position(); + UperEncoder.encodeConstrainedInt(bitbuffer, bitstring.size(), sizeRange.minValue(), + sizeRange.maxValue()); + int position2 = bitbuffer.position(); + for (int i = 0; i < bitstring.size(); i++) { + bitbuffer.put(bitstring.getBit(i)); + } + UperEncoder.logger.debug(String.format("BITSTRING %s size %s: %S", obj.getClass().getName(), + bitbuffer.toBooleanString(position1, position2 - position1), + bitbuffer.toBooleanStringFromPosition(position2))); + return; + } else { + throw new IllegalArgumentException("Both SizeRange and FixedSize are null"); + } + } + } + + @Override public boolean canDecode(Class classOfT, Annotation[] extraAnnotations) { + AnnotationStore annotations = new AnnotationStore(classOfT.getAnnotations(), + extraAnnotations); + return annotations.getAnnotation(Bitstring.class) != null; + } + + @Override public T decode(BitBuffer bitbuffer, + Class classOfT, Field field, + Annotation[] extraAnnotations) { + AnnotationStore annotations = new AnnotationStore(classOfT.getAnnotations(), + extraAnnotations); + if (!Asn1VarSizeBitstring.class.isAssignableFrom(classOfT)) { + UperEncoder.logger.debug("Bitlist(fixed-size, all-named)"); + FixedSize fixedSize = annotations.getAnnotation(FixedSize.class); + if (fixedSize == null) { throw new UnsupportedOperationException( + "bitstrings of non-fixed size that do not extend Asn1VarSizeBitstring are not supported yet"); + } + Asn1ContainerFieldSorter sorter = new Asn1ContainerFieldSorter(classOfT); + if (fixedSize.value() != sorter.ordinaryFields.size()) { throw new IllegalArgumentException( + "Fixed size annotation " + fixedSize.value() + + " does not match the number of fields " + + sorter.ordinaryFields.size() + " in " + classOfT.getName()); } + if (UperEncoder.hasExtensionMarker(annotations)) { + boolean extensionPresent = bitbuffer.get(); + if (extensionPresent) { throw new UnsupportedOperationException( + "extensions in fixed-size bitlist are not supported yet"); } + } + T result = UperEncoder.instantiate(classOfT); + for (Field f : sorter.ordinaryFields) { + boolean value = bitbuffer.get(); + UperEncoder.logger.debug(String.format("Field %s set to %s", f.getName(), value)); + try { + f.set(result, value); + } catch (IllegalArgumentException | IllegalAccessException e) { + throw new IllegalArgumentException("can't decode " + classOfT, e); + } + } + return result; + } else { + UperEncoder.logger.debug("Bitlist(var-size)"); + FixedSize fixedSize = annotations.getAnnotation(FixedSize.class); + SizeRange sizeRange = annotations.getAnnotation(SizeRange.class); + // We use reflection here to access protected method of Asn1VarSizeBitstring. + // Alternative would be to mandate BitSet constructors for all subclasses of + // Asn1VarSizeBitstring. + Method setBitMethod; + try { + setBitMethod = Asn1VarSizeBitstring.class.getDeclaredMethod("setBit", int.class, + boolean.class); + setBitMethod.setAccessible(true); + } catch (SecurityException | NoSuchMethodException e) { + throw new AssertionError("Can't find/access setBit " + e); + } + Long size = (fixedSize != null) ? fixedSize.value() : + (sizeRange != null) ? UperEncoder.decodeConstrainedInt(bitbuffer, + UperEncoder.intRangeFromSizeRange(sizeRange)) : + badSize(classOfT); + T result = UperEncoder.instantiate(classOfT); + for (int i = 0; i < size; i++) { + try { + setBitMethod.invoke(result, i, bitbuffer.get()); + } catch (IllegalArgumentException | InvocationTargetException | IllegalAccessException e) { + throw new IllegalArgumentException("Can't invoke setBit", e); + } + } + return result; + } + } + + /** This function only throws an exception, to be used in ternary (a?b:c) expression. */ + static Long badSize(Class classOfT) { + throw new IllegalArgumentException("both size range and fixed size are null for " + + classOfT.getName()); + } + + @Override + public T getDefault(Class classOfT, Annotation[] extraAnnotations) { + throw new IllegalArgumentException("Default Sequence not yet implemented"); + } +} \ No newline at end of file diff --git a/src/net/gcdc/asn1/uper/BooleanCoder.java b/src/net/gcdc/asn1/uper/BooleanCoder.java new file mode 100644 index 0000000..b0b9a22 --- /dev/null +++ b/src/net/gcdc/asn1/uper/BooleanCoder.java @@ -0,0 +1,35 @@ +package net.gcdc.asn1.uper; + +import java.lang.annotation.Annotation; +import java.lang.reflect.Field; + +class BooleanCoder implements Decoder, Encoder { + + @Override public boolean canEncode(T obj, Annotation[] extraAnnotations) { + return obj instanceof Boolean; + } + + @Override public void encode(BitBuffer bitbuffer, T obj, Annotation[] extraAnnotations) { + UperEncoder.logger.debug(String.format("BOOLEAN %s", obj)); + bitbuffer.put((Boolean) obj); + } + + @Override public boolean canDecode(Class classOfT, Annotation[] extraAnnotations) { + return Boolean.class.isAssignableFrom(classOfT) + || boolean.class.isAssignableFrom(classOfT); + } + + @SuppressWarnings("unchecked") + @Override public T decode(BitBuffer bitbuffer, + Class classOfT, Field field, + Annotation[] extraAnnotations) { + Boolean result = new Boolean(bitbuffer.get()); + UperEncoder.logger.debug(String.format("BOOL: decoded as %s",result)); + return (T) result; + } + + @Override + public T getDefault(Class classOfT, Annotation[] extraAnnotations) { + throw new IllegalArgumentException("Default Boolean not yet implemented"); + } +} \ No newline at end of file diff --git a/src/net/gcdc/asn1/uper/ByteBitBuffer.java b/src/net/gcdc/asn1/uper/ByteBitBuffer.java new file mode 100644 index 0000000..e55d9d5 --- /dev/null +++ b/src/net/gcdc/asn1/uper/ByteBitBuffer.java @@ -0,0 +1,271 @@ +package net.gcdc.asn1.uper; + + + +public class ByteBitBuffer implements BitBuffer { + + byte[] bytes; + byte[] mask = new byte[] { + (byte) 0b1000_0000, + 0b0100_0000, + 0b0010_0000, + 0b0001_0000, + 0b0000_1000, + 0b0000_0100, + 0b0000_0010, + 0b0000_0001, + }; + + boolean isFinite; + + int mark; + int position; + int limit; + + + @Override public boolean get(int index) { + if (index < 0) { + throw new IndexOutOfBoundsException("Index " + index + " is less than 0"); + } else if (index >= limit) { + throw new IndexOutOfBoundsException("Index " + index + " violates the limit " + limit); + } + boolean result = (bytes[index / 8] & mask[index % 8]) != 0; + return result; + } + + @Override public boolean get() { + boolean result = get(position); + position++; + return result; + } + + private void grow() { + byte[] newbytes = new byte[2 * bytes.length]; + System.arraycopy(bytes, 0, newbytes, 0, bytes.length); + bytes = newbytes; + } + + @Override public BitBuffer put(int index, boolean element) { + if (bytes.length <= index / 8) { + if (isFinite) { throw new IndexOutOfBoundsException(); } + else { grow(); } + } + if (element) { + bytes[index / 8] |= mask[index % 8]; + } else { + bytes[index / 8] &= ~mask[index % 8]; + } + return this; + } + + @Override public BitBuffer put(boolean element) { + put(position, element); + position++; + limit = limit < position ? position : limit; // TODO: should it be here? + return this; + } + + @Override public BitBuffer putByte(byte element) { + for (int i = 0; i < 8; i++) { + put((element & mask[i]) != 0); + } + return this; + } + + @Override public BitBuffer putByteArray(int index, byte[] data) { + + for (int l = 0; l < data.length;l++) { + for (int i = 0; i < 8; i++) { + put((data[l] & mask[i]) != 0); + } + } + return this; + } + + + @Override public byte getByte() { + byte result = 0; + for (int i = 0; i < 8; i++) { + result |= (get() ? 1 : 0) << (7 - i); + } + return result; + } + + @Override public int limit() { + return limit; + } + + @Override public String toBooleanString(int startIndex, int length) { + StringBuilder sb = new StringBuilder(length); + for (int i = startIndex; i < startIndex + length; i++) { + sb.append(get(i) ? "1" : "0"); + } + return sb.toString(); + } + + @Override public int capacity() { + return isFinite ? bytes.length * 8 : Integer.MAX_VALUE; + } + + @Override public int position() { + return position; + } + + @Override public int remaining() { + return limit - position; + } + + public ByteBitBuffer(byte[] backingArray) { + this.bytes = backingArray; + this.isFinite = true; + } + + private ByteBitBuffer(int initialCapacity) { + this.bytes = new byte[initialCapacity]; + this.isFinite = false; + } + + public static ByteBitBuffer allocate(int lengthInBits) { + return new ByteBitBuffer(new byte[(lengthInBits + 7) / 8]); + } + + public static ByteBitBuffer createInfinite() { + return new ByteBitBuffer(64); + } + + @Override public BitBuffer flip() { + limit = position; + position = 0; + return this; + } + + @Override public String toBooleanStringFromPosition(int startIndex) { + return toBooleanString(startIndex, position-startIndex); + } + + @Override public byte[] array() { + return bytes; + } + + @Override + public void putInteger(int position, int length,int number) { + String s = Integer.toBinaryString(number); + if (s.length() > length) { + //value is to large + return; + } + + for (int i = 0;i < length;i++){ + int index = position + i; + this.put(index,false); + } + + + int startIndex = position + length - s.length(); + for (int i = 0;i < s.length();i++){ + /* + * i = max --> index = position + length - 1 + * i = 0 --> index = position + + */ + int index = startIndex + i; + if (s.charAt(i) == '1') { + this.put(index, true ); + } else { + this.put(index, false); + } + } + + } + + @Override + public void putChar5String(int position, int length, String s) { + + String upperCaseString = s.toUpperCase(); + int offset = 0; + for (int i = 0; i < s.length() ; i++) { + char character = upperCaseString.charAt(i); + int intValue = (int) character - 32; + if (intValue > -1 && intValue < 64) { + this.putInteger(position + offset,5, intValue); + offset = offset + 5; + } else { + this.putInteger(position + offset,5,0); + position = position + 5; + } + } + } + + @Override + public void putChar6String(int position, int length, String s) { + + String upperCaseString = s.toUpperCase(); + int offset = 0; + for (int i = 0; i < s.length() ; i++) { + char character = upperCaseString.charAt(i); + int intValue = (int) character - 32; + if (intValue > -1 && intValue < 64) { + this.putInteger(position + offset,6, intValue); + offset = offset + 6; + } else { + this.putInteger(position + offset,6,0); + position = position + 6; + } + } + } + + @Override + public int getInteger(int position, int length) { + StringBuffer sb = new StringBuffer(); + for (int i = 0;i < length;i++){ + if (this.get(position + i)) { + sb.append("1"); + } else { + sb.append("0"); + } + } + return Integer.parseInt(sb.toString(), 2); + } + + @Override + public String getChar6String(int position, int length) { + + StringBuilder stringBuilder = new StringBuilder(); + + int chars = length / 6; + + for (int i = 0; i < chars; i++) { + int newPosition = position + i * 6; + + int x = this.getInteger(newPosition, 6); + x = x + 32; + + char c = (char) x; + stringBuilder.append(c); + + } + + return stringBuilder.toString().trim(); + } + + @Override + public String getChar5String(int position, int length) { + + StringBuilder stringBuilder = new StringBuilder(); + + int chars = length / 5; + + for (int i = 0; i < chars; i++) { + int newPosition = position + i * 5; + + int x = getInteger(newPosition, 5); + x = x + 42; + + char c = (char) x; + stringBuilder.append(c); + + } + + return stringBuilder.toString().trim(); + } + +} diff --git a/src/net/gcdc/asn1/uper/ByteCoder.java b/src/net/gcdc/asn1/uper/ByteCoder.java new file mode 100644 index 0000000..bdbbdf5 --- /dev/null +++ b/src/net/gcdc/asn1/uper/ByteCoder.java @@ -0,0 +1,34 @@ +package net.gcdc.asn1.uper; + +import java.lang.annotation.Annotation; +import java.lang.reflect.Field; + +class ByteCoder implements Decoder, Encoder { + + @Override public boolean canEncode(T obj, Annotation[] extraAnnotations) { + return obj instanceof Byte; + } + + @Override public void encode(BitBuffer bitbuffer, T obj, Annotation[] extraAnnotations) throws Asn1EncodingException { + UperEncoder.encodeConstrainedInt(bitbuffer, ((Byte) obj).byteValue() & 0xff, 0, 255); + UperEncoder.logger.debug(String.format("BYTE %s", ((Byte) obj).byteValue())); + } + + @Override public boolean canDecode(Class classOfT, Annotation[] extraAnnotations) { + return Byte.class.isAssignableFrom(classOfT) || byte.class.isAssignableFrom(classOfT); + } + + @SuppressWarnings("unchecked") + @Override public T decode(BitBuffer bitbuffer, + Class classOfT, Field field, + Annotation[] extraAnnotations) { + UperEncoder.logger.debug("BYTE"); + return (T) new Byte((byte) UperEncoder.decodeConstrainedInt(bitbuffer, UperEncoder.newRange(0, 255, false))); + } + + @Override + public T getDefault(Class classOfT, Annotation[] extraAnnotations) { + throw new IllegalArgumentException("Default Byte not yet implemented"); + } + +} \ No newline at end of file diff --git a/src/net/gcdc/asn1/uper/ChoiceCoder.java b/src/net/gcdc/asn1/uper/ChoiceCoder.java new file mode 100644 index 0000000..4e258a5 --- /dev/null +++ b/src/net/gcdc/asn1/uper/ChoiceCoder.java @@ -0,0 +1,161 @@ +package net.gcdc.asn1.uper; + +import java.lang.annotation.Annotation; +import java.lang.reflect.Field; + +import net.gcdc.asn1.datatypes.Choice; +import net.gcdc.asn1.uper.UperEncoder.Asn1ContainerFieldSorter; + +class ChoiceCoder implements Decoder, Encoder { + + @Override public boolean canEncode(T obj, Annotation[] extraAnnotations) { + Class type = obj.getClass(); + AnnotationStore annotations = new AnnotationStore(type.getAnnotations(), + extraAnnotations); + return annotations.getAnnotation(Choice.class) != null; + } + + @Override public void encode(BitBuffer bitbuffer, T obj, Annotation[] extraAnnotations) throws Asn1EncodingException { + Class type = obj.getClass(); + AnnotationStore annotations = new AnnotationStore(type.getAnnotations(),extraAnnotations); + UperEncoder.logger.debug("CHOICE"); + int nonNullIndex = 0; + Field nonNullField = null; + Object nonNullFieldValue = null; + int currentIndex = 0; + Asn1ContainerFieldSorter sorter = new Asn1ContainerFieldSorter(type); + try { + for (Field f : sorter.ordinaryFields) { + if (f.get(obj) != null) { + nonNullIndex = currentIndex; + nonNullFieldValue = f.get(obj); + nonNullField = f; + break; + } + currentIndex++; + } + if (nonNullFieldValue != null) { + if (UperEncoder.hasExtensionMarker(annotations)) { + boolean extensionBit = false; + UperEncoder.logger.debug(String.format("with extension marker, set to %s", extensionBit)); + bitbuffer.put(extensionBit); + } + if (sorter.ordinaryFields.size() > 1) { // Encode index only if more than one. + UperEncoder.logger.debug(String.format("with chosen element indexed %d", nonNullIndex)); + UperEncoder.encodeConstrainedInt(bitbuffer, nonNullIndex, 0, + sorter.ordinaryFields.size() - 1); + } + UperEncoder.encode2(bitbuffer, nonNullFieldValue, nonNullField.getAnnotations()); + return; + } else if (UperEncoder.hasExtensionMarker(annotations)) { + //CG encoding of extension fields + currentIndex = 0; + for (Field f : sorter.extensionFields) { + if (f.get(obj) != null) { + nonNullIndex = currentIndex; + nonNullFieldValue = f.get(obj); + nonNullField = f; + break; + } + currentIndex++; + } + if (nonNullField == null) { + UperEncoder.logger.debug(String.format("without choice of extension")); + return; + } + boolean extensionBit = true; + UperEncoder.logger.debug(String.format("with extension marker, set to <%s>", extensionBit)); + bitbuffer.put(extensionBit); + + //CG encode extension values + //Always encode index of the element + UperEncoder.logger.debug(String.format("with chosen extension element indexed %d", nonNullIndex)); + + //encode small integer even with value 0 + UperEncoder.encodeSmallInt(bitbuffer, nonNullIndex); + + //Encode as open field + UperEncoder.encodeAsOpenType(bitbuffer, nonNullFieldValue, nonNullField.getAnnotations()); + return; + } else { + throw new IllegalArgumentException("Not Extension and All ordinary fields of Choice are null"); + } + } catch (IllegalArgumentException | IllegalAccessException e) { + throw new IllegalArgumentException("can't encode " + obj, e); + } catch (Asn1EncodingException e) { + throw new Asn1EncodingException("." + type.getName(), e); + } + } + + @Override public boolean canDecode(Class classOfT, Annotation[] extraAnnotations) { + AnnotationStore annotations = new AnnotationStore(classOfT.getAnnotations(), + extraAnnotations); + return annotations.getAnnotation(Choice.class) != null; + } + + @Override public T decode(BitBuffer bitbuffer, + Class classOfT, Field field1, + Annotation[] extraAnnotations) { + AnnotationStore annotations = new AnnotationStore(classOfT.getAnnotations(),extraAnnotations); + UperEncoder.logger.debug(String.format("CHOICE: %s", classOfT.getName())); + T result = UperEncoder.instantiate(classOfT); + Asn1ContainerFieldSorter sorter = new Asn1ContainerFieldSorter(classOfT); + + // Reset all fields, since default constructor initializes one. + for (Field f : sorter.allFields) { + try { + f.set(result, null); + } catch (IllegalArgumentException | IllegalAccessException e) { + throw new IllegalArgumentException("can't decode " + classOfT, e); + } + } + if (UperEncoder.hasExtensionMarker(annotations)) { + UperEncoder.logger.debug("with extension marker"); + boolean extensionPresent = bitbuffer.get(); + if (extensionPresent) { + //CG extension support added + int i = (int) UperEncoder.decodeSmallInt(bitbuffer); + UperEncoder.logger.debug(String.format("extension with index %d is present",i)); + Field field = sorter.extensionFields.size() > i ? sorter.extensionFields.get(i) : null; + Class classOfElement = field != null ? field.getType() : null; + if (field != null) { + try { + Object decodedValue = UperEncoder.decodeAsOpenType(bitbuffer, classOfElement,field, field.getAnnotations()); + if (field != null) { + field.set(result, decodedValue); + } + return result; + } catch (IllegalArgumentException | IllegalAccessException e) { + throw new IllegalArgumentException("can't decode " + classOfT, e); + } + } else { + //CG skip the unknown extension element + UperEncoder.decodeSkipUnknownElement(bitbuffer, classOfT.getSimpleName()); + return null; + } + //throw new UnsupportedOperationException("choice extension is not implemented yet"); + } else { + UperEncoder.logger.debug(String.format("no extension present")); + //no extension is present + //We already consumed the bit, keep processing as if there were no extension. + } + } + int index = (int) UperEncoder.decodeConstrainedInt(bitbuffer, + UperEncoder.newRange(0, sorter.ordinaryFields.size() - 1, false)); + Field f = sorter.ordinaryFields.get(index); + UperEncoder.logger.debug(String.format("CHOICE: selected %s", f.getName())); + Object fieldValue = UperEncoder.decodeAny(bitbuffer, f.getType(),f, f.getAnnotations()); + try { + f.set(result, fieldValue); + } catch (IllegalArgumentException | IllegalAccessException e) { + throw new IllegalArgumentException("can't decode " + classOfT, e); + } + return result; + } + + @Override + public T getDefault(Class classOfT, Annotation[] extraAnnotations) { + throw new IllegalArgumentException("Default Choice not yet implemented"); + } + +} \ No newline at end of file diff --git a/src/net/gcdc/asn1/uper/Decoder.java b/src/net/gcdc/asn1/uper/Decoder.java new file mode 100644 index 0000000..36a7bbe --- /dev/null +++ b/src/net/gcdc/asn1/uper/Decoder.java @@ -0,0 +1,10 @@ +package net.gcdc.asn1.uper; + +import java.lang.annotation.Annotation; +import java.lang.reflect.Field; + +public interface Decoder { + boolean canDecode(Class classOfT, Annotation[] extraAnnotations); + T decode(BitBuffer bitbuffer, Class classOfT,Field f, Annotation[] extraAnnotations); + T getDefault(Class classOfT, Annotation[] extraAnnotations); +} diff --git a/src/net/gcdc/asn1/uper/Document2.txt b/src/net/gcdc/asn1/uper/Document2.txt new file mode 100644 index 0000000..176ec23 --- /dev/null +++ b/src/net/gcdc/asn1/uper/Document2.txt @@ -0,0 +1,34 @@ + if (restrictionAnnotation.value() == CharacterRestriction.UTF8String) { + // UTF8 length + BitBuffer stringbuffer = ByteBitBuffer.createInfinite(); + + //char array replaced - begin + byte[] stringasbytearray = string.getBytes(StandardCharsets.UTF_8); + + for (byte b: stringasbytearray){ + UperEncoder.encodeConstrainedInt(stringbuffer, byte & 0xff, 0, 255); + } + //char array replaced - end + + stringbuffer.flip(); + if (stringbuffer.limit() % 8 != 0) { + throw new AssertionError("utf8 encoding resulted not in multiple of 8 bits"); + } + int numOctets = (stringbuffer.limit() + 7) / 8; // Actually +7 is not needed here, + // since we already checked with %8. + int position1 = bitbuffer.position(); + UperEncoder.encodeLengthDeterminant(bitbuffer, numOctets); + UperEncoder.logger.debug(String.format("UTF8String %s, length %d octets, encoded as %s", string, numOctets, bitbuffer.toBooleanStringFromPosition(position1))); + int position2 = bitbuffer.position(); + for (int i = 0; i < stringbuffer.limit(); i++) { + bitbuffer.put(stringbuffer.get()); + } + UperEncoder.logger.debug(String.format("UTF8String %s, encoded length %d octets, value bits: %s", string, numOctets, bitbuffer.toBooleanStringFromPosition(position2))); + return; + + + + + + +new String(bytearray, StandardCharsets.UTF_8)); \ No newline at end of file diff --git a/src/net/gcdc/asn1/uper/Encoder.java b/src/net/gcdc/asn1/uper/Encoder.java new file mode 100644 index 0000000..8932d3f --- /dev/null +++ b/src/net/gcdc/asn1/uper/Encoder.java @@ -0,0 +1,8 @@ +package net.gcdc.asn1.uper; + +import java.lang.annotation.Annotation; + +public interface Encoder { + boolean canEncode(T obj, Annotation[] extraAnnotations); + void encode(BitBuffer bitbuffer, T obj, Annotation[] extraAnnotations) throws Asn1EncodingException; +} diff --git a/src/net/gcdc/asn1/uper/EnumCoder.java b/src/net/gcdc/asn1/uper/EnumCoder.java new file mode 100644 index 0000000..f86fe5f --- /dev/null +++ b/src/net/gcdc/asn1/uper/EnumCoder.java @@ -0,0 +1,156 @@ +package net.gcdc.asn1.uper; + +import java.lang.annotation.Annotation; +import java.lang.reflect.Field; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import net.gcdc.asn1.datatypes.Asn1Default; +import net.gcdc.asn1.datatypes.IsExtension; + +class EnumCoder implements Decoder, Encoder { + + @Override public boolean canEncode(T obj, Annotation[] extraAnnotations) { + Class type = obj.getClass(); + return type.isEnum(); + } + + @Override public void encode(BitBuffer bitbuffer, T obj, Annotation[] extraAnnotations) throws Asn1EncodingException { + Class type = obj.getClass(); + AnnotationStore annotations = new AnnotationStore(type.getAnnotations(), extraAnnotations); + String pos = String.format("%d.%d", bitbuffer.position()/8 , bitbuffer.position() % 8); + UperEncoder.logger.debug(String.format("Position %s ENUM",pos)); + try { + int position = bitbuffer.position(); + + List values = Arrays.asList(type.getEnumConstants()); + int enumIndex = values.indexOf(obj); + + if (!UperEncoder.hasExtensionMarker(annotations)) { + UperEncoder.logger.debug(String.format("enum without extension: index %d value %s, encoding index...", enumIndex,obj.toString())); + UperEncoder.encodeConstrainedInt(bitbuffer, enumIndex, 0, values.size() - 1); + return; + } else { + List valuesWithinExtensionRoot = new ArrayList<>(); + List valuesOutsideExtensionRoot = new ArrayList<>(); + for (Object c : type.getEnumConstants()) { + String field = c.toString(); + boolean isExtension = false; + try { + isExtension = type.getField(field).isAnnotationPresent(IsExtension.class); + } catch (NoSuchFieldException e) { + throw new IllegalArgumentException("Illegal value for enum field " , e); + } catch (SecurityException e) { + throw new IllegalArgumentException("Illegal access restriction for enum field " , e); + } + + if (!isExtension) { + valuesWithinExtensionRoot.add(c); + } else { + valuesOutsideExtensionRoot.add(c); + } + } + + if (valuesWithinExtensionRoot.contains(obj)) { + UperEncoder.logger.debug(String.format("Extension indicator set to false")); + bitbuffer.put(false); + int index = valuesWithinExtensionRoot.indexOf(obj); + UperEncoder.encodeConstrainedInt(bitbuffer, index, 0, valuesWithinExtensionRoot.size() - 1); + UperEncoder.logger.debug(String.format("ENUM with extension: index %d value %s, encoded as root value <%s>", index, obj.toString(), + bitbuffer.toBooleanStringFromPosition(position))); + return; + } else { + //CG encode the index in the extension list as small integer + UperEncoder.logger.debug(String.format("Extension indicator set to true")); + bitbuffer.put(true); + int index = valuesOutsideExtensionRoot.indexOf(obj); + + UperEncoder.encodeSmallInt(bitbuffer, index); + UperEncoder.logger.debug(String.format("ENUM with extension: index %d value %s, encoded as extension <%s>", index, obj.toString(), + bitbuffer.toBooleanStringFromPosition(position))); + } + } + } catch (Asn1EncodingException e) { + throw new Asn1EncodingException(type.getName(), e); + } + } + + @Override public boolean canDecode(Class classOfT, Annotation[] extraAnnotations) { + return classOfT.isEnum(); + } + + @Override public T decode(BitBuffer bitbuffer, + Class classOfT, Field field, + Annotation[] extraAnnotations) { + AnnotationStore annotations = new AnnotationStore(classOfT.getAnnotations(), extraAnnotations); + UperEncoder.logger.debug("ENUM"); + boolean extensionPresent = false; + if (UperEncoder.hasExtensionMarker(annotations)) { + extensionPresent = bitbuffer.get(); + UperEncoder.logger.debug(String.format("with extension marker, %s" , extensionPresent ? "present" : "absent")); + } + T[] enumValues = classOfT.getEnumConstants(); + + int rootValues = 0; + + boolean isExtension = false; + for (Object c : enumValues) { + String value = c.toString(); + try { + isExtension = classOfT.getField(value).isAnnotationPresent(IsExtension.class); + } catch (NoSuchFieldException e) { + throw new IllegalArgumentException("Illegal value for extension field " , e); + } catch (SecurityException e) { + throw new IllegalArgumentException("Illegal value for extension field " , e); + } + + if (!isExtension) rootValues++; + } + + // + int index = 0; + if (!extensionPresent){ + //root element + index = (int) UperEncoder.decodeConstrainedInt(bitbuffer, + UperEncoder.newRange(0, rootValues - 1, false)); + } else { + //extension element, decode as small int without restriction + index = (int) UperEncoder.decodeSmallInt(bitbuffer); + //the encoded index is an index within the extensions list only + index = index + rootValues; + } + + if (index > enumValues.length - 1 && extensionPresent) { + //this is an unknown extension + UperEncoder.logger.debug(String.format("Enum contains unknown extendion index %d" , index)); + return null; + } + if (index > enumValues.length - 1 && !extensionPresent) { + //this should not happen + throw new IllegalArgumentException( + "decoded enum index " + index + " is larger then number of elements (0.." + + enumValues.length + ") in " + classOfT.getName()); + } + T value = enumValues[index]; + UperEncoder.logger.debug(String.format("Enum decoded as %s" , value.toString())); + return value; + } + + @SuppressWarnings("unchecked") + @Override + public T getDefault(Class classOfT, Annotation[] extraAnnotations) { + AnnotationStore annotations = new AnnotationStore(classOfT.getAnnotations(), extraAnnotations); + Asn1Default defaultAnnotation = annotations.getAnnotation(Asn1Default.class); + if (defaultAnnotation == null) return null; + + for (Object c : classOfT.getEnumConstants()) { + if (c.toString().equals(defaultAnnotation.value())) { + return (T) c; + } + } + + return null; + } + +} \ No newline at end of file diff --git a/src/net/gcdc/asn1/uper/IntCoder.java b/src/net/gcdc/asn1/uper/IntCoder.java new file mode 100644 index 0000000..97f427d --- /dev/null +++ b/src/net/gcdc/asn1/uper/IntCoder.java @@ -0,0 +1,266 @@ +package net.gcdc.asn1.uper; + +import java.lang.annotation.Annotation; +import java.lang.reflect.Field; +import java.math.BigInteger; +import net.gcdc.asn1.datatypes.Asn1BigInteger; +import net.gcdc.asn1.datatypes.Asn1Default; +import net.gcdc.asn1.datatypes.Asn1Integer; +import net.gcdc.asn1.datatypes.IntMinValue; +import net.gcdc.asn1.datatypes.IntRange; + + +class IntCoder implements Encoder, Decoder { + + + + @Override public boolean canDecode(Class classOfT, Annotation[] extraAnnotations) { + return classOfT == Asn1Integer.class || + classOfT == Asn1BigInteger.class|| + classOfT == BigInteger.class || + classOfT == Long.class || + classOfT == Integer.class || + classOfT == Short.class ; + } + + + @Override public T decode(BitBuffer bitbuffer, + Class classOfT, Field field, + Annotation[] extraAnnotations) { + AnnotationStore annotations = new AnnotationStore(classOfT.getAnnotations(),extraAnnotations); + String pos = String.format("Position: %d.%d", bitbuffer.position()/8 , bitbuffer.position() % 8); + UperEncoder.logger.debug(String.format("%s: INTEGER",pos)); + IntRange intRange = annotations.getAnnotation(IntRange.class); + IntMinValue minValue = annotations.getAnnotation(IntMinValue.class); + + + if (intRange == null) { + return decodeUnconstrainedInteger(bitbuffer, classOfT, extraAnnotations, minValue); + } + UperEncoder.logger.debug(String.format("Integer, range %d..%d", intRange.minValue(), intRange.maxValue())); + return decodeConstrainedInteger(bitbuffer, classOfT, intRange, extraAnnotations); + } + + @SuppressWarnings("unchecked") + private T decodeConstrainedInteger(BitBuffer bitbuffer, Class classOfT, IntRange intRange, Annotation[] extraAnnotations) { + + long value = UperEncoder.decodeConstrainedInt(bitbuffer, intRange); + UperEncoder.logger.debug(String.format("decoded as %d", value)); + + try { + if (classOfT == Asn1BigInteger.class) { + return ((T) new Asn1BigInteger(value)); + } else if (classOfT == Asn1Integer.class) { + return (T) new Asn1Integer(value); + } else if (classOfT == BigInteger.class) { + return (T) BigInteger.valueOf(value); + } else if (classOfT == Long.class) { + return (T) Long.valueOf(value); + } else if (classOfT == Integer.class) { + return (T) Integer.valueOf(Long.valueOf(value).intValue()); + } else if (classOfT == Short.class) { + return (T) Short.valueOf(Long.valueOf(value).shortValue()); + } + } catch (Exception e) { + throw new IllegalArgumentException("size too small " + classOfT.getName() + ": " + e); + } + + return null; + + + } + + @Override public boolean canEncode(T obj, Annotation[] extraAnnotations) { + return obj instanceof Asn1Integer || + obj instanceof Asn1BigInteger || + obj instanceof BigInteger || + obj instanceof Long || + obj instanceof Integer || + obj instanceof Short; + } + + @Override public void encode(BitBuffer bitbuffer, T obj, Annotation[] extraAnnotations) throws Asn1EncodingException { + Class type = obj.getClass(); + AnnotationStore annotations = new AnnotationStore(type.getAnnotations(), extraAnnotations); + IntRange range = annotations.getAnnotation(IntRange.class); + IntMinValue minValue = annotations.getAnnotation(IntMinValue.class); + int position = bitbuffer.position(); + + //get value + if (range == null) { + + try { + encodeUnconstrainedInteger(bitbuffer, obj, extraAnnotations,minValue); + } catch (Asn1EncodingException e) { + throw new Asn1EncodingException(" " + type.getSimpleName(), e); + } catch (Exception e1){ + throw new Asn1EncodingException(" " + type.getSimpleName() + " - " + e1.getLocalizedMessage()); + } + UperEncoder.logger.debug(String.format("INT(%s): %s", obj, bitbuffer.toBooleanStringFromPosition(position))); + + + } else { + + try { + + long value = 0; + if (obj instanceof BigInteger) { + try { + value = ((BigInteger) obj).longValue(); + } catch (Exception e) { + + UperEncoder.logger.debug("constrained BigInteger is too big for constrained int"); + throw new Asn1EncodingException("constrained BigInteger is too big for constrained int" + type.getSimpleName()); + } + } if (obj instanceof Asn1BigInteger) { + try { + value = ((Asn1BigInteger) obj).longValue(); + } catch (Exception e) { + + UperEncoder.logger.debug("constrained Asn1BigInteger is too big for constrained int"); + throw new Asn1EncodingException("constrained Asn1BigInteger is too big for constrained int" + type.getSimpleName()); + } + } if (obj instanceof Asn1Integer) { + try { + value = Asn1Integer.toLong((Asn1Integer) obj); + } catch (Exception e) { + + UperEncoder.logger.debug("constrained BigInteger is too big for constrained int"); + throw new Asn1EncodingException("constrained BigInteger is too big for constrained int" + type.getSimpleName()); + } + } else if (obj instanceof Long) { + value = ((Long) obj).longValue(); + } else if (obj instanceof Integer) { + value = ((Integer) obj).longValue(); + } else if (obj instanceof Short) { + value = ((Short) obj).longValue(); + } + + UperEncoder.encodeConstrainedInt(bitbuffer, value, range.minValue(), range.maxValue(), range.hasExtensionMarker()); + } catch (Asn1EncodingException e) { + throw new Asn1EncodingException(" " + type.getSimpleName(), e); + } catch (Exception e1){ + throw new Asn1EncodingException(" " + type.getSimpleName() + " - " + e1.getLocalizedMessage()); + } + UperEncoder.logger.debug(String.format("INT(%s): %s", obj, bitbuffer.toBooleanStringFromPosition(position))); + } + return; + } + + private void encodeUnconstrainedInteger(BitBuffer bitbuffer, Object obj, Annotation[] annotations, IntMinValue minValue) throws Asn1EncodingException { + + + BigInteger bint = null; + try { + if (obj instanceof BigInteger) { + bint = (BigInteger) obj; + } else if (obj instanceof Asn1BigInteger) { + bint = BigInteger.valueOf(((Asn1BigInteger) obj).longValue()); + } else if (obj instanceof Asn1Integer) { + bint = BigInteger.valueOf(((Asn1Integer) obj).value()); + } else if (obj instanceof Long) { + bint = BigInteger.valueOf(((Long) obj).longValue()); + } else if (obj instanceof Integer) { + bint = BigInteger.valueOf(((Integer) obj).longValue()); + } else if (obj instanceof Short) { + bint = BigInteger.valueOf(((Short) obj).longValue()); + } + } catch (Exception e1){ + throw new Asn1EncodingException(" " + obj.getClass().getSimpleName() + " - " + e1.getLocalizedMessage()); + } + + + if (minValue != null) { + bint.subtract(BigInteger.valueOf(minValue.minValue())); + } + + byte[] array = bint.toByteArray(); + int lengthInOctets = array.length; + int position1 = bitbuffer.position(); + try { + UperEncoder.encodeLengthDeterminant(bitbuffer, lengthInOctets); + } catch (Asn1EncodingException e) { + throw new Asn1EncodingException(" length determinant of INTEGER", e); + } + int position2 = bitbuffer.position(); + for (byte b : array) { + bitbuffer.putByte(b); + } + UperEncoder.logger.debug(String.format("INTEGER Int(%s): len %s, val %s", bint.toString(), + bitbuffer.toBooleanString(position1, position2 - position1), + bitbuffer.toBooleanStringFromPosition(position2))); + return; + } + + @SuppressWarnings("unchecked") + public T decodeUnconstrainedInteger(BitBuffer bitbuffer, Class classOfT, Annotation[] extraAnnotations,IntMinValue minValue) { + AnnotationStore annotations = new AnnotationStore(classOfT.getAnnotations(), extraAnnotations); + + String pos = String.format("%d.%d", bitbuffer.position()/8 , bitbuffer.position() % 8); + UperEncoder.logger.debug(String.format("Position %s BIG INT",pos)); + IntRange intRange = annotations.getAnnotation(IntRange.class); + if (intRange != null && intRange.maxValue() > 0) { + throw new UnsupportedOperationException("Big int with upper range is not supported yet"); + } + + int lengthInOctets = (int) UperEncoder.decodeLengthDeterminant(bitbuffer); + BitBuffer valueBits = ByteBitBuffer.allocate(lengthInOctets * 8); + for (int i = 0; i < lengthInOctets * 8; i++) { + valueBits.put(bitbuffer.get()); + } + valueBits.flip(); + BigInteger resultValue = new BigInteger(+1, valueBits.array()); + if (minValue != null) { + resultValue.add(BigInteger.valueOf(minValue.minValue())); + } + + UperEncoder.logger.debug(String.format("INTEGER Decoded as %s", resultValue)); + + try { + if (classOfT == Asn1BigInteger.class) { + return (T) new Asn1BigInteger(resultValue); + } else if (classOfT == BigInteger.class) { + return (T) resultValue; + } else if (classOfT == Long.class) { + return (T) Long.valueOf(resultValue.longValueExact()); + } else if (classOfT == Integer.class) { + return (T) Integer.valueOf(resultValue.intValueExact()); + } else if (classOfT == Short.class) { + return (T) Short.valueOf(resultValue.shortValueExact()); + } + } catch (Exception e){ + UperEncoder.logger.debug(String.format("INTEGER Decoded as %s is too big for data type", resultValue)); + } + return null; + } + + @SuppressWarnings("unchecked") + @Override + public T getDefault(Class classOfT, Annotation[] extraAnnotations) { + AnnotationStore annotations = new AnnotationStore(classOfT.getAnnotations(), extraAnnotations); + Asn1Default defaultAnnotation = annotations.getAnnotation(Asn1Default.class); + if (defaultAnnotation == null) return null; + //check whether the class has a constructor for numeric types + String valueString = defaultAnnotation.value(); + long value = Long.parseLong(valueString); + + try { + if (classOfT == Asn1BigInteger.class) { + return ((T) new Asn1BigInteger(value)); + } else if (classOfT == BigInteger.class) { + return (T) BigInteger.valueOf(value); + } else if (classOfT == Long.class) { + return (T) Long.valueOf(value); + } else if (classOfT == Integer.class) { + return (T) Integer.valueOf(Long.valueOf(value).intValue()); + } else if (classOfT == Short.class) { + return (T) Short.valueOf(Long.valueOf(value).shortValue()); + } + } catch (Exception e) { + throw new IllegalArgumentException("size too small " + classOfT.getName() + ": " + e); + } + + return null; + } + +} \ No newline at end of file diff --git a/src/net/gcdc/asn1/uper/SeqOfCoder.java b/src/net/gcdc/asn1/uper/SeqOfCoder.java new file mode 100644 index 0000000..488e51b --- /dev/null +++ b/src/net/gcdc/asn1/uper/SeqOfCoder.java @@ -0,0 +1,156 @@ +package net.gcdc.asn1.uper; + +import java.lang.annotation.Annotation; +import java.lang.reflect.Field; +import java.lang.reflect.ParameterizedType; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Iterator; +import java.util.List; + +import net.gcdc.asn1.datatypes.FixedSize; +import net.gcdc.asn1.datatypes.SizeRange; +import net.gcdc.asn1.uper.SimpleTypeResolver.Unknown; + + +class SeqOfCoder implements Decoder, Encoder { + + @Override public boolean canEncode(T obj, Annotation[] extraAnnotations) { + return obj instanceof List; + } + + @Override public void encode(BitBuffer bitbuffer, T obj, Annotation[] extraAnnotations) throws Asn1EncodingException { + Class type = obj.getClass(); + UperEncoder.logger.debug(String.format("SEQUENCE OF %s",obj.getClass().getName())); + AnnotationStore annotations = new AnnotationStore(type.getAnnotations(), extraAnnotations); + List list = (List) obj; + + final FixedSize fixedSize = annotations.getAnnotation(FixedSize.class); + + //CG pass annotations too each field encoding + Annotation[] annotationArray = new Annotation[] {}; + if (annotations != null & annotations.getAnnotations() != null && !annotations.getAnnotations().isEmpty()) { + ArrayList fieldAnnotations = new ArrayList(); + fieldAnnotations.addAll(annotations.getAnnotations()); + annotationArray = new Annotation[fieldAnnotations.size()]; + for (int i = 0; i< fieldAnnotations.size();i++){ + annotationArray[i] = fieldAnnotations.get(i); + } + } + + SizeRange sizeRange = annotations.getAnnotation(SizeRange.class); + if (fixedSize != null) + sizeRange = new SizeRange() { + @Override public Class annotationType() { return SizeRange.class; } + @Override public int minValue() { return fixedSize.value(); } + @Override public int maxValue() { return fixedSize.value(); } + @Override public boolean hasExtensionMarker() { return false; } + }; + if (sizeRange == null) { + int position1 = bitbuffer.position(); + try { + UperEncoder.encodeLengthDeterminant(bitbuffer, list.size()); + } catch (Asn1EncodingException e) { + throw new Asn1EncodingException(" number of elements ", e); + } + UperEncoder.logger.debug(String.format("unbound size %d, encoded as %s", list.size(), + bitbuffer.toBooleanStringFromPosition(position1))); + UperEncoder.logger.debug(String.format(" all elems of Seq Of: %s", list )); + for (Object elem : list) { + try { + UperEncoder.encode2(bitbuffer, elem, annotationArray); + } catch (Asn1EncodingException e) { + throw new Asn1EncodingException(" element " + elem.toString(), e); + } + } + return; + } + boolean outsideOfRange = list.size() < sizeRange.minValue() + || sizeRange.maxValue() < list.size(); + if (outsideOfRange && !sizeRange.hasExtensionMarker()) { throw new IllegalArgumentException( + "Out-of-range size for " + obj.getClass() + ", expected " + + sizeRange.minValue() + ".." + sizeRange.maxValue() + ", got " + + list.size()); } + if (sizeRange.hasExtensionMarker()) { + bitbuffer.put(outsideOfRange); + UperEncoder.logger.debug(String.format("With Extension Marker, %s of range (%d <= %d <= %d)", + (outsideOfRange ? "outside" : "inside"), sizeRange.minValue(), list.size(), + sizeRange.maxValue())); + if (outsideOfRange) { throw new UnsupportedOperationException( + "Sequence-of size range extensions are not implemented yet, range " + + sizeRange.minValue() + ".." + sizeRange.maxValue() + + ", requested size " + list.size()); } + } + UperEncoder.logger.debug(String.format("seq-of of constrained size %d, encoding size...", list.size())); + UperEncoder.encodeConstrainedInt(bitbuffer, list.size(), sizeRange.minValue(), sizeRange.maxValue()); + UperEncoder.logger.debug(String.format(" all elems of Seq Of: %s", list)); + for (Object elem : list) { + UperEncoder.encode2(bitbuffer, elem, new Annotation[] {}); + } + } + + @Override public boolean canDecode(Class classOfT, Annotation[] extraAnnotations) { + return List.class.isAssignableFrom(classOfT); + } + + @SuppressWarnings("unchecked") + @Override public T decode(BitBuffer bitbuffer, + Class classOfT,Field field, + Annotation[] extraAnnotations) { + AnnotationStore annotations = new AnnotationStore(classOfT.getAnnotations(), + extraAnnotations); + UperEncoder.logger.debug(String.format("SEQUENCE OF for %s", classOfT)); + FixedSize fixedSize = annotations.getAnnotation(FixedSize.class); + SizeRange sizeRange = annotations.getAnnotation(SizeRange.class); + + //CG pass annotations from the sequence to each element encoding + Annotation[] annotationArray = new Annotation[] {}; + + if (annotations != null && annotations.getAnnotations() != null && !annotations.getAnnotations().isEmpty()){ + annotationArray = new Annotation[annotations.getAnnotations().size()]; + Iterator it = annotations.getAnnotations().iterator(); + int i = 0; + while (it.hasNext()) { + annotationArray[i] = it.next(); + i++; + } + } + + + long size = + (fixedSize != null) ? fixedSize.value() : + (sizeRange != null) ? UperEncoder.decodeConstrainedInt(bitbuffer, UperEncoder.intRangeFromSizeRange(sizeRange)) : + UperEncoder.decodeLengthDeterminant(bitbuffer); + Collection coll = new ArrayList((int) size); + + Class classOfElements; + Class[] typeArgs = SimpleTypeResolver.resolveRawArguments(List.class, classOfT); + classOfElements = typeArgs[0]; + if (classOfElements == null || classOfElements == Unknown.class) { + try { + ParameterizedType elementType = (ParameterizedType) field.getGenericType(); + classOfElements = (Class) elementType.getActualTypeArguments()[0]; + } catch (SecurityException e) { + throw new IllegalArgumentException("Can't resolve type of elements for " + classOfT.getName()); + } + } + for (int i = 0; i < size; i++) { + coll.add(UperEncoder.decodeAny(bitbuffer, classOfElements,field, annotationArray)); + } + + T result = null; + try { + result = UperEncoder.instantiate(classOfT, coll); + } catch (Exception e) { + result = (T) coll; + } + return result; + + } + + + @Override + public T getDefault(Class classOfT, Annotation[] extraAnnotations) { + throw new IllegalArgumentException("Default Sequence not yet implemented"); + } +} \ No newline at end of file diff --git a/src/net/gcdc/asn1/uper/SeqOfFixedSize.java b/src/net/gcdc/asn1/uper/SeqOfFixedSize.java new file mode 100644 index 0000000..f9029a3 --- /dev/null +++ b/src/net/gcdc/asn1/uper/SeqOfFixedSize.java @@ -0,0 +1,18 @@ +package net.gcdc.asn1.uper; + +import java.util.Arrays; +import java.util.Collection; + +import net.gcdc.asn1.datatypes.Asn1SequenceOf; +import net.gcdc.asn1.datatypes.FixedSize; + + +public class SeqOfFixedSize { + @FixedSize(3) + public static class Bar extends Asn1SequenceOf { + public Bar(Byte... coll) { this(Arrays.asList(coll)); } + public Bar(Collection coll) { super(coll); } + } + + +} diff --git a/src/net/gcdc/asn1/uper/SequenceCoder.java b/src/net/gcdc/asn1/uper/SequenceCoder.java new file mode 100644 index 0000000..d9ca491 --- /dev/null +++ b/src/net/gcdc/asn1/uper/SequenceCoder.java @@ -0,0 +1,269 @@ +package net.gcdc.asn1.uper; + +import java.lang.annotation.Annotation; +import java.lang.reflect.Field; +import java.util.ArrayDeque; +import java.util.Deque; + +import net.gcdc.asn1.datatypes.Asn1Default; +import net.gcdc.asn1.datatypes.Asn1SequenceOf; +import net.gcdc.asn1.datatypes.Sequence; +import net.gcdc.asn1.uper.UperEncoder.Asn1ContainerFieldSorter; + +class SequenceCoder implements Decoder, Encoder { + + @Override public boolean canEncode(T obj, Annotation[] extraAnnotations) { + Class type = obj.getClass(); + AnnotationStore annotations = new AnnotationStore(type.getAnnotations(), extraAnnotations); + + return annotations.getAnnotation(Sequence.class) != null; + } + + @Override public void encode(BitBuffer bitbuffer, T obj, Annotation[] extraAnnotations) throws Asn1EncodingException { + Class type = obj.getClass(); + AnnotationStore annotations = new AnnotationStore(type.getAnnotations(), extraAnnotations); + String pos = String.format("%d.%d", bitbuffer.position()/8 , bitbuffer.position() % 8); + UperEncoder.logger.debug(String.format("Position %s: SEQUENCE %s", pos, type.getSimpleName())); + + Asn1ContainerFieldSorter sorter = new Asn1ContainerFieldSorter(type); + try { + if (UperEncoder.hasExtensionMarker(annotations)) { + boolean extensionsPresent = !sorter.extensionFields.isEmpty() + && UperEncoder.hasNonNullExtensions(obj, sorter); + UperEncoder.logger.debug(String.format("with extension marker, %s extensions, extensionBit: <%s>", + extensionsPresent ? "with" : "without", extensionsPresent)); + bitbuffer.put(extensionsPresent); + } + // Bitmask for optional fields. + for (Field f : sorter.optionalOrdinaryFields) { + + boolean fieldPresent = isPresent(f, f.get(obj)); + + UperEncoder.logger.debug(String.format("with optional field %s %s, presence encoded as bit <%s>", + f.getName(), fieldPresent ? "present" : "absent", fieldPresent)); + + bitbuffer.put(fieldPresent); // null means the field is absent. + } + + // All ordinary fields (fields within extension root). + for (Field f : sorter.ordinaryFields) { + //CG do not include default values + if (UperEncoder.isMandatory(f) || isPresent(f,f.get(obj))) { + + pos = String.format("Position: %d.%d", bitbuffer.position()/8 , bitbuffer.position() % 8); + UperEncoder.logger.debug(String.format("%s: Field %s", pos, f.getName())); + try { + UperEncoder.encode2(bitbuffer, f.get(obj), f.getAnnotations()); + } catch (Asn1EncodingException e) { + throw new Asn1EncodingException("." + f.getName(), e); + } catch (IllegalArgumentException e) { + throw new IllegalArgumentException("Illegal value for field " + f.getName(), e); + } + } + } + // Extension fields. + if (UperEncoder.hasExtensionMarker(annotations) + && !sorter.extensionFields.isEmpty() + && UperEncoder.hasNonNullExtensions(obj, sorter)) { + // Total extensions count. + int numExtensions = sorter.extensionFields.size(); + UperEncoder.logger.debug(String.format("continuing sequence : %d extension(s) are present, encoding length determinant for them...", numExtensions)); + UperEncoder.encodeLengthOfBitmask(bitbuffer, numExtensions); + // Bitmask for present extensions. + for (Field f : sorter.extensionFields) { + boolean fieldIsPresent = isPresent(f,f.get(obj)); + + UperEncoder.logger.debug(String.format("Extension %s is %s, presence encoded as <%s>", f.getName(), + fieldIsPresent ? "present" : "absent", fieldIsPresent ? "1" : "0")); + + bitbuffer.put(fieldIsPresent); + } + // Values of extensions themselves. + for (Field f : sorter.extensionFields) { + //CG do not encode default values + if (UperEncoder.isMandatory(f) || isPresent(f,f.get(obj))) { + UperEncoder.logger.debug(String.format("Encoding extension field %s", f.getName())); + try { + UperEncoder.encodeAsOpenType(bitbuffer, f.get(obj), f.getAnnotations()); + } catch (IllegalArgumentException e) { + throw new IllegalArgumentException("Illegal value for extension field " + f.getName(), e); + } + } + } + } + } catch (IllegalArgumentException | IllegalAccessException e) { + throw new IllegalArgumentException("can't encode " + obj, e); + } + sorter.revertAccess(); + } + + @SuppressWarnings("unchecked") + protected boolean isPresent(Field f, Object fieldObject){ + + if (fieldObject == null) return false; + + boolean fieldPresent = fieldObject != null; + + if (fieldObject instanceof Asn1SequenceOf) { + if (((Asn1SequenceOf)fieldObject).size() == 0){ + //CG do not encode optional empty sequences + fieldPresent = false; + } + } + + if (fieldObject instanceof String) { + if (((String)fieldObject).length() == 0){ + //CG do not encode optional empty sequences + fieldPresent = false; + } + } + + //CG DEFAULT VALUES + if (fieldPresent && UperEncoder.isDefault(f,fieldObject)) { + UperEncoder.logger.debug(String.format("Field %s has default value", f.getName())); + fieldPresent = false; + } + //CG No ASN1 + if (UperEncoder.isNotAsn1(f)) { + fieldPresent = false; + } + + return fieldPresent; + + } + + @Override public boolean canDecode(Class classOfT, Annotation[] extraAnnotations) { + AnnotationStore annotations = new AnnotationStore(classOfT.getAnnotations(), + extraAnnotations); + return annotations.getAnnotation(Sequence.class) != null; + } + + @Override public T decode(BitBuffer bitbuffer, + Class classOfT,Field f1, + Annotation[] extraAnnotations) { + UperEncoder.logger.debug(String.format("decode SEQUENCE %s",classOfT.getSimpleName())); + AnnotationStore annotations = new AnnotationStore(classOfT.getAnnotations(),extraAnnotations); + T result = UperEncoder.instantiate(classOfT); + Asn1ContainerFieldSorter sorter = new Asn1ContainerFieldSorter(classOfT); + boolean hasExtensionMarker = UperEncoder.hasExtensionMarker(annotations); + boolean extensionPresent = false; + if (hasExtensionMarker) { + extensionPresent = bitbuffer.get(); + UperEncoder.logger.debug(String.format("with extension marker, extension %s", extensionPresent ? "present!" : "absent")); + } + // Bitmask for optional fields. + Deque optionalFieldsMask = new ArrayDeque<>(sorter.optionalOrdinaryFields.size()); + for (Field f : sorter.optionalOrdinaryFields) { + optionalFieldsMask.add(bitbuffer.get()); + UperEncoder.logger.debug(String.format("with optional field %s %s" , f.getName() , optionalFieldsMask.getLast() ? "present" : "absent")); + } + // All ordinary fields (fields within extension root). + + for (Field f : sorter.ordinaryFields) { + if (!UperEncoder.isTestInstrumentation(f) && (UperEncoder.isMandatory(f) + || + (UperEncoder.isOptional(f) && optionalFieldsMask.pop()))) { + UperEncoder.logger.debug(String.format("Field : %s", f.getName())); + try { + f.set(result, UperEncoder.decodeAny(bitbuffer,f.getType(),f, f.getAnnotations())); + } catch (IllegalAccessException e) { + throw new IllegalArgumentException("can't access 'set method' for field " + f + " of class " + classOfT + " " + e, e); + } + } else { + //CG might have a default value + if (f.getAnnotation(Asn1Default.class) != null) { + try { + UperEncoder.logger.debug(String.format(String.format("Retrieve default for element : %s",f.getName()))); + f.set(result,UperEncoder.getDefault(f.getType(),f.getAnnotations())); + } catch (IllegalArgumentException e) { + throw new IllegalArgumentException("can't decode " + classOfT, e); + } catch (IllegalAccessException e) { + throw new IllegalArgumentException("can't decode " + classOfT, e); + } + } + } + } + if (!hasExtensionMarker) { + //done + sorter.revertAccess(); + return result; + } + + // Possible extensions + int numExtensions = 0; + if (UperEncoder.hasExtensionMarker(annotations)){ + if (extensionPresent) { + // Number of extensions. + numExtensions = (int) UperEncoder.decodeLengthOfBitmask(bitbuffer); + UperEncoder.logger.debug(String.format("sequence has %d extension(s)", numExtensions)); + // Bitmask for extensions. + boolean[] bitmaskValueIsPresent = new boolean[numExtensions]; + for (int i = 0; i < numExtensions; i++) { + bitmaskValueIsPresent[i] = bitbuffer.get(); + UperEncoder.logger.debug(String.format("extension %s is %s", i, bitmaskValueIsPresent[i] ? "present" : "absent")); + } + // Values. + UperEncoder.logger.debug("decoding extensions values..."); + for (int i = 0; i < numExtensions; i++) { + UperEncoder.logger.debug(String.format("sequence extension %s %s", i, bitmaskValueIsPresent[i] ? "present" : "absent")); + if (bitmaskValueIsPresent[i]) { + UperEncoder.logger.debug(String.format("decoding extension %d...", i)); + Field field = sorter.extensionFields.size() > i ? sorter.extensionFields.get(i) : null; + Class classOfElement = field != null ? field.getType() : null; + if (field != null) { + try { + Object decodedValue = UperEncoder.decodeAsOpenType(bitbuffer, classOfElement,field, field.getAnnotations()); + if (field != null) { + field.set(result, decodedValue); + } + } catch (IllegalArgumentException | IllegalAccessException e) { + throw new IllegalArgumentException("can't decode " + classOfT, e); + } + } else { + //CG skip the unknown extension element + UperEncoder.decodeSkipUnknownElement(bitbuffer, classOfT.getSimpleName()); + } + } else { + //CG the absent extension filed might have a default value + Field field = sorter.extensionFields.size() > i ? sorter.extensionFields.get(i) : null; + Class classOfElement = field != null ? field.getType() : null; + if (field != null && field.getAnnotation(Asn1Default.class) != null) { + try { + field.set(result,UperEncoder.getDefault(classOfElement,field.getAnnotations())); + } catch (IllegalArgumentException e) { + throw new IllegalArgumentException("can't decode " + classOfElement.getSimpleName(), e); + } catch (IllegalAccessException e) { + throw new IllegalArgumentException("can't decode " + classOfElement.getSimpleName(), e); + } + UperEncoder.logger.debug(String.format("Default set for %s", field.getName())); + } + } + }//end of loop on present extension fields + } else { + //CG there is an extension marker but the extension is not present + // then there sill might be an element with a default value + for (Field field : sorter.extensionFields) { + if ( numExtensions <= sorter.extensionFields.indexOf(field)) { + if (field.getAnnotation(Asn1Default.class) != null) { + Class classOfElement = field != null ? field.getType() : null; + try { + field.set(result,UperEncoder.getDefault(classOfElement, field.getAnnotations())); + } catch (IllegalArgumentException e) { + throw new IllegalArgumentException("can't decode default" + classOfElement.getSimpleName(), e); + } catch (IllegalAccessException e) { + throw new IllegalArgumentException("can't decode default" + classOfElement.getSimpleName(), e); + } + } + } + } + } // end of extension handling + } + sorter.revertAccess(); + return result; + } + + @Override + public T getDefault(Class classOfT, Annotation[] annotations) { + throw new IllegalArgumentException("Default Sequence not yet implemented"); + } +} \ No newline at end of file diff --git a/src/net/gcdc/asn1/uper/SimpleTypeResolver.java b/src/net/gcdc/asn1/uper/SimpleTypeResolver.java new file mode 100644 index 0000000..64c2e5e --- /dev/null +++ b/src/net/gcdc/asn1/uper/SimpleTypeResolver.java @@ -0,0 +1,515 @@ +package net.gcdc.asn1.uper; + +/* + * Copyright 2002-2017 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + +import java.lang.ref.Reference; +import java.lang.ref.WeakReference; +import java.lang.reflect.Array; +import java.lang.reflect.Constructor; +import java.lang.reflect.GenericArrayType; +import java.lang.reflect.Member; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; +import java.lang.reflect.TypeVariable; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.WeakHashMap; + + +/** + * Enhanced type resolution utilities. + * + * @author Jonathan Halterman + */ +public final class SimpleTypeResolver { + /** Cache of type variable/argument pairs */ + private static final Map, Reference, Type>>> TYPE_VARIABLE_CACHE = Collections + .synchronizedMap(new WeakHashMap, Reference, Type>>>()); + private static volatile boolean CACHE_ENABLED = true; + private static boolean RESOLVES_LAMBDAS; + private static Method GET_CONSTANT_POOL; + private static Method GET_CONSTANT_POOL_SIZE; + private static Method GET_CONSTANT_POOL_METHOD_AT; + private static final Map OBJECT_METHODS = new HashMap(); + private static final Map, Class> PRIMITIVE_WRAPPERS; + private static final Double JAVA_VERSION; + + static { + JAVA_VERSION = Double.parseDouble(System.getProperty("java.specification.version", "0")); + + try { + + GET_CONSTANT_POOL = Class.class.getDeclaredMethod("getConstantPool"); + String constantPoolName = JAVA_VERSION < 9 ? "sun.reflect.ConstantPool" : "jdk.internal.reflect.ConstantPool"; + Class constantPoolClass = Class.forName(constantPoolName); + GET_CONSTANT_POOL_SIZE = constantPoolClass.getDeclaredMethod("getSize"); + GET_CONSTANT_POOL_METHOD_AT = constantPoolClass.getDeclaredMethod("getMethodAt", int.class); + + // setting the methods as accessible + // additional checks - make sure we get a result when invoking the Class::getConstantPool and + // ConstantPool::getSize on a class + Object constantPool = GET_CONSTANT_POOL.invoke(Object.class); + GET_CONSTANT_POOL_SIZE.invoke(constantPool); + + for (Method method : Object.class.getDeclaredMethods()) + OBJECT_METHODS.put(method.getName(), method); + + RESOLVES_LAMBDAS = true; + } catch (Exception ignore) { + } + + Map, Class> types = new HashMap, Class>(); + types.put(boolean.class, Boolean.class); + types.put(byte.class, Byte.class); + types.put(char.class, Character.class); + types.put(double.class, Double.class); + types.put(float.class, Float.class); + types.put(int.class, Integer.class); + types.put(long.class, Long.class); + types.put(short.class, Short.class); + types.put(void.class, Void.class); + PRIMITIVE_WRAPPERS = Collections.unmodifiableMap(types); + } + + /** An unknown type. */ + public static final class Unknown { + private Unknown() { + } + } + + /** + * Enables the internal caching of resolved TypeVariables. + */ + public static void enableCache() { + CACHE_ENABLED = true; + } + + /** + * Disables the internal caching of resolved TypeVariables. + */ + public static void disableCache() { + TYPE_VARIABLE_CACHE.clear(); + CACHE_ENABLED = false; + } + + /** + * Returns the raw class representing the argument for the {@code type} using type variable information from the + * {@code subType}. If no arguments can be resolved then {@code Unknown.class} is returned. + * + * @param type to resolve argument for + * @param subType to extract type variable information from + * @return argument for {@code type} else {@link Unknown}.class if no type arguments are declared + * @throws IllegalArgumentException if more or less than one argument is resolved for the {@code type} + */ + public static Class resolveRawArgument(Class type, Class subType) { + return resolveRawArgument(resolveGenericType(type, subType), subType); + } + + /** + * Returns the raw class representing the argument for the {@code genericType} using type variable information from + * the {@code subType}. If {@code genericType} is an instance of class, then {@code genericType} is returned. If no + * arguments can be resolved then {@code Unknown.class} is returned. + * + * @param genericType to resolve argument for + * @param subType to extract type variable information from + * @return argument for {@code genericType} else {@link Unknown}.class if no type arguments are declared + * @throws IllegalArgumentException if more or less than one argument is resolved for the {@code genericType} + */ + public static Class resolveRawArgument(Type genericType, Class subType) { + Class[] arguments = resolveRawArguments(genericType, subType); + if (arguments == null) + return Unknown.class; + + if (arguments.length != 1) + throw new IllegalArgumentException( + "Expected 1 argument for generic type " + genericType + " but found " + arguments.length); + + return arguments[0]; + } + + /** + * Returns an array of raw classes representing arguments for the {@code type} using type variable information from + * the {@code subType}. Arguments for {@code type} that cannot be resolved are returned as {@code Unknown.class}. If + * no arguments can be resolved then {@code null} is returned. + * + * @param type to resolve arguments for + * @param subType to extract type variable information from + * @return array of raw classes representing arguments for the {@code type} else {@code null} if no type arguments are + * declared + */ + public static Class[] resolveRawArguments(Class type, Class subType) { + return resolveRawArguments(resolveGenericType(type, subType), subType); + } + + /** + * Returns an array of raw classes representing arguments for the {@code genericType} using type variable information + * from the {@code subType}. Arguments for {@code genericType} that cannot be resolved are returned as + * {@code Unknown.class}. If no arguments can be resolved then {@code null} is returned. + * + * @param genericType to resolve arguments for + * @param subType to extract type variable information from + * @return array of raw classes representing arguments for the {@code genericType} else {@code null} if no type + * arguments are declared + */ + public static Class[] resolveRawArguments(Type genericType, Class subType) { + Class[] result = null; + Class functionalInterface = null; + + // Handle lambdas + if (RESOLVES_LAMBDAS && subType.isSynthetic()) { + Class fi = genericType instanceof ParameterizedType + && ((ParameterizedType) genericType).getRawType() instanceof Class + ? (Class) ((ParameterizedType) genericType).getRawType() + : genericType instanceof Class ? (Class) genericType : null; + if (fi != null && fi.isInterface()) + functionalInterface = fi; + } + + if (genericType instanceof ParameterizedType) { + ParameterizedType paramType = (ParameterizedType) genericType; + Type[] arguments = paramType.getActualTypeArguments(); + result = new Class[arguments.length]; + for (int i = 0; i < arguments.length; i++) + result[i] = resolveRawClass(arguments[i], subType, functionalInterface); + } else if (genericType instanceof TypeVariable) { + result = new Class[1]; + result[0] = resolveRawClass(genericType, subType, functionalInterface); + } else if (genericType instanceof Class) { + TypeVariable[] typeParams = ((Class) genericType).getTypeParameters(); + result = new Class[typeParams.length]; + for (int i = 0; i < typeParams.length; i++) + result[i] = resolveRawClass(typeParams[i], subType, functionalInterface); + } + + return result; + } + + /** + * Returns the generic {@code type} using type variable information from the {@code subType} else {@code null} if the + * generic type cannot be resolved. + * + * @param type to resolve generic type for + * @param subType to extract type variable information from + * @return generic {@code type} else {@code null} if it cannot be resolved + */ + public static Type resolveGenericType(Class type, Type subType) { + Class rawType; + if (subType instanceof ParameterizedType) + rawType = (Class) ((ParameterizedType) subType).getRawType(); + else + rawType = (Class) subType; + + if (type.equals(rawType)) + return subType; + + Type result; + if (type.isInterface()) { + for (Type superInterface : rawType.getGenericInterfaces()) + if (superInterface != null && !superInterface.equals(Object.class)) + if ((result = resolveGenericType(type, superInterface)) != null) + return result; + } + + Type superClass = rawType.getGenericSuperclass(); + if (superClass != null && !superClass.equals(Object.class)) + if ((result = resolveGenericType(type, superClass)) != null) + return result; + + return null; + } + + /** + * Resolves the raw class for the {@code genericType}, using the type variable information from the {@code subType} + * else {@link Unknown} if the raw class cannot be resolved. + * + * @param genericType to resolve raw class for + * @param subType to extract type variable information from + * @return raw class for the {@code genericType} else {@link Unknown} if it cannot be resolved + */ + public static Class resolveRawClass(Type genericType, Class subType) { + return resolveRawClass(genericType, subType, null); + } + + private static Class resolveRawClass(Type genericType, Class subType, Class functionalInterface) { + if (genericType instanceof Class) { + return (Class) genericType; + } else if (genericType instanceof ParameterizedType) { + return resolveRawClass(((ParameterizedType) genericType).getRawType(), subType, functionalInterface); + } else if (genericType instanceof GenericArrayType) { + GenericArrayType arrayType = (GenericArrayType) genericType; + Class component = resolveRawClass(arrayType.getGenericComponentType(), subType, functionalInterface); + return Array.newInstance(component, 0).getClass(); + } else if (genericType instanceof TypeVariable) { + TypeVariable variable = (TypeVariable) genericType; + genericType = getTypeVariableMap(subType, functionalInterface).get(variable); + genericType = genericType == null ? resolveBound(variable) + : resolveRawClass(genericType, subType, functionalInterface); + } + + return genericType instanceof Class ? (Class) genericType : Unknown.class; + } + + private static Map, Type> getTypeVariableMap(final Class targetType, + Class functionalInterface) { + Reference, Type>> ref = TYPE_VARIABLE_CACHE.get(targetType); + Map, Type> map = ref != null ? ref.get() : null; + + if (map == null) { + map = new HashMap, Type>(); + + // Populate lambdas + if (functionalInterface != null) + populateLambdaArgs(functionalInterface, targetType, map); + + // Populate interfaces + populateSuperTypeArgs(targetType.getGenericInterfaces(), map, functionalInterface != null); + + // Populate super classes and interfaces + Type genericType = targetType.getGenericSuperclass(); + Class type = targetType.getSuperclass(); + while (type != null && !Object.class.equals(type)) { + if (genericType instanceof ParameterizedType) + populateTypeArgs((ParameterizedType) genericType, map, false); + populateSuperTypeArgs(type.getGenericInterfaces(), map, false); + + genericType = type.getGenericSuperclass(); + type = type.getSuperclass(); + } + + // Populate enclosing classes + type = targetType; + while (type.isMemberClass()) { + genericType = type.getGenericSuperclass(); + if (genericType instanceof ParameterizedType) + populateTypeArgs((ParameterizedType) genericType, map, functionalInterface != null); + + type = type.getEnclosingClass(); + } + + if (CACHE_ENABLED) + TYPE_VARIABLE_CACHE.put(targetType, new WeakReference, Type>>(map)); + } + + return map; + } + + /** + * Populates the {@code map} with with variable/argument pairs for the given {@code types}. + */ + private static void populateSuperTypeArgs(final Type[] types, final Map, Type> map, + boolean depthFirst) { + for (Type type : types) { + if (type instanceof ParameterizedType) { + ParameterizedType parameterizedType = (ParameterizedType) type; + if (!depthFirst) + populateTypeArgs(parameterizedType, map, depthFirst); + Type rawType = parameterizedType.getRawType(); + if (rawType instanceof Class) + populateSuperTypeArgs(((Class) rawType).getGenericInterfaces(), map, depthFirst); + if (depthFirst) + populateTypeArgs(parameterizedType, map, depthFirst); + } else if (type instanceof Class) { + populateSuperTypeArgs(((Class) type).getGenericInterfaces(), map, depthFirst); + } + } + } + + /** + * Populates the {@code map} with variable/argument pairs for the given {@code type}. + */ + private static void populateTypeArgs(ParameterizedType type, Map, Type> map, boolean depthFirst) { + if (type.getRawType() instanceof Class) { + TypeVariable[] typeVariables = ((Class) type.getRawType()).getTypeParameters(); + Type[] typeArguments = type.getActualTypeArguments(); + + if (type.getOwnerType() != null) { + Type owner = type.getOwnerType(); + if (owner instanceof ParameterizedType) + populateTypeArgs((ParameterizedType) owner, map, depthFirst); + } + + for (int i = 0; i < typeArguments.length; i++) { + TypeVariable variable = typeVariables[i]; + Type typeArgument = typeArguments[i]; + + if (typeArgument instanceof Class) { + map.put(variable, typeArgument); + } else if (typeArgument instanceof GenericArrayType) { + map.put(variable, typeArgument); + } else if (typeArgument instanceof ParameterizedType) { + map.put(variable, typeArgument); + } else if (typeArgument instanceof TypeVariable) { + TypeVariable typeVariableArgument = (TypeVariable) typeArgument; + if (depthFirst) { + Type existingType = map.get(variable); + if (existingType != null) { + map.put(typeVariableArgument, existingType); + continue; + } + } + + Type resolvedType = map.get(typeVariableArgument); + if (resolvedType == null) + resolvedType = resolveBound(typeVariableArgument); + map.put(variable, resolvedType); + } + } + } + } + + /** + * Resolves the first bound for the {@code typeVariable}, returning {@code Unknown.class} if none can be resolved. + */ + public static Type resolveBound(TypeVariable typeVariable) { + Type[] bounds = typeVariable.getBounds(); + if (bounds.length == 0) + return Unknown.class; + + Type bound = bounds[0]; + if (bound instanceof TypeVariable) + bound = resolveBound((TypeVariable) bound); + + return bound == Object.class ? Unknown.class : bound; + } + + /** + * Populates the {@code map} with variable/argument pairs for the {@code functionalInterface}. + */ + private static void populateLambdaArgs(Class functionalInterface, final Class lambdaType, + Map, Type> map) { + if (RESOLVES_LAMBDAS) { + // Find SAM + for (Method m : functionalInterface.getMethods()) { + if (!isDefaultMethod(m) && !Modifier.isStatic(m.getModifiers()) && !m.isBridge()) { + // Skip methods that override Object.class + Method objectMethod = OBJECT_METHODS.get(m.getName()); + if (objectMethod != null && Arrays.equals(m.getTypeParameters(), objectMethod.getTypeParameters())) + continue; + + // Get functional interface's type params + Type returnTypeVar = m.getGenericReturnType(); + Type[] paramTypeVars = m.getGenericParameterTypes(); + + Member member = getMemberRef(lambdaType); + if (member == null) + return; + + // Populate return type argument + if (returnTypeVar instanceof TypeVariable) { + Class returnType = member instanceof Method ? ((Method) member).getReturnType() + : ((Constructor) member).getDeclaringClass(); + returnType = wrapPrimitives(returnType); + if (!returnType.equals(Void.class)) + map.put((TypeVariable) returnTypeVar, returnType); + } + + Class[] arguments = member instanceof Method ? ((Method) member).getParameterTypes() + : ((Constructor) member).getParameterTypes(); + + // Populate object type from arbitrary object method reference + int paramOffset = 0; + if (paramTypeVars.length > 0 && paramTypeVars[0] instanceof TypeVariable + && paramTypeVars.length == arguments.length + 1) { + Class instanceType = member.getDeclaringClass(); + map.put((TypeVariable) paramTypeVars[0], instanceType); + paramOffset = 1; + } + + // Handle additional arguments that are captured from the lambda's enclosing scope + int argOffset = 0; + if (paramTypeVars.length < arguments.length) { + argOffset = arguments.length - paramTypeVars.length; + } + + // Populate type arguments + for (int i = 0; i + argOffset < arguments.length; i++) { + if (paramTypeVars[i] instanceof TypeVariable) + map.put((TypeVariable) paramTypeVars[i + paramOffset], wrapPrimitives(arguments[i + argOffset])); + } + + return; + } + } + } + } + + private static boolean isDefaultMethod(Method m) { + //CG + return false; + //return JAVA_VERSION >= 1.8 && m.isDefault(); + } + + private static Member getMemberRef(Class type) { + Object constantPool; + try { + constantPool = GET_CONSTANT_POOL.invoke(type); + } catch (Exception ignore) { + return null; + } + + Member result = null; + for (int i = getConstantPoolSize(constantPool) - 1; i >= 0; i--) { + Member member = getConstantPoolMethodAt(constantPool, i); + // Skip SerializedLambda constructors and members of the "type" class + if (member == null + || (member instanceof Constructor + && member.getDeclaringClass().getName().equals("java.lang.invoke.SerializedLambda")) + || member.getDeclaringClass().isAssignableFrom(type)) + continue; + + result = member; + + // Return if not valueOf method + if (!(member instanceof Method) || !isAutoBoxingMethod((Method) member)) + break; + } + + return result; + } + + private static boolean isAutoBoxingMethod(Method method) { + Class[] parameters = method.getParameterTypes(); + return method.getName().equals("valueOf") && parameters.length == 1 && parameters[0].isPrimitive() + && wrapPrimitives(parameters[0]).equals(method.getDeclaringClass()); + } + + private static Class wrapPrimitives(Class clazz) { + return clazz.isPrimitive() ? PRIMITIVE_WRAPPERS.get(clazz) : clazz; + } + + private static int getConstantPoolSize(Object constantPool) { + try { + return (Integer) GET_CONSTANT_POOL_SIZE.invoke(constantPool); + } catch (Exception ignore) { + return 0; + } + } + + private static Member getConstantPoolMethodAt(Object constantPool, int i) { + try { + return (Member) GET_CONSTANT_POOL_METHOD_AT.invoke(constantPool, i); + } catch (Exception ignore) { + return null; + } + } +} + diff --git a/src/net/gcdc/asn1/uper/StringCoder.java b/src/net/gcdc/asn1/uper/StringCoder.java new file mode 100644 index 0000000..d42238b --- /dev/null +++ b/src/net/gcdc/asn1/uper/StringCoder.java @@ -0,0 +1,299 @@ +package net.gcdc.asn1.uper; + +import java.lang.annotation.Annotation; +import java.lang.reflect.Field; +import java.math.BigInteger; +import java.nio.ByteBuffer; +import java.nio.CharBuffer; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import logger.Logger; +import logger.LoggerFactory; + +import net.gcdc.asn1.datatypes.Asn1Default; +import net.gcdc.asn1.datatypes.Asn1String; +import net.gcdc.asn1.datatypes.CharacterRestriction; +import net.gcdc.asn1.datatypes.DefaultAlphabet; +import net.gcdc.asn1.datatypes.FixedSize; +import net.gcdc.asn1.datatypes.RestrictedString; +import net.gcdc.asn1.datatypes.SizeRange; + + +class StringCoder implements Decoder, Encoder { + + private static final Logger LOGGER = LoggerFactory.getLogger("asnLogger"); + + @Override public boolean canEncode(T obj, Annotation[] extraAnnotations) { + return obj instanceof String || obj instanceof Asn1String; + } + + @Override public void encode(BitBuffer bitbuffer, T obj, Annotation[] extraAnnotations) throws Asn1EncodingException { + String pos = String.format("Position: %d.%d", bitbuffer.position()/8 , bitbuffer.position() % 8); + UperEncoder.logger.debug(String.format("%s: encode STRING %s of type %s", pos, obj, obj.getClass().getName())); + Class type = obj.getClass(); + AnnotationStore annotations = new AnnotationStore(type.getAnnotations(), extraAnnotations); + String string = (obj instanceof String) ? ((String) obj) : ((Asn1String) obj).value(); + RestrictedString restrictionAnnotation = annotations.getAnnotation(RestrictedString.class); + if (restrictionAnnotation == null) { + throw new UnsupportedOperationException("Unrestricted character strings are not supported yet. All annotations: " + Arrays.asList(type.getAnnotations())); + } + + FixedSize fixedSize = annotations.getAnnotation(FixedSize.class); + SizeRange sizeRange = annotations.getAnnotation(SizeRange.class); + if (fixedSize != null && fixedSize.value() != string.length()) { + throw new IllegalArgumentException( + "Bad string length, expected " + fixedSize.value() + ", got " + string.length()); + } + if (sizeRange != null + && !sizeRange.hasExtensionMarker() + && (string.length() < sizeRange.minValue() || sizeRange.maxValue() < string + .length())) { throw new IllegalArgumentException( + "Bad string length, expected " + sizeRange.minValue() + ".." + + sizeRange.maxValue() + ", got " + string.length()); } + if (restrictionAnnotation.value() == CharacterRestriction.UTF8String) { + // UTF8 length + BitBuffer stringbuffer = ByteBitBuffer.createInfinite(); + + //char array replaced - begin + byte[] stringasbytearray = string.getBytes(StandardCharsets.UTF_8); + + for (byte b: stringasbytearray){ + UperEncoder.encodeConstrainedInt(stringbuffer, b & 0xff, 0, 255); + } + //-for (char c : string.toCharArray()) { + //- encodeChar(stringbuffer, c, restrictionAnnotation); + //-} + //char array replaced - end + + stringbuffer.flip(); + if (stringbuffer.limit() % 8 != 0) { + throw new AssertionError("utf8 encoding resulted not in multiple of 8 bits"); + } + int numOctets = (stringbuffer.limit() + 7) / 8; // Actually +7 is not needed here, + // since we already checked with %8. + int position1 = bitbuffer.position(); + UperEncoder.encodeLengthDeterminant(bitbuffer, numOctets); + UperEncoder.logger.debug(String.format("UTF8String %s, length %d octets, encoded as %s", string, numOctets, bitbuffer.toBooleanStringFromPosition(position1))); + int position2 = bitbuffer.position(); + for (int i = 0; i < stringbuffer.limit(); i++) { + bitbuffer.put(stringbuffer.get()); + } + UperEncoder.logger.debug(String.format("UTF8String %s, encoded length %d octets, value bits: %s", string, numOctets, bitbuffer.toBooleanStringFromPosition(position2))); + return; + } else if (fixedSize != null) { + if (fixedSize.value() != string.length()) { throw new IllegalArgumentException( + "String length does not match constraints"); } + int position = bitbuffer.position(); + for (int i = 0; i < fixedSize.value(); i++) { + encodeChar(bitbuffer, string.charAt(i), restrictionAnnotation); + } + UperEncoder.logger.debug(String.format("string encoded as <%s>", bitbuffer.toBooleanStringFromPosition(position))); + return; + } else if (sizeRange != null) { + UperEncoder.logger.debug("string length"); + int position1 = bitbuffer.position(); + UperEncoder.encodeConstrainedInt(bitbuffer, string.length(), sizeRange.minValue(),sizeRange.maxValue(), sizeRange.hasExtensionMarker()); + int position2 = bitbuffer.position(); + UperEncoder.logger.debug("string content"); + for (int i = 0; i < string.length(); i++) { + encodeChar(bitbuffer, string.charAt(i), restrictionAnnotation); + } + UperEncoder.logger.debug(String.format("STRING %s size %d: %s", obj.getClass().getName(), bitbuffer.toBooleanString(position1, position2 - position1),bitbuffer.toBooleanStringFromPosition(position2))); + return; + } else { + int position1 = bitbuffer.position(); + UperEncoder.encodeLengthDeterminant(bitbuffer, string.length()); + int position2 = bitbuffer.position(); + for (int i = 0; i < string.length(); i++) { + encodeChar(bitbuffer, string.charAt(i), restrictionAnnotation); + } + UperEncoder.logger.debug(String.format("STRING %s size %s: %s", obj.getClass().getName(), bitbuffer.toBooleanString(position1, position2 - position1),bitbuffer.toBooleanStringFromPosition(position2))); + return; + } + } + + @Override public boolean canDecode(Class classOfT, Annotation[] extraAnnotations) { + return String.class.isAssignableFrom(classOfT) || Asn1String.class.isAssignableFrom(classOfT); + } + + @Override public T decode(BitBuffer bitbuffer, + Class classOfT, Field field, + Annotation[] extraAnnotations) { + UperEncoder.logger.debug("decode String"); + AnnotationStore annotations = new AnnotationStore(classOfT.getAnnotations(), extraAnnotations); + RestrictedString restrictionAnnotation = annotations.getAnnotation(RestrictedString.class); + if (restrictionAnnotation == null) { + throw new UnsupportedOperationException( + "Unrestricted character strings are not supported yet. All annotations: " + Arrays.asList(classOfT.getAnnotations())); + } + if (restrictionAnnotation.value() == CharacterRestriction.UTF8String) { + Long numOctets = UperEncoder.decodeLengthDeterminant(bitbuffer); + List content = new ArrayList(); + for (int i = 0; i < numOctets * 8; i++) { + content.add(bitbuffer.get()); + } + byte[] contentBytes = UperEncoder.bytesFromCollection(content); + UperEncoder.logger.debug(String.format("Content bytes (hex): %s", UperEncoder.hexStringFromBytes(contentBytes))); + String resultStr = StandardCharsets.UTF_8.decode(ByteBuffer.wrap(contentBytes)).toString(); + UperEncoder.logger.debug(String.format("Decoded as %s", resultStr)); + T result = UperEncoder.instantiate(classOfT, resultStr); + return result; + } else { + FixedSize fixedSize = annotations.getAnnotation(FixedSize.class); + SizeRange sizeRange = annotations.getAnnotation(SizeRange.class); + long numChars = (fixedSize != null) ? fixedSize.value() : + (sizeRange != null) ? UperEncoder.decodeConstrainedInt(bitbuffer, + UperEncoder.intRangeFromSizeRange(sizeRange)) : + UperEncoder.decodeLengthDeterminant(bitbuffer); + UperEncoder.logger.debug(String.format("known-multiplier string, numchars: %d", numChars)); + StringBuilder stringBuilder = new StringBuilder((int) numChars); + for (int c = 0; c < numChars; c++) { + stringBuilder.append(decodeRestrictedChar(bitbuffer, restrictionAnnotation)); + } + String resultStr = stringBuilder.toString(); + UperEncoder.logger.debug(String.format("Decoded as %s", resultStr)); + T result = UperEncoder.instantiate(classOfT, resultStr); + return result; + } + } + + private static void encodeChar(BitBuffer bitbuffer, char c, RestrictedString restriction) throws Asn1EncodingException { + UperEncoder.logger.debug(String.format("char %s", c)); + switch (restriction.value()) { + case IA5String: + if (restriction.alphabet() != DefaultAlphabet.class) { + throw new UnsupportedOperationException("alphabet for IA5String is not supported yet."); + } + UperEncoder.encodeConstrainedInt( + bitbuffer, + StandardCharsets.US_ASCII.encode(CharBuffer.wrap(new char[] { c })).get() & 0xff, + 0, + 127); + return; + case UTF8String: + if (restriction.alphabet() != DefaultAlphabet.class) { + throw new UnsupportedOperationException("alphabet for UTF8 is not supported yet."); + } + ByteBuffer buffer = StandardCharsets.UTF_8.encode(CharBuffer.wrap(new char[] { c })); + for (int i = 0; i < buffer.limit(); i++) { + UperEncoder.encodeConstrainedInt(bitbuffer, buffer.get() & 0xff, 0, 255); + } + return; + case VisibleString: + case ISO646String: + if (restriction.alphabet() != DefaultAlphabet.class) { + char[] chars; + try { + chars = UperEncoder.instantiate(restriction.alphabet()).chars().toCharArray(); + } catch (IllegalArgumentException e) { + LOGGER.info("Uninstantinatable alphabet ", e); + throw new IllegalArgumentException("Uninstantinatable alphabet" + restriction.alphabet().getName()); + } + if (BigInteger.valueOf(chars.length - 1).bitLength() < BigInteger.valueOf(126) + .bitLength()) { + Arrays.sort(chars); + String strAlphabet = new String(chars); + int index = strAlphabet.indexOf(c); + if (index < 0) { throw new IllegalArgumentException("can't find character " + c + " in alphabet " + strAlphabet); } + UperEncoder.encodeConstrainedInt( + bitbuffer, + index, + 0, + chars.length - 1); + return; + } else { + UperEncoder.encodeConstrainedInt( + bitbuffer, + StandardCharsets.US_ASCII.encode(CharBuffer.wrap(new char[] { c })) + .get() & 0xff, + 0, + 126); + return; + } + } else { + UperEncoder.encodeConstrainedInt( + bitbuffer, + StandardCharsets.US_ASCII.encode(CharBuffer.wrap(new char[] { c })) + .get() & 0xff, + 0, + 126); + return; + } + default: + throw new UnsupportedOperationException("String type " + restriction + + " is not supported yet"); + } + } + + private static String decodeRestrictedChar(BitBuffer bitqueue, + RestrictedString restrictionAnnotation) { + switch (restrictionAnnotation.value()) { + case IA5String: { + if (restrictionAnnotation.alphabet() != DefaultAlphabet.class) { + throw new UnsupportedOperationException( + "alphabet for IA5String is not supported yet."); + } + byte charByte = (byte) UperEncoder.decodeConstrainedInt(bitqueue, UperEncoder.newRange(0, 127, false)); + byte[] bytes = new byte[] { charByte }; + String result = StandardCharsets.US_ASCII.decode(ByteBuffer.wrap(bytes)).toString(); + if (result.length() != 1) { + throw new AssertionError("decoded more than one char (" + result + ")"); + } + return result; + } + case VisibleString: + case ISO646String: { + if (restrictionAnnotation.alphabet() != DefaultAlphabet.class) { + char[] chars; + try { + chars = UperEncoder.instantiate(restrictionAnnotation.alphabet()).chars().toCharArray(); + } catch (IllegalArgumentException e) { + LOGGER.info("Uninstantinatable alphabet ", e); + throw new IllegalArgumentException("Uninstantinatable alphabet " + restrictionAnnotation.alphabet().getName()); + } + if (BigInteger.valueOf(chars.length - 1).bitLength() < BigInteger.valueOf(126) + .bitLength()) { + Arrays.sort(chars); + int index = (byte) UperEncoder.decodeConstrainedInt(bitqueue, UperEncoder.newRange(0, chars.length - 1, false)); + String strAlphabet = new String(chars); + char c = strAlphabet.charAt(index); + String result = new String("" + c); + return result; + } else { // Encode normally + byte charByte = (byte) UperEncoder.decodeConstrainedInt(bitqueue, UperEncoder.newRange(0, 126, false)); + byte[] bytes = new byte[] { charByte }; + String result = StandardCharsets.US_ASCII.decode(ByteBuffer.wrap(bytes)).toString(); + if (result.length() != 1) { throw new AssertionError( + "decoded more than one char (" + result + ")"); + } + return result; + } + } else { // Encode normally + byte charByte = (byte) UperEncoder.decodeConstrainedInt(bitqueue, UperEncoder.newRange(0, 126, false)); + byte[] bytes = new byte[] { charByte }; + String result = StandardCharsets.US_ASCII.decode(ByteBuffer.wrap(bytes)).toString(); + if (result.length() != 1) { + throw new AssertionError("decoded more than one char (" + result + ")"); + } + return result; + } + } + default: + throw new UnsupportedOperationException("String type " + restrictionAnnotation + " is not supported yet"); + + } + } + + @Override + public T getDefault(Class classOfT, Annotation[] extraAnnotations) { + AnnotationStore annotations = new AnnotationStore(classOfT.getAnnotations(), extraAnnotations); + Asn1Default defaultAnnotation = annotations.getAnnotation(Asn1Default.class); + if (defaultAnnotation == null) return null; + T result = UperEncoder.instantiate(classOfT, defaultAnnotation.value()); + return result; + } + +} \ No newline at end of file diff --git a/src/net/gcdc/asn1/uper/UperEncoder.java b/src/net/gcdc/asn1/uper/UperEncoder.java new file mode 100644 index 0000000..f9c2f2a --- /dev/null +++ b/src/net/gcdc/asn1/uper/UperEncoder.java @@ -0,0 +1,694 @@ +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 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; + } + + + + +} -- cgit v1.2.3