ASN1. Indefinitive length encoding support with added tests
diff --git a/kerby-asn1/src/main/java/org/apache/kerby/asn1/Asn1.java b/kerby-asn1/src/main/java/org/apache/kerby/asn1/Asn1.java
index c1f46fc..6da9a25 100644
--- a/kerby-asn1/src/main/java/org/apache/kerby/asn1/Asn1.java
+++ b/kerby-asn1/src/main/java/org/apache/kerby/asn1/Asn1.java
@@ -19,9 +19,10 @@
  */
 package org.apache.kerby.asn1;
 
-import org.apache.kerby.asn1.type.Asn1Item;
+import org.apache.kerby.asn1.type.Asn1ParsingContainer;
 import org.apache.kerby.asn1.type.Asn1Type;
 import org.apache.kerby.asn1.util.Asn1Reader1;
+import org.apache.kerby.asn1.util.Asn1Reader2;
 import org.apache.kerby.asn1.util.HexUtil;
 
 import java.io.IOException;
@@ -54,18 +55,30 @@
         return decode(ByteBuffer.wrap(content));
     }
 
+    /*
     public static Asn1Type decode(ByteBuffer content) throws IOException {
         Asn1Reader1 reader = new Asn1Reader1(content);
         Asn1Header header = reader.readHeader();
 
-        Asn1Item result = new Asn1Item(header.getTag(), header.getValueBuffer());
+        Asn1Item result = new Asn1Item(header.getTag(), header.getBuffer());
         result.useDefinitiveLength(header.isDefinitiveLength());
 
         return result;
+    }*/
+
+    public static Asn1Type decode(ByteBuffer content) throws IOException {
+        return Asn1ParsingContainer.decodeOne(content);
     }
 
     public static void dump(Asn1Type value) {
-        Asn1Dumper dumper = new Asn1Dumper();
+        dump(value, true);
+    }
+
+    public static void dump(Asn1Type value, boolean withType) {
+        Asn1Dumper dumper = new Asn1Dumper(withType);
+        if (!withType) {
+            dumper.dumpTypeInfo(value.getClass());
+        }
         dumper.dumpType(0, value);
         String output = dumper.output();
         System.out.println(output);
@@ -77,6 +90,8 @@
         Asn1Dumper dumper = new Asn1Dumper();
         byte[] data = HexUtil.hex2bytes(hexStr);
         dumper.dump(data);
+        String output = dumper.output();
+        System.out.println(output);
     }
 
     public static void dump(byte[] content) throws IOException {
@@ -85,6 +100,8 @@
         System.out.println(hexStr);
         Asn1Dumper dumper = new Asn1Dumper();
         dumper.dump(content);
+        String output = dumper.output();
+        System.out.println(output);
     }
 
     public static void dump(ByteBuffer content) throws IOException {
diff --git a/kerby-asn1/src/main/java/org/apache/kerby/asn1/Asn1Dumper.java b/kerby-asn1/src/main/java/org/apache/kerby/asn1/Asn1Dumper.java
index 739e59d..4eee1e2 100644
--- a/kerby-asn1/src/main/java/org/apache/kerby/asn1/Asn1Dumper.java
+++ b/kerby-asn1/src/main/java/org/apache/kerby/asn1/Asn1Dumper.java
@@ -19,6 +19,7 @@
  */
 package org.apache.kerby.asn1;
 
+import org.apache.kerby.asn1.type.Asn1ParsingItem;
 import org.apache.kerby.asn1.type.Asn1Simple;
 import org.apache.kerby.asn1.type.Asn1Type;
 
@@ -26,9 +27,21 @@
 import java.nio.ByteBuffer;
 
 public final class Asn1Dumper {
-
+    private boolean withType;
     private StringBuilder builder = new StringBuilder();
 
+    public Asn1Dumper() {
+        this.withType = true;
+    }
+
+    public Asn1Dumper(boolean withType) {
+        this.withType = withType;
+    }
+
+    public boolean withType() {
+        return withType;
+    }
+
     public String output() {
         return builder.toString();
     }
@@ -50,17 +63,21 @@
         dumpType(0, value);
     }
 
-    public void dumpType(int indents, Asn1Type value) {
+    public Asn1Dumper dumpType(int indents, Asn1Type value) {
         if (value == null) {
             indent(indents).append("null");
         } else if (value instanceof Asn1Simple) {
             indent(indents).append(value.toString());
+        }  else if (value instanceof Asn1ParsingItem) {
+            indent(indents).append(value.toString());
         } else if (value instanceof Asn1Dumpable) {
             Asn1Dumpable dumpable = (Asn1Dumpable) value;
             dumpable.dumpWith(this, indents);
         } else {
             append("<UNKNOWN>");
         }
+
+        return this;
     }
 
     public Asn1Dumper indent(int numSpaces) {
@@ -84,6 +101,23 @@
         return this;
     }
 
+    public Asn1Dumper dumpTypeInfo(Class<?> cls) {
+        appendType(cls).newLine();
+        return this;
+    }
+
+    public Asn1Dumper dumpTypeInfo(int indents, Class<?> cls) {
+        if (withType()) {
+            indent(indents).appendType(cls).newLine();
+        }
+        return this;
+    }
+
+    private Asn1Dumper appendType(Class<?> cls) {
+        builder.append("<").append(cls.getSimpleName()).append(">");
+        return this;
+    }
+
     public Asn1Dumper newLine() {
         builder.append("\n");
         return this;
diff --git a/kerby-asn1/src/main/java/org/apache/kerby/asn1/Asn1Header.java b/kerby-asn1/src/main/java/org/apache/kerby/asn1/Asn1Header.java
index 0fbd047..1284039 100644
--- a/kerby-asn1/src/main/java/org/apache/kerby/asn1/Asn1Header.java
+++ b/kerby-asn1/src/main/java/org/apache/kerby/asn1/Asn1Header.java
@@ -24,24 +24,63 @@
 public class Asn1Header {
     private Tag tag;
     private int length;
-    private ByteBuffer valueBuffer;
+    private int bodyStart;
+    private int bodyEnd;
+    private ByteBuffer buffer;
 
-    public Asn1Header(Tag tag, int length, ByteBuffer valueBuffer) {
+    public Asn1Header(Tag tag, int length,
+                      int bodyStart, ByteBuffer buffer) {
         this.tag = tag;
         this.length = length;
-        this.valueBuffer = valueBuffer;
+        this.bodyStart = bodyStart;
+        this.buffer = buffer;
+
+        this.bodyEnd = isDefinitiveLength() ? bodyStart + length : -1;
     }
 
     public Tag getTag() {
         return tag;
     }
 
+    public int getActualBodyLength() {
+        if (isDefinitiveLength()) {
+            return getLength();
+        } else if (getBodyEnd() != -1) {
+            return getBodyEnd() - getBodyStart();
+        }
+        return -1;
+    }
+
     public int getLength() {
         return length;
     }
 
-    public ByteBuffer getValueBuffer() {
-        return valueBuffer;
+    public int getBodyStart() {
+        return bodyStart;
+    }
+
+    public int getBodyEnd() {
+        return bodyEnd;
+    }
+
+    public void setBodyEnd(int bodyEnd) {
+        this.bodyEnd = bodyEnd;
+    }
+
+    public ByteBuffer getBuffer() {
+        return buffer;
+    }
+
+    public ByteBuffer getBodyBuffer() {
+        ByteBuffer result = buffer.duplicate();
+        result.position(bodyStart);
+
+        int end = getBodyEnd();
+        if (end >= bodyStart) {
+            result.limit(end);
+        }
+
+        return result;
     }
 
     public boolean isEOC() {
@@ -51,4 +90,11 @@
     public boolean isDefinitiveLength() {
         return length != -1;
     }
+
+    public byte[] readBodyBytes() {
+        ByteBuffer bodyBuffer = getBodyBuffer();
+        byte[] result = new byte[bodyBuffer.remaining()];
+        bodyBuffer.get(result);
+        return result;
+    }
 }
diff --git a/kerby-asn1/src/main/java/org/apache/kerby/asn1/Tag.java b/kerby-asn1/src/main/java/org/apache/kerby/asn1/Tag.java
index 2cf5e6b..6b19e1e 100644
--- a/kerby-asn1/src/main/java/org/apache/kerby/asn1/Tag.java
+++ b/kerby-asn1/src/main/java/org/apache/kerby/asn1/Tag.java
@@ -97,8 +97,8 @@
         return tagClass().isContextSpecific();
     }
 
-    public boolean isTagged() {
-        return tagClass().isTagged();
+    public boolean isSpecific() {
+        return tagClass().isSpecific();
     }
 
     @Override
diff --git a/kerby-asn1/src/main/java/org/apache/kerby/asn1/TagClass.java b/kerby-asn1/src/main/java/org/apache/kerby/asn1/TagClass.java
index 8070692..8d02917 100644
--- a/kerby-asn1/src/main/java/org/apache/kerby/asn1/TagClass.java
+++ b/kerby-asn1/src/main/java/org/apache/kerby/asn1/TagClass.java
@@ -63,7 +63,7 @@
         return this == CONTEXT_SPECIFIC;
     }
 
-    public boolean isTagged() {
+    public boolean isSpecific() {
         return this == APPLICATION || this == CONTEXT_SPECIFIC;
     }
 
diff --git a/kerby-asn1/src/main/java/org/apache/kerby/asn1/type/Asn1Any.java b/kerby-asn1/src/main/java/org/apache/kerby/asn1/type/Asn1Any.java
index 617ec12..cda997c 100644
--- a/kerby-asn1/src/main/java/org/apache/kerby/asn1/type/Asn1Any.java
+++ b/kerby-asn1/src/main/java/org/apache/kerby/asn1/type/Asn1Any.java
@@ -20,6 +20,7 @@
 package org.apache.kerby.asn1.type;
 
 import org.apache.kerby.asn1.Asn1FieldInfo;
+import org.apache.kerby.asn1.Asn1Header;
 import org.apache.kerby.asn1.UniversalTag;
 
 import java.io.IOException;
@@ -66,6 +67,11 @@
     }
 
     @Override
+    protected void decodeBody(Asn1Header header) throws IOException {
+
+    }
+
+    @Override
     protected void encodeBody(ByteBuffer buffer) {
         ((AbstractAsn1Type<?>) getValue()).encodeBody(buffer);
     }
diff --git a/kerby-asn1/src/main/java/org/apache/kerby/asn1/type/Asn1BmpString.java b/kerby-asn1/src/main/java/org/apache/kerby/asn1/type/Asn1BmpString.java
index c915e94..7c3d920 100644
--- a/kerby-asn1/src/main/java/org/apache/kerby/asn1/type/Asn1BmpString.java
+++ b/kerby-asn1/src/main/java/org/apache/kerby/asn1/type/Asn1BmpString.java
@@ -19,10 +19,10 @@
  */
 package org.apache.kerby.asn1.type;
 
+import org.apache.kerby.asn1.Asn1Header;
 import org.apache.kerby.asn1.UniversalTag;
 
 import java.io.IOException;
-import java.nio.ByteBuffer;
 
 public class Asn1BmpString extends Asn1Simple<String> {
     public Asn1BmpString() {
@@ -61,10 +61,10 @@
     }
 
     @Override
-    protected void decodeBody(ByteBuffer content) throws IOException {
-        if (content.remaining() % 2 != 0) {
+    protected void decodeBody(Asn1Header header) throws IOException {
+        if (header.getLength() % 2 != 0) {
             throw new IOException("Bad stream, BMP string expecting multiple of 2 bytes");
         }
-        super.decodeBody(content);
+        super.decodeBody(header);
     }
 }
diff --git a/kerby-asn1/src/main/java/org/apache/kerby/asn1/type/Asn1Boolean.java b/kerby-asn1/src/main/java/org/apache/kerby/asn1/type/Asn1Boolean.java
index 319011d..61b8ef2 100644
--- a/kerby-asn1/src/main/java/org/apache/kerby/asn1/type/Asn1Boolean.java
+++ b/kerby-asn1/src/main/java/org/apache/kerby/asn1/type/Asn1Boolean.java
@@ -19,10 +19,10 @@
  */
 package org.apache.kerby.asn1.type;
 
+import org.apache.kerby.asn1.Asn1Header;
 import org.apache.kerby.asn1.UniversalTag;
 
 import java.io.IOException;
-import java.nio.ByteBuffer;
 
 /**
  * ASN1 Boolean type
@@ -55,11 +55,11 @@
     }
 
     @Override
-    protected void decodeBody(ByteBuffer content) throws IOException {
-        if (content.remaining() != 1) {
+    protected void decodeBody(Asn1Header header) throws IOException {
+        if (header.getLength() != 1) {
             throw new IOException("More than 1 byte found for Boolean");
         }
-        super.decodeBody(content);
+        super.decodeBody(header);
     }
 
     @Override
diff --git a/kerby-asn1/src/main/java/org/apache/kerby/asn1/type/Asn1Choice.java b/kerby-asn1/src/main/java/org/apache/kerby/asn1/type/Asn1Choice.java
index e7eb853..dd6d873 100644
--- a/kerby-asn1/src/main/java/org/apache/kerby/asn1/type/Asn1Choice.java
+++ b/kerby-asn1/src/main/java/org/apache/kerby/asn1/type/Asn1Choice.java
@@ -21,6 +21,7 @@
 
 import org.apache.kerby.asn1.Asn1;
 import org.apache.kerby.asn1.Asn1FieldInfo;
+import org.apache.kerby.asn1.Asn1Header;
 import org.apache.kerby.asn1.EnumType;
 import org.apache.kerby.asn1.TaggingOption;
 import org.apache.kerby.asn1.UniversalTag;
@@ -108,6 +109,11 @@
         fields[foundPos] = item.getValue();
     }
 
+    @Override
+    protected void decodeBody(Asn1Header header) throws IOException {
+
+    }
+
     protected void decodeBody(ByteBuffer content) throws IOException {
         // Not used
     }
diff --git a/kerby-asn1/src/main/java/org/apache/kerby/asn1/type/Asn1CollectionOf.java b/kerby-asn1/src/main/java/org/apache/kerby/asn1/type/Asn1CollectionOf.java
index 26acc0b..5e4bd70 100644
--- a/kerby-asn1/src/main/java/org/apache/kerby/asn1/type/Asn1CollectionOf.java
+++ b/kerby-asn1/src/main/java/org/apache/kerby/asn1/type/Asn1CollectionOf.java
@@ -6,19 +6,21 @@
  *  to you 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. 
- *  
+ *
  */
 package org.apache.kerby.asn1.type;
 
+import org.apache.kerby.asn1.Asn1Dumper;
+import org.apache.kerby.asn1.Asn1Header;
 import org.apache.kerby.asn1.UniversalTag;
 
 import java.io.IOException;
@@ -26,33 +28,39 @@
 import java.util.ArrayList;
 import java.util.List;
 
-public abstract class Asn1CollectionOf<T extends Asn1Type> extends Asn1Collection
-{
+public abstract class Asn1CollectionOf<T extends Asn1Type>
+    extends Asn1Collection {
+
+    private List<T> elements = new ArrayList<>();
+
     public Asn1CollectionOf(UniversalTag universalTag) {
         super(universalTag);
     }
 
-    public List<T> getElements() {
+    @Override
+    protected void decodeBody(Asn1Header header) throws IOException {
+        super.decodeBody(header);
+
+        decodeElements();
+    }
+
+    private void decodeElements() throws IOException {
         List<Asn1Type> items = getValue();
-        int nElements = items != null ? items.size() : 0;
-        List<T> results = new ArrayList<T>(nElements);
-        if (nElements > 0) {
-            for (Asn1Type itemObj : items) {
-                if (itemObj instanceof Asn1Item) {
-                    Asn1Item item = (Asn1Item) itemObj;
-                    if (!item.isFullyDecoded()) {
-                        try {
-                            item.decodeValueAs(getElementType());
-                        } catch (IOException e) {
-                            throw new RuntimeException(e);
-                        }
-                    }
-                    itemObj = item.getValue();
+        for (Asn1Type itemObj : items) {
+            if (itemObj instanceof Asn1Item) {
+                Asn1Item item = (Asn1Item) itemObj;
+                if (!item.isFullyDecoded()) {
+                    Asn1Type tmpValue = createElement();
+                    item.decodeValueWith(tmpValue);
                 }
-                results.add((T) itemObj);
+                itemObj = item.getValue();
             }
+            elements.add((T) itemObj);
         }
-        return results;
+    }
+
+    public List<T> getElements() {
+        return elements;
     }
 
     public void setElements(List<T> elements) {
@@ -71,11 +79,31 @@
 
     public void addElement(T element) {
         super.addItem(element);
+        this.elements.add(element);
     }
 
-    protected Class<T> getElementType() {
+    private Class<T> getElementType() {
         Class<T> elementType = (Class<T>) ((ParameterizedType)
-                getClass().getGenericSuperclass()).getActualTypeArguments()[0];
+            getClass().getGenericSuperclass()).getActualTypeArguments()[0];
         return elementType;
     }
+
+    protected T createElement() throws IOException {
+        Class<?> eleType = getElementType();
+        try {
+            T result = (T) eleType.newInstance();
+            return result;
+        } catch (Exception e) {
+            throw new IOException("Failed to create element type", e);
+        }
+    }
+
+    @Override
+    public void dumpWith(Asn1Dumper dumper, int indents) {
+        dumper.dumpTypeInfo(indents, getClass());
+
+        for (Asn1Type aObj : elements) {
+            dumper.dumpType(indents + 4, aObj).newLine();
+        }
+    }
 }
diff --git a/kerby-asn1/src/main/java/org/apache/kerby/asn1/type/Asn1CollectionType.java b/kerby-asn1/src/main/java/org/apache/kerby/asn1/type/Asn1CollectionType.java
index 36082c4..70b78da 100644
--- a/kerby-asn1/src/main/java/org/apache/kerby/asn1/type/Asn1CollectionType.java
+++ b/kerby-asn1/src/main/java/org/apache/kerby/asn1/type/Asn1CollectionType.java
@@ -22,12 +22,14 @@
 import org.apache.kerby.asn1.Asn1Dumpable;
 import org.apache.kerby.asn1.Asn1Dumper;
 import org.apache.kerby.asn1.Asn1FieldInfo;
+import org.apache.kerby.asn1.Asn1Header;
 import org.apache.kerby.asn1.EnumType;
 import org.apache.kerby.asn1.TaggingOption;
 import org.apache.kerby.asn1.UniversalTag;
 
 import java.io.IOException;
 import java.nio.ByteBuffer;
+import java.util.List;
 
 /**
  * For collection type that may consist of tagged fields
@@ -80,15 +82,16 @@
     }
 
     @Override
-    protected void decodeBody(ByteBuffer content) throws IOException {
-        initFields();
+    protected void decodeBody(Asn1Header header) throws IOException {
+        checkAndInitFields();
 
         Asn1Collection coll = createCollection();
         coll.setLazy(true);
-        coll.decode(tag(), content);
+        coll.decodeBody(header);
 
         int lastPos = -1, foundPos = -1;
-        for (Asn1Type itemObj : coll.getValue()) {
+        List<Asn1Type> decodedItems = coll.getValue();
+        for (Asn1Type itemObj : decodedItems) {
             foundPos = -1;
             Asn1Item item = (Asn1Item) itemObj;
             for (int i = lastPos + 1; i < fieldInfos.length; ++i) {
@@ -123,12 +126,15 @@
         }
     }
 
-    private void initFields() {
+    private void checkAndInitFields() {
         for (int i = 0; i < fieldInfos.length; ++i) {
-            try {
-                fields[i] = fieldInfos[i].getType().newInstance();
-            } catch (Exception e) {
-                throw new IllegalArgumentException("Bad field info specified at index of " + i, e);
+            if (fields[i] == null) {
+                try {
+                    fields[i] = fieldInfos[i].getType().newInstance();
+                } catch (Exception e) {
+                    throw new IllegalArgumentException(
+                        "Bad field info specified at index of " + i, e);
+                }
             }
         }
     }
@@ -203,9 +209,7 @@
 
     @Override
     public void dumpWith(Asn1Dumper dumper, int indents) {
-        String type = getClass().getSimpleName();
-
-        dumper.indent(indents).append(type).newLine();
+        dumper.dumpTypeInfo(indents, getClass());
 
         String fdName;
         for (int i = 0; i < fieldInfos.length; i++) {
diff --git a/kerby-asn1/src/main/java/org/apache/kerby/asn1/type/Asn1Constructed.java b/kerby-asn1/src/main/java/org/apache/kerby/asn1/type/Asn1Constructed.java
index 6008282..0ffa1db 100644
--- a/kerby-asn1/src/main/java/org/apache/kerby/asn1/type/Asn1Constructed.java
+++ b/kerby-asn1/src/main/java/org/apache/kerby/asn1/type/Asn1Constructed.java
@@ -19,7 +19,9 @@
  */
 package org.apache.kerby.asn1.type;
 
-import org.apache.kerby.asn1.Asn1;
+import org.apache.kerby.asn1.Asn1Dumpable;
+import org.apache.kerby.asn1.Asn1Dumper;
+import org.apache.kerby.asn1.Asn1Header;
 import org.apache.kerby.asn1.Tag;
 
 import java.io.IOException;
@@ -30,7 +32,9 @@
 /**
  * ASN1 constructed types, mainly structured ones, but also some primitive ones.
  */
-public class Asn1Constructed extends AbstractAsn1Type<List<Asn1Type>> {
+public class Asn1Constructed
+    extends AbstractAsn1Type<List<Asn1Type>> implements Asn1Dumpable {
+
     private boolean lazy = false;
 
     public Asn1Constructed(Tag tag) {
@@ -78,30 +82,27 @@
     }
 
     @Override
-    protected void decodeBody(ByteBuffer content) throws IOException {
-        while (content.remaining() > 0) {
-            Asn1Item item = (Asn1Item) Asn1.decode(content);
-            if (item != null) {
-                if (item.isSimple() && !isLazy()) {
-                    item.decodeValueAsSimple();
-                    addItem(item.getValue());
-                } else {
-                    addItem(item);
-                }
+    protected void decodeBody(Asn1Header header) throws IOException {
+        Asn1ParsingContainer container = new Asn1ParsingContainer(tag());
+        container.decode(header);
+
+        for (Asn1ParsingResult result : container.getParsingResults()) {
+            Asn1Item item = new Asn1Item(result);
+            if (item.isSimple() && !isLazy()) {
+                item.decodeValueAsSimple();
+                addItem(item.getValue());
+            } else {
+                addItem(item);
             }
         }
     }
 
     @Override
-    public String toString() {
-        String typeStr;
-        if (tag().isUniversal()) {
-            typeStr = tag().universalTag().toStr();
-        } else if (tag().isAppSpecific()) {
-            typeStr = "application " + tagNo();
-        } else {
-            typeStr = "[" + tagNo() + "]";
+    public void dumpWith(Asn1Dumper dumper, int indents) {
+        dumper.indent(indents).append(toString()).newLine();
+
+        for (Asn1Type aObj : getValue()) {
+            dumper.dumpType(indents + 4, aObj).newLine();
         }
-        return typeStr;
     }
-}
+}
\ No newline at end of file
diff --git a/kerby-asn1/src/main/java/org/apache/kerby/asn1/type/Asn1Eoc.java b/kerby-asn1/src/main/java/org/apache/kerby/asn1/type/Asn1Eoc.java
index fe80979..f8610ea 100644
--- a/kerby-asn1/src/main/java/org/apache/kerby/asn1/type/Asn1Eoc.java
+++ b/kerby-asn1/src/main/java/org/apache/kerby/asn1/type/Asn1Eoc.java
@@ -19,10 +19,10 @@
  */
 package org.apache.kerby.asn1.type;
 
+import org.apache.kerby.asn1.Asn1Header;
 import org.apache.kerby.asn1.UniversalTag;
 
 import java.io.IOException;
-import java.nio.ByteBuffer;
 
 /**
  * To represent Asn1 End Of Content type
@@ -46,9 +46,14 @@
     }
 
     @Override
-    protected void decodeBody(ByteBuffer content) throws IOException {
-        if (content.remaining() != 0) {
+    protected void decodeBody(Asn1Header header) throws IOException {
+        if (header.getLength() != 0) {
             throw new IOException("Unexpected bytes found for EOC");
         }
     }
+
+    @Override
+    public String toString() {
+        return "EOC";
+    }
 }
diff --git a/kerby-asn1/src/main/java/org/apache/kerby/asn1/type/Asn1GeneralizedTime.java b/kerby-asn1/src/main/java/org/apache/kerby/asn1/type/Asn1GeneralizedTime.java
index 0c2e1f9..48d0cef 100644
--- a/kerby-asn1/src/main/java/org/apache/kerby/asn1/type/Asn1GeneralizedTime.java
+++ b/kerby-asn1/src/main/java/org/apache/kerby/asn1/type/Asn1GeneralizedTime.java
@@ -43,7 +43,7 @@
     }
 
     public Asn1GeneralizedTime(Date date) {
-        super(UniversalTag.UTC_TIME, date);
+        super(UniversalTag.GENERALIZED_TIME, date);
     }
 
     protected void toValue() throws IOException {
diff --git a/kerby-asn1/src/main/java/org/apache/kerby/asn1/type/Asn1Item.java b/kerby-asn1/src/main/java/org/apache/kerby/asn1/type/Asn1Item.java
index efd0673..b3994ff 100644
--- a/kerby-asn1/src/main/java/org/apache/kerby/asn1/type/Asn1Item.java
+++ b/kerby-asn1/src/main/java/org/apache/kerby/asn1/type/Asn1Item.java
@@ -20,6 +20,7 @@
 package org.apache.kerby.asn1.type;
 
 import org.apache.kerby.asn1.Asn1Factory;
+import org.apache.kerby.asn1.Asn1Header;
 import org.apache.kerby.asn1.Tag;
 import org.apache.kerby.asn1.TaggingOption;
 
@@ -36,34 +37,27 @@
  * tagged value.
  *
  * For not fully decoded value, you tell your case using isSimple/isCollection/
- * isTagged/isContextSpecific etc., then call decodeValueAsSimple/
+ * isSpecific/isContextSpecific etc., then call decodeValueAsSimple/
  * decodeValueAsCollection/decodeValueAsImplicitTagged/decodeValueAsExplicitTagged etc.
  * to decode it fully. Or if you have already derived the value holder or
  * the holder type, you can use decodeValueWith or decodeValueAs with your
  * holder or hodler type.
  */
 public class Asn1Item extends AbstractAsn1Type<Asn1Type> {
-    private ByteBuffer bodyContent;
+    private final Asn1ParsingResult parsingResult;
 
-    public Asn1Item(Asn1Type value) {
-        super(value.tag(), value);
+    public Asn1Item(Asn1ParsingResult parsingResult) {
+        super(parsingResult.tag());
+        this.parsingResult = parsingResult;
     }
 
-    public Asn1Item(Tag tag) {
+    public Asn1Item(Tag tag, Asn1ParsingResult parsingResult) {
         super(tag);
+        this.parsingResult = parsingResult;
     }
 
-    public Asn1Item(Tag tag, ByteBuffer bodyContent) {
-        super(tag);
-        this.bodyContent = bodyContent;
-    }
-
-    public void setBodyContent(ByteBuffer bodyContent) {
-        this.bodyContent = bodyContent;
-    }
-
-    public ByteBuffer getBodyContent() {
-        return bodyContent;
+    public Asn1ParsingResult getParsingResult() {
+        return parsingResult;
     }
 
     @Override
@@ -71,7 +65,7 @@
         if (getValue() != null) {
             return ((AbstractAsn1Type<?>) getValue()).encodingBodyLength();
         }
-        return (int) bodyContent.remaining();
+        return parsingResult.encodingBodyLength();
     }
 
     @Override
@@ -82,8 +76,8 @@
     }
 
     @Override
-    protected void decodeBody(ByteBuffer bodyContent) throws IOException {
-        this.bodyContent = bodyContent;
+    protected void decodeBody(Asn1Header header) throws IOException {
+        this.parsingResult.decodeBody(header);
     }
 
     public boolean isFullyDecoded() {
@@ -132,15 +126,15 @@
     public void decodeValueWith(Asn1Type value) throws IOException {
         setValue(value);
         value.useDefinitiveLength(isDefinitiveLength());
-        ((AbstractAsn1Type<?>) value).decode(tag(), bodyContent);
+        ((Asn1Object) value).decode(parsingResult.getHeader());
     }
 
     public void decodeValueWith(Asn1Type value, TaggingOption taggingOption) throws IOException {
-        if (!isTagged()) {
+        if (!isTagSpecific()) {
             throw new IllegalArgumentException(
                 "Attempting to decode non-tagged value using tagging way");
         }
-        ((Asn1Object) value).taggedDecode(tag(), getBodyContent(), taggingOption);
+        ((Asn1Object) value).taggedDecode(parsingResult.getHeader(), taggingOption);
         setValue(value);
     }
 }
diff --git a/kerby-asn1/src/main/java/org/apache/kerby/asn1/type/Asn1Null.java b/kerby-asn1/src/main/java/org/apache/kerby/asn1/type/Asn1Null.java
index f1a4416..6affb95 100644
--- a/kerby-asn1/src/main/java/org/apache/kerby/asn1/type/Asn1Null.java
+++ b/kerby-asn1/src/main/java/org/apache/kerby/asn1/type/Asn1Null.java
@@ -19,10 +19,10 @@
  */
 package org.apache.kerby.asn1.type;
 
+import org.apache.kerby.asn1.Asn1Header;
 import org.apache.kerby.asn1.UniversalTag;
 
 import java.io.IOException;
-import java.nio.ByteBuffer;
 
 /**
  * The Asn1 Null type
@@ -46,9 +46,14 @@
     }
 
     @Override
-    protected void decodeBody(ByteBuffer content) throws IOException {
-        if (content.remaining() != 0) {
+    protected void decodeBody(Asn1Header header) throws IOException {
+        if (header.getLength() != 0) {
             throw new IOException("Unexpected bytes found for NULL");
         }
     }
+
+    @Override
+    public String toString() {
+        return "null";
+    }
 }
diff --git a/kerby-asn1/src/main/java/org/apache/kerby/asn1/type/Asn1Object.java b/kerby-asn1/src/main/java/org/apache/kerby/asn1/type/Asn1Object.java
index b4a5b7b..6b4a20c 100644
--- a/kerby-asn1/src/main/java/org/apache/kerby/asn1/type/Asn1Object.java
+++ b/kerby-asn1/src/main/java/org/apache/kerby/asn1/type/Asn1Object.java
@@ -23,7 +23,8 @@
 import org.apache.kerby.asn1.Tag;
 import org.apache.kerby.asn1.TaggingOption;
 import org.apache.kerby.asn1.UniversalTag;
-import org.apache.kerby.asn1.util.Asn1Reader1;
+import org.apache.kerby.asn1.util.Asn1Reader;
+import org.apache.kerby.asn1.util.Asn1Reader2;
 import org.apache.kerby.asn1.util.Asn1Util;
 
 import java.io.IOException;
@@ -194,8 +195,12 @@
         return tag.isContextSpecific();
     }
 
-    protected boolean isTagged() {
-        return tag.isTagged();
+    protected boolean isTagSpecific() {
+        return tag.isSpecific();
+    }
+
+    protected boolean isEOC() {
+        return tag().isEOC();
     }
 
     public boolean isSimple() {
@@ -210,23 +215,23 @@
 
     @Override
     public void decode(ByteBuffer content) throws IOException {
-        Asn1Reader1 reader = new Asn1Reader1(content);
+        Asn1Reader reader = new Asn1Reader2(content);
         Asn1Header header = reader.readHeader();
 
         useDefinitiveLength(header.isDefinitiveLength());
-        decode(header.getTag(), header.getValueBuffer());
+        decode(header);
     }
 
-    public void decode(Tag tag, ByteBuffer content) throws IOException {
-        if (!tag().equals(tag)) {
-            throw new IOException("Unexpected tag " + tag
-                    + ", expecting " + tag());
+    public void decode(Asn1Header header) throws IOException {
+        if (!tag().equals(header.getTag())) {
+            throw new IOException("Unexpected tag " + header.getTag()
+                + ", expecting " + tag());
         }
 
-        decodeBody(content);
+        decodeBody(header);
     }
 
-    protected abstract void decodeBody(ByteBuffer content) throws IOException;
+    protected abstract void decodeBody(Asn1Header header) throws IOException;
 
     protected int taggedEncodingLength(TaggingOption taggingOption) {
         int taggingTagNo = taggingOption.getTagNo();
@@ -266,25 +271,25 @@
     @Override
     public void taggedDecode(ByteBuffer content,
                              TaggingOption taggingOption) throws IOException {
-        Asn1Reader1 reader = new Asn1Reader1(content);
+        Asn1Reader reader = new Asn1Reader2(content);
         Asn1Header header = reader.readHeader();
 
         useDefinitiveLength(header.isDefinitiveLength());
-        taggedDecode(header.getTag(), header.getValueBuffer(), taggingOption);
+        taggedDecode(header, taggingOption);
     }
 
-    protected void taggedDecode(Tag taggingTag, ByteBuffer content,
+    protected void taggedDecode(Asn1Header header,
                                 TaggingOption taggingOption) throws IOException {
         Tag expectedTaggingTagFlags = taggingOption.getTag(!isPrimitive());
-        if (!expectedTaggingTagFlags.equals(taggingTag)) {
-            throw new IOException("Unexpected tag " + taggingTag
+        if (!expectedTaggingTagFlags.equals(header.getTag())) {
+            throw new IOException("Unexpected tag " + header.getTag()
                     + ", expecting " + expectedTaggingTagFlags);
         }
 
         if (taggingOption.isImplicit()) {
-            decodeBody(content);
+            decodeBody(header);
         } else {
-            decode(content);
+            decode(header.getBodyBuffer());
         }
     }
 }
diff --git a/kerby-asn1/src/main/java/org/apache/kerby/asn1/type/Asn1OctetString.java b/kerby-asn1/src/main/java/org/apache/kerby/asn1/type/Asn1OctetString.java
index 46c9c32..88c4f46 100644
--- a/kerby-asn1/src/main/java/org/apache/kerby/asn1/type/Asn1OctetString.java
+++ b/kerby-asn1/src/main/java/org/apache/kerby/asn1/type/Asn1OctetString.java
@@ -19,11 +19,10 @@
  */
 package org.apache.kerby.asn1.type;
 
+import org.apache.kerby.asn1.Asn1Header;
 import org.apache.kerby.asn1.UniversalTag;
-import org.apache.kerby.asn1.util.Asn1Util;
 
 import java.io.IOException;
-import java.nio.ByteBuffer;
 
 public class Asn1OctetString extends Asn1Simple<byte[]> {
     public Asn1OctetString() {
@@ -45,8 +44,8 @@
     }
 
     @Override
-    protected void decodeBody(ByteBuffer content) throws IOException {
-        setValue(Asn1Util.readAllLeftBytes(content));
+    protected void decodeBody(Asn1Header header) throws IOException {
+        setValue(header.readBodyBytes());
     }
 
     @Override
diff --git a/kerby-asn1/src/main/java/org/apache/kerby/asn1/type/Asn1ParsingContainer.java b/kerby-asn1/src/main/java/org/apache/kerby/asn1/type/Asn1ParsingContainer.java
new file mode 100644
index 0000000..3bd5a7f
--- /dev/null
+++ b/kerby-asn1/src/main/java/org/apache/kerby/asn1/type/Asn1ParsingContainer.java
@@ -0,0 +1,165 @@
+/**
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you 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. 
+ *  
+ */
+package org.apache.kerby.asn1.type;
+
+import org.apache.kerby.asn1.Asn1Dumpable;
+import org.apache.kerby.asn1.Asn1Dumper;
+import org.apache.kerby.asn1.Asn1Header;
+import org.apache.kerby.asn1.Tag;
+import org.apache.kerby.asn1.util.Asn1Reader;
+import org.apache.kerby.asn1.util.Asn1Reader2;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * ASN1 constructed types, mainly structured ones, but also some primitive ones.
+ */
+public class Asn1ParsingContainer
+    extends Asn1ParsingResult implements Asn1Dumpable {
+
+    private List<Asn1ParsingResult> parsingResults = new ArrayList<>();
+
+    public Asn1ParsingContainer(Tag tag) {
+        super(tag);
+    }
+
+    public Asn1ParsingContainer(Asn1Header header) {
+        super(header);
+        usePrimitive(false);
+    }
+
+    public List<Asn1ParsingResult> getParsingResults() {
+        return parsingResults;
+    }
+
+    public void addItem(Asn1ParsingResult value) {
+        parsingResults.add(value);
+    }
+
+    public void clear() {
+        parsingResults.clear();
+    }
+
+    @Override
+    public void decode(ByteBuffer content) throws IOException {
+        Asn1Reader2 reader = new Asn1Reader2(content);
+        Asn1Header header = reader.readHeader();
+        Tag tmpTag = header.getTag();
+        useDefinitiveLength(header.isDefinitiveLength());
+
+        if (!tag().equals(tmpTag)) {
+            throw new IOException("Unexpected tag " + tmpTag
+                + ", expecting " + tag());
+        }
+
+        decode(header);
+    }
+
+    @Override
+    public void decode(Asn1Header header) throws IOException {
+        this.header = header;
+
+        Asn1Reader reader = new Asn1Reader2(header.getBuffer());
+        int pos = header.getBodyStart();
+        while (true) {
+            reader.setPosition(pos);
+            Asn1ParsingResult asn1Obj = decodeOne(reader);
+            if (asn1Obj == null) {
+                break;
+            }
+
+            pos += asn1Obj.encodingLength();
+            if (asn1Obj.isEOC()) {
+                break;
+            }
+
+            if (header.getBodyEnd() != -1 && pos >= header.getBodyEnd()) {
+                break;
+            }
+        }
+
+        header.setBodyEnd(pos);
+    }
+
+    private Asn1ParsingResult decodeOne(Asn1Reader reader) throws IOException {
+        return decodeOne(reader, this);
+    }
+
+    public static Asn1ParsingResult decodeOne(ByteBuffer content) throws IOException {
+        Asn1Reader reader = new Asn1Reader2(content);
+        return Asn1ParsingContainer.decodeOne(reader, null);
+    }
+
+    public static Asn1ParsingResult decodeOne(Asn1Reader reader,
+                                     Asn1ParsingContainer parent) throws IOException {
+        if (!reader.available()) {
+            return null;
+        }
+
+        Asn1Header header = reader.readHeader();
+        Tag tmpTag = header.getTag();
+        Asn1ParsingResult parsingResult;
+
+        if (tmpTag.isPrimitive()) {
+            parsingResult = new Asn1ParsingItem(header);
+            parsingResult.useDefinitiveLength(header.isDefinitiveLength());
+        } else {
+            Asn1ParsingContainer container = new Asn1ParsingContainer(tmpTag);
+            container.useDefinitiveLength(header.isDefinitiveLength());
+            container.decode(header);
+            parsingResult = container;
+        }
+
+        if (parsingResult != null && parent != null) {
+            parent.addItem(parsingResult);
+        }
+
+        return parsingResult;
+    }
+
+    @Override
+    public void dumpWith(Asn1Dumper dumper, int indents) {
+        dumper.indent(indents).append(toString()).newLine();
+
+        for (Asn1ParsingResult aObj : parsingResults) {
+            dumper.dumpType(indents + 4, aObj).newLine();
+        }
+    }
+
+    @Override
+    public String toString() {
+        String typeStr;
+        if (tag().isUniversal()) {
+            typeStr = tag().universalTag().toStr();
+        } else if (tag().isAppSpecific()) {
+            typeStr = "application " + tagNo();
+        } else {
+            typeStr = "[" + tagNo() + "]";
+        }
+        return typeStr + " ["
+            + "off=" + getOffset()
+            + ", len=" + encodingHeaderLength() + "+" + encodingBodyLength()
+            + (isDefinitiveLength() ? "" : "(undefined)")
+            + "]";
+    }
+}
diff --git a/kerby-asn1/src/main/java/org/apache/kerby/asn1/type/Asn1ParsingItem.java b/kerby-asn1/src/main/java/org/apache/kerby/asn1/type/Asn1ParsingItem.java
new file mode 100644
index 0000000..e7cffa3
--- /dev/null
+++ b/kerby-asn1/src/main/java/org/apache/kerby/asn1/type/Asn1ParsingItem.java
@@ -0,0 +1,49 @@
+/**
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you 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. 
+ *  
+ */
+package org.apache.kerby.asn1.type;
+
+import org.apache.kerby.asn1.Asn1Header;
+
+/**
+ * Asn1Item serves two purposes:
+ * 1. Wrapping an existing Asn1Type value for Asn1Collection;
+ * 2. Wrapping a half decoded value whose body content is left to be decoded
+ * later when appropriate.
+ * Why not fully decoded at once? Lazy and decode on demand for collection, or
+ * impossible due to lacking key parameters.
+ */
+public class Asn1ParsingItem extends Asn1ParsingResult {
+
+    public Asn1ParsingItem(Asn1Header header) {
+        super(header);
+    }
+
+    @Override
+    public String toString() {
+        String valueStr = "##undecoded##";
+        String typeStr = tag().isUniversal() ? tag().universalTag().toStr()
+            : tag().tagClass().name().toLowerCase();
+        return typeStr + " ["
+            + "off=" + getOffset()
+            + ", len=" + encodingHeaderLength() + "+" + encodingBodyLength()
+            + "] "
+            + valueStr;
+    }
+}
diff --git a/kerby-asn1/src/main/java/org/apache/kerby/asn1/type/Asn1ParsingResult.java b/kerby-asn1/src/main/java/org/apache/kerby/asn1/type/Asn1ParsingResult.java
new file mode 100644
index 0000000..004fd02
--- /dev/null
+++ b/kerby-asn1/src/main/java/org/apache/kerby/asn1/type/Asn1ParsingResult.java
@@ -0,0 +1,84 @@
+/**
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you 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. 
+ *  
+ */
+package org.apache.kerby.asn1.type;
+
+import org.apache.kerby.asn1.Asn1Header;
+import org.apache.kerby.asn1.Tag;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+
+public abstract class Asn1ParsingResult extends AbstractAsn1Type<Asn1Type> {
+    protected Asn1Header header;
+
+    public Asn1ParsingResult(Tag tag) {
+        super(tag);
+        setValue(this);
+    }
+
+    public Asn1ParsingResult(Asn1Header header) {
+        super(header.getTag());
+        setValue(this);
+        this.header = header;
+    }
+
+    protected ByteBuffer getBodyBuffer() {
+        return header.getBodyBuffer();
+    }
+
+    protected Asn1Header getHeader() {
+        return header;
+    }
+
+    @Override
+    protected int encodingBodyLength() {
+        return header.getActualBodyLength();
+    }
+
+    @Override
+    protected void encodeBody(ByteBuffer buffer) {
+        buffer.put(header.getBodyBuffer());
+    }
+
+    protected int getOffset() {
+        return header.getBodyStart() - encodingHeaderLength();
+    }
+
+    @Override
+    protected void decodeBody(Asn1Header header) throws IOException {
+        // NOT USED FOR NOW SINCE WE DON'T DECODE THE BODY.
+    }
+
+    @Override
+    public String toString() {
+        String valueStr = "undecoded";
+        if (getValue() != null) {
+            Asn1Type val = getValue();
+            valueStr = (val != null ? val.toString() : "null");
+        }
+        String typeStr = tag().isUniversal() ? tag().universalTag().toStr()
+            : tag().tagClass().name().toLowerCase();
+        return typeStr + " ["
+            + "off=" + getOffset()
+            + ", len=" + encodingHeaderLength() + "+" + encodingBodyLength()
+            + "] "
+            + valueStr;
+    }
+}
diff --git a/kerby-asn1/src/main/java/org/apache/kerby/asn1/type/Asn1Simple.java b/kerby-asn1/src/main/java/org/apache/kerby/asn1/type/Asn1Simple.java
index 4358f65..6d08f70 100644
--- a/kerby-asn1/src/main/java/org/apache/kerby/asn1/type/Asn1Simple.java
+++ b/kerby-asn1/src/main/java/org/apache/kerby/asn1/type/Asn1Simple.java
@@ -19,6 +19,7 @@
  */
 package org.apache.kerby.asn1.type;
 
+import org.apache.kerby.asn1.Asn1Header;
 import org.apache.kerby.asn1.Tag;
 import org.apache.kerby.asn1.UniversalTag;
 import org.apache.kerby.asn1.util.Asn1Util;
@@ -92,8 +93,8 @@
     }
 
     @Override
-    protected void decodeBody(ByteBuffer content) throws IOException {
-        byte[] leftBytes = Asn1Util.readAllLeftBytes(content);
+    protected void decodeBody(Asn1Header header) throws IOException {
+        byte[] leftBytes = header.readBodyBytes();
         if (leftBytes.length > 0) {
             setBytes(leftBytes);
             toValue();
diff --git a/kerby-asn1/src/main/java/org/apache/kerby/asn1/type/Asn1Tagging.java b/kerby-asn1/src/main/java/org/apache/kerby/asn1/type/Asn1Tagging.java
index 8818c98..8a8ee3b 100644
--- a/kerby-asn1/src/main/java/org/apache/kerby/asn1/type/Asn1Tagging.java
+++ b/kerby-asn1/src/main/java/org/apache/kerby/asn1/type/Asn1Tagging.java
@@ -21,6 +21,7 @@
 
 import org.apache.kerby.asn1.Asn1Dumpable;
 import org.apache.kerby.asn1.Asn1Dumper;
+import org.apache.kerby.asn1.Asn1Header;
 import org.apache.kerby.asn1.Tag;
 
 import java.io.IOException;
@@ -61,7 +62,7 @@
 
     @Override
     protected int encodingBodyLength() {
-        AbstractAsn1Type<?> value = (AbstractAsn1Type<?>) getValue();
+        Asn1Object value = (Asn1Object) getValue();
         if (isImplicit()) {
             return value.encodingBodyLength();
         } else {
@@ -71,7 +72,7 @@
 
     @Override
     protected void encodeBody(ByteBuffer buffer) {
-        AbstractAsn1Type<?> value = (AbstractAsn1Type<?>) getValue();
+        Asn1Object value = (Asn1Object) getValue();
         if (isImplicit()) {
             value.encodeBody(buffer);
         } else {
@@ -80,12 +81,12 @@
     }
 
     @Override
-    protected void decodeBody(ByteBuffer content) throws IOException {
-        AbstractAsn1Type<?> value = (AbstractAsn1Type<?>) getValue();
+    protected void decodeBody(Asn1Header header) throws IOException {
+        Asn1Object value = (Asn1Object) getValue();
         if (isImplicit()) {
-            value.decodeBody(content);
+            value.decodeBody(header);
         } else {
-            value.decode(content);
+            value.decode(header.getBodyBuffer());
         }
     }
 
@@ -104,6 +105,7 @@
     @Override
     public void dumpWith(Asn1Dumper dumper, int indents) {
         Asn1Type taggedValue = getValue();
+        dumper.dumpTypeInfo(indents, getClass());
         dumper.dumpType(indents, taggedValue);
     }
 }
diff --git a/kerby-asn1/src/main/java/org/apache/kerby/asn1/type/Asn1TaggingCollection.java b/kerby-asn1/src/main/java/org/apache/kerby/asn1/type/Asn1TaggingCollection.java
index 25dfbf3..4d3cbe2 100644
--- a/kerby-asn1/src/main/java/org/apache/kerby/asn1/type/Asn1TaggingCollection.java
+++ b/kerby-asn1/src/main/java/org/apache/kerby/asn1/type/Asn1TaggingCollection.java
@@ -22,6 +22,7 @@
 import org.apache.kerby.asn1.Asn1Dumpable;
 import org.apache.kerby.asn1.Asn1Dumper;
 import org.apache.kerby.asn1.Asn1FieldInfo;
+import org.apache.kerby.asn1.Asn1Header;
 import org.apache.kerby.asn1.EnumType;
 import org.apache.kerby.asn1.Tag;
 
@@ -43,7 +44,7 @@
         super(makeTag(isAppSpecific, taggingTagNo));
         this.tagged = createTaggedCollection(tags);
         setValue(tagged);
-        this.tagging = new Asn1Tagging<Asn1CollectionType>(taggingTagNo,
+        this.tagging = new Asn1Tagging<>(taggingTagNo,
             tagged, isAppSpecific, isImplicit);
     }
 
@@ -135,8 +136,13 @@
     }
 
     @Override
-    protected void decodeBody(ByteBuffer content) throws IOException {
-        tagging.decodeBody(content);
+    public void decode(ByteBuffer content) throws IOException {
+        tagging.decode(content);
+    }
+
+    @Override
+    protected void decodeBody(Asn1Header header) throws IOException {
+        tagging.decodeBody(header);
     }
 
     protected <T extends Asn1Type> T getFieldAs(EnumType index, Class<T> t) {
@@ -178,6 +184,7 @@
     @Override
     public void dumpWith(Asn1Dumper dumper, int indents) {
         Asn1Type taggedValue = getValue();
+        dumper.dumpTypeInfo(indents, getClass());
         dumper.dumpType(indents, taggedValue);
     }
 }
diff --git a/kerby-asn1/src/main/java/org/apache/kerby/asn1/util/Asn1Reader.java b/kerby-asn1/src/main/java/org/apache/kerby/asn1/util/Asn1Reader.java
index 451e8ff..2590a48 100644
--- a/kerby-asn1/src/main/java/org/apache/kerby/asn1/util/Asn1Reader.java
+++ b/kerby-asn1/src/main/java/org/apache/kerby/asn1/util/Asn1Reader.java
@@ -42,12 +42,19 @@
     public Asn1Header readHeader() throws IOException {
         Tag tag = readTag();
         int valueLength = readLength();
+        int bodyStart = getPosition();
         Asn1Header header = new Asn1Header(tag, valueLength,
-            getValueBuffer(valueLength));
+            bodyStart, getValueBuffer(valueLength));
         return header;
     }
 
-    protected abstract ByteBuffer getValueBuffer(int valueLength);
+    public abstract void setPosition(int position);
+
+    public abstract int getPosition();
+
+    public abstract boolean available();
+
+    public abstract ByteBuffer getValueBuffer(int valueLength);
 
     protected abstract byte readByte() throws IOException;
 
diff --git a/kerby-asn1/src/main/java/org/apache/kerby/asn1/util/Asn1Reader1.java b/kerby-asn1/src/main/java/org/apache/kerby/asn1/util/Asn1Reader1.java
index f53f99d..503507f 100644
--- a/kerby-asn1/src/main/java/org/apache/kerby/asn1/util/Asn1Reader1.java
+++ b/kerby-asn1/src/main/java/org/apache/kerby/asn1/util/Asn1Reader1.java
@@ -32,12 +32,27 @@
     }
 
     @Override
+    public void setPosition(int position) {
+        buffer.position(position);
+    }
+
+    @Override
+    public int getPosition() {
+        return buffer.position();
+    }
+
+    @Override
+    public boolean available() {
+        return buffer.remaining() > 0;
+    }
+
+    @Override
     protected byte readByte() throws IOException {
         return buffer.get();
     }
 
     @Override
-    protected ByteBuffer getValueBuffer(int valueLength) {
+    public ByteBuffer getValueBuffer(int valueLength) {
         ByteBuffer result = buffer.duplicate();
         result.limit(buffer.position() + valueLength);
         buffer.position(buffer.position() + valueLength);
diff --git a/kerby-asn1/src/main/java/org/apache/kerby/asn1/util/Asn1Reader2.java b/kerby-asn1/src/main/java/org/apache/kerby/asn1/util/Asn1Reader2.java
index 9351c3f..0136db4 100644
--- a/kerby-asn1/src/main/java/org/apache/kerby/asn1/util/Asn1Reader2.java
+++ b/kerby-asn1/src/main/java/org/apache/kerby/asn1/util/Asn1Reader2.java
@@ -38,20 +38,23 @@
         this.position = buffer.position();
     }
 
+    @Override
     public int getPosition() {
         return position;
     }
 
+    @Override
     public void setPosition(int position) {
         this.position = position;
     }
 
+    @Override
     public boolean available() {
         return position < buffer.limit();
     }
 
     @Override
-    protected ByteBuffer getValueBuffer(int valueLength) {
+    public ByteBuffer getValueBuffer(int valueLength) {
         return buffer;
     }
 
diff --git a/kerby-asn1/src/test/java/org/apache/kerby/asn1/TestAsn1Dump.java b/kerby-asn1/src/test/java/org/apache/kerby/asn1/TestAsn1Dump.java
index 540de22..a5c64d9 100644
--- a/kerby-asn1/src/test/java/org/apache/kerby/asn1/TestAsn1Dump.java
+++ b/kerby-asn1/src/test/java/org/apache/kerby/asn1/TestAsn1Dump.java
@@ -19,15 +19,17 @@
  */
 package org.apache.kerby.asn1;
 
+import org.apache.kerby.asn1.util.IOUtil;
 import org.junit.Assert;
 import org.junit.Test;
 
 import java.io.IOException;
+import java.io.InputStream;
 
 public class TestAsn1Dump {
 
-    @Test
-    public void testDecoding() throws IOException {
+    //@Test
+    public void testDump1WithPersonnelRecord() throws IOException {
         try {
             PersonnelRecord pr = TestData.createSamplePersonnel();
             Asn1.dump(pr);
@@ -35,7 +37,51 @@
             byte[] data = TestData.createSammplePersonnelEncodingData();
             Asn1.dump(data);
         } catch (Exception e) {
+            e.printStackTrace();
             Assert.fail();
         }
     }
+
+    @Test
+    public void testDump1WithCompressedData() throws IOException {
+        String hexStr = readTxtFile("/compressed-data.txt");
+        try {
+            Asn1.dump(hexStr);
+        } catch (Exception e) {
+            e.printStackTrace();
+            Assert.fail();
+        }
+    }
+
+    @Test
+    public void testDump1WithSignedData() throws IOException {
+        String hexStr = readTxtFile("/signed-data.txt");
+        try {
+            Asn1.dump(hexStr);
+        } catch (Exception e) {
+            e.printStackTrace();
+            Assert.fail();
+        }
+    }
+
+    @Test
+    public void testDump1WithDerData() throws IOException {
+        byte[] data = readBinFile("/der-data.dat");
+        try {
+            Asn1.dump(data);
+        } catch (Exception e) {
+            e.printStackTrace();
+            Assert.fail();
+        }
+    }
+
+    static String readTxtFile(String resource) throws IOException {
+        InputStream is = TestAsn1Dump.class.getResourceAsStream(resource);
+        return IOUtil.readInput(is);
+    }
+
+    static byte[] readBinFile(String resource) throws IOException {
+        InputStream is = TestAsn1Dump.class.getResourceAsStream(resource);
+        return IOUtil.readInputStream(is);
+    }
 }
diff --git a/kerby-asn1/src/test/java/org/apache/kerby/asn1/TestPersonnelRecord.java b/kerby-asn1/src/test/java/org/apache/kerby/asn1/TestPersonnelRecord.java
index 81ce341..88349e2 100644
--- a/kerby-asn1/src/test/java/org/apache/kerby/asn1/TestPersonnelRecord.java
+++ b/kerby-asn1/src/test/java/org/apache/kerby/asn1/TestPersonnelRecord.java
@@ -72,12 +72,14 @@
         assertThat(encoded).isEqualTo(data);
     }
 
-    //@Test
+    @Test
     public void testDecoding() throws IOException {
         PersonnelRecord expected = TestData.createSamplePersonnel();
         byte[] data = TestData.createSammplePersonnelEncodingData();
         PersonnelRecord decoded = new PersonnelRecord();
+        //Asn1.dump(data);
         decoded.decode(data);
+        Asn1.dump(decoded);
 
         assertThat(expected.getName().getGivenName())
                 .isEqualTo(decoded.getName().getGivenName());
@@ -97,6 +99,8 @@
                 .isEqualTo(decoded.getNameOfSpouse().getInitial());
         assertThat(expected.getNameOfSpouse().getFamilyName())
                 .isEqualTo(decoded.getNameOfSpouse().getFamilyName());
+        assertThat(decoded.getChildren().getElements().size())
+            .isEqualTo(expected.getChildren().getElements().size());
         assertThat(expected.getChildren().getElements().get(0).getName().getGivenName())
                 .isEqualTo(decoded.getChildren().getElements().get(0).getName().getGivenName());
         assertThat(expected.getChildren().getElements().get(0).getName().getInitial())
diff --git a/kerby-asn1/src/test/resources/compressed-data.txt b/kerby-asn1/src/test/resources/compressed-data.txt
new file mode 100644
index 0000000..3012abe
--- /dev/null
+++ b/kerby-asn1/src/test/resources/compressed-data.txt
@@ -0,0 +1 @@
+3080060B2A864886F70D0109100109A0803080020100300D060B2A864886F70D0109100308308006092A864886F70D010701A08024800482021E789CED945D6FDA301486EF2BF93FE4FA9560F15718AD76618C9BA15140492A75BD0B591A22D1002644E3DFD71E8516ED2FF4B573FC11FBB17D4E62BD69DAB2697BD9715BDE06F976BBAE8BBCAD37CD37339EF49E28BB0B9AFCB5FC11DBCD61CBFA7F292337FA3CC7E6CDFEA5B43DD3149B3F7553DD06CBBAC9EDF163C8B8DE6F37FBDA036F83BA59D74D7917BCD4EBF23F28B999A40A6188FD45BE652FC2F333F46F3D9DCF4CF02EDFB550493633C9A56B18D1301C808594E3D111988384EFA2089101244EB19823A5570909BD4A57A43341E0C9D57928439266F82E4F6CE1D9646462BFE39101CB2138678271D149C9F805C5DB427447263AB6E29DCFFE041333453844629C25FA3101A5C8921964242344D29511260FF78E1DBD834862EE1129BF88A505B75CB85C704ED9AE68AB420A24A58B4BD91465303BBC2E4B4B16268152F8E92216C487D605077A01DAE78CB987F78513BC6C5589B2EA442B48A69E20DAAA5DB6A2B52E239EA25A552B9C154393FBF908D9025AE1D1C78F613C7F80D66E07D5619DDB60BA397D4FFB605BDA202BEDEB9E6837616CA09D3B85E460D2F90FE32152E709C61915CE8A93FF9CF34C0AC198F075922A0D85911FAF0CB814125CB8176178F6DC201A60E04D24C9624E5D17EDE5790E7A0A20548A61D8F7711A43FDC2BF2564C72517BBE8C5179DF056F26EC7779CE1438AE2D8DAD517F58BFA4575FF70064A5277D9B14F77606C403F2E4B3231EA539B923773F79734000000000000000000000000
\ No newline at end of file
diff --git a/kerby-asn1/src/test/resources/der-data.dat b/kerby-asn1/src/test/resources/der-data.dat
new file mode 100644
index 0000000..6d503b3
--- /dev/null
+++ b/kerby-asn1/src/test/resources/der-data.dat
Binary files differ
diff --git a/kerby-asn1/src/test/resources/signed-data.txt b/kerby-asn1/src/test/resources/signed-data.txt
new file mode 100644
index 0000000..bf018ef
--- /dev/null
+++ b/kerby-asn1/src/test/resources/signed-data.txt
@@ -0,0 +1 @@
+308006092A864886F70D010702A0803080020101310B300906052B0E03021A0500308006092A864886F70D010701A0802480040C48656C6C6F20576F726C6421000000000000A08204623082020D30820176A003020102020101300D06092A864886F70D0101040500302531163014060355040A130D426F756E637920436173746C65310B3009060355040613024155301E170D3034313032343034333035385A170D3035303230313034333035385A302531163014060355040A130D426F756E637920436173746C65310B300906035504061302415530819F300D06092A864886F70D010101050003818D003081890281810098F7380B2100E80398F7186758DD352BA1387447F55842FCF1B5EC5762227546736BA52611227C44206BFBAA642A527759C7B095192A6F64D5B567796CC2D3DE358BD6677F181BDA39D75EC6B99300762845444AB396B118D246D9D888B82A046D2BCE968C9F90399EDBBB374E34E847AC0617B5F5C8D901A81E1071990DBB9F0203010001A34D304B301D0603551D0E041604147F88734A3A8E9FE01C96145CB044D67AE574ABD6301F0603551D230418301680147F88734A3A8E9FE01C96145CB044D67AE574ABD630090603551D1304023000300D06092A864886F70D010104050003818100530927B40EA47A37D1B9E5438372DCF0A72BE49EF86C374E96981A9F38F9AF1F183F4399EEFC1BA344B20BE91464F2A0BC6F278081F382B3CACFB5A761DD5F3A198B25C07ABE52E08C29B44DD3C711702B28F6F1C46B33A74AE3B0C3C97524574AB004030D96EC2793F9E8E0AEE297337631993997BC17C8D6BDC89E821D6E9D3082024D308201B6A003020102020102300D06092A864886F70D0101040500302531163014060355040A130D426F756E637920436173746C65310B3009060355040613024155301E170D3034313032343034333035395A170D3035303230313034333035395A3065311830160603550403130F4572696320482E2045636869646E613124302206092A864886F70D01090116156572696340626F756E6379636173746C652E6F726731163014060355040A130D426F756E637920436173746C65310B300906035504061302415530819F300D06092A864886F70D010101050003818D00308189028181009BEE429C653A5B8E625290AC686927E600EBB99BF78FFA3B37A6A09916618A468B1B6245E8409A5F5DE28A85497E6CC1B0F2B1000096C2E4D70A84925C6F2F88AFEAA521F02697D01D8121B5C0B40B122A96796DBF7290006C21FCF770A523EF48D9E44011AFB45ABEFBD8275C305BCA77CFF1BD8BD848ACD17F54DB2EA016230203010001A34D304B301D0603551D0E0416041440263A4B2717AE85A109BA21005537AEAF268D53301F0603551D230418301680147F88734A3A8E9FE01C96145CB044D67AE574ABD630090603551D1304023000300D06092A864886F70D01010405000381810011E223BCD90A30F53F6580AED53AA31993C4AA2FA0967B60DA10BF085D281B21C5A4CB86B4C7A9177A6E5BEBB328CD6CEB56ABDDA862769DD7161AFF18453F5F12B6E00A25237C858313FC3D18145A6422CD3417A56526865B017B5B8829511F340CC282D61F250F072482AC50271D59E08A54AB388D18A89EC7A4D6BA3390F13182012F3082012B020101302A302531163014060355040A130D426F756E637920436173746C65310B3009060355040613024155020102300906052B0E03021A0500A05D301806092A864886F70D010903310B06092A864886F70D010701301C06092A864886F70D010905310F170D3034313032343034333035395A302306092A864886F70D010904311604142EF7BDE608CE5404E97D5F042F95F89F1C232871300D06092A864886F70D010101050004818061DB7B7FE3719BBA6FF7AB4617373C48E23143BC98421188D601AF9FEF14D864AF2F13AC67CEA48E399BB82ED4C4B4FEF75AB3BCA4BE2765FEC3597CED06A6110851A2233114F753ACAE4D617868BA5AB496DB46C21EA493BF07691D2006D5E5209B6605432483CE476AA7AEE1CDDB02C56BE7C4BF827CC10F17C2F9340BC88C000000000000
\ No newline at end of file
diff --git a/kerby-kerb/kerb-core-test/src/test/java/org/apache/kerby/kerberos/kerb/codec/TestAsReqCodec.java b/kerby-kerb/kerb-core-test/src/test/java/org/apache/kerby/kerberos/kerb/codec/TestAsReqCodec.java
index 4c1786b..b8599a7 100644
--- a/kerby-kerb/kerb-core-test/src/test/java/org/apache/kerby/kerberos/kerb/codec/TestAsReqCodec.java
+++ b/kerby-kerb/kerb-core-test/src/test/java/org/apache/kerby/kerberos/kerb/codec/TestAsReqCodec.java
@@ -19,6 +19,7 @@
  */
 package org.apache.kerby.kerberos.kerb.codec;
 
+import org.apache.kerby.asn1.Asn1;
 import org.apache.kerby.kerberos.kerb.type.base.EncryptionType;
 import org.apache.kerby.kerberos.kerb.type.base.HostAddrType;
 import org.apache.kerby.kerberos.kerb.type.base.HostAddress;
@@ -44,18 +45,21 @@
 import static org.assertj.core.api.Assertions.assertThat;
 
 /**
- * Test AsReq message using a real 'correct' network packet captured from MS-AD to detective programming errors
- * and compatibility issues particularly regarding Kerberos crypto.
+ * Test AsReq message using a real 'correct' network packet captured from MS-AD
+ * to detective programming errors and compatibility issues particularly
+ * regarding Kerberos crypto.
  */
 public class TestAsReqCodec {
 
     @Test
     public void test() throws IOException, ParseException {
         byte[] bytes = CodecTestUtil.readBinaryFile("/asreq.token");
+        Asn1.dump(bytes);
         ByteBuffer asReqToken = ByteBuffer.wrap(bytes);
 
         AsReq asReq = new AsReq();
         asReq.decode(asReqToken);
+        Asn1.dump(asReq, false);
 
         assertThat(asReq.getPvno()).isEqualTo(5);
         assertThat(asReq.getMsgType()).isEqualTo(KrbMessageType.AS_REQ);
diff --git a/kerby-kerb/kerb-core/src/main/java/org/apache/kerby/kerberos/kerb/KrbCodec.java b/kerby-kerb/kerb-core/src/main/java/org/apache/kerby/kerberos/kerb/KrbCodec.java
index 87d5731..43d4540 100644
--- a/kerby-kerb/kerb-core/src/main/java/org/apache/kerby/kerberos/kerb/KrbCodec.java
+++ b/kerby-kerb/kerb-core/src/main/java/org/apache/kerby/kerberos/kerb/KrbCodec.java
@@ -88,7 +88,7 @@
             throw new IOException("To be supported krb message type with tag: " + tag);
         }
 
-        msg.decode(tag, header.getValueBuffer());
+        msg.decode(header);
         return msg;
     }