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/datatypes/Alphabet.java | 20 + src/net/gcdc/asn1/datatypes/AlphabetBuilder.java | 32 + src/net/gcdc/asn1/datatypes/Asn1AnonymousType.java | 15 + src/net/gcdc/asn1/datatypes/Asn1BigInteger.java | 72 +++ src/net/gcdc/asn1/datatypes/Asn1Default.java | 12 + src/net/gcdc/asn1/datatypes/Asn1Integer.java | 56 ++ src/net/gcdc/asn1/datatypes/Asn1Optional.java | 20 + src/net/gcdc/asn1/datatypes/Asn1SequenceOf.java | 70 +++ src/net/gcdc/asn1/datatypes/Asn1String.java | 17 + .../gcdc/asn1/datatypes/Asn1VarSizeBitstring.java | 58 ++ src/net/gcdc/asn1/datatypes/Bitstring.java | 16 + .../gcdc/asn1/datatypes/CharacterRestriction.java | 12 + src/net/gcdc/asn1/datatypes/Choice.java | 12 + src/net/gcdc/asn1/datatypes/DefaultAlphabet.java | 8 + src/net/gcdc/asn1/datatypes/FixedSize.java | 12 + .../gcdc/asn1/datatypes/HasExtensionMarker.java | 12 + src/net/gcdc/asn1/datatypes/IntMinValue.java | 13 + src/net/gcdc/asn1/datatypes/IntRange.java | 14 + src/net/gcdc/asn1/datatypes/IsExtension.java | 12 + src/net/gcdc/asn1/datatypes/NoAsn1Field.java | 10 + src/net/gcdc/asn1/datatypes/Optional.java | 96 +++ src/net/gcdc/asn1/datatypes/RestrictedString.java | 13 + src/net/gcdc/asn1/datatypes/Sequence.java | 9 + src/net/gcdc/asn1/datatypes/SizeRange.java | 14 + src/net/gcdc/asn1/datatypes/package-info.java | 7 + src/net/gcdc/asn1/datatypesimpl/OctetString.java | 46 ++ .../gcdc/asn1/datatypesimpl/SequenceOfLong.java | 27 + .../asn1/datatypesimpl/SequenceOfStringIA5.java | 13 + .../asn1/datatypesimpl/SequenceOfStringUTF8.java | 13 + .../datatypesimpl/SequenceOfUnrestrictedLong.java | 35 ++ src/net/gcdc/asn1/test/TestSequenceOfLong.java | 24 + src/net/gcdc/asn1/test/UperEncodeBooleanTest.java | 83 +++ .../asn1/test/UperEncodeChoiceExtensionTest.java | 90 +++ src/net/gcdc/asn1/test/UperEncodeChoiceTest.java | 72 +++ .../asn1/test/UperEncodeEnumExtensionTest.java | 146 +++++ src/net/gcdc/asn1/test/UperEncodeEnumTest.java | 126 ++++ .../test/UperEncodeIntegerConstrainedTest.java | 68 ++ .../asn1/test/UperEncodeIntegerExtensionTest.java | 99 +++ .../gcdc/asn1/test/UperEncodeIntegerSmallTest.java | 129 ++++ src/net/gcdc/asn1/test/UperEncodeIntegerTest.java | 64 ++ .../gcdc/asn1/test/UperEncodeOctetStringTest.java | 80 +++ .../UperEncodeOptionalSequenceExtensionTest.java | 117 ++++ .../asn1/test/UperEncodeRestrictedIntegerTest.java | 62 ++ .../asn1/test/UperEncodeSequenceExtensionTest.java | 91 +++ .../asn1/test/UperEncodeSequenceOfIntegerTest.java | 73 +++ .../UperEncodeSequenceOfRestrictedIntegerTest.java | 77 +++ .../test/UperEncodeSequenceOfStringListTest.java | 77 +++ .../asn1/test/UperEncodeSequenceOfStringTest.java | 76 +++ .../test/UperEncodeSequenceOfUtf8StringTest.java | 96 +++ .../asn1/test/UperEncodeStringDefaultTest.java | 67 ++ .../gcdc/asn1/test/UperEncodeStringLengthTest.java | 151 +++++ src/net/gcdc/asn1/test/UperEncodeStringTest.java | 95 +++ 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 +++++++++++++++++++++ 72 files changed, 5997 insertions(+) create mode 100644 src/net/gcdc/asn1/datatypes/Alphabet.java create mode 100644 src/net/gcdc/asn1/datatypes/AlphabetBuilder.java create mode 100644 src/net/gcdc/asn1/datatypes/Asn1AnonymousType.java create mode 100644 src/net/gcdc/asn1/datatypes/Asn1BigInteger.java create mode 100644 src/net/gcdc/asn1/datatypes/Asn1Default.java create mode 100644 src/net/gcdc/asn1/datatypes/Asn1Integer.java create mode 100644 src/net/gcdc/asn1/datatypes/Asn1Optional.java create mode 100644 src/net/gcdc/asn1/datatypes/Asn1SequenceOf.java create mode 100644 src/net/gcdc/asn1/datatypes/Asn1String.java create mode 100644 src/net/gcdc/asn1/datatypes/Asn1VarSizeBitstring.java create mode 100644 src/net/gcdc/asn1/datatypes/Bitstring.java create mode 100644 src/net/gcdc/asn1/datatypes/CharacterRestriction.java create mode 100644 src/net/gcdc/asn1/datatypes/Choice.java create mode 100644 src/net/gcdc/asn1/datatypes/DefaultAlphabet.java create mode 100644 src/net/gcdc/asn1/datatypes/FixedSize.java create mode 100644 src/net/gcdc/asn1/datatypes/HasExtensionMarker.java create mode 100644 src/net/gcdc/asn1/datatypes/IntMinValue.java create mode 100644 src/net/gcdc/asn1/datatypes/IntRange.java create mode 100644 src/net/gcdc/asn1/datatypes/IsExtension.java create mode 100644 src/net/gcdc/asn1/datatypes/NoAsn1Field.java create mode 100644 src/net/gcdc/asn1/datatypes/Optional.java create mode 100644 src/net/gcdc/asn1/datatypes/RestrictedString.java create mode 100644 src/net/gcdc/asn1/datatypes/Sequence.java create mode 100644 src/net/gcdc/asn1/datatypes/SizeRange.java create mode 100644 src/net/gcdc/asn1/datatypes/package-info.java create mode 100644 src/net/gcdc/asn1/datatypesimpl/OctetString.java create mode 100644 src/net/gcdc/asn1/datatypesimpl/SequenceOfLong.java create mode 100644 src/net/gcdc/asn1/datatypesimpl/SequenceOfStringIA5.java create mode 100644 src/net/gcdc/asn1/datatypesimpl/SequenceOfStringUTF8.java create mode 100644 src/net/gcdc/asn1/datatypesimpl/SequenceOfUnrestrictedLong.java create mode 100644 src/net/gcdc/asn1/test/TestSequenceOfLong.java create mode 100644 src/net/gcdc/asn1/test/UperEncodeBooleanTest.java create mode 100644 src/net/gcdc/asn1/test/UperEncodeChoiceExtensionTest.java create mode 100644 src/net/gcdc/asn1/test/UperEncodeChoiceTest.java create mode 100644 src/net/gcdc/asn1/test/UperEncodeEnumExtensionTest.java create mode 100644 src/net/gcdc/asn1/test/UperEncodeEnumTest.java create mode 100644 src/net/gcdc/asn1/test/UperEncodeIntegerConstrainedTest.java create mode 100644 src/net/gcdc/asn1/test/UperEncodeIntegerExtensionTest.java create mode 100644 src/net/gcdc/asn1/test/UperEncodeIntegerSmallTest.java create mode 100644 src/net/gcdc/asn1/test/UperEncodeIntegerTest.java create mode 100644 src/net/gcdc/asn1/test/UperEncodeOctetStringTest.java create mode 100644 src/net/gcdc/asn1/test/UperEncodeOptionalSequenceExtensionTest.java create mode 100644 src/net/gcdc/asn1/test/UperEncodeRestrictedIntegerTest.java create mode 100644 src/net/gcdc/asn1/test/UperEncodeSequenceExtensionTest.java create mode 100644 src/net/gcdc/asn1/test/UperEncodeSequenceOfIntegerTest.java create mode 100644 src/net/gcdc/asn1/test/UperEncodeSequenceOfRestrictedIntegerTest.java create mode 100644 src/net/gcdc/asn1/test/UperEncodeSequenceOfStringListTest.java create mode 100644 src/net/gcdc/asn1/test/UperEncodeSequenceOfStringTest.java create mode 100644 src/net/gcdc/asn1/test/UperEncodeSequenceOfUtf8StringTest.java create mode 100644 src/net/gcdc/asn1/test/UperEncodeStringDefaultTest.java create mode 100644 src/net/gcdc/asn1/test/UperEncodeStringLengthTest.java create mode 100644 src/net/gcdc/asn1/test/UperEncodeStringTest.java 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 diff --git a/src/net/gcdc/asn1/datatypes/Alphabet.java b/src/net/gcdc/asn1/datatypes/Alphabet.java new file mode 100644 index 0000000..b89c481 --- /dev/null +++ b/src/net/gcdc/asn1/datatypes/Alphabet.java @@ -0,0 +1,20 @@ +package net.gcdc.asn1.datatypes; + +/** + * Alphabet class for Restricted Strings. + * + * Use {@link AlphabetBuilder} for convenient construction of restriction alphabets. + */ +public abstract class Alphabet { + + private final String chars; + + protected Alphabet(String chars) { + this.chars = chars; + } + + public final String chars() { + return chars; + } + +} diff --git a/src/net/gcdc/asn1/datatypes/AlphabetBuilder.java b/src/net/gcdc/asn1/datatypes/AlphabetBuilder.java new file mode 100644 index 0000000..cdff30e --- /dev/null +++ b/src/net/gcdc/asn1/datatypes/AlphabetBuilder.java @@ -0,0 +1,32 @@ +package net.gcdc.asn1.datatypes; + + +public class AlphabetBuilder { + + private final StringBuilder sb = new StringBuilder(); + + public AlphabetBuilder() {} + + public String chars() { + return sb.toString(); + } + + public AlphabetBuilder withRange(char from, char to) { + for (char c = from; c <= to; c++) { + sb.append(c); + } + return this; + } + + public AlphabetBuilder withChars(String str) { + sb.append(str); + return this; + } + + public AlphabetBuilder withChars(Character... chars) { + for (char c : chars) { + sb.append(c); + } + return this; + } +} diff --git a/src/net/gcdc/asn1/datatypes/Asn1AnonymousType.java b/src/net/gcdc/asn1/datatypes/Asn1AnonymousType.java new file mode 100644 index 0000000..4a30d23 --- /dev/null +++ b/src/net/gcdc/asn1/datatypes/Asn1AnonymousType.java @@ -0,0 +1,15 @@ +package net.gcdc.asn1.datatypes; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Target; +/** + * This annotation indicates that the class is not present in the original ASN.1 declaration. + * This happens when SEQUENCE members have restrictions (ranges, alphabets etc). + * + * This annotation plays no role in the UPER encoding. + * + */ +@Target({ElementType.TYPE}) +public @interface Asn1AnonymousType { + +} diff --git a/src/net/gcdc/asn1/datatypes/Asn1BigInteger.java b/src/net/gcdc/asn1/datatypes/Asn1BigInteger.java new file mode 100644 index 0000000..23fd584 --- /dev/null +++ b/src/net/gcdc/asn1/datatypes/Asn1BigInteger.java @@ -0,0 +1,72 @@ +package net.gcdc.asn1.datatypes; + +import java.math.BigInteger; + +//outdated: use BigInteger +public class Asn1BigInteger { + + private final BigInteger value; + + public Asn1BigInteger(final BigInteger value) { + this.value = value; + } + + @Override public String toString() { + return "" + value; + } + + public BigInteger value() { return value; } + + public Long longValue() { + return value.longValue(); + } + + public Integer intValue() { + return value.intValue(); + } + + public Asn1BigInteger(Long num) { + this.value = BigInteger.valueOf(num); + } + + public Asn1BigInteger(long num) { + this.value = BigInteger.valueOf(num); + } + + public Asn1BigInteger(Integer num) { + this.value = BigInteger.valueOf(num); + } + + public Asn1BigInteger(int num) { + this.value = BigInteger.valueOf(num); + } + + public static Long toLong(Asn1BigInteger object) { + if (object == null) return null; + return object.longValue(); + } + + public static Asn1BigInteger toAsn1(Long object) { + if (object == null) return null; + return new Asn1BigInteger(object); + } + + public static Asn1BigInteger toAsn1(Integer object) { + if (object == null) return null; + return new Asn1BigInteger(object); + } + + public Long toLong(){ + if (this.value != null) { + return this.value.longValue(); + } + return null; + } + + public BigInteger toBigInteger(){ + return value; + } + + + +} diff --git a/src/net/gcdc/asn1/datatypes/Asn1Default.java b/src/net/gcdc/asn1/datatypes/Asn1Default.java new file mode 100644 index 0000000..03bb8d0 --- /dev/null +++ b/src/net/gcdc/asn1/datatypes/Asn1Default.java @@ -0,0 +1,12 @@ +package net.gcdc.asn1.datatypes; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Target({ElementType.TYPE, ElementType.FIELD}) +@Retention(RetentionPolicy.RUNTIME) +public @interface Asn1Default { + String value(); +} diff --git a/src/net/gcdc/asn1/datatypes/Asn1Integer.java b/src/net/gcdc/asn1/datatypes/Asn1Integer.java new file mode 100644 index 0000000..188a363 --- /dev/null +++ b/src/net/gcdc/asn1/datatypes/Asn1Integer.java @@ -0,0 +1,56 @@ +package net.gcdc.asn1.datatypes; + + + +//outdated: use BigInteger +public class Asn1Integer { + + public long value; + + public Asn1Integer() {} + public Asn1Integer(long value) { + this.value = value; + } + + public Long value() { return value; } + + @Override public String toString() { + return "" + value; + } + + public Long longObject () { + return new Long(value()); + } + + public Asn1Integer(Long num) { + this.value = num; + } + + + public Asn1Integer(Integer num) { + this.value = num; + } + + public Asn1Integer(int num) { + this.value = num; + } + + public static Long toLong(Asn1Integer object) { + if (object == null) return null; + return object.value(); + } + + + public static Asn1Integer toAsn1(Long object) { + if (object == null) return null; + return new Asn1Integer(object); + } + + + + + + + + +} diff --git a/src/net/gcdc/asn1/datatypes/Asn1Optional.java b/src/net/gcdc/asn1/datatypes/Asn1Optional.java new file mode 100644 index 0000000..12865e3 --- /dev/null +++ b/src/net/gcdc/asn1/datatypes/Asn1Optional.java @@ -0,0 +1,20 @@ +package net.gcdc.asn1.datatypes; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Indicates that the field is OPTIONAL in ASN.1. Implemented as null. Equivalent to @Nullable. + * + * Using Optional would require Manifests to capture generics (like in Gson). + * + */ +@Target({ElementType.FIELD}) +@Retention(RetentionPolicy.RUNTIME) +@Documented +public @interface Asn1Optional { + +} diff --git a/src/net/gcdc/asn1/datatypes/Asn1SequenceOf.java b/src/net/gcdc/asn1/datatypes/Asn1SequenceOf.java new file mode 100644 index 0000000..2985c96 --- /dev/null +++ b/src/net/gcdc/asn1/datatypes/Asn1SequenceOf.java @@ -0,0 +1,70 @@ +package net.gcdc.asn1.datatypes; + +import java.lang.reflect.ParameterizedType; +import java.util.*; + +import logger.Logger; +import logger.LoggerFactory; + + +/** + * Class to represent ASN.1 construct "SEQUENCE OF". + *

+ * Extending classes should specify concrete types for T, generic collections can't be decoded (yet?). + *

+ * Usage example: + *

+ * 
+ * {@literal @}Sequence
+ * public class Person {
+ *     {@literal @}IntRange(minValue=0, maxValue=100, hasExtensionMarker=true)
+ *     int age;
+ *     Children children;
+ * }
+ * public class Children extends {@code Asn1SequenceOf } {
+ *     public Children() { super(); }
+ *     public Children({@code Collection} coll) { super(coll); }
+ * }
+ * 
+ * 
+ * + *

+ * Actually, UPER decoder and encoder consider anything that extends {@code List} as a SEQUENCE OF. + * + * + * @param type of elements contained. + */ +public abstract class Asn1SequenceOf extends AbstractList { + private final static Logger logger = LoggerFactory.getLogger("asnLogger"); + + private final List bakingList; + + @Override public T get(int index) { return bakingList.get(index); } + @Override public int size() { return bakingList.size(); } + @Override public boolean add (T e){ return bakingList.add(e);} + + public Asn1SequenceOf() { this(new ArrayList()); } + public Asn1SequenceOf(Collection coll) { + logger.debug(String.format("Instantiating Sequence Of %s with %s", + ((ParameterizedType)getClass().getGenericSuperclass()).getActualTypeArguments()[0], + coll)); + bakingList = new ArrayList<>(coll); + } + + + + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + if (!super.equals(o)) return false; + Asn1SequenceOf that = (Asn1SequenceOf) o; + return Objects.equals(bakingList, that.bakingList); + } + + @Override + public int hashCode() { + return Objects.hash(super.hashCode(), bakingList); + } +} diff --git a/src/net/gcdc/asn1/datatypes/Asn1String.java b/src/net/gcdc/asn1/datatypes/Asn1String.java new file mode 100644 index 0000000..6d3305d --- /dev/null +++ b/src/net/gcdc/asn1/datatypes/Asn1String.java @@ -0,0 +1,17 @@ +package net.gcdc.asn1.datatypes; + +public class Asn1String { + + private String value; + + public Asn1String() { this(""); } + + public Asn1String(String value) { + this.value = value; + } + + @Override public String toString() { return value; } + + public String value() { return value; } + +} diff --git a/src/net/gcdc/asn1/datatypes/Asn1VarSizeBitstring.java b/src/net/gcdc/asn1/datatypes/Asn1VarSizeBitstring.java new file mode 100644 index 0000000..631072f --- /dev/null +++ b/src/net/gcdc/asn1/datatypes/Asn1VarSizeBitstring.java @@ -0,0 +1,58 @@ +package net.gcdc.asn1.datatypes; + +import java.util.AbstractList; +import java.util.BitSet; +import java.util.Collection; +import java.util.Objects; + +/** + * Convenience class for Bitstrings of variable size. + * For UPER, {@code List} works just as well. + */ +public class Asn1VarSizeBitstring extends AbstractList { + + private final BitSet backing; + + @Override public Boolean get(int index) { + return backing.get(index); + } + + @Override public int size() { + return backing.length(); + } + + public Asn1VarSizeBitstring(Collection coll) { + backing = new BitSet(); + int bitIndex = 0; + for (Boolean b : coll) { + backing.set(bitIndex, b); + bitIndex++; + } + } + + public Asn1VarSizeBitstring(BitSet bitset) { + backing = (BitSet) bitset.clone(); + } + + protected void setBit(int bitIndex, boolean value) { + backing.set(bitIndex, value); + } + + public boolean getBit(int bitIndex) { + return backing.get(bitIndex); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + if (!super.equals(o)) return false; + Asn1VarSizeBitstring booleen = (Asn1VarSizeBitstring) o; + return Objects.equals(backing, booleen.backing); + } + + @Override + public int hashCode() { + return Objects.hash(super.hashCode(), backing); + } +} diff --git a/src/net/gcdc/asn1/datatypes/Bitstring.java b/src/net/gcdc/asn1/datatypes/Bitstring.java new file mode 100644 index 0000000..387f2aa --- /dev/null +++ b/src/net/gcdc/asn1/datatypes/Bitstring.java @@ -0,0 +1,16 @@ +package net.gcdc.asn1.datatypes; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** This annotation is used for bitstrings. + * In UPER, a SEQUENCE OF Booleans would look exactly as bitstring, so this annotation can be + * omitted for {@code List}. + */ +@Target({ElementType.TYPE}) +@Retention(RetentionPolicy.RUNTIME) +public @interface Bitstring { + +} diff --git a/src/net/gcdc/asn1/datatypes/CharacterRestriction.java b/src/net/gcdc/asn1/datatypes/CharacterRestriction.java new file mode 100644 index 0000000..ac40e25 --- /dev/null +++ b/src/net/gcdc/asn1/datatypes/CharacterRestriction.java @@ -0,0 +1,12 @@ +package net.gcdc.asn1.datatypes; + +public enum CharacterRestriction { + NumericString, + PrintableString, + VisibleString, + ISO646String, + IA5String, + BMPString, + UniversalString, + UTF8String; +} diff --git a/src/net/gcdc/asn1/datatypes/Choice.java b/src/net/gcdc/asn1/datatypes/Choice.java new file mode 100644 index 0000000..c58f71a --- /dev/null +++ b/src/net/gcdc/asn1/datatypes/Choice.java @@ -0,0 +1,12 @@ +package net.gcdc.asn1.datatypes; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Target({ElementType.TYPE}) +@Retention(RetentionPolicy.RUNTIME) +public @interface Choice { + +} diff --git a/src/net/gcdc/asn1/datatypes/DefaultAlphabet.java b/src/net/gcdc/asn1/datatypes/DefaultAlphabet.java new file mode 100644 index 0000000..8f6697f --- /dev/null +++ b/src/net/gcdc/asn1/datatypes/DefaultAlphabet.java @@ -0,0 +1,8 @@ +package net.gcdc.asn1.datatypes; + +public class DefaultAlphabet extends Alphabet { + + public DefaultAlphabet() { + super(""); + } +} diff --git a/src/net/gcdc/asn1/datatypes/FixedSize.java b/src/net/gcdc/asn1/datatypes/FixedSize.java new file mode 100644 index 0000000..cb893d8 --- /dev/null +++ b/src/net/gcdc/asn1/datatypes/FixedSize.java @@ -0,0 +1,12 @@ +package net.gcdc.asn1.datatypes; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Target({ElementType.TYPE, ElementType.FIELD}) +@Retention(RetentionPolicy.RUNTIME) +public @interface FixedSize { + int value(); +} diff --git a/src/net/gcdc/asn1/datatypes/HasExtensionMarker.java b/src/net/gcdc/asn1/datatypes/HasExtensionMarker.java new file mode 100644 index 0000000..560eca2 --- /dev/null +++ b/src/net/gcdc/asn1/datatypes/HasExtensionMarker.java @@ -0,0 +1,12 @@ +package net.gcdc.asn1.datatypes; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Target({ElementType.TYPE}) +@Retention(RetentionPolicy.RUNTIME) +public @interface HasExtensionMarker { + +} diff --git a/src/net/gcdc/asn1/datatypes/IntMinValue.java b/src/net/gcdc/asn1/datatypes/IntMinValue.java new file mode 100644 index 0000000..4c1b49a --- /dev/null +++ b/src/net/gcdc/asn1/datatypes/IntMinValue.java @@ -0,0 +1,13 @@ +package net.gcdc.asn1.datatypes; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Target({ElementType.TYPE, ElementType.FIELD}) +@Retention(RetentionPolicy.RUNTIME) +public @interface IntMinValue { + long minValue(); + boolean hasExtensionMarker() default false; +} diff --git a/src/net/gcdc/asn1/datatypes/IntRange.java b/src/net/gcdc/asn1/datatypes/IntRange.java new file mode 100644 index 0000000..417b299 --- /dev/null +++ b/src/net/gcdc/asn1/datatypes/IntRange.java @@ -0,0 +1,14 @@ +package net.gcdc.asn1.datatypes; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Target({ElementType.TYPE, ElementType.FIELD}) +@Retention(RetentionPolicy.RUNTIME) +public @interface IntRange { + long minValue(); + long maxValue(); + boolean hasExtensionMarker() default false; +} diff --git a/src/net/gcdc/asn1/datatypes/IsExtension.java b/src/net/gcdc/asn1/datatypes/IsExtension.java new file mode 100644 index 0000000..0cc7b3c --- /dev/null +++ b/src/net/gcdc/asn1/datatypes/IsExtension.java @@ -0,0 +1,12 @@ +package net.gcdc.asn1.datatypes; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Target({ElementType.FIELD}) +@Retention(RetentionPolicy.RUNTIME) +public @interface IsExtension { + +} diff --git a/src/net/gcdc/asn1/datatypes/NoAsn1Field.java b/src/net/gcdc/asn1/datatypes/NoAsn1Field.java new file mode 100644 index 0000000..3fa2442 --- /dev/null +++ b/src/net/gcdc/asn1/datatypes/NoAsn1Field.java @@ -0,0 +1,10 @@ +package net.gcdc.asn1.datatypes; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Target({ElementType.TYPE, ElementType.FIELD}) +@Retention(RetentionPolicy.RUNTIME) +public @interface NoAsn1Field {} \ No newline at end of file diff --git a/src/net/gcdc/asn1/datatypes/Optional.java b/src/net/gcdc/asn1/datatypes/Optional.java new file mode 100644 index 0000000..18f495f --- /dev/null +++ b/src/net/gcdc/asn1/datatypes/Optional.java @@ -0,0 +1,96 @@ +package net.gcdc.asn1.datatypes; + +import java.util.NoSuchElementException; +import java.util.Objects; + +/** Represents optional values. + * + * Should be replaced by java.util.Optional from Java 8, when project moves to Java 8. + * + * @param type of contained elements */ +public class Optional { + + private final T element; + private final boolean isPresent; + + private Optional(T element, boolean isPresent) { + this.element = element; + this.isPresent = isPresent; + } + + /** @return true if the Option contains a value */ + public boolean isPresent() { + return isPresent; + } + + /** @return the element if the option is not empty + * @throws java.util.NoSuchElementException if the option is empty */ + public T get() { + if (isPresent) { + return element; + } else { + throw new NoSuchElementException("None.get"); + } + } + + /** @return the value, if present, otherwise return {@code other} + * @param other the value to be returned if there is no value present */ + public T orElse(T other) { + return isPresent() ? get() : other; + } + + /** + * Indicates whether some other object is "equal to" this Optional. The + * other object is considered equal if: + *

+ * + * @param obj an object to be tested for equality + * @return {code true} if the other object is "equal to" this object + * otherwise {@code false} + */ + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + + if (!(obj instanceof Optional)) { + return false; + } + + Optional other = (Optional) obj; + return Objects.equals(element, other.element); + } + + /** + * Returns the hash code value of the present value, if any, or 0 (zero) if + * no value is present. + * + * @return hash code value of the present value or 0 if no value is present + */ + @Override + public int hashCode() { + return Objects.hashCode(element); + } + + /** Returns an Option containing the value. + * + * @param the type of the value + * @param element contained value + * @return a new Option that contains the value */ + public static Optional of(final A element) { + return new Optional(element, true); + } + + /** Returns an empty option. + * + * @param + * @return an empty Option */ + public static Optional empty() { + return new Optional(null, false); + } +} diff --git a/src/net/gcdc/asn1/datatypes/RestrictedString.java b/src/net/gcdc/asn1/datatypes/RestrictedString.java new file mode 100644 index 0000000..6ad6bdc --- /dev/null +++ b/src/net/gcdc/asn1/datatypes/RestrictedString.java @@ -0,0 +1,13 @@ +package net.gcdc.asn1.datatypes; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Target({ElementType.TYPE, ElementType.FIELD}) +@Retention(RetentionPolicy.RUNTIME) +public @interface RestrictedString { + CharacterRestriction value(); + Class alphabet() default DefaultAlphabet.class; +} diff --git a/src/net/gcdc/asn1/datatypes/Sequence.java b/src/net/gcdc/asn1/datatypes/Sequence.java new file mode 100644 index 0000000..2a823ec --- /dev/null +++ b/src/net/gcdc/asn1/datatypes/Sequence.java @@ -0,0 +1,9 @@ +package net.gcdc.asn1.datatypes; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +//@Target({ElementType.ANNOTATION_TYPE}) +@Retention(RetentionPolicy.RUNTIME) +public @interface Sequence { + +} diff --git a/src/net/gcdc/asn1/datatypes/SizeRange.java b/src/net/gcdc/asn1/datatypes/SizeRange.java new file mode 100644 index 0000000..835d80c --- /dev/null +++ b/src/net/gcdc/asn1/datatypes/SizeRange.java @@ -0,0 +1,14 @@ +package net.gcdc.asn1.datatypes; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Target({ElementType.TYPE, ElementType.FIELD}) +@Retention(RetentionPolicy.RUNTIME) +public @interface SizeRange { + int minValue(); + int maxValue(); + boolean hasExtensionMarker() default false; +} diff --git a/src/net/gcdc/asn1/datatypes/package-info.java b/src/net/gcdc/asn1/datatypes/package-info.java new file mode 100644 index 0000000..33644d5 --- /dev/null +++ b/src/net/gcdc/asn1/datatypes/package-info.java @@ -0,0 +1,7 @@ +/** Annotations to create Java classes that correspond to ASN.1 specifications. + * + * Some annotations (e.g. {@link SizeRange}, {@link FixedSize}, {@link IntRange},{@link IntMaxValue} + * {@link RestrictedString}) are Type-only annotations and sometime require creating extra classes, + * they can be extended to work as Field annotations too, but this will require modifications to the + * Encoder. */ +package net.gcdc.asn1.datatypes; diff --git a/src/net/gcdc/asn1/datatypesimpl/OctetString.java b/src/net/gcdc/asn1/datatypesimpl/OctetString.java new file mode 100644 index 0000000..e21a098 --- /dev/null +++ b/src/net/gcdc/asn1/datatypesimpl/OctetString.java @@ -0,0 +1,46 @@ +package net.gcdc.asn1.datatypesimpl; + +import java.util.Collection; +import java.util.List; + +import net.gcdc.asn1.datatypes.Asn1SequenceOf; + +/* + * Sequence of Asn1Integer for restricted integers + * + * + */ +public class OctetString extends Asn1SequenceOf { + public OctetString() { super(); } + public OctetString(Collection coll) { super(coll); } + + public OctetString(List numbers) { + super(); + this.addAll(numbers); + } + + public static OctetString getSequence(List numList) { + if (numList == null || numList.isEmpty()) return null; + return new OctetString(numList); + } + + + public byte[] toByteArray () { + + byte[] bytes= new byte[this.size()]; + + for (int i = 0; i < this.size(); i++){ + bytes[i] = this.get(i); + } + + return bytes; + } + + public OctetString(byte[] bytes){ + super(); + for (int i= 0;i < bytes.length; i++){ + this.add(bytes[i]); + } + } + +} diff --git a/src/net/gcdc/asn1/datatypesimpl/SequenceOfLong.java b/src/net/gcdc/asn1/datatypesimpl/SequenceOfLong.java new file mode 100644 index 0000000..547ae80 --- /dev/null +++ b/src/net/gcdc/asn1/datatypesimpl/SequenceOfLong.java @@ -0,0 +1,27 @@ +package net.gcdc.asn1.datatypesimpl; + +import java.util.Collection; +import java.util.List; + +import net.gcdc.asn1.datatypes.Asn1SequenceOf; + +/* + * Sequence of Asn1Integer for restricted integers + * + * + */ +public class SequenceOfLong extends Asn1SequenceOf { + public SequenceOfLong() { super(); } + public SequenceOfLong(Collection coll) { super(coll); } + + public SequenceOfLong(List numbers) { + super(); + this.addAll(numbers); + } + + public static SequenceOfLong getSequence(List numList) { + if (numList == null || numList.isEmpty()) return null; + return new SequenceOfLong(numList); + } + +} diff --git a/src/net/gcdc/asn1/datatypesimpl/SequenceOfStringIA5.java b/src/net/gcdc/asn1/datatypesimpl/SequenceOfStringIA5.java new file mode 100644 index 0000000..d816261 --- /dev/null +++ b/src/net/gcdc/asn1/datatypesimpl/SequenceOfStringIA5.java @@ -0,0 +1,13 @@ +package net.gcdc.asn1.datatypesimpl; + +import java.util.Collection; + +import net.gcdc.asn1.datatypes.Asn1SequenceOf; +import net.gcdc.asn1.datatypes.CharacterRestriction; +import net.gcdc.asn1.datatypes.RestrictedString; + +@RestrictedString(CharacterRestriction.IA5String) +public class SequenceOfStringIA5 extends Asn1SequenceOf { + public SequenceOfStringIA5() { super(); } + public SequenceOfStringIA5(Collection coll) { super(coll); } +} diff --git a/src/net/gcdc/asn1/datatypesimpl/SequenceOfStringUTF8.java b/src/net/gcdc/asn1/datatypesimpl/SequenceOfStringUTF8.java new file mode 100644 index 0000000..b5ad31c --- /dev/null +++ b/src/net/gcdc/asn1/datatypesimpl/SequenceOfStringUTF8.java @@ -0,0 +1,13 @@ +package net.gcdc.asn1.datatypesimpl; + +import java.util.Collection; + +import net.gcdc.asn1.datatypes.Asn1SequenceOf; +import net.gcdc.asn1.datatypes.CharacterRestriction; +import net.gcdc.asn1.datatypes.RestrictedString; + +@RestrictedString(CharacterRestriction.UTF8String) +public class SequenceOfStringUTF8 extends Asn1SequenceOf { + public SequenceOfStringUTF8() { super(); } + public SequenceOfStringUTF8(Collection coll) { super(coll); } +} diff --git a/src/net/gcdc/asn1/datatypesimpl/SequenceOfUnrestrictedLong.java b/src/net/gcdc/asn1/datatypesimpl/SequenceOfUnrestrictedLong.java new file mode 100644 index 0000000..96f8734 --- /dev/null +++ b/src/net/gcdc/asn1/datatypesimpl/SequenceOfUnrestrictedLong.java @@ -0,0 +1,35 @@ +package net.gcdc.asn1.datatypesimpl; + +import java.util.Collection; +import java.util.List; + +import net.gcdc.asn1.datatypes.Asn1BigInteger; +import net.gcdc.asn1.datatypes.Asn1SequenceOf; + +/* + * Sequence of Asn1Integer for restricted integers + * + */ +public class SequenceOfUnrestrictedLong extends Asn1SequenceOf { + public SequenceOfUnrestrictedLong() { super(); } + public SequenceOfUnrestrictedLong(Collection coll) { super(coll); } + + public void add(Long num) { + add (new Asn1BigInteger(num)); + } + + + public SequenceOfUnrestrictedLong(List numbers) { + super(); + for (Long number: numbers){ + this.add(new Asn1BigInteger(number)); + } + } + + + public static SequenceOfUnrestrictedLong getSequence(List numList) { + if (numList == null || numList.isEmpty()) return null; + return new SequenceOfUnrestrictedLong(numList); + } + +} diff --git a/src/net/gcdc/asn1/test/TestSequenceOfLong.java b/src/net/gcdc/asn1/test/TestSequenceOfLong.java new file mode 100644 index 0000000..f5c295f --- /dev/null +++ b/src/net/gcdc/asn1/test/TestSequenceOfLong.java @@ -0,0 +1,24 @@ +package net.gcdc.asn1.test; + +import java.util.Collection; +import java.util.List; + +import net.gcdc.asn1.datatypes.Asn1SequenceOf; + +public class TestSequenceOfLong extends Asn1SequenceOf { + public TestSequenceOfLong() { super(); } + public TestSequenceOfLong(Collection coll) { super(coll); } + + + public TestSequenceOfLong(List numbers) { + super(); + for (Long number: numbers){ + this.add(new Long(number)); + } + } + + public static TestSequenceOfLong getSequence(List numList) { + if (numList == null || numList.isEmpty()) return null; + return new TestSequenceOfLong(numList); + } +} diff --git a/src/net/gcdc/asn1/test/UperEncodeBooleanTest.java b/src/net/gcdc/asn1/test/UperEncodeBooleanTest.java new file mode 100644 index 0000000..9716474 --- /dev/null +++ b/src/net/gcdc/asn1/test/UperEncodeBooleanTest.java @@ -0,0 +1,83 @@ +package net.gcdc.asn1.test; + +import static org.junit.Assert.assertEquals; + +import java.util.logging.Level; + +import net.gcdc.asn1.datatypes.Asn1Optional; +import net.gcdc.asn1.datatypes.Sequence; + +import net.gcdc.asn1.uper.UperEncoder; + +import org.junit.Test; + + +public class UperEncodeBooleanTest { + + /** + * Example from the Standard on UPER. +
+         TestRecord ::= [APPLICATION 0] IMPLICIT SEQUENCE {
+            value BOOLEAN OPTIONAL,
+}
+    
+ */ + @Sequence + public static class TestRecord { + + + @Asn1Optional() Boolean value; + + public TestRecord() { + this(false); + } + + public TestRecord(Boolean value) { + this.value = value; + } + } + + + @Test public void testTrue() throws IllegalArgumentException, IllegalAccessException { + TestRecord record = new TestRecord(new Boolean(true)); + byte[] encoded = UperEncoder.encode(record); + String hex = UperEncoder.hexStringFromBytes(encoded); + UperEncoder.logger.log(Level.FINEST,String.format("data hex: %s", hex)); + assertEquals("C0",hex); + } + + @Test public void testFalse() throws IllegalArgumentException, IllegalAccessException { + TestRecord record = new TestRecord(new Boolean(false)); + byte[] encoded = UperEncoder.encode(record); + String hex = UperEncoder.hexStringFromBytes(encoded); + UperEncoder.logger.log(Level.FINEST,String.format("data hex: %s", hex)); + assertEquals("80",hex); + } + + @Test public void testDecodeTrue() throws IllegalArgumentException, IllegalAccessException { + TestRecord record = new TestRecord(new Boolean(true)); + byte[] encoded = UperEncoder.encode(record); + String hex = UperEncoder.hexStringFromBytes(encoded); + UperEncoder.logger.log(Level.FINEST,String.format("data hex: %s", hex)); + assertEquals("C0",hex); + TestRecord result = UperEncoder.decode(encoded, TestRecord.class); + assertEquals(result.value,record.value); + + } + + @Test public void testDecodeFalse() throws IllegalArgumentException, IllegalAccessException { + TestRecord record = new TestRecord(new Boolean(false)); + byte[] encoded = UperEncoder.encode(record); + String hex = UperEncoder.hexStringFromBytes(encoded); + UperEncoder.logger.log(Level.FINEST,String.format("data hex: %s", hex)); + assertEquals("80",hex); + TestRecord result = UperEncoder.decode(encoded, TestRecord.class); + assertEquals(result.value,record.value); + } + + + + + + +} diff --git a/src/net/gcdc/asn1/test/UperEncodeChoiceExtensionTest.java b/src/net/gcdc/asn1/test/UperEncodeChoiceExtensionTest.java new file mode 100644 index 0000000..acbbd0b --- /dev/null +++ b/src/net/gcdc/asn1/test/UperEncodeChoiceExtensionTest.java @@ -0,0 +1,90 @@ +package net.gcdc.asn1.test; + +import static org.junit.Assert.assertEquals; + +import java.util.logging.Level; + +import net.gcdc.asn1.datatypes.Asn1Optional; +import net.gcdc.asn1.datatypes.CharacterRestriction; +import net.gcdc.asn1.datatypes.Choice; +import net.gcdc.asn1.datatypes.HasExtensionMarker; +import net.gcdc.asn1.datatypes.IsExtension; +import net.gcdc.asn1.datatypes.RestrictedString; +import net.gcdc.asn1.uper.UperEncoder; + +import org.junit.Test; + + +public class UperEncodeChoiceExtensionTest { + + /** Example for extended sequence + TestRecord ::= [APPLICATION 0] CHOICE { + value1 IA5String + ,... + ,value2 IA5String + } + + value TestRecord ::= value2: "extension" + */ + @Choice + @HasExtensionMarker + public static class TestRecordExtended { + + @RestrictedString(CharacterRestriction.IA5String) + String value1 = null; + + @IsExtension + @RestrictedString(CharacterRestriction.IA5String) + String value2 = "extension"; + + public TestRecordExtended() { } + } + + /** Example for extended sequence + TestRecord ::= [APPLICATION 0] CHOICE { + value1 IA5String, + ,... + } + */ + @Choice + @HasExtensionMarker + public static class TestRecord { + + @RestrictedString(CharacterRestriction.IA5String) + @Asn1Optional() String value1 = "regular"; + + public TestRecord() { } + } + + + @Test public void testEncode() throws IllegalArgumentException, IllegalAccessException { + TestRecordExtended record = new TestRecordExtended(); + byte[] encoded = UperEncoder.encode(record); + String hex = UperEncoder.hexStringFromBytes(encoded); + UperEncoder.logger.log(Level.FINEST,String.format("data hex: %s", hex)); + assertEquals("800909CBE3A65DDCF4EFDC",hex); + } + + @Test public void testDecodeExtended() throws IllegalArgumentException, IllegalAccessException { + TestRecordExtended record = new TestRecordExtended(); + byte[] encoded = UperEncoder.encode(record); + String hex = UperEncoder.hexStringFromBytes(encoded); + UperEncoder.logger.log(Level.FINEST,String.format("data hex: %s", hex)); + assertEquals("800909CBE3A65DDCF4EFDC",hex); + TestRecordExtended result = UperEncoder.decode(encoded, TestRecordExtended.class); + assertEquals(result.value2,record.value2); + } + + @Test public void testDecode() throws IllegalArgumentException, IllegalAccessException { + TestRecordExtended record = new TestRecordExtended(); + byte[] encoded = UperEncoder.encode(record); + String hex = UperEncoder.hexStringFromBytes(encoded); + UperEncoder.logger.log(Level.FINEST,String.format("data hex: %s", hex)); + assertEquals("800909CBE3A65DDCF4EFDC",hex); + TestRecord result = UperEncoder.decode(encoded, TestRecord.class); + assert(result == null); + } + + + +} diff --git a/src/net/gcdc/asn1/test/UperEncodeChoiceTest.java b/src/net/gcdc/asn1/test/UperEncodeChoiceTest.java new file mode 100644 index 0000000..d35e717 --- /dev/null +++ b/src/net/gcdc/asn1/test/UperEncodeChoiceTest.java @@ -0,0 +1,72 @@ +package net.gcdc.asn1.test; + +import static org.junit.Assert.assertEquals; + +import java.util.logging.Level; + +import net.gcdc.asn1.datatypes.CharacterRestriction; +import net.gcdc.asn1.datatypes.Choice; +import net.gcdc.asn1.datatypes.RestrictedString; +import net.gcdc.asn1.uper.UperEncoder; + +import org.junit.Test; + + +public class UperEncodeChoiceTest { + + /** + * Example from the Standard on UPER. +
+     TestRecord ::= [APPLICATION 0] IMPLICIT SEQUENCE {
+            value EnumType DEFAULT value2,
+     }
+
+     EnumType			::= ENUMERATED {	
+		value1 (0),
+		value2 (1)
+		,...
+	 }		
+     
+ */ + @Choice + public static class TestRecord { + + @RestrictedString(CharacterRestriction.UTF8String) + String valueUtf8; + + @RestrictedString(CharacterRestriction.IA5String) + String valueIA5; + + public TestRecord() { + } + + public TestRecord(String utf8, String ia5) { + this.valueUtf8 = utf8; + this.valueIA5 = ia5; + } + } + + @Test public void testEncode() throws IllegalArgumentException, IllegalAccessException { + TestRecord record = new TestRecord(null, "Meier"); + byte[] encoded = UperEncoder.encode(record); + String hex = UperEncoder.hexStringFromBytes(encoded); + UperEncoder.logger.log(Level.FINEST,String.format("data hex: %s", hex)); + assertEquals("82CDCBA72F20",hex); + } + + @Test public void testDecode() throws IllegalArgumentException, IllegalAccessException { + TestRecord record = new TestRecord(null, "Meier"); + byte[] encoded = UperEncoder.encode(record); + String hex = UperEncoder.hexStringFromBytes(encoded); + UperEncoder.logger.log(Level.FINEST,String.format("data hex: %s", hex)); + assertEquals("82CDCBA72F20",hex); + + TestRecord result = UperEncoder.decode(encoded, TestRecord.class); + assertEquals(null,record.valueUtf8); + assertEquals(result.valueIA5,record.valueIA5); + } + + + + +} diff --git a/src/net/gcdc/asn1/test/UperEncodeEnumExtensionTest.java b/src/net/gcdc/asn1/test/UperEncodeEnumExtensionTest.java new file mode 100644 index 0000000..c098839 --- /dev/null +++ b/src/net/gcdc/asn1/test/UperEncodeEnumExtensionTest.java @@ -0,0 +1,146 @@ +package net.gcdc.asn1.test; + +import static org.junit.Assert.assertEquals; + +import java.util.logging.Level; + +import net.gcdc.asn1.datatypes.Asn1Optional; +import net.gcdc.asn1.datatypes.HasExtensionMarker; +import net.gcdc.asn1.datatypes.IsExtension; +import net.gcdc.asn1.datatypes.Sequence; + +import net.gcdc.asn1.uper.UperEncoder; + +import org.junit.Test; + + +public class UperEncodeEnumExtensionTest { + + /*** Example from the Standard on UPER. +
+    World-Schema DEFINITIONS AUTOMATIC TAGS ::= 
+    BEGIN
+    TestRecord ::= [APPLICATION 0] IMPLICIT SEQUENCE {
+            value EnumType DEFAULT value2
+    }
+
+    EnumType ::= ENUMERATED {	
+		value1 (0),
+		value2 (1)
+		,...
+    }	                                  
+    END
+    
+ */ + @Sequence + public static class TestRecord { + + @Asn1Optional EnumType value = EnumType.value1; + public TestRecord() {} + public void setValue(EnumType value) { + this.value = value; + } + } + + + /*** Example from the Standard on UPER. + TestRecord ::= [APPLICATION 0] IMPLICIT SEQUENCE { + value EnumType DEFAULT value2, + } + + EnumType ::= ENUMERATED { + value1 (0), + value2 (1) + ,... + value3 (2) + } + */ + @Sequence + public static class TestRecordExtended { + + @Asn1Optional EnumTypeExtended value = EnumTypeExtended.value3; + + public TestRecordExtended() {} + + public void setValue(EnumTypeExtended value) { + this.value = value; + } + + + } + + @HasExtensionMarker + public enum EnumType { + value1("value1"), + value2("value2"); + + public String text; + + EnumType(String text) { + this.text = text; + } + + public String toString(){ + return text; + } + } + + + @HasExtensionMarker + public enum EnumTypeExtended { + value1("value1"), + value2("value2"), + + @IsExtension + value3("value3"); + + public String text; + + EnumTypeExtended(String text) { + this.text = text; + } + + public String toString(){ + return text; + } + } + + + + @Test public void testExtension() throws IllegalArgumentException, IllegalAccessException { + + TestRecordExtended record = new TestRecordExtended(); + record.setValue(EnumTypeExtended.value3); + byte[] encoded = UperEncoder.encode(record); + String hex = UperEncoder.hexStringFromBytes(encoded); + UperEncoder.logger.log(Level.FINEST,String.format("Enum value3: data hex: %s", hex)); + assertEquals("C000", hex); + } + + @Test public void testExtensionDecoding() throws IllegalArgumentException, IllegalAccessException { + + TestRecordExtended record = new TestRecordExtended(); + record.setValue(EnumTypeExtended.value3); + byte[] encoded = UperEncoder.encode(record); + String hex = UperEncoder.hexStringFromBytes(encoded); + UperEncoder.logger.log(Level.FINEST,String.format("Enum value3: data hex: %s", hex)); + assertEquals("C000", hex); + + TestRecordExtended result = UperEncoder.decode(encoded, TestRecordExtended.class); + assertEquals(result.value,EnumTypeExtended.value3); + } + + @Test public void testUnknownExtensionDecoding() throws IllegalArgumentException, IllegalAccessException { + + TestRecordExtended record = new TestRecordExtended(); + byte[] encoded = UperEncoder.encode(record); + String hex = UperEncoder.hexStringFromBytes(encoded); + UperEncoder.logger.log(Level.FINEST,String.format("Enum value3: data hex: %s", hex)); + assertEquals("C000", hex); + + TestRecord result = UperEncoder.decode(encoded, TestRecord.class); + assert(result.value == null); + } + + +} diff --git a/src/net/gcdc/asn1/test/UperEncodeEnumTest.java b/src/net/gcdc/asn1/test/UperEncodeEnumTest.java new file mode 100644 index 0000000..66fbc05 --- /dev/null +++ b/src/net/gcdc/asn1/test/UperEncodeEnumTest.java @@ -0,0 +1,126 @@ +package net.gcdc.asn1.test; + +import static org.junit.Assert.assertEquals; + +import java.util.logging.Level; + +import net.gcdc.asn1.datatypes.Asn1Default; +import net.gcdc.asn1.datatypes.Asn1Optional; +import net.gcdc.asn1.datatypes.Sequence; + +import net.gcdc.asn1.uper.UperEncoder; + +import org.junit.Test; + + +public class UperEncodeEnumTest { + + /** + * Example from the Standard on UPER. +
+     TestRecord ::= [APPLICATION 0] IMPLICIT SEQUENCE {
+            value EnumType DEFAULT value2,
+     }
+
+     EnumType			::= ENUMERATED {	
+		value1 (0),
+		value2 (1)
+		,...
+	 }		
+     
+ */ + @Sequence + public static class TestRecord { + + @Asn1Default(value="value2") + @Asn1Optional EnumType value = EnumType.value2; + + + public TestRecord() {} + + public TestRecord(EnumType value) { + this.value = value; + } + } + + public enum EnumType { + value1("value1"), + value2("value2"), + value3("value3"), + value4("value4"), + value5("value5"), + value6("value6"), + value7("value7"), + value8("value8"), + value9("value9"), + value10("value10"), + value11("value11"), + value12("value12"), + value13("value13"), + value14("value14"), + value15("value15"), + value16("value16"), + value17("value17"), + value18("value18"), + value19("value19"), + value20("value20"), + value21("value21"), + value22("value22"); + + + public String text; + + EnumType(String text) { + this.text = text; + } + + public String toString(){ + return text; + } + } + + + + @Test public void testNonDefaultValue() throws IllegalArgumentException, IllegalAccessException { + + TestRecord record = new TestRecord(EnumType.value4); + byte[] encoded = UperEncoder.encode(record); + String hex = UperEncoder.hexStringFromBytes(encoded); + UperEncoder.logger.log(Level.FINEST,String.format("Enum value4: data hex: %s", hex)); + assertEquals("8C", hex); + } + + @Test public void testDefaultValue() throws IllegalArgumentException, IllegalAccessException { + + TestRecord record = new TestRecord(EnumType.value2); + byte[] encoded = UperEncoder.encode(record); + String hex = UperEncoder.hexStringFromBytes(encoded); + UperEncoder.logger.log(Level.FINEST,String.format("Enum value2: data hex: %s", hex)); + assertEquals("00", UperEncoder.hexStringFromBytes(encoded)); + } + + @Test public void testDecodeNonDefaultValue() throws IllegalArgumentException, IllegalAccessException { + + TestRecord record = new TestRecord(EnumType.value4); + byte[] encoded = UperEncoder.encode(record); + String hex = UperEncoder.hexStringFromBytes(encoded); + UperEncoder.logger.log(Level.FINEST,String.format("Enum value4: data hex: %s", hex)); + assertEquals("8C", hex); + + TestRecord result = UperEncoder.decode(encoded, TestRecord.class); + assertEquals(result.value,EnumType.value4); + } + + @Test public void testDecodeDefaultValue() throws IllegalArgumentException, IllegalAccessException { + + TestRecord record = new TestRecord(EnumType.value2); + byte[] encoded = UperEncoder.encode(record); + String hex = UperEncoder.hexStringFromBytes(encoded); + UperEncoder.logger.log(Level.FINEST,String.format("Enum value2: data hex: %s", hex)); + assertEquals("00", UperEncoder.hexStringFromBytes(encoded)); + + TestRecord result = UperEncoder.decode(encoded, TestRecord.class); + assertEquals(result.value,EnumType.value2); + } + +} diff --git a/src/net/gcdc/asn1/test/UperEncodeIntegerConstrainedTest.java b/src/net/gcdc/asn1/test/UperEncodeIntegerConstrainedTest.java new file mode 100644 index 0000000..9450406 --- /dev/null +++ b/src/net/gcdc/asn1/test/UperEncodeIntegerConstrainedTest.java @@ -0,0 +1,68 @@ +package net.gcdc.asn1.test; + +import static org.junit.Assert.assertEquals; + +import java.util.logging.Level; + +import net.gcdc.asn1.datatypes.IntRange; +import net.gcdc.asn1.datatypes.Sequence; +import net.gcdc.asn1.uper.UperEncoder; + +import org.junit.Test; + + +public class UperEncodeIntegerConstrainedTest { + + /** + * Example from the Standard on UPER. +
+		TestRecord ::= [APPLICATION 0] IMPLICIT SEQUENCE {
+  			number1 INTEGER (1..999),
+  			number2 INTEGER (0..999),
+  			number3 INTEGER (63..999)
+		}
+    
+ */ + @Sequence + public static class TestRecord { + + @IntRange(minValue=1, maxValue=999) + public Long value1; + + @IntRange(minValue=0, maxValue=999) + public Long value2; + + @IntRange(minValue=63, maxValue=999) + public Long value3; + + + public TestRecord() { + this(new Long(63L)); + } + + public TestRecord(Long num) { + value1 = num; + value2 = num; + value3 = num; + } + } + + + + @Test public void test() throws IllegalArgumentException, IllegalAccessException { + + TestRecord record = new TestRecord(63L); + byte[] encoded = UperEncoder.encode(record); + String hex = UperEncoder.hexStringFromBytes(encoded); + UperEncoder.logger.log(Level.FINEST,String.format("data hex: %s", hex)); + assertEquals("0F83F000",hex); + TestRecord result = UperEncoder.decode(encoded, TestRecord.class); + assertEquals(result.value1.longValue(),record.value1.longValue()); + assertEquals(result.value2.longValue(),record.value2.longValue()); + assertEquals(result.value3.longValue(),record.value3.longValue()); + + } + + + +} diff --git a/src/net/gcdc/asn1/test/UperEncodeIntegerExtensionTest.java b/src/net/gcdc/asn1/test/UperEncodeIntegerExtensionTest.java new file mode 100644 index 0000000..5a33368 --- /dev/null +++ b/src/net/gcdc/asn1/test/UperEncodeIntegerExtensionTest.java @@ -0,0 +1,99 @@ +package net.gcdc.asn1.test; + +import static org.junit.Assert.assertEquals; + +import java.util.logging.Level; + +import net.gcdc.asn1.datatypes.Asn1BigInteger; +import net.gcdc.asn1.datatypes.HasExtensionMarker; +import net.gcdc.asn1.datatypes.IsExtension; +import net.gcdc.asn1.datatypes.Sequence; + +import net.gcdc.asn1.uper.UperEncoder; + +import org.junit.Test; + + +public class UperEncodeIntegerExtensionTest { + + /** + * Example from the Standard on UPER. +
+     TestRecord ::= [APPLICATION 0] IMPLICIT SEQUENCE {
+           number1 INTEGER,
+           ...,
+           number2 INTEGER,
+           number3 INTEGER 
+    }
+    
+    value TestRecord ::=  {     
+        value1       12345678909999899,
+        value2       5555555555,
+        value3       32001      
+       }
+    
+Encoding to the file 'data.uper' using PER UNALIGNED encoding rule...
+TestRecord SEQUENCE [root fieldcount (not encoded) = 1]
+  value1 INTEGER [length = 7.0]
+    12345678909999899
+  value2 INTEGER [length = 5.0]
+    5555555555
+  value3 INTEGER [length = 2.0]
+    32001
+Total encoded length = 20.2
+Encoded successfully in 21 bytes:
+8395EE2A 2EF8858D 81C18140 52C8C338 C0C09F40 40
+    
+    
+    
+ */ + @Sequence + @HasExtensionMarker + public static class TestRecord { + + + Asn1BigInteger value1; + + @IsExtension + Asn1BigInteger value2; + + @IsExtension + Asn1BigInteger value3; + + public TestRecord() { + value1 = new Asn1BigInteger(12345678909999899L); + value2 = new Asn1BigInteger(5555555555L); + value3 = new Asn1BigInteger(32001L); + } + + + } + + + @Test public void test() throws IllegalArgumentException, IllegalAccessException { + + TestRecord record = new TestRecord(); + byte[] encoded = UperEncoder.encode(record); + String hex = UperEncoder.hexStringFromBytes(encoded); + UperEncoder.logger.log(Level.FINEST,String.format("data hex: %s", hex)); + assertEquals("8395EE2A2EF8858D81C1814052C8C338C0C09F4040",hex); + + + } + + @Test public void testDecode() throws IllegalArgumentException, IllegalAccessException { + + TestRecord record = new TestRecord(); + byte[] encoded = UperEncoder.encode(record); + String hex = UperEncoder.hexStringFromBytes(encoded); + UperEncoder.logger.log(Level.FINEST,String.format("data hex: %s", hex)); + assertEquals("8395EE2A2EF8858D81C1814052C8C338C0C09F4040",hex); + + TestRecord result = UperEncoder.decode(encoded, TestRecord.class); + assertEquals(result.value1.longValue(),record.value1.longValue()); + assertEquals(result.value2.longValue(),record.value2.longValue()); + assertEquals(result.value3.longValue(),record.value3.longValue()); + + } + +} diff --git a/src/net/gcdc/asn1/test/UperEncodeIntegerSmallTest.java b/src/net/gcdc/asn1/test/UperEncodeIntegerSmallTest.java new file mode 100644 index 0000000..9ad0e63 --- /dev/null +++ b/src/net/gcdc/asn1/test/UperEncodeIntegerSmallTest.java @@ -0,0 +1,129 @@ +package net.gcdc.asn1.test; + +import static org.junit.Assert.assertEquals; + +import java.util.logging.Level; + +import net.gcdc.asn1.datatypes.Sequence; + +import net.gcdc.asn1.uper.UperEncoder; + +import org.junit.Test; + + +public class UperEncodeIntegerSmallTest { + + /** + * Example from the Standard on UPER. +
+		TestRecord ::= [APPLICATION 0] IMPLICIT SEQUENCE {
+  			number1 INTEGER,
+  			number2 INTEGER
+		}
+    
+ */ + @Sequence + public static class TestRecord { + + public Long value1; + + public Integer value2; + + public TestRecord() { + this(new Long(12345678909999899L)); + } + + public TestRecord(Long num) { + value1 = num; + value2 = Integer.valueOf(num.intValue()); + } + } + + + + @Test public void test1() throws IllegalArgumentException, IllegalAccessException { + + TestRecord record = new TestRecord(1L); + byte[] encoded = UperEncoder.encode(record); + String hex = UperEncoder.hexStringFromBytes(encoded); + UperEncoder.logger.log(Level.FINEST,String.format("data hex: %s", hex)); + assertEquals("01010101",hex); + + TestRecord result = UperEncoder.decode(encoded, TestRecord.class); + assertEquals(result.value1.longValue(),record.value1.longValue()); + assertEquals(result.value2.longValue(),record.value2.longValue()); + + } + + @Test public void test16() throws IllegalArgumentException, IllegalAccessException { + + TestRecord record = new TestRecord(16L); + byte[] encoded = UperEncoder.encode(record); + String hex = UperEncoder.hexStringFromBytes(encoded); + UperEncoder.logger.log(Level.FINEST,String.format("data hex: %s", hex)); + assertEquals("01100110",hex); + + TestRecord result = UperEncoder.decode(encoded, TestRecord.class); + assertEquals(result.value1.longValue(),record.value1.longValue()); + assertEquals(result.value2.longValue(),record.value2.longValue()); + + } + + + @Test public void test63() throws IllegalArgumentException, IllegalAccessException { + + TestRecord record = new TestRecord(63L); + byte[] encoded = UperEncoder.encode(record); + String hex = UperEncoder.hexStringFromBytes(encoded); + UperEncoder.logger.log(Level.FINEST,String.format("data hex: %s", hex)); + assertEquals("013F013F",hex); + + TestRecord result = UperEncoder.decode(encoded, TestRecord.class); + assertEquals(result.value1.longValue(),record.value1.longValue()); + assertEquals(result.value2.longValue(),record.value2.longValue()); + + } + + @Test public void test64() throws IllegalArgumentException, IllegalAccessException { + + TestRecord record = new TestRecord(64L); + byte[] encoded = UperEncoder.encode(record); + String hex = UperEncoder.hexStringFromBytes(encoded); + UperEncoder.logger.log(Level.FINEST,String.format("data hex: %s", hex)); + assertEquals("01400140",hex); + + TestRecord result = UperEncoder.decode(encoded, TestRecord.class); + assertEquals(result.value1.longValue(),record.value1.longValue()); + assertEquals(result.value2.longValue(),record.value2.longValue()); + + } + + @Test public void test127() throws IllegalArgumentException, IllegalAccessException { + + TestRecord record = new TestRecord(127L); + byte[] encoded = UperEncoder.encode(record); + String hex = UperEncoder.hexStringFromBytes(encoded); + UperEncoder.logger.log(Level.FINEST,String.format("data hex: %s", hex)); + assertEquals("017F017F",hex); + + TestRecord result = UperEncoder.decode(encoded, TestRecord.class); + assertEquals(result.value1.longValue(),record.value1.longValue()); + assertEquals(result.value2.longValue(),record.value2.longValue()); + + } + + @Test public void test128() throws IllegalArgumentException, IllegalAccessException { + + TestRecord record = new TestRecord(128L); + byte[] encoded = UperEncoder.encode(record); + String hex = UperEncoder.hexStringFromBytes(encoded); + UperEncoder.logger.log(Level.FINEST,String.format("data hex: %s", hex)); + assertEquals("020080020080",hex); + + TestRecord result = UperEncoder.decode(encoded, TestRecord.class); + assertEquals(result.value1.longValue(),record.value1.longValue()); + assertEquals(result.value2.longValue(),record.value2.longValue()); + + } + +} diff --git a/src/net/gcdc/asn1/test/UperEncodeIntegerTest.java b/src/net/gcdc/asn1/test/UperEncodeIntegerTest.java new file mode 100644 index 0000000..4eab78a --- /dev/null +++ b/src/net/gcdc/asn1/test/UperEncodeIntegerTest.java @@ -0,0 +1,64 @@ +package net.gcdc.asn1.test; + +import static org.junit.Assert.assertEquals; + +import java.util.logging.Level; + +import net.gcdc.asn1.datatypes.Asn1BigInteger; +import net.gcdc.asn1.datatypes.Sequence; + +import net.gcdc.asn1.uper.UperEncoder; + +import org.junit.Test; + + +public class UperEncodeIntegerTest { + + /** + * Example from the Standard on UPER. +
+		TestRecord ::= [APPLICATION 0] IMPLICIT SEQUENCE {
+  			number INTEGER,
+		}
+    
+ */ + @Sequence + public static class TestRecord { + + Asn1BigInteger value; + + public TestRecord() { + this(new Long(12345678909999899L)); + } + + public TestRecord(Long num) { + value = new Asn1BigInteger(num); + } + } + + + + @Test public void test() throws IllegalArgumentException, IllegalAccessException { + + TestRecord record = new TestRecord(12345678909999899L); + byte[] encoded = UperEncoder.encode(record); + String hex = UperEncoder.hexStringFromBytes(encoded); + UperEncoder.logger.log(Level.FINEST,String.format("data hex: %s", hex)); + assertEquals("072BDC545DF10B1B",hex); + + } + + @Test public void testDecode() throws IllegalArgumentException, IllegalAccessException { + + TestRecord record = new TestRecord(12345678909999899L); + byte[] encoded = UperEncoder.encode(record); + String hex = UperEncoder.hexStringFromBytes(encoded); + UperEncoder.logger.log(Level.FINEST,String.format("data hex: %s", hex)); + assertEquals("072BDC545DF10B1B",hex); + + TestRecord result = UperEncoder.decode(encoded, TestRecord.class); + assertEquals(result.value.longValue(),record.value.longValue()); + + } + +} diff --git a/src/net/gcdc/asn1/test/UperEncodeOctetStringTest.java b/src/net/gcdc/asn1/test/UperEncodeOctetStringTest.java new file mode 100644 index 0000000..7604d6a --- /dev/null +++ b/src/net/gcdc/asn1/test/UperEncodeOctetStringTest.java @@ -0,0 +1,80 @@ +package net.gcdc.asn1.test; + +import static org.junit.Assert.assertEquals; + +import java.util.logging.Level; + +import net.gcdc.asn1.datatypes.Sequence; +import net.gcdc.asn1.datatypesimpl.OctetString; + +import net.gcdc.asn1.uper.UperEncoder; + +import org.junit.Test; + + +public class UperEncodeOctetStringTest { + + /** + * Example from the Standard on UPER. +
+		World-Schema DEFINITIONS AUTOMATIC TAGS ::= 
+		BEGIN
+		TestRecord ::= [APPLICATION 0] IMPLICIT SEQUENCE {
+            value OCTET STRING
+         }                                                    
+		END
+         
+		value TestRecord ::= { value '83DA'H }
+		
+		Encoding to the file 'data.uper' using PER UNALIGNED encoding rule...
+		TestRecord SEQUENCE [fieldcount (not encoded) = 1]
+  		value OCTET STRING [length = 2.0]
+    	0x83da
+		Total encoded length = 3.0
+		Encoded successfully in 3 bytes:
+		0283DA
+         
+    
+ */ + @Sequence + public static class TestRecord { + + OctetString value; + + public TestRecord() { + value = new OctetString(); + value.add(hexToByte("83")); + value.add(hexToByte("DA")); + } + } + + + @Test public void test() throws IllegalArgumentException, IllegalAccessException { + + TestRecord record = new TestRecord(); + byte[] encoded = UperEncoder.encode(record); + String hex = UperEncoder.hexStringFromBytes(encoded); + UperEncoder.logger.log(Level.FINEST,String.format("data hex: %s", hex)); + assertEquals("0283DA",hex); + + } + + @Test public void testDecode() throws IllegalArgumentException, IllegalAccessException { + + TestRecord record = new TestRecord(); + byte[] encoded = UperEncoder.encode(record); + String hex = UperEncoder.hexStringFromBytes(encoded); + UperEncoder.logger.log(Level.FINEST,String.format("data hex: %s", hex)); + assertEquals("0283DA",hex); + + TestRecord result = UperEncoder.decode(encoded, TestRecord.class); + assertEquals(result.value,record.value); + + } + + public static byte hexToByte(String s){ + return (byte) ((Character.digit(s.charAt(0), 16) << 4) + + Character.digit(s.charAt(1), 16)); + } + +} diff --git a/src/net/gcdc/asn1/test/UperEncodeOptionalSequenceExtensionTest.java b/src/net/gcdc/asn1/test/UperEncodeOptionalSequenceExtensionTest.java new file mode 100644 index 0000000..1b2fa09 --- /dev/null +++ b/src/net/gcdc/asn1/test/UperEncodeOptionalSequenceExtensionTest.java @@ -0,0 +1,117 @@ +package net.gcdc.asn1.test; + +import static org.junit.Assert.assertEquals; + +import java.util.logging.Level; + +import net.gcdc.asn1.datatypes.Asn1Optional; +import net.gcdc.asn1.datatypes.CharacterRestriction; +import net.gcdc.asn1.datatypes.HasExtensionMarker; +import net.gcdc.asn1.datatypes.IsExtension; +import net.gcdc.asn1.datatypes.RestrictedString; +import net.gcdc.asn1.datatypes.Sequence; + +import net.gcdc.asn1.uper.UperEncoder; + +import org.junit.Test; + + +public class UperEncodeOptionalSequenceExtensionTest { + + /** Example for extended sequence including extension + World-Schema DEFINITIONS AUTOMATIC TAGS ::= + BEGIN + TestRecord ::= [APPLICATION 0] IMPLICIT SEQUENCE { + value1 IA5String, + ..., + value2 IA5String OPTIONAL + } + END + */ + @Sequence + @HasExtensionMarker + public static class TestRecordExtended { + + @RestrictedString(CharacterRestriction.IA5String) + String value1; + + @IsExtension + @RestrictedString(CharacterRestriction.IA5String) + @Asn1Optional() String value2; + + public TestRecordExtended() { } + + public void setValue1(String value1) { + this.value1 = value1; + } + + public void setValue2(String value2) { + this.value2 = value2; + } + + + + } + + /** Example for extended sequence + TestRecord ::= [APPLICATION 0] IMPLICIT SEQUENCE { + value1 IA5String, + ,... + } + */ + @Sequence + @HasExtensionMarker + public static class TestRecord { + + @RestrictedString(CharacterRestriction.IA5String) + String value1 = "regular"; + public TestRecord() { } + } + + + @Test public void testEncode() throws IllegalArgumentException, IllegalAccessException { + TestRecordExtended record = new TestRecordExtended(); + record.setValue1("regular"); + byte[] encoded = UperEncoder.encode(record); + String hex = UperEncoder.hexStringFromBytes(encoded); + UperEncoder.logger.log(Level.FINEST,String.format("data hex: %s", hex)); + assertEquals("03F2CB9FAECC3C80",hex); + } + + @Test public void testEncodeWithoutOptionalElement() throws IllegalArgumentException, IllegalAccessException { + TestRecordExtended record = new TestRecordExtended(); + record.setValue1("regular"); + record.setValue2("extension"); + byte[] encoded = UperEncoder.encode(record); + String hex = UperEncoder.hexStringFromBytes(encoded); + UperEncoder.logger.log(Level.FINEST,String.format("data hex: %s", hex)); + assertEquals("83F2CB9FAECC3C80424272F8E997773D3BF700",hex); + } + + @Test public void testDecodeExtended() throws IllegalArgumentException, IllegalAccessException { + TestRecordExtended record = new TestRecordExtended(); + record.setValue1("regular"); + byte[] encoded = UperEncoder.encode(record); + String hex = UperEncoder.hexStringFromBytes(encoded); + UperEncoder.logger.log(Level.FINEST,String.format("data hex: %s", hex)); + assertEquals("03F2CB9FAECC3C80",hex); + TestRecordExtended result = UperEncoder.decode(encoded, TestRecordExtended.class); + assertEquals(result.value1,record.value1); + } + + @Test public void testDecode() throws IllegalArgumentException, IllegalAccessException { + TestRecordExtended record = new TestRecordExtended(); + record.setValue1("regular"); + record.setValue2("extension"); + byte[] encoded = UperEncoder.encode(record); + String hex = UperEncoder.hexStringFromBytes(encoded); + UperEncoder.logger.log(Level.FINEST,String.format("data hex: %s", hex)); + assertEquals("83F2CB9FAECC3C80424272F8E997773D3BF700",hex); + TestRecord result = UperEncoder.decode(encoded, TestRecord.class); + assertEquals(result.value1,record.value1); + } + + + + +} diff --git a/src/net/gcdc/asn1/test/UperEncodeRestrictedIntegerTest.java b/src/net/gcdc/asn1/test/UperEncodeRestrictedIntegerTest.java new file mode 100644 index 0000000..27dc5f4 --- /dev/null +++ b/src/net/gcdc/asn1/test/UperEncodeRestrictedIntegerTest.java @@ -0,0 +1,62 @@ +package net.gcdc.asn1.test; + +import static org.junit.Assert.assertEquals; + +import java.util.logging.Level; + +import net.gcdc.asn1.datatypes.IntRange; +import net.gcdc.asn1.datatypes.Sequence; + +import net.gcdc.asn1.uper.UperEncoder; + +import org.junit.Test; + + +public class UperEncodeRestrictedIntegerTest { + + /** + * Example from the Standard on UPER. +
+TestRecord ::= [APPLICATION 0] IMPLICIT SEQUENCE {
+  number INTEGER(32000..63000),
+}
+    
+ */ + @Sequence + public static class TestRecord { + + @IntRange(maxValue = 63000, minValue = 33000) + Long value; + + public TestRecord() { + this(new Long(33005)); + } + + public TestRecord(Long num) { + value = num; + } + } + + + @Test public void test() throws IllegalArgumentException, IllegalAccessException { + + TestRecord record = new TestRecord(33005L); + byte[] encoded = UperEncoder.encode(record); + String hex = UperEncoder.hexStringFromBytes(encoded); + UperEncoder.logger.log(Level.FINEST,String.format("data hex: %s", hex)); + assertEquals("000A",hex); + } + + @Test public void testDecode() throws IllegalArgumentException, IllegalAccessException { + + TestRecord record = new TestRecord(33005L); + byte[] encoded = UperEncoder.encode(record); + String hex = UperEncoder.hexStringFromBytes(encoded); + UperEncoder.logger.log(Level.FINEST,String.format("data hex: %s", hex)); + assertEquals("000A",hex); + + TestRecord result = UperEncoder.decode(encoded, TestRecord.class); + assertEquals(result.value,record.value); + } + +} diff --git a/src/net/gcdc/asn1/test/UperEncodeSequenceExtensionTest.java b/src/net/gcdc/asn1/test/UperEncodeSequenceExtensionTest.java new file mode 100644 index 0000000..7934354 --- /dev/null +++ b/src/net/gcdc/asn1/test/UperEncodeSequenceExtensionTest.java @@ -0,0 +1,91 @@ +package net.gcdc.asn1.test; + +import static org.junit.Assert.assertEquals; + +import java.util.logging.Level; + +import net.gcdc.asn1.datatypes.Asn1Optional; +import net.gcdc.asn1.datatypes.CharacterRestriction; +import net.gcdc.asn1.datatypes.HasExtensionMarker; +import net.gcdc.asn1.datatypes.IsExtension; +import net.gcdc.asn1.datatypes.RestrictedString; +import net.gcdc.asn1.datatypes.Sequence; + +import net.gcdc.asn1.uper.UperEncoder; + +import org.junit.Test; + + +public class UperEncodeSequenceExtensionTest { + + /** Example for extended sequence including extension + TestRecord ::= [APPLICATION 0] IMPLICIT SEQUENCE { + value1 IA5String, + ,... + value2 IA5String + } + */ + @Sequence + @HasExtensionMarker + public static class TestRecordExtended { + + @RestrictedString(CharacterRestriction.IA5String) + @Asn1Optional() String value1 = "regular"; + + @IsExtension + @RestrictedString(CharacterRestriction.IA5String) + @Asn1Optional() String value2 = "extension"; + + public TestRecordExtended() { } + } + + /** Example for extended sequence + TestRecord ::= [APPLICATION 0] IMPLICIT SEQUENCE { + value1 IA5String, + ,... + } + */ + @Sequence + @HasExtensionMarker + public static class TestRecord { + + @RestrictedString(CharacterRestriction.IA5String) + @Asn1Optional() String value1 = "regular"; + + public TestRecord() { } + } + + + @Test public void testEncode() throws IllegalArgumentException, IllegalAccessException { + TestRecordExtended record = new TestRecordExtended(); + byte[] encoded = UperEncoder.encode(record); + String hex = UperEncoder.hexStringFromBytes(encoded); + UperEncoder.logger.log(Level.FINEST,String.format("data hex: %s", hex)); + assertEquals("C1F965CFD7661E402121397C74CBBB9E9DFB80",hex); + } + + @Test public void testDecodeExtended() throws IllegalArgumentException, IllegalAccessException { + TestRecordExtended record = new TestRecordExtended(); + byte[] encoded = UperEncoder.encode(record); + String hex = UperEncoder.hexStringFromBytes(encoded); + UperEncoder.logger.log(Level.FINEST,String.format("data hex: %s", hex)); + assertEquals("C1F965CFD7661E402121397C74CBBB9E9DFB80",hex); + TestRecordExtended result = UperEncoder.decode(encoded, TestRecordExtended.class); + assertEquals(result.value1,record.value1); + assertEquals(result.value2,record.value2); + } + + @Test public void testDecode() throws IllegalArgumentException, IllegalAccessException { + TestRecordExtended record = new TestRecordExtended(); + byte[] encoded = UperEncoder.encode(record); + String hex = UperEncoder.hexStringFromBytes(encoded); + UperEncoder.logger.log(Level.FINEST,String.format("data hex: %s", hex)); + assertEquals("C1F965CFD7661E402121397C74CBBB9E9DFB80",hex); + TestRecord result = UperEncoder.decode(encoded, TestRecord.class); + assertEquals(result.value1,record.value1); + } + + + + +} diff --git a/src/net/gcdc/asn1/test/UperEncodeSequenceOfIntegerTest.java b/src/net/gcdc/asn1/test/UperEncodeSequenceOfIntegerTest.java new file mode 100644 index 0000000..6028a29 --- /dev/null +++ b/src/net/gcdc/asn1/test/UperEncodeSequenceOfIntegerTest.java @@ -0,0 +1,73 @@ +package net.gcdc.asn1.test; + +import static org.junit.Assert.assertEquals; + +import java.util.ArrayList; +import java.util.List; +import java.util.logging.Level; + +import net.gcdc.asn1.datatypes.Sequence; +import net.gcdc.asn1.datatypesimpl.SequenceOfUnrestrictedLong; + +import net.gcdc.asn1.uper.UperEncoder; + +import org.junit.Test; + + +public class UperEncodeSequenceOfIntegerTest { + + /** + * Example from the Standard on UPER. +
+TestRecord ::= [APPLICATION 0] IMPLICIT SEQUENCE {
+  number INTEGER,
+}
+    
+ */ + @Sequence + public static class TestRecord { + + + SequenceOfUnrestrictedLong numbers; + + public TestRecord() { + } + + public TestRecord(List nums) { + numbers = new SequenceOfUnrestrictedLong(nums); + } + } + + + @Test public void test() throws IllegalArgumentException, IllegalAccessException { + + ArrayList nums = new ArrayList(); + nums.add(new Long(12345678909999899L)); + nums.add(new Long(12345678909999899L)); + TestRecord record = new TestRecord(nums); + byte[] encoded = UperEncoder.encode(record); + String hex = UperEncoder.hexStringFromBytes(encoded); + UperEncoder.logger.log(Level.FINEST,String.format("data hex: %s", UperEncoder.hexStringFromBytes(encoded))); + assertEquals("02072BDC545DF10B1B072BDC545DF10B1B",hex); + + } + + @Test public void testDecode() throws IllegalArgumentException, IllegalAccessException { + + ArrayList nums = new ArrayList(); + nums.add(new Long(12345678909999899L)); + nums.add(new Long(12345678909999899L)); + TestRecord record = new TestRecord(nums); + byte[] encoded = UperEncoder.encode(record); + String hex = UperEncoder.hexStringFromBytes(encoded); + UperEncoder.logger.log(Level.FINEST,String.format("data hex: %s", UperEncoder.hexStringFromBytes(encoded))); + assertEquals("02072BDC545DF10B1B072BDC545DF10B1B",hex); + + TestRecord result = UperEncoder.decode(encoded, TestRecord.class); + assertEquals(result.numbers.get(0).longValue(),record.numbers.get(0).longValue()); + assertEquals(result.numbers.get(1).longValue(),record.numbers.get(1).longValue()); + + + } + +} diff --git a/src/net/gcdc/asn1/test/UperEncodeSequenceOfRestrictedIntegerTest.java b/src/net/gcdc/asn1/test/UperEncodeSequenceOfRestrictedIntegerTest.java new file mode 100644 index 0000000..5ac9834 --- /dev/null +++ b/src/net/gcdc/asn1/test/UperEncodeSequenceOfRestrictedIntegerTest.java @@ -0,0 +1,77 @@ +package net.gcdc.asn1.test; + +import static org.junit.Assert.assertEquals; + +import java.util.logging.Level; + +import net.gcdc.asn1.datatypes.IntRange; +import net.gcdc.asn1.datatypes.Sequence; + +import net.gcdc.asn1.uper.UperEncoder; + +import org.junit.Test; + + +public class UperEncodeSequenceOfRestrictedIntegerTest { + + /** + * Example from the Standard on UPER. +
+TestRecord ::= [APPLICATION 0] IMPLICIT SEQUENCE {
+  numbers SEQUENCE OF INTEGER(0..9999999),
+}
+    
+ */ + @Sequence + public static class TestRecord { + + @IntRange(minValue=9500000,maxValue=99900001) + TestSequenceOfLong numbers = null;; + + public TestRecord() { + } + + public void addNumber(Long longValue){ + if (numbers == null) { + numbers = new TestSequenceOfLong(); + } + numbers.add(longValue); + } + } + + + @Test public void test() throws IllegalArgumentException, IllegalAccessException { + + TestRecord record = new TestRecord(); + + record.addNumber(new Long(9500001L)); + record.addNumber(new Long(9699999L)); + + + byte[] encoded = UperEncoder.encode(record); + String hex = UperEncoder.hexStringFromBytes(encoded); + UperEncoder.logger.log(Level.FINEST,String.format("data hex: %s", UperEncoder.hexStringFromBytes(encoded))); + assertEquals("02000000200C34FC",hex); + } + + @Test public void testDecode() throws IllegalArgumentException, IllegalAccessException { + + TestRecord record = new TestRecord(); + + record.addNumber(new Long(9500001L)); + record.addNumber(new Long(9699999L)); + + + byte[] encoded = UperEncoder.encode(record); + String hex = UperEncoder.hexStringFromBytes(encoded); + UperEncoder.logger.log(Level.FINEST,String.format("data hex: %s", UperEncoder.hexStringFromBytes(encoded))); + assertEquals("02000000200C34FC",hex); + + + TestRecord result = UperEncoder.decode(encoded, TestRecord.class); + assertEquals(result.numbers.get(0).longValue(),record.numbers.get(0).longValue()); + assertEquals(result.numbers.get(1).longValue(),record.numbers.get(1).longValue()); + } + + +} diff --git a/src/net/gcdc/asn1/test/UperEncodeSequenceOfStringListTest.java b/src/net/gcdc/asn1/test/UperEncodeSequenceOfStringListTest.java new file mode 100644 index 0000000..1a8f68e --- /dev/null +++ b/src/net/gcdc/asn1/test/UperEncodeSequenceOfStringListTest.java @@ -0,0 +1,77 @@ +package net.gcdc.asn1.test; + +import static org.junit.Assert.assertEquals; + +import java.util.ArrayList; +import java.util.List; +import java.util.logging.Level; + +import net.gcdc.asn1.datatypes.CharacterRestriction; +import net.gcdc.asn1.datatypes.RestrictedString; +import net.gcdc.asn1.datatypes.Sequence; +import net.gcdc.asn1.uper.UperEncoder; + +import org.junit.Test; + + +public class UperEncodeSequenceOfStringListTest { + + /** + * Example from the Standard on UPER. +
+            TestRecord ::= [APPLICATION 0] IMPLICIT SEQUENCE {
+                strings SEQUENCE OF IA5String,
+         }
+    
+ */ + @Sequence + public static class TestRecord { + + @RestrictedString(CharacterRestriction.IA5String) + ArrayList strings = new ArrayList(); + + public TestRecord() { + } + + public List getStrings() { + return strings; + } + + + } + + + @Test public void test() throws IllegalArgumentException, IllegalAccessException { + + TestRecord record = new TestRecord(); + record.getStrings().add("test1"); + record.getStrings().add("test2"); + record.getStrings().add("test3"); + + byte[] encoded = UperEncoder.encode(record); + String hex = UperEncoder.hexStringFromBytes(encoded); + UperEncoder.logger.log(Level.FINEST,String.format("data hex: %s", UperEncoder.hexStringFromBytes(encoded))); + assertEquals("0305E9979F4620BD32F3E8C817A65E7D1980",hex); + } + + @Test public void testDecode() throws IllegalArgumentException, IllegalAccessException { + + TestRecord record = new TestRecord(); + record.getStrings().add("test1"); + record.getStrings().add("test2"); + record.getStrings().add("test3"); + + byte[] encoded = UperEncoder.encode(record); + String hex = UperEncoder.hexStringFromBytes(encoded); + UperEncoder.logger.log(Level.FINEST,String.format("data hex: %s", UperEncoder.hexStringFromBytes(encoded))); + assertEquals("0305E9979F4620BD32F3E8C817A65E7D1980",hex); + + TestRecord result = UperEncoder.decode(encoded, TestRecord.class, null); + assert(result.getStrings().contains("test1")); + assert(result.getStrings().contains("test2")); + assert(result.getStrings().contains("test3")); + + + } + +} diff --git a/src/net/gcdc/asn1/test/UperEncodeSequenceOfStringTest.java b/src/net/gcdc/asn1/test/UperEncodeSequenceOfStringTest.java new file mode 100644 index 0000000..c7c82f7 --- /dev/null +++ b/src/net/gcdc/asn1/test/UperEncodeSequenceOfStringTest.java @@ -0,0 +1,76 @@ +package net.gcdc.asn1.test; + +import static org.junit.Assert.assertEquals; + +import java.util.logging.Level; + +import net.gcdc.asn1.datatypes.CharacterRestriction; +import net.gcdc.asn1.datatypes.RestrictedString; +import net.gcdc.asn1.datatypes.Sequence; +import net.gcdc.asn1.datatypesimpl.SequenceOfStringIA5; +import net.gcdc.asn1.uper.UperEncoder; + +import org.junit.Test; + + +public class UperEncodeSequenceOfStringTest { + + /** + * Example from the Standard on UPER. +
+            TestRecord ::= [APPLICATION 0] IMPLICIT SEQUENCE {
+                strings SEQUENCE OF IA5String,
+         }
+    
+ */ + @Sequence + public static class TestRecord { + + + SequenceOfStringIA5 strings = new SequenceOfStringIA5(); + + public TestRecord() { + } + + public SequenceOfStringIA5 getStrings() { + return strings; + } + + + } + + + @Test public void test() throws IllegalArgumentException, IllegalAccessException { + + TestRecord record = new TestRecord(); + record.getStrings().add("test1"); + record.getStrings().add("test2"); + record.getStrings().add("test3"); + + byte[] encoded = UperEncoder.encode(record); + String hex = UperEncoder.hexStringFromBytes(encoded); + UperEncoder.logger.log(Level.FINEST,String.format("data hex: %s", UperEncoder.hexStringFromBytes(encoded))); + assertEquals("0305E9979F4620BD32F3E8C817A65E7D1980",hex); + } + + @Test public void testDecode() throws IllegalArgumentException, IllegalAccessException { + + TestRecord record = new TestRecord(); + record.getStrings().add("test1"); + record.getStrings().add("test2"); + record.getStrings().add("test3"); + + byte[] encoded = UperEncoder.encode(record); + String hex = UperEncoder.hexStringFromBytes(encoded); + UperEncoder.logger.log(Level.FINEST,String.format("data hex: %s", UperEncoder.hexStringFromBytes(encoded))); + assertEquals("0305E9979F4620BD32F3E8C817A65E7D1980",hex); + + TestRecord result = UperEncoder.decode(encoded, TestRecord.class); + assert(result.getStrings().contains("test1")); + assert(result.getStrings().contains("test2")); + assert(result.getStrings().contains("test3")); + + + } + +} diff --git a/src/net/gcdc/asn1/test/UperEncodeSequenceOfUtf8StringTest.java b/src/net/gcdc/asn1/test/UperEncodeSequenceOfUtf8StringTest.java new file mode 100644 index 0000000..b2c855d --- /dev/null +++ b/src/net/gcdc/asn1/test/UperEncodeSequenceOfUtf8StringTest.java @@ -0,0 +1,96 @@ +package net.gcdc.asn1.test; + +import static org.junit.Assert.assertEquals; + +import java.util.logging.Level; + +import net.gcdc.asn1.datatypes.Sequence; +import net.gcdc.asn1.datatypesimpl.SequenceOfStringUTF8; +import net.gcdc.asn1.uper.UperEncoder; + +import org.junit.Test; + + +public class UperEncodeSequenceOfUtf8StringTest { + + /** + * Example from the Standard on UPER. +
+     	World-Schema DEFINITIONS AUTOMATIC TAGS ::= 
+		BEGIN
+ 		TestRecord ::= SEQUENCE {
+                strings SEQUENCE OF UTF8String
+         }                                                 
+		END
+
+         value TestRecord ::= { 
+             strings {"test1" , "test2" , "test3" }
+         }
+         
+
+Encoding to the file 'data.uper' using PER UNALIGNED encoding rule...
+TestRecord SEQUENCE [fieldcount (not encoded) = 1]
+  strings SEQUENCE OF [count = 3]
+    UTF8String [length = 5.0]
+      0x7465737431
+    UTF8String [length = 5.0]
+      0x7465737432
+    UTF8String [length = 5.0]
+      0x7465737433
+Total encoded length = 19.0
+Encoded successfully in 19 bytes:
+03057465 73743105 74657374 32057465 737433         
+         
+    
+ */ + @Sequence + public static class TestRecord { + + + SequenceOfStringUTF8 strings = new SequenceOfStringUTF8(); + + public TestRecord() { + } + + public SequenceOfStringUTF8 getStrings() { + return strings; + } + + + } + + + @Test public void test() throws IllegalArgumentException, IllegalAccessException { + + TestRecord record = new TestRecord(); + record.getStrings().add("test1"); + record.getStrings().add("test2"); + record.getStrings().add("test3"); + + byte[] encoded = UperEncoder.encode(record); + String hex = UperEncoder.hexStringFromBytes(encoded); + UperEncoder.logger.log(Level.FINEST,String.format("data hex: %s", UperEncoder.hexStringFromBytes(encoded))); + assertEquals("03057465737431057465737432057465737433",hex); + } + + @Test public void testDecode() throws IllegalArgumentException, IllegalAccessException { + + TestRecord record = new TestRecord(); + record.getStrings().add("test1"); + record.getStrings().add("test2"); + record.getStrings().add("test3"); + + byte[] encoded = UperEncoder.encode(record); + String hex = UperEncoder.hexStringFromBytes(encoded); + UperEncoder.logger.log(Level.FINEST,String.format("data hex: %s", UperEncoder.hexStringFromBytes(encoded))); + assertEquals("03057465737431057465737432057465737433",hex); + + TestRecord result = UperEncoder.decode(encoded, TestRecord.class); + assert(result.getStrings().contains("test1")); + assert(result.getStrings().contains("test2")); + assert(result.getStrings().contains("test3")); + + + } + +} diff --git a/src/net/gcdc/asn1/test/UperEncodeStringDefaultTest.java b/src/net/gcdc/asn1/test/UperEncodeStringDefaultTest.java new file mode 100644 index 0000000..e43d76d --- /dev/null +++ b/src/net/gcdc/asn1/test/UperEncodeStringDefaultTest.java @@ -0,0 +1,67 @@ +package net.gcdc.asn1.test; + +import static org.junit.Assert.assertEquals; + +import java.util.logging.Level; + +import net.gcdc.asn1.datatypes.Asn1Default; +import net.gcdc.asn1.datatypes.Asn1Optional; +import net.gcdc.asn1.datatypes.CharacterRestriction; +import net.gcdc.asn1.datatypes.RestrictedString; +import net.gcdc.asn1.datatypes.Sequence; +import net.gcdc.asn1.uper.UperEncoder; + +import org.junit.Test; + + +public class UperEncodeStringDefaultTest { + + /** + * Example from the Standard on UPER. +
+         TestRecord ::= [APPLICATION 0] IMPLICIT SEQUENCE {
+            testString1 UTF8String OPTIONAL,
+            testString2 IA5String DEFAULT("TestString")
+     }
+    
+ */ + @Sequence + public static class TestRecord { + + @RestrictedString(CharacterRestriction.UTF8String) + @Asn1Optional() String valueUtf8; + + @RestrictedString(CharacterRestriction.IA5String) + @Asn1Default(value="testString") String valueIA5; + + public TestRecord() { + } + + public TestRecord(String utf8, String ia5) { + this.valueUtf8 = utf8; + this.valueIA5 = ia5; + } + } + + + @Test public void testEncode() throws IllegalArgumentException, IllegalAccessException { + TestRecord record = new TestRecord("Müller", "testString"); + byte[] encoded = UperEncoder.encode(record); + String hex = UperEncoder.hexStringFromBytes(encoded); + UperEncoder.logger.log(Level.FINEST,String.format("data hex: %s", hex)); + assertEquals("81D370EF1B1B195C80",hex); + } + + @Test public void testEncodeDefault() throws IllegalArgumentException, IllegalAccessException { + TestRecord record = new TestRecord("Müller", "testString"); + byte[] encoded = UperEncoder.encode(record); + TestRecord result = UperEncoder.decode(encoded, TestRecord.class); + String hex = UperEncoder.hexStringFromBytes(encoded); + UperEncoder.logger.log(Level.FINEST,String.format("data hex: %s", hex)); + assertEquals(result.valueIA5,"testString"); + } + + + + +} diff --git a/src/net/gcdc/asn1/test/UperEncodeStringLengthTest.java b/src/net/gcdc/asn1/test/UperEncodeStringLengthTest.java new file mode 100644 index 0000000..378bc06 --- /dev/null +++ b/src/net/gcdc/asn1/test/UperEncodeStringLengthTest.java @@ -0,0 +1,151 @@ +package net.gcdc.asn1.test; + +import static org.junit.Assert.assertEquals; + +import java.util.logging.Level; + +import net.gcdc.asn1.datatypes.Asn1Optional; +import net.gcdc.asn1.datatypes.CharacterRestriction; +import net.gcdc.asn1.datatypes.RestrictedString; +import net.gcdc.asn1.datatypes.Sequence; + +import net.gcdc.asn1.uper.UperEncoder; + +import org.junit.Test; + + +public class UperEncodeStringLengthTest { + + /** + * Example from the Standard on UPER. +
+        World-Schema DEFINITIONS AUTOMATIC TAGS ::= 
+        BEGIN
+        TestRecord ::= [APPLICATION 0] IMPLICIT SEQUENCE {
+            testString1 UTF8String OPTIONAL
+         }                                   
+        END
+        
+        value TestRecord ::= {
+  			testString1 "A"
+		}
+
+    
+ */ + + + + @Sequence + public static class TestRecord { + + @RestrictedString(CharacterRestriction.UTF8String) + @Asn1Optional() String valueUtf8; + + public TestRecord() { + } + + public TestRecord(String utf8) { + this.valueUtf8 = utf8; + } + } + + + @Test public void testEncode1() throws IllegalArgumentException, IllegalAccessException { + TestRecord record = new TestRecord("A"); + byte[] encoded = UperEncoder.encode(record); + String hex = UperEncoder.hexStringFromBytes(encoded); + UperEncoder.logger.log(Level.FINEST,String.format("data hex: %s", hex)); + assertEquals("80A080",hex); + TestRecord result = UperEncoder.decode(encoded, TestRecord.class); + assertEquals(record.valueUtf8,result.valueUtf8); + } + + + @Test public void testEncode63() throws IllegalArgumentException, IllegalAccessException { + TestRecord record = new TestRecord("123456789012345678901234567890123456789012345678901234567890123"); + byte[] encoded = UperEncoder.encode(record); + String hex = UperEncoder.hexStringFromBytes(encoded); + UperEncoder.logger.log(Level.FINEST,String.format("data hex: %s", hex)); + assertEquals("9F9899199A1A9B1B9C1C981899199A1A9B1B9C1C981899199A1A9B1B9C1C981899199A1A9B1B9C1C981899199A1A9B1B9C1C981899199A1A9B1B9C1C9818991980",hex); + TestRecord result = UperEncoder.decode(encoded, TestRecord.class); + assertEquals(record.valueUtf8,result.valueUtf8); + } + + @Test public void testEncode64() throws IllegalArgumentException, IllegalAccessException { + TestRecord record = new TestRecord("1234567890123456789012345678901234567890123456789012345678901234"); + byte[] encoded = UperEncoder.encode(record); + String hex = UperEncoder.hexStringFromBytes(encoded); + UperEncoder.logger.log(Level.FINEST,String.format("data hex: %s", hex)); + assertEquals("A01899199A1A9B1B9C1C981899199A1A9B1B9C1C981899199A1A9B1B9C1C981899199A1A9B1B9C1C981899199A1A9B1B9C1C981899199A1A9B1B9C1C981899199A00",hex); + TestRecord result = UperEncoder.decode(encoded, TestRecord.class); + assertEquals(record.valueUtf8,result.valueUtf8); + } + + @Test public void testEncode65() throws IllegalArgumentException, IllegalAccessException { + TestRecord record = new TestRecord("12345678901234567890123456789012345678901234567890123456789012345"); + byte[] encoded = UperEncoder.encode(record); + String hex = UperEncoder.hexStringFromBytes(encoded); + UperEncoder.logger.log(Level.FINEST,String.format("data hex: %s", hex)); + assertEquals("A09899199A1A9B1B9C1C981899199A1A9B1B9C1C981899199A1A9B1B9C1C981899199A1A9B1B9C1C981899199A1A9B1B9C1C981899199A1A9B1B9C1C981899199A1A80",hex); + TestRecord result = UperEncoder.decode(encoded, TestRecord.class); + assertEquals(record.valueUtf8,result.valueUtf8); + } + + @Test public void testEncode126() throws IllegalArgumentException, IllegalAccessException { + TestRecord record = new TestRecord("123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456"); + byte[] encoded = UperEncoder.encode(record); + String hex = UperEncoder.hexStringFromBytes(encoded); + UperEncoder.logger.log(Level.FINEST,String.format("data hex: %s", hex)); + assertEquals("BF1899199A1A9B1B9C1C981899199A1A9B1B9C1C981899199A1A9B1B9C1C9818" + + "99199A1A9B1B9C1C981899199A1A9B1B9C1C981899199A1A9B1B9C1C98189919" + + "9A1A9B1B9C1C981899199A1A9B1B9C1C981899199A1A9B1B9C1C981899199A1A" + + "9B1B9C1C981899199A1A9B1B9C1C981899199A1A9B1B9C1C981899199A1A9B00",hex); + TestRecord result = UperEncoder.decode(encoded, TestRecord.class); + assertEquals(record.valueUtf8,result.valueUtf8); + } + + + @Test public void testEncode127() throws IllegalArgumentException, IllegalAccessException { + TestRecord record = new TestRecord("1234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567"); + byte[] encoded = UperEncoder.encode(record); + String hex = UperEncoder.hexStringFromBytes(encoded); + UperEncoder.logger.log(Level.FINEST,String.format("data hex: %s", hex)); + assertEquals("BF9899199A1A9B1B9C1C981899199A1A9B1B9C1C981899199A1A9B1B9C1C9818" + + "99199A1A9B1B9C1C981899199A1A9B1B9C1C981899199A1A9B1B9C1C98189919" + + "9A1A9B1B9C1C981899199A1A9B1B9C1C981899199A1A9B1B9C1C981899199A1A" + + "9B1B9C1C981899199A1A9B1B9C1C981899199A1A9B1B9C1C981899199A1A9B1B" + + "80",hex); + TestRecord result = UperEncoder.decode(encoded, TestRecord.class); + assertEquals(record.valueUtf8,result.valueUtf8); + } + + @Test public void testEncode128() throws IllegalArgumentException, IllegalAccessException { + TestRecord record = new TestRecord("12345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678"); + + byte[] encoded = UperEncoder.encode(record); + String hex = UperEncoder.hexStringFromBytes(encoded); + UperEncoder.logger.log(Level.FINEST,String.format("data hex: %s", hex)); + assertEquals("C0401899199A1A9B1B9C1C981899199A1A9B1B9C1C981899199A1A9B1B9C1C98" + + "1899199A1A9B1B9C1C981899199A1A9B1B9C1C981899199A1A9B1B9C1C981899" + + "199A1A9B1B9C1C981899199A1A9B1B9C1C981899199A1A9B1B9C1C981899199A" + + "1A9B1B9C1C981899199A1A9B1B9C1C981899199A1A9B1B9C1C981899199A1A9B" + + "1B9C00",hex); + TestRecord result = UperEncoder.decode(encoded, TestRecord.class); + assertEquals(record.valueUtf8,result.valueUtf8); + } + + @Test public void testEncode129() throws IllegalArgumentException, IllegalAccessException { + TestRecord record = new TestRecord("123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789"); + byte[] encoded = UperEncoder.encode(record); + String hex = UperEncoder.hexStringFromBytes(encoded); + UperEncoder.logger.log(Level.FINEST,String.format("data hex: %s", hex)); + assertEquals("C0409899199A1A9B1B9C1C981899199A1A9B1B9C1C981899199A1A9B1B9C1C98" + + "1899199A1A9B1B9C1C981899199A1A9B1B9C1C981899199A1A9B1B9C1C981899" + + "199A1A9B1B9C1C981899199A1A9B1B9C1C981899199A1A9B1B9C1C981899199A" + + "1A9B1B9C1C981899199A1A9B1B9C1C981899199A1A9B1B9C1C981899199A1A9B" + + "1B9C1C80",hex); + TestRecord result = UperEncoder.decode(encoded, TestRecord.class); + assertEquals(record.valueUtf8,result.valueUtf8); + } + +} diff --git a/src/net/gcdc/asn1/test/UperEncodeStringTest.java b/src/net/gcdc/asn1/test/UperEncodeStringTest.java new file mode 100644 index 0000000..521075c --- /dev/null +++ b/src/net/gcdc/asn1/test/UperEncodeStringTest.java @@ -0,0 +1,95 @@ +package net.gcdc.asn1.test; + +import static org.junit.Assert.assertEquals; + +import java.util.logging.Level; + +import net.gcdc.asn1.datatypes.Asn1Optional; +import net.gcdc.asn1.datatypes.CharacterRestriction; +import net.gcdc.asn1.datatypes.RestrictedString; +import net.gcdc.asn1.datatypes.Sequence; + +import net.gcdc.asn1.uper.UperEncoder; + +import org.junit.Test; + + +public class UperEncodeStringTest { + + /** + * Example from the Standard on UPER. +
+        World-Schema DEFINITIONS AUTOMATIC TAGS ::= 
+        BEGIN
+        TestRecord ::= [APPLICATION 0] IMPLICIT SEQUENCE {
+            testString1 UTF8String OPTIONAL,
+            testString2 IA5String OPTIONAL
+         }                                   
+        END
+    
+ */ + @Sequence + public static class TestRecord { + + @RestrictedString(CharacterRestriction.UTF8String) + @Asn1Optional() String valueUtf8; + + @RestrictedString(CharacterRestriction.IA5String) + @Asn1Optional() String valueIA5; + + public TestRecord() { + } + + public TestRecord(String utf8, String ia5) { + this.valueUtf8 = utf8; + this.valueIA5 = ia5; + } + } + + + @Test public void testEncode() throws IllegalArgumentException, IllegalAccessException { + TestRecord record = new TestRecord("Müller", "Meier"); + byte[] encoded = UperEncoder.encode(record); + String hex = UperEncoder.hexStringFromBytes(encoded); + UperEncoder.logger.log(Level.FINEST,String.format("data hex: %s", hex)); + assertEquals("C1D370EF1B1B195C8166E5D39790",hex); + } + + @Test public void testEncodeUtf8() throws IllegalArgumentException, IllegalAccessException { + TestRecord record = new TestRecord("你好å—", "Meier"); + byte[] encoded = UperEncoder.encode(record); + String hex = UperEncoder.hexStringFromBytes(encoded); + UperEncoder.logger.log(Level.FINEST,String.format("data hex: %s", hex)); + assertEquals("C2792F6839696F796425C166E5D39790",hex); + } + + + + @Test public void testDecode() throws IllegalArgumentException, IllegalAccessException { + TestRecord record = new TestRecord("Müller", "Meier"); + byte[] encoded = UperEncoder.encode(record); + String hex = UperEncoder.hexStringFromBytes(encoded); + UperEncoder.logger.log(Level.FINEST,String.format("data hex: %s", hex)); + assertEquals("C1D370EF1B1B195C8166E5D39790",hex); + TestRecord result = UperEncoder.decode(encoded, TestRecord.class); + assertEquals(result.valueUtf8,record.valueUtf8); + assertEquals(result.valueIA5,record.valueIA5); + } + + + @Test public void testDecodeUtf8() throws IllegalArgumentException, IllegalAccessException { + TestRecord record = new TestRecord("你好å—", "Meier"); + byte[] encoded = UperEncoder.encode(record); + String hex = UperEncoder.hexStringFromBytes(encoded); + UperEncoder.logger.log(Level.FINEST,String.format("data hex: %s", hex)); + assertEquals("C2792F6839696F796425C166E5D39790",hex); + TestRecord result = UperEncoder.decode(encoded, TestRecord.class); + assertEquals(result.valueUtf8,record.valueUtf8); + assertEquals(result.valueIA5,record.valueIA5); + } + + + + + +} 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