diff options
Diffstat (limited to 'src/net/gcdc')
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; + } + + + + +} |