summaryrefslogtreecommitdiffstats
path: root/src/net/gcdc/asn1
diff options
context:
space:
mode:
Diffstat (limited to 'src/net/gcdc/asn1')
-rw-r--r--src/net/gcdc/asn1/datatypes/Alphabet.java20
-rw-r--r--src/net/gcdc/asn1/datatypes/AlphabetBuilder.java32
-rw-r--r--src/net/gcdc/asn1/datatypes/Asn1AnonymousType.java15
-rw-r--r--src/net/gcdc/asn1/datatypes/Asn1BigInteger.java72
-rw-r--r--src/net/gcdc/asn1/datatypes/Asn1Default.java12
-rw-r--r--src/net/gcdc/asn1/datatypes/Asn1Integer.java56
-rw-r--r--src/net/gcdc/asn1/datatypes/Asn1Optional.java20
-rw-r--r--src/net/gcdc/asn1/datatypes/Asn1SequenceOf.java70
-rw-r--r--src/net/gcdc/asn1/datatypes/Asn1String.java17
-rw-r--r--src/net/gcdc/asn1/datatypes/Asn1VarSizeBitstring.java58
-rw-r--r--src/net/gcdc/asn1/datatypes/Bitstring.java16
-rw-r--r--src/net/gcdc/asn1/datatypes/CharacterRestriction.java12
-rw-r--r--src/net/gcdc/asn1/datatypes/Choice.java12
-rw-r--r--src/net/gcdc/asn1/datatypes/DefaultAlphabet.java8
-rw-r--r--src/net/gcdc/asn1/datatypes/FixedSize.java12
-rw-r--r--src/net/gcdc/asn1/datatypes/HasExtensionMarker.java12
-rw-r--r--src/net/gcdc/asn1/datatypes/IntMinValue.java13
-rw-r--r--src/net/gcdc/asn1/datatypes/IntRange.java14
-rw-r--r--src/net/gcdc/asn1/datatypes/IsExtension.java12
-rw-r--r--src/net/gcdc/asn1/datatypes/NoAsn1Field.java10
-rw-r--r--src/net/gcdc/asn1/datatypes/Optional.java96
-rw-r--r--src/net/gcdc/asn1/datatypes/RestrictedString.java13
-rw-r--r--src/net/gcdc/asn1/datatypes/Sequence.java9
-rw-r--r--src/net/gcdc/asn1/datatypes/SizeRange.java14
-rw-r--r--src/net/gcdc/asn1/datatypes/package-info.java7
-rw-r--r--src/net/gcdc/asn1/datatypesimpl/OctetString.java46
-rw-r--r--src/net/gcdc/asn1/datatypesimpl/SequenceOfLong.java27
-rw-r--r--src/net/gcdc/asn1/datatypesimpl/SequenceOfStringIA5.java13
-rw-r--r--src/net/gcdc/asn1/datatypesimpl/SequenceOfStringUTF8.java13
-rw-r--r--src/net/gcdc/asn1/datatypesimpl/SequenceOfUnrestrictedLong.java35
-rw-r--r--src/net/gcdc/asn1/test/TestSequenceOfLong.java24
-rw-r--r--src/net/gcdc/asn1/test/UperEncodeBooleanTest.java83
-rw-r--r--src/net/gcdc/asn1/test/UperEncodeChoiceExtensionTest.java90
-rw-r--r--src/net/gcdc/asn1/test/UperEncodeChoiceTest.java72
-rw-r--r--src/net/gcdc/asn1/test/UperEncodeEnumExtensionTest.java146
-rw-r--r--src/net/gcdc/asn1/test/UperEncodeEnumTest.java126
-rw-r--r--src/net/gcdc/asn1/test/UperEncodeIntegerConstrainedTest.java68
-rw-r--r--src/net/gcdc/asn1/test/UperEncodeIntegerExtensionTest.java99
-rw-r--r--src/net/gcdc/asn1/test/UperEncodeIntegerSmallTest.java129
-rw-r--r--src/net/gcdc/asn1/test/UperEncodeIntegerTest.java64
-rw-r--r--src/net/gcdc/asn1/test/UperEncodeOctetStringTest.java80
-rw-r--r--src/net/gcdc/asn1/test/UperEncodeOptionalSequenceExtensionTest.java117
-rw-r--r--src/net/gcdc/asn1/test/UperEncodeRestrictedIntegerTest.java62
-rw-r--r--src/net/gcdc/asn1/test/UperEncodeSequenceExtensionTest.java91
-rw-r--r--src/net/gcdc/asn1/test/UperEncodeSequenceOfIntegerTest.java73
-rw-r--r--src/net/gcdc/asn1/test/UperEncodeSequenceOfRestrictedIntegerTest.java77
-rw-r--r--src/net/gcdc/asn1/test/UperEncodeSequenceOfStringListTest.java77
-rw-r--r--src/net/gcdc/asn1/test/UperEncodeSequenceOfStringTest.java76
-rw-r--r--src/net/gcdc/asn1/test/UperEncodeSequenceOfUtf8StringTest.java96
-rw-r--r--src/net/gcdc/asn1/test/UperEncodeStringDefaultTest.java67
-rw-r--r--src/net/gcdc/asn1/test/UperEncodeStringLengthTest.java151
-rw-r--r--src/net/gcdc/asn1/test/UperEncodeStringTest.java95
-rw-r--r--src/net/gcdc/asn1/uper/AnnotationStore.java31
-rw-r--r--src/net/gcdc/asn1/uper/Asn1EncodingException.java18
-rw-r--r--src/net/gcdc/asn1/uper/BigIntCoder.java96
-rw-r--r--src/net/gcdc/asn1/uper/BitBuffer.java32
-rw-r--r--src/net/gcdc/asn1/uper/BitStringCoder.java165
-rw-r--r--src/net/gcdc/asn1/uper/BooleanCoder.java35
-rw-r--r--src/net/gcdc/asn1/uper/ByteBitBuffer.java271
-rw-r--r--src/net/gcdc/asn1/uper/ByteCoder.java34
-rw-r--r--src/net/gcdc/asn1/uper/ChoiceCoder.java161
-rw-r--r--src/net/gcdc/asn1/uper/Decoder.java10
-rw-r--r--src/net/gcdc/asn1/uper/Document2.txt34
-rw-r--r--src/net/gcdc/asn1/uper/Encoder.java8
-rw-r--r--src/net/gcdc/asn1/uper/EnumCoder.java156
-rw-r--r--src/net/gcdc/asn1/uper/IntCoder.java266
-rw-r--r--src/net/gcdc/asn1/uper/SeqOfCoder.java156
-rw-r--r--src/net/gcdc/asn1/uper/SeqOfFixedSize.java18
-rw-r--r--src/net/gcdc/asn1/uper/SequenceCoder.java269
-rw-r--r--src/net/gcdc/asn1/uper/SimpleTypeResolver.java515
-rw-r--r--src/net/gcdc/asn1/uper/StringCoder.java299
-rw-r--r--src/net/gcdc/asn1/uper/UperEncoder.java694
72 files changed, 5997 insertions, 0 deletions
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<T> 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".
+ * <p/>
+ * Extending classes should specify concrete types for T, generic collections can't be decoded (yet?).
+ * <p/>
+ * Usage example:
+ * <PRE>
+ * <code>
+ * {@literal @}Sequence
+ * public class Person {
+ * {@literal @}IntRange(minValue=0, maxValue=100, hasExtensionMarker=true)
+ * int age;
+ * Children children;
+ * }
+ * public class Children extends {@code Asn1SequenceOf<ChildInformation> } {
+ * public Children() { super(); }
+ * public Children({@code Collection<ChildInformation>} coll) { super(coll); }
+ * }
+ * </code>
+ * </PRE>
+ *
+ * <p/>
+ * Actually, UPER decoder and encoder consider anything that extends {@code List<T>} as a SEQUENCE OF.
+ *
+ *
+ * @param <T> type of elements contained.
+ */
+public abstract class Asn1SequenceOf<T> extends AbstractList<T> {
+ private final static Logger logger = LoggerFactory.getLogger("asnLogger");
+
+ private final List<T> 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<T>()); }
+ public Asn1SequenceOf(Collection<T> 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<Boolean>} works just as well.
+ */
+public class Asn1VarSizeBitstring extends AbstractList<Boolean> {
+
+ private final BitSet backing;
+
+ @Override public Boolean get(int index) {
+ return backing.get(index);
+ }
+
+ @Override public int size() {
+ return backing.length();
+ }
+
+ public Asn1VarSizeBitstring(Collection<Boolean> 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<Boolean>}.
+ */
+@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 <T> type of contained elements */
+public class Optional<T> {
+
+ 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:
+ * <ul>
+ * <li>it is also an {@code Optional} and;
+ * <li>both instances have no value present or;
+ * <li>the present values are "equal to" each other via {@code equals()}.
+ * </ul>
+ *
+ * @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 <A> the type of the value
+ * @param element contained value
+ * @return a new Option that contains the value */
+ public static <A> Optional<A> of(final A element) {
+ return new Optional<A>(element, true);
+ }
+
+ /** Returns an empty option.
+ *
+ * @param <A>
+ * @return an empty Option */
+ public static <A> Optional<A> empty() {
+ return new Optional<A>(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<? extends Alphabet> 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<Byte> {
+ public OctetString() { super(); }
+ public OctetString(Collection<Byte> coll) { super(coll); }
+
+ public OctetString(List<Byte> numbers) {
+ super();
+ this.addAll(numbers);
+ }
+
+ public static OctetString getSequence(List<Byte> 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<Long> {
+ public SequenceOfLong() { super(); }
+ public SequenceOfLong(Collection<Long> coll) { super(coll); }
+
+ public SequenceOfLong(List<Long> numbers) {
+ super();
+ this.addAll(numbers);
+ }
+
+ public static SequenceOfLong getSequence(List<Long> 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<String> {
+ public SequenceOfStringIA5() { super(); }
+ public SequenceOfStringIA5(Collection<String> 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<String> {
+ public SequenceOfStringUTF8() { super(); }
+ public SequenceOfStringUTF8(Collection<String> 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<Asn1BigInteger> {
+ public SequenceOfUnrestrictedLong() { super(); }
+ public SequenceOfUnrestrictedLong(Collection<Asn1BigInteger> coll) { super(coll); }
+
+ public void add(Long num) {
+ add (new Asn1BigInteger(num));
+ }
+
+
+ public SequenceOfUnrestrictedLong(List<Long> numbers) {
+ super();
+ for (Long number: numbers){
+ this.add(new Asn1BigInteger(number));
+ }
+ }
+
+
+ public static SequenceOfUnrestrictedLong getSequence(List<Long> 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<Long> {
+ public TestSequenceOfLong() { super(); }
+ public TestSequenceOfLong(Collection<Long> coll) { super(coll); }
+
+
+ public TestSequenceOfLong(List<Long> numbers) {
+ super();
+ for (Long number: numbers){
+ this.add(new Long(number));
+ }
+ }
+
+ public static TestSequenceOfLong getSequence(List<Long> 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.
+ <pre>
+ TestRecord ::= [APPLICATION 0] IMPLICIT SEQUENCE {
+ value BOOLEAN OPTIONAL,
+}
+ </pre>
+ */
+ @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.
+ <pre>
+ TestRecord ::= [APPLICATION 0] IMPLICIT SEQUENCE {
+ value EnumType DEFAULT value2,
+ }
+
+ EnumType ::= ENUMERATED {
+ value1 (0),
+ value2 (1)
+ ,...
+ }
+ </pre>
+ */
+ @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.
+ <pre>
+ World-Schema DEFINITIONS AUTOMATIC TAGS ::=
+ BEGIN
+ TestRecord ::= [APPLICATION 0] IMPLICIT SEQUENCE {
+ value EnumType DEFAULT value2
+ }
+
+ EnumType ::= ENUMERATED {
+ value1 (0),
+ value2 (1)
+ ,...
+ }
+ END
+ </pre>
+ */
+ @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.
+ <pre>
+ TestRecord ::= [APPLICATION 0] IMPLICIT SEQUENCE {
+ value EnumType DEFAULT value2,
+ }
+
+ EnumType ::= ENUMERATED {
+ value1 (0),
+ value2 (1)
+ ,...
+ }
+ </pre>
+ */
+ @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.
+ <pre>
+ TestRecord ::= [APPLICATION 0] IMPLICIT SEQUENCE {
+ number1 INTEGER (1..999),
+ number2 INTEGER (0..999),
+ number3 INTEGER (63..999)
+ }
+ </pre>
+ */
+ @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.
+ <pre>
+ 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
+
+
+ </pre>
+ */
+ @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.
+ <pre>
+ TestRecord ::= [APPLICATION 0] IMPLICIT SEQUENCE {
+ number1 INTEGER,
+ number2 INTEGER
+ }
+ </pre>
+ */
+ @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.
+ <pre>
+ TestRecord ::= [APPLICATION 0] IMPLICIT SEQUENCE {
+ number INTEGER,
+ }
+ </pre>
+ */
+ @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.
+ <pre>
+ 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
+
+ </pre>
+ */
+ @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.
+ <pre>
+TestRecord ::= [APPLICATION 0] IMPLICIT SEQUENCE {
+ number INTEGER(32000..63000),
+}
+ </pre>
+ */
+ @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.
+ <pre>
+TestRecord ::= [APPLICATION 0] IMPLICIT SEQUENCE {
+ number INTEGER,
+}
+ </pre>
+ */
+ @Sequence
+ public static class TestRecord {
+
+
+ SequenceOfUnrestrictedLong numbers;
+
+ public TestRecord() {
+ }
+
+ public TestRecord(List<Long> nums) {
+ numbers = new SequenceOfUnrestrictedLong(nums);
+ }
+ }
+
+
+ @Test public void test() throws IllegalArgumentException, IllegalAccessException {
+
+ ArrayList<Long> nums = new ArrayList<Long>();
+ 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<Long> nums = new ArrayList<Long>();
+ 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.
+ <pre>
+TestRecord ::= [APPLICATION 0] IMPLICIT SEQUENCE {
+ numbers SEQUENCE OF INTEGER(0..9999999),
+}
+ </pre>
+ */
+ @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.
+ <pre>
+ TestRecord ::= [APPLICATION 0] IMPLICIT SEQUENCE {
+ strings SEQUENCE OF IA5String,
+ }
+ </pre>
+ */
+ @Sequence
+ public static class TestRecord {
+
+ @RestrictedString(CharacterRestriction.IA5String)
+ ArrayList<String> strings = new ArrayList<String>();
+
+ public TestRecord() {
+ }
+
+ public List<String> 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.
+ <pre>
+ TestRecord ::= [APPLICATION 0] IMPLICIT SEQUENCE {
+ strings SEQUENCE OF IA5String,
+ }
+ </pre>
+ */
+ @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.
+ <pre>
+ 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
+
+ </pre>
+ */
+ @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.
+ <pre>
+ TestRecord ::= [APPLICATION 0] IMPLICIT SEQUENCE {
+ testString1 UTF8String OPTIONAL,
+ testString2 IA5String DEFAULT("TestString")
+ }
+ </pre>
+ */
+ @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.
+ <pre>
+ World-Schema DEFINITIONS AUTOMATIC TAGS ::=
+ BEGIN
+ TestRecord ::= [APPLICATION 0] IMPLICIT SEQUENCE {
+ testString1 UTF8String OPTIONAL
+ }
+ END
+
+ value TestRecord ::= {
+ testString1 "A"
+ }
+
+ </pre>
+ */
+
+
+
+ @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.
+ <pre>
+ World-Schema DEFINITIONS AUTOMATIC TAGS ::=
+ BEGIN
+ TestRecord ::= [APPLICATION 0] IMPLICIT SEQUENCE {
+ testString1 UTF8String OPTIONAL,
+ testString2 IA5String OPTIONAL
+ }
+ END
+ </pre>
+ */
+ @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<Class<? extends Annotation>, 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 extends Annotation> T getAnnotation(Class<T> classOfT) {
+ @SuppressWarnings("unchecked")
+ // Annotations were added with value T for key classOfT.
+ T result = (T) annotations.get(classOfT);
+ return result;
+ }
+
+ public Collection<Annotation> 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 <T> boolean canDecode(Class<T> classOfT, Annotation[] extraAnnotations) {
+ return Asn1BigInteger.class.isAssignableFrom(classOfT);
+ }
+
+ @Override public <T> T decode(BitBuffer bitbuffer,
+ Class<T> 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 <T> boolean canEncode(T obj, Annotation[] extraAnnotations) {
+ return obj instanceof Asn1BigInteger;
+ }
+
+ @Override public <T> 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> T getDefault(Class<T> 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 <T> 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 <T> 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 <T> boolean canDecode(Class<T> classOfT, Annotation[] extraAnnotations) {
+ AnnotationStore annotations = new AnnotationStore(classOfT.getAnnotations(),
+ extraAnnotations);
+ return annotations.getAnnotation(Bitstring.class) != null;
+ }
+
+ @Override public <T> T decode(BitBuffer bitbuffer,
+ Class<T> 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 <T> Long badSize(Class<T> classOfT) {
+ throw new IllegalArgumentException("both size range and fixed size are null for "
+ + classOfT.getName());
+ }
+
+ @Override
+ public <T> T getDefault(Class<T> 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 <T> boolean canEncode(T obj, Annotation[] extraAnnotations) {
+ return obj instanceof Boolean;
+ }
+
+ @Override public <T> void encode(BitBuffer bitbuffer, T obj, Annotation[] extraAnnotations) {
+ UperEncoder.logger.debug(String.format("BOOLEAN %s", obj));
+ bitbuffer.put((Boolean) obj);
+ }
+
+ @Override public <T> boolean canDecode(Class<T> classOfT, Annotation[] extraAnnotations) {
+ return Boolean.class.isAssignableFrom(classOfT)
+ || boolean.class.isAssignableFrom(classOfT);
+ }
+
+ @SuppressWarnings("unchecked")
+ @Override public <T> T decode(BitBuffer bitbuffer,
+ Class<T> 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> T getDefault(Class<T> 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 <T> boolean canEncode(T obj, Annotation[] extraAnnotations) {
+ return obj instanceof Byte;
+ }
+
+ @Override public <T> 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 <T> boolean canDecode(Class<T> classOfT, Annotation[] extraAnnotations) {
+ return Byte.class.isAssignableFrom(classOfT) || byte.class.isAssignableFrom(classOfT);
+ }
+
+ @SuppressWarnings("unchecked")
+ @Override public <T> T decode(BitBuffer bitbuffer,
+ Class<T> 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> T getDefault(Class<T> 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 <T> 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 <T> 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 <T> boolean canDecode(Class<T> classOfT, Annotation[] extraAnnotations) {
+ AnnotationStore annotations = new AnnotationStore(classOfT.getAnnotations(),
+ extraAnnotations);
+ return annotations.getAnnotation(Choice.class) != null;
+ }
+
+ @Override public <T> T decode(BitBuffer bitbuffer,
+ Class<T> 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> T getDefault(Class<T> 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 {
+ <T> boolean canDecode(Class<T> classOfT, Annotation[] extraAnnotations);
+ <T> T decode(BitBuffer bitbuffer, Class<T> classOfT,Field f, Annotation[] extraAnnotations);
+ <T> T getDefault(Class<T> 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 {
+ <T> boolean canEncode(T obj, Annotation[] extraAnnotations);
+ <T> 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 <T> boolean canEncode(T obj, Annotation[] extraAnnotations) {
+ Class<?> type = obj.getClass();
+ return type.isEnum();
+ }
+
+ @Override public <T> 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<Object> valuesWithinExtensionRoot = new ArrayList<>();
+ List<Object> 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 <T> boolean canDecode(Class<T> classOfT, Annotation[] extraAnnotations) {
+ return classOfT.isEnum();
+ }
+
+ @Override public <T> T decode(BitBuffer bitbuffer,
+ Class<T> 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> T getDefault(Class<T> 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 <T> boolean canDecode(Class<T> 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> T decode(BitBuffer bitbuffer,
+ Class<T> 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> T decodeConstrainedInteger(BitBuffer bitbuffer, Class<T> 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 <T> 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 <T> 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 <T> 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> T decodeUnconstrainedInteger(BitBuffer bitbuffer, Class<T> 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> T getDefault(Class<T> 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 <T> boolean canEncode(T obj, Annotation[] extraAnnotations) {
+ return obj instanceof List<?>;
+ }
+
+ @Override public <T> 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<Annotation> fieldAnnotations = new ArrayList<Annotation>();
+ 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<? extends Annotation> 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 <T> boolean canDecode(Class<T> classOfT, Annotation[] extraAnnotations) {
+ return List.class.isAssignableFrom(classOfT);
+ }
+
+ @SuppressWarnings("unchecked")
+ @Override public <T> T decode(BitBuffer bitbuffer,
+ Class<T> 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<Annotation> 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<Object> coll = new ArrayList<Object>((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> T getDefault(Class<T> 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<Byte> {
+ public Bar(Byte... coll) { this(Arrays.asList(coll)); }
+ public Bar(Collection<Byte> 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 <T> 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 <T> 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 <T> boolean isPresent(Field f, Object fieldObject){
+
+ if (fieldObject == null) return false;
+
+ boolean fieldPresent = fieldObject != null;
+
+ if (fieldObject instanceof Asn1SequenceOf) {
+ if (((Asn1SequenceOf<T>)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 <T> boolean canDecode(Class<T> classOfT, Annotation[] extraAnnotations) {
+ AnnotationStore annotations = new AnnotationStore(classOfT.getAnnotations(),
+ extraAnnotations);
+ return annotations.getAnnotation(Sequence.class) != null;
+ }
+
+ @Override public <T> T decode(BitBuffer bitbuffer,
+ Class<T> 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<Boolean> 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> T getDefault(Class<T> 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<Class<?>, Reference<Map<TypeVariable<?>, Type>>> TYPE_VARIABLE_CACHE = Collections
+ .synchronizedMap(new WeakHashMap<Class<?>, Reference<Map<TypeVariable<?>, 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<String, Method> OBJECT_METHODS = new HashMap<String, Method>();
+ private static final Map<Class<?>, 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<?>, Class<?>> types = new HashMap<Class<?>, 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 <T, S extends T> Class<?> resolveRawArgument(Class<T> type, Class<S> 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 <T, S extends T> Class<?>[] resolveRawArguments(Class<T> type, Class<S> 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<TypeVariable<?>, Type> getTypeVariableMap(final Class<?> targetType,
+ Class<?> functionalInterface) {
+ Reference<Map<TypeVariable<?>, Type>> ref = TYPE_VARIABLE_CACHE.get(targetType);
+ Map<TypeVariable<?>, Type> map = ref != null ? ref.get() : null;
+
+ if (map == null) {
+ map = new HashMap<TypeVariable<?>, 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<Map<TypeVariable<?>, 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<TypeVariable<?>, 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<TypeVariable<?>, 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<TypeVariable<?>, 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 <T> boolean canEncode(T obj, Annotation[] extraAnnotations) {
+ return obj instanceof String || obj instanceof Asn1String;
+ }
+
+ @Override public <T> 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 <T> boolean canDecode(Class<T> classOfT, Annotation[] extraAnnotations) {
+ return String.class.isAssignableFrom(classOfT) || Asn1String.class.isAssignableFrom(classOfT);
+ }
+
+ @Override public <T> T decode(BitBuffer bitbuffer,
+ Class<T> 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<Boolean> content = new ArrayList<Boolean>();
+ 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> T getDefault(Class<T> 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 <a
+ * href="http://www.itu.int/ITU-T/recommendations/rec.aspx?rec=x.691">X.691</a>
+ *
+ * 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 <T> 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> T decode(byte[] bytes, Class<T> 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> T decode(byte[] bytes, Class<T> 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 <T> 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> T decodeAny(BitBuffer bitbuffer,Class<T> 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> T getDefault(Class<T> classOfT, Annotation[] annotations) {
+ AnnotationStore annots = new AnnotationStore(classOfT.getAnnotations(), annotations);
+ Asn1Default defaultAnnotation = annots.getAnnotation(Asn1Default.class);
+
+ if (defaultAnnotation == null){
+ return null;
+ }
+
+ Annotation[] defaultAnnots = new Annotation[] {defaultAnnotation};
+
+ for (Decoder e : decoders) {
+ if (e.canDecode(classOfT, defaultAnnots)) {
+ return e.getDefault(classOfT, defaultAnnots);
+ }
+ }
+ logger.debug(String.format("Can't find decoder for %s",classOfT.getSimpleName()));
+
+ throw new IllegalArgumentException("Can't find default for " + classOfT.getName()
+ + " with extra annotations " + defaultAnnotation.toString());
+ }
+
+ static IntRange newRange(
+ final long minValue,
+ final long maxValue,
+ final boolean hasExtensionMarker) {
+
+ return new IntRange() {
+ @Override public Class<? extends Annotation> annotationType() {
+ return IntRange.class;
+ }
+ @Override public long minValue() { return minValue; }
+ @Override public long maxValue() { return maxValue; }
+ @Override public boolean hasExtensionMarker() { return hasExtensionMarker; }
+ };
+ }
+
+ static IntRange intRangeFromSizeRange(SizeRange sizeRange) {
+ return newRange(sizeRange.minValue(), sizeRange.maxValue(), sizeRange.hasExtensionMarker());
+ }
+
+ private static List<Encoder> encoders = new ArrayList<>();
+ private static List<Decoder> 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 <T> 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> T decodeAsOpenType(BitBuffer bitbuffer, Class<T> 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 <T> 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 <T> Constructor<T> findConsturctor(Class<T> classOfT, Object... parameters) {
+ @SuppressWarnings("unchecked")
+ Constructor<T>[] declaredConstructors = (Constructor<T>[]) classOfT
+ .getDeclaredConstructors();
+ for (Constructor<T> 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> T instantiate(Class<T> classOfT, Object... parameters) {
+ Class<?>[] parameterTypes = new Class<?>[parameters.length];
+ for (int i = 0; i < parameters.length; i++) {
+ parameterTypes[i] = parameters[i].getClass();
+ }
+ Constructor<T> 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<Field> extensionFields = new ArrayList<>();
+ List<Field> optionalExtensionFields = new ArrayList<>();
+ List<Field> mandatoryExtensionField = new ArrayList<>();
+ /** "Within extension root" */
+ List<Field> ordinaryFields = new ArrayList<>();
+ List<Field> mandatoryOrdinaryFields = new ArrayList<>();
+ List<Field> optionalOrdinaryFields = new ArrayList<>();
+ List<Field> allFields = new ArrayList<>(); // Excluding test instrumentation.
+
+ Map<Field, Boolean> 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<Field, Boolean> 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<Boolean> 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;
+ }
+
+
+
+
+}