DIRKRB-490. Separate ASN1 parser
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 17279b8..8ca411d 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,9 @@
  */
 package org.apache.kerby.asn1;
 
-import org.apache.kerby.asn1.type.Asn1ParsingContainer;
+import org.apache.kerby.asn1.parse.Asn1Parser;
+import org.apache.kerby.asn1.parse.Asn1ParsingResult;
 import org.apache.kerby.asn1.type.Asn1Type;
-import org.apache.kerby.asn1.util.Asn1Reader1;
 import org.apache.kerby.asn1.util.HexUtil;
 
 import java.io.IOException;
@@ -45,17 +45,21 @@
         return value.encode();
     }
 
-    public static Asn1Header decodeHeader(ByteBuffer content) throws IOException {
-        Asn1Reader1 reader = new Asn1Reader1(content);
-        return reader.readHeader();
-    }
-
     public static Asn1Type decode(byte[] content) throws IOException {
         return decode(ByteBuffer.wrap(content));
     }
 
     public static Asn1Type decode(ByteBuffer content) throws IOException {
-        return Asn1ParsingContainer.decodeOne(content);
+        Asn1ParsingResult parsingResult = Asn1Parser.parse(content);
+        return DecodingUtil.decodeValue(parsingResult);
+    }
+
+    public static Asn1ParsingResult parse(byte[] content) throws IOException {
+        return parse(ByteBuffer.wrap(content));
+    }
+
+    public static Asn1ParsingResult parse(ByteBuffer content) throws IOException {
+        return Asn1Parser.parse(content);
     }
 
     public static void dump(Asn1Type value) {
@@ -72,36 +76,27 @@
         System.out.println(output);
     }
 
-    public static void dump(String hexStr) throws IOException {
-        System.out.println("Dumping data:");
-        System.out.println(hexStr);
-        Asn1Dumper dumper = new Asn1Dumper();
+    public static void dump(String hexStr,
+                            boolean useRawFormat) throws IOException {
         byte[] data = HexUtil.hex2bytes(hexStr);
-        dumper.dump(data);
-        String output = dumper.output();
-        System.out.println(output);
+        dump(data, useRawFormat);
     }
 
-    public static void dump(byte[] content) throws IOException {
-        String hexStr = HexUtil.bytesToHex(content);
-        System.out.println("Dumping data:");
-        int range = 100;
-        int pos = range;
-        while (pos < hexStr.length()) {
-            System.out.println(hexStr.substring(pos - range, pos));
-            pos = pos + range;
-        }
-        System.out.println(hexStr.substring(pos - range, hexStr.length()));
-
-        Asn1Dumper dumper = new Asn1Dumper();
-        dumper.dump(content);
-        String output = dumper.output();
-        System.out.println(output);
-    }
-
-    public static void dump(ByteBuffer content) throws IOException {
+    public static void dump(ByteBuffer content,
+                            boolean useRawFormat) throws IOException {
         byte[] bytes = new byte[content.remaining()];
         content.get(bytes);
-        dump(bytes);
+        dump(bytes, useRawFormat);
+    }
+
+    public static void dump(byte[] content,
+                            boolean useRawFormat) throws IOException {
+        String hexStr = HexUtil.bytesToHex(content);
+        Asn1Dumper dumper = new Asn1Dumper();
+        System.out.println("Dumping data:");
+        dumper.dumpData(hexStr);
+        dumper.dump(content, useRawFormat);
+        String output = dumper.output();
+        System.out.println(output);
     }
 }
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 d8354b0..f06eb5c 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,10 +19,13 @@
  */
 package org.apache.kerby.asn1;
 
+import org.apache.kerby.asn1.parse.Asn1Parser;
+import org.apache.kerby.asn1.parse.Asn1ParsingResult;
+import org.apache.kerby.asn1.parse.Asn1Item;
 import org.apache.kerby.asn1.type.Asn1Any;
-import org.apache.kerby.asn1.type.Asn1ParsingItem;
 import org.apache.kerby.asn1.type.Asn1Simple;
 import org.apache.kerby.asn1.type.Asn1Type;
+import org.apache.kerby.asn1.util.HexUtil;
 
 import java.io.IOException;
 import java.nio.ByteBuffer;
@@ -47,17 +50,28 @@
         return builder.toString();
     }
 
-    public void dump(byte[] content) throws IOException {
-        dump(ByteBuffer.wrap(content));
+    public void dumpWithHex(byte[] content,
+                                  boolean useRawFormat) throws IOException {
+        String hexStr = HexUtil.bytesToHex(content);
+        append("Dumping data:").newLine();
+        dumpData(hexStr);
+        dump(content, useRawFormat);
     }
 
-    public void dump(ByteBuffer content) throws IOException {
-        Asn1Type value = Asn1.decode(content);
-        if (value == null) {
-            return;
-        }
+    public void dump(byte[] content,
+                     boolean useRawFormat) throws IOException {
+        dump(ByteBuffer.wrap(content), useRawFormat);
+    }
 
-        dumpType(0, value);
+    public void dump(ByteBuffer content,
+                     boolean useRawFormat) throws IOException {
+        if (useRawFormat) {
+            Asn1ParsingResult parsingResult = Asn1Parser.parse(content);
+            dumpParseResult(0, parsingResult);
+        } else {
+            Asn1Type value = Asn1.decode(content);
+            dumpType(0, value);
+        }
     }
 
     public void dumpType(Asn1Type value) {
@@ -69,7 +83,7 @@
             indent(indents).append("Null");
         } else if (value instanceof Asn1Simple) {
             indent(indents).append(value.toString());
-        }  else if (value instanceof Asn1ParsingItem) {
+        }  else if (value instanceof Asn1Item) {
             indent(indents).append(value.toString());
         } else if (value instanceof Asn1Dumpable) {
             Asn1Dumpable dumpable = (Asn1Dumpable) value;
@@ -83,6 +97,21 @@
         return this;
     }
 
+    public Asn1Dumper dumpParseResult(int indents, Asn1ParsingResult value) {
+        if (value == null) {
+            indent(indents).append("Null");
+        } else if (value instanceof Asn1Item) {
+            indent(indents).append(value.toString());
+        } else if (value instanceof Asn1Dumpable) {
+            Asn1Dumpable dumpable = (Asn1Dumpable) value;
+            dumpable.dumpWith(this, indents);
+        } else {
+            indent(indents).append("<Unknown>");
+        }
+
+        return this;
+    }
+
     public Asn1Dumper indent(int numSpaces) {
         for (int i = 0; i < numSpaces; i++) {
             builder.append(' ');
@@ -125,4 +154,17 @@
         builder.append("\n");
         return this;
     }
+
+    public Asn1Dumper dumpData(String hexData) {
+        int range = 100;
+        int pos = range;
+
+        while (pos < hexData.length()) {
+            System.out.println(hexData.substring(pos - range, pos));
+            pos = pos + range;
+        }
+        System.out.println(hexData.substring(pos - range, hexData.length()));
+
+        return this;
+    }
 }
diff --git a/kerby-asn1/src/main/java/org/apache/kerby/asn1/DecodingUtil.java b/kerby-asn1/src/main/java/org/apache/kerby/asn1/DecodingUtil.java
new file mode 100644
index 0000000..ef39540
--- /dev/null
+++ b/kerby-asn1/src/main/java/org/apache/kerby/asn1/DecodingUtil.java
@@ -0,0 +1,88 @@
+/**
+ *  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;
+
+import org.apache.kerby.asn1.parse.Asn1ParsingResult;
+import org.apache.kerby.asn1.type.Asn1Collection;
+import org.apache.kerby.asn1.type.Asn1Constructed;
+import org.apache.kerby.asn1.type.Asn1Object;
+import org.apache.kerby.asn1.type.Asn1Simple;
+import org.apache.kerby.asn1.type.Asn1Type;
+
+import java.io.IOException;
+
+public class DecodingUtil {
+
+    public static Asn1Type decodeValue(Asn1ParsingResult parsingResult) throws IOException {
+        if (Asn1Simple.isSimple(parsingResult.tag())) {
+            return DecodingUtil.decodeValueAsSimple(parsingResult);
+        } else if (Asn1Collection.isCollection(parsingResult.tag())) {
+            return DecodingUtil.decodeValueAsCollection(parsingResult);
+        } else if (!parsingResult.tag().isPrimitive()) {
+            Asn1Object tmpValue = new Asn1Constructed(parsingResult.tag());
+            tmpValue.decode(parsingResult);
+            return tmpValue;
+        } else {
+            throw new IOException("Unknow type of tag=" + parsingResult.tag());
+        }
+    }
+
+    public static Asn1Type decodeValueAsSimple(Asn1ParsingResult parsingResult) throws IOException {
+        Asn1Object value = (Asn1Object) Asn1Simple.createSimple(parsingResult.tagNo());
+        value.useDefinitiveLength(parsingResult.isDefinitiveLength());
+        decodeValueWith(parsingResult, value);
+        return value;
+    }
+
+    public static Asn1Type decodeValueAsCollection(Asn1ParsingResult parsingResult) throws IOException {
+        Asn1Collection value = Asn1Collection.createCollection(parsingResult.tag());
+        value.useDefinitiveLength(parsingResult.isDefinitiveLength());
+        value.setLazy(true);
+        decodeValueWith(parsingResult, value);
+        return value;
+    }
+
+    public static Asn1Type decodeValueAs(Asn1ParsingResult parsingResult,
+                                         Class<? extends Asn1Type> type) throws IOException {
+        Asn1Type value;
+        try {
+            value = type.newInstance();
+        } catch (Exception e) {
+            throw new RuntimeException("Invalid type: "
+                + type.getCanonicalName(), e);
+        }
+        decodeValueWith(parsingResult, value);
+        return value;
+    }
+
+    public static void decodeValueWith(Asn1ParsingResult parsingResult, Asn1Type value) throws IOException {
+        value.useDefinitiveLength(parsingResult.isDefinitiveLength());
+        ((Asn1Object) value).decode(parsingResult);
+    }
+
+    public static void decodeValueWith(Asn1ParsingResult parsingResult,
+                                       Asn1Type value, TaggingOption taggingOption) throws IOException {
+        if (!parsingResult.isTagSpecific()) {
+            throw new IllegalArgumentException(
+                "Attempting to decode non-tagged value using tagging way");
+        }
+        ((Asn1Object) value).taggedDecode(parsingResult, taggingOption);
+    }
+}
diff --git a/kerby-asn1/src/main/java/org/apache/kerby/asn1/parse/Asn1Container.java b/kerby-asn1/src/main/java/org/apache/kerby/asn1/parse/Asn1Container.java
new file mode 100644
index 0000000..1e796ec
--- /dev/null
+++ b/kerby-asn1/src/main/java/org/apache/kerby/asn1/parse/Asn1Container.java
@@ -0,0 +1,81 @@
+/**
+ *  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.parse;
+
+import org.apache.kerby.asn1.Asn1Dumpable;
+import org.apache.kerby.asn1.Asn1Dumper;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * ASN1 constructed types, mainly structured ones, but also some primitive ones.
+ */
+public class Asn1Container
+    extends Asn1ParsingResult implements Asn1Dumpable {
+
+    private List<Asn1ParsingResult> children = new ArrayList<>();
+
+    public Asn1Container(Asn1Header header) {
+        super(header);
+    }
+
+    public Asn1Container(Asn1ParsingResult parsingResult) {
+        super(parsingResult.header);
+    }
+
+    public List<Asn1ParsingResult> getChildren() {
+        return children;
+    }
+
+    public void addItem(Asn1ParsingResult value) {
+        children.add(value);
+    }
+
+    public void clear() {
+        children.clear();
+    }
+
+    @Override
+    public void dumpWith(Asn1Dumper dumper, int indents) {
+        dumper.indent(indents).append(toString()).newLine();
+
+        for (Asn1ParsingResult aObj : children) {
+            dumper.dumpParseResult(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=" + getHeaderLength() + "+" + getBodyLength()
+            + (isDefinitiveLength() ? "" : "(undefined)")
+            + "]";
+    }
+}
diff --git a/kerby-asn1/src/main/java/org/apache/kerby/asn1/Asn1Header.java b/kerby-asn1/src/main/java/org/apache/kerby/asn1/parse/Asn1Header.java
similarity index 90%
rename from kerby-asn1/src/main/java/org/apache/kerby/asn1/Asn1Header.java
rename to kerby-asn1/src/main/java/org/apache/kerby/asn1/parse/Asn1Header.java
index 1284039..bf3177b 100644
--- a/kerby-asn1/src/main/java/org/apache/kerby/asn1/Asn1Header.java
+++ b/kerby-asn1/src/main/java/org/apache/kerby/asn1/parse/Asn1Header.java
@@ -17,7 +17,9 @@
  *  under the License. 
  *  
  */
-package org.apache.kerby.asn1;
+package org.apache.kerby.asn1.parse;
+
+import org.apache.kerby.asn1.Tag;
 
 import java.nio.ByteBuffer;
 
@@ -91,10 +93,7 @@
         return length != -1;
     }
 
-    public byte[] readBodyBytes() {
-        ByteBuffer bodyBuffer = getBodyBuffer();
-        byte[] result = new byte[bodyBuffer.remaining()];
-        bodyBuffer.get(result);
-        return result;
+    public boolean checkBodyFinished(int pos) {
+        return getBodyEnd() != -1 && pos >= getBodyEnd();
     }
 }
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/parse/Asn1Item.java
similarity index 66%
rename from kerby-asn1/src/main/java/org/apache/kerby/asn1/type/Asn1ParsingItem.java
rename to kerby-asn1/src/main/java/org/apache/kerby/asn1/parse/Asn1Item.java
index e7cffa3..b1f0ef9 100644
--- a/kerby-asn1/src/main/java/org/apache/kerby/asn1/type/Asn1ParsingItem.java
+++ b/kerby-asn1/src/main/java/org/apache/kerby/asn1/parse/Asn1Item.java
@@ -17,21 +17,11 @@
  *  under the License. 
  *  
  */
-package org.apache.kerby.asn1.type;
+package org.apache.kerby.asn1.parse;
 
-import org.apache.kerby.asn1.Asn1Header;
+public class Asn1Item extends Asn1ParsingResult {
 
-/**
- * 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) {
+    public Asn1Item(Asn1Header header) {
         super(header);
     }
 
@@ -42,7 +32,7 @@
             : tag().tagClass().name().toLowerCase();
         return typeStr + " ["
             + "off=" + getOffset()
-            + ", len=" + encodingHeaderLength() + "+" + encodingBodyLength()
+            + ", len=" + getHeaderLength() + "+" + getBodyLength()
             + "] "
             + valueStr;
     }
diff --git a/kerby-asn1/src/main/java/org/apache/kerby/asn1/parse/Asn1Parser.java b/kerby-asn1/src/main/java/org/apache/kerby/asn1/parse/Asn1Parser.java
new file mode 100644
index 0000000..9c79768
--- /dev/null
+++ b/kerby-asn1/src/main/java/org/apache/kerby/asn1/parse/Asn1Parser.java
@@ -0,0 +1,88 @@
+/**
+ *  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.parse;
+
+import org.apache.kerby.asn1.Tag;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+
+/**
+ * ASN1 parser.
+ */
+public class Asn1Parser {
+
+    public static void parse(Asn1Container container) throws IOException {
+        parse(container.header, container);
+    }
+
+    public static void parse(Asn1Header header,
+                      Asn1Container parent) throws IOException {
+        Asn1Reader reader = new Asn1Reader(header.getBuffer());
+        int pos = header.getBodyStart();
+        while (true) {
+            reader.setPosition(pos);
+            Asn1ParsingResult asn1Obj = parse(reader);
+            if (asn1Obj == null) {
+                break;
+            }
+
+            if (parent != null) {
+                parent.addItem(asn1Obj);
+            }
+
+            pos += asn1Obj.getAllLength();
+            if (asn1Obj.isEOC()) {
+                break;
+            }
+
+            if (header.checkBodyFinished(pos)) {
+                break;
+            }
+        }
+
+        header.setBodyEnd(pos);
+    }
+
+    public static Asn1ParsingResult parse(ByteBuffer content) throws IOException {
+        Asn1Reader reader = new Asn1Reader(content);
+        return parse(reader);
+    }
+
+    public static Asn1ParsingResult parse(Asn1Reader reader) throws IOException {
+        if (!reader.available()) {
+            return null;
+        }
+
+        Asn1Header header = reader.readHeader();
+        Tag tmpTag = header.getTag();
+        Asn1ParsingResult parsingResult;
+
+        if (tmpTag.isPrimitive()) {
+            parsingResult = new Asn1Item(header);
+        } else {
+            Asn1Container container = new Asn1Container(header);
+            parse(container);
+            parsingResult = container;
+        }
+
+        return parsingResult;
+    }
+}
diff --git a/kerby-asn1/src/main/java/org/apache/kerby/asn1/parse/Asn1ParsingResult.java b/kerby-asn1/src/main/java/org/apache/kerby/asn1/parse/Asn1ParsingResult.java
new file mode 100644
index 0000000..7be817f
--- /dev/null
+++ b/kerby-asn1/src/main/java/org/apache/kerby/asn1/parse/Asn1ParsingResult.java
@@ -0,0 +1,100 @@
+/**
+ *  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.parse;
+
+import org.apache.kerby.asn1.Tag;
+import org.apache.kerby.asn1.util.Asn1Util;
+
+import java.nio.ByteBuffer;
+
+public abstract class Asn1ParsingResult {
+    protected Asn1Header header;
+
+    public Asn1ParsingResult(Asn1Header header) {
+        this.header = header;
+    }
+
+    public Tag tag() {
+        return header.getTag();
+    }
+
+    public int tagNo() {
+        return tag().tagNo();
+    }
+
+    public boolean isPrimitive() {
+        return tag().isPrimitive();
+    }
+
+    public boolean isUniversal() {
+        return tag().isUniversal();
+    }
+
+    public boolean isAppSpecific() {
+        return tag().isAppSpecific();
+    }
+
+    public boolean isContextSpecific() {
+        return tag().isContextSpecific();
+    }
+
+    public boolean isTagSpecific() {
+        return tag().isSpecific();
+    }
+
+    public boolean isEOC() {
+        return header.isEOC();
+    }
+
+    public Asn1Header getHeader() {
+        return header;
+    }
+
+    public boolean isDefinitiveLength() {
+        return header.isDefinitiveLength();
+    }
+
+    public int getAllLength() {
+        return getHeaderLength() + getBodyLength();
+    }
+
+    public int getHeaderLength() {
+        int bodyLen = getBodyLength();
+        int headerLen = Asn1Util.lengthOfTagLength(header.getTag().tagNo());
+        headerLen += (header.isDefinitiveLength()
+            ? Asn1Util.lengthOfBodyLength(bodyLen) : 1);
+        return headerLen;
+    }
+
+    public int getBodyLength() {
+        return header.getActualBodyLength();
+    }
+
+    protected int getOffset() {
+        return header.getBodyStart() - getHeaderLength();
+    }
+
+    public byte[] readBodyBytes() {
+        ByteBuffer bodyBuffer = header.getBodyBuffer();
+        byte[] result = new byte[bodyBuffer.remaining()];
+        bodyBuffer.get(result);
+        return result;
+    }
+}
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/parse/Asn1Reader.java
similarity index 82%
rename from kerby-asn1/src/main/java/org/apache/kerby/asn1/util/Asn1Reader.java
rename to kerby-asn1/src/main/java/org/apache/kerby/asn1/parse/Asn1Reader.java
index 2590a48..555d7a8 100644
--- a/kerby-asn1/src/main/java/org/apache/kerby/asn1/util/Asn1Reader.java
+++ b/kerby-asn1/src/main/java/org/apache/kerby/asn1/parse/Asn1Reader.java
@@ -6,34 +6,30 @@
  *  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.util;
+package org.apache.kerby.asn1.parse;
 
-import org.apache.kerby.asn1.Asn1Header;
 import org.apache.kerby.asn1.Tag;
 
 import java.io.IOException;
 import java.nio.ByteBuffer;
 
 /**
- * ASN1 reader from a byte buffer.
+ * ASN1 reader for positional reading.
  */
-public abstract class Asn1Reader {
-    protected ByteBuffer buffer;
-
-    public Asn1Reader(ByteBuffer buffer) {
-        this.buffer = buffer;
-    }
+public final class Asn1Reader {
+    private ByteBuffer buffer;
+    private int position;
 
     public ByteBuffer getBuffer() {
         return buffer;
@@ -48,15 +44,30 @@
         return header;
     }
 
-    public abstract void setPosition(int position);
+    public Asn1Reader(ByteBuffer buffer) {
+        this.buffer = buffer;
+        this.position = buffer.position();
+    }
 
-    public abstract int getPosition();
+    public int getPosition() {
+        return position;
+    }
 
-    public abstract boolean available();
+    public void setPosition(int position) {
+        this.position = position;
+    }
 
-    public abstract ByteBuffer getValueBuffer(int valueLength);
+    public boolean available() {
+        return position < buffer.limit();
+    }
 
-    protected abstract byte readByte() throws IOException;
+    public ByteBuffer getValueBuffer(int valueLength) {
+        return buffer;
+    }
+
+    protected byte readByte() throws IOException {
+        return buffer.get(position++);
+    }
 
     private Tag readTag() throws IOException {
         int tagFlags = readTagFlags();
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 7ad58f9..f63481f 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,8 +20,9 @@
 package org.apache.kerby.asn1.type;
 
 import org.apache.kerby.asn1.Asn1FieldInfo;
-import org.apache.kerby.asn1.Asn1Header;
+import org.apache.kerby.asn1.DecodingUtil;
 import org.apache.kerby.asn1.UniversalTag;
+import org.apache.kerby.asn1.parse.Asn1ParsingResult;
 
 import java.io.IOException;
 import java.nio.ByteBuffer;
@@ -31,7 +32,7 @@
  */
 public class Asn1Any extends AbstractAsn1Type<Asn1Type> {
     private Asn1FieldInfo fieldInfo;
-    private Asn1Item field;
+    private Asn1ParsingResult field;
 
     public Asn1Any() {
         super(UniversalTag.ANY);
@@ -42,7 +43,7 @@
         setValue(anyValue);
     }
 
-    public void setField(Asn1Item field) {
+    public void setField(Asn1ParsingResult field) {
         this.field = field;
     }
 
@@ -50,7 +51,7 @@
         this.fieldInfo = fieldInfo;
     }
 
-    public Asn1Type getField() {
+    public Asn1ParsingResult getField() {
         return field;
     }
 
@@ -60,8 +61,8 @@
     }
 
     @Override
-    protected void decodeBody(Asn1Header header) throws IOException {
-        ((Asn1Object) getValue()).decodeBody(header);
+    protected void decodeBody(Asn1ParsingResult parsingResult) throws IOException {
+        ((Asn1Object) getValue()).decodeBody(parsingResult);
     }
 
     @Override
@@ -84,10 +85,10 @@
 
         try {
             if (field.isContextSpecific()) {
-                field.decodeValueWith(result,
+                DecodingUtil.decodeValueWith(field, result,
                     fieldInfo.getTaggingOption());
             } else {
-                field.decodeValueWith(result);
+                DecodingUtil.decodeValueWith(field, result);
             }
         } catch (IOException e) {
             throw new RuntimeException("Fully decoding failed", e);
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 7c3d920..929b989 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,8 +19,8 @@
  */
 package org.apache.kerby.asn1.type;
 
-import org.apache.kerby.asn1.Asn1Header;
 import org.apache.kerby.asn1.UniversalTag;
+import org.apache.kerby.asn1.parse.Asn1ParsingResult;
 
 import java.io.IOException;
 
@@ -61,10 +61,10 @@
     }
 
     @Override
-    protected void decodeBody(Asn1Header header) throws IOException {
-        if (header.getLength() % 2 != 0) {
+    protected void decodeBody(Asn1ParsingResult parsingResult) throws IOException {
+        if (parsingResult.getBodyLength() % 2 != 0) {
             throw new IOException("Bad stream, BMP string expecting multiple of 2 bytes");
         }
-        super.decodeBody(header);
+        super.decodeBody(parsingResult);
     }
 }
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 61b8ef2..af2cc4e 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,8 +19,8 @@
  */
 package org.apache.kerby.asn1.type;
 
-import org.apache.kerby.asn1.Asn1Header;
 import org.apache.kerby.asn1.UniversalTag;
+import org.apache.kerby.asn1.parse.Asn1ParsingResult;
 
 import java.io.IOException;
 
@@ -55,11 +55,11 @@
     }
 
     @Override
-    protected void decodeBody(Asn1Header header) throws IOException {
-        if (header.getLength() != 1) {
+    protected void decodeBody(Asn1ParsingResult parsingResult) throws IOException {
+        if (parsingResult.getBodyLength() != 1) {
             throw new IOException("More than 1 byte found for Boolean");
         }
-        super.decodeBody(header);
+        super.decodeBody(parsingResult);
     }
 
     @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 dd6d873..260c426 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
@@ -19,12 +19,11 @@
  */
 package org.apache.kerby.asn1.type;
 
-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;
+import org.apache.kerby.asn1.parse.Asn1ParsingResult;
 
 import java.io.IOException;
 import java.nio.ByteBuffer;
@@ -75,6 +74,7 @@
 
     @Override
     public void decode(ByteBuffer content) throws IOException {
+        /*
         int foundPos = -1;
         Asn1Item item = (Asn1Item) Asn1.decode(content);
         for (int i = 0; i < fieldInfos.length; ++i) {
@@ -107,24 +107,22 @@
             }
         }
         fields[foundPos] = item.getValue();
+        */
     }
 
     @Override
-    protected void decodeBody(Asn1Header header) throws IOException {
+    protected void decodeBody(Asn1ParsingResult parsingResult) throws IOException {
 
     }
 
-    protected void decodeBody(ByteBuffer content) throws IOException {
-        // Not used
-    }
-
+    /*
     private void initField(int idx) {
         try {
             fields[idx] = fieldInfos[idx].getType().newInstance();
         } catch (Exception e) {
             throw new IllegalArgumentException("Bad field info specified at index of " + idx, e);
         }
-    }
+    }*/
 
     protected <T extends Asn1Type> T getFieldAs(EnumType index, Class<T> t) {
         Asn1Type value = fields[index.getValue()];
diff --git a/kerby-asn1/src/main/java/org/apache/kerby/asn1/type/Asn1Collection.java b/kerby-asn1/src/main/java/org/apache/kerby/asn1/type/Asn1Collection.java
index 9cbf89a..982c29e 100644
--- a/kerby-asn1/src/main/java/org/apache/kerby/asn1/type/Asn1Collection.java
+++ b/kerby-asn1/src/main/java/org/apache/kerby/asn1/type/Asn1Collection.java
@@ -23,7 +23,7 @@
 import org.apache.kerby.asn1.UniversalTag;
 
 /**
- * ASN1 complex type, may be better named.
+ * ASN1 complex type of multiple ASN1 objects.
  */
 public class Asn1Collection extends Asn1Constructed {
 
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 5e4bd70..08eb317 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
@@ -20,47 +20,36 @@
 package org.apache.kerby.asn1.type;
 
 import org.apache.kerby.asn1.Asn1Dumper;
-import org.apache.kerby.asn1.Asn1Header;
+import org.apache.kerby.asn1.DecodingUtil;
 import org.apache.kerby.asn1.UniversalTag;
+import org.apache.kerby.asn1.parse.Asn1ParsingResult;
 
 import java.io.IOException;
 import java.lang.reflect.ParameterizedType;
-import java.util.ArrayList;
 import java.util.List;
 
 public abstract class Asn1CollectionOf<T extends Asn1Type>
     extends Asn1Collection {
 
-    private List<T> elements = new ArrayList<>();
-
     public Asn1CollectionOf(UniversalTag universalTag) {
         super(universalTag);
     }
 
     @Override
-    protected void decodeBody(Asn1Header header) throws IOException {
-        super.decodeBody(header);
-
-        decodeElements();
-    }
-
-    private void decodeElements() throws IOException {
-        List<Asn1Type> items = getValue();
-        for (Asn1Type itemObj : items) {
-            if (itemObj instanceof Asn1Item) {
-                Asn1Item item = (Asn1Item) itemObj;
-                if (!item.isFullyDecoded()) {
-                    Asn1Type tmpValue = createElement();
-                    item.decodeValueWith(tmpValue);
-                }
-                itemObj = item.getValue();
+    protected void decodeElements() throws IOException {
+        for (Asn1ParsingResult parsingItem : getContainer().getChildren()) {
+            if (parsingItem.isEOC()) {
+                continue;
             }
-            elements.add((T) itemObj);
+
+            Asn1Type tmpValue = createElement();
+            DecodingUtil.decodeValueWith(parsingItem, tmpValue);
+            addItem(tmpValue);
         }
     }
 
     public List<T> getElements() {
-        return elements;
+        return (List<T>) getValue();
     }
 
     public void setElements(List<T> elements) {
@@ -79,7 +68,6 @@
 
     public void addElement(T element) {
         super.addItem(element);
-        this.elements.add(element);
     }
 
     private Class<T> getElementType() {
@@ -102,7 +90,7 @@
     public void dumpWith(Asn1Dumper dumper, int indents) {
         dumper.dumpTypeInfo(indents, getClass());
 
-        for (Asn1Type aObj : elements) {
+        for (Asn1Type aObj : getValue()) {
             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 81c00fc..2039b08 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,10 +22,12 @@
 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.DecodingUtil;
 import org.apache.kerby.asn1.EnumType;
 import org.apache.kerby.asn1.TaggingOption;
 import org.apache.kerby.asn1.UniversalTag;
+import org.apache.kerby.asn1.parse.Asn1Container;
+import org.apache.kerby.asn1.parse.Asn1ParsingResult;
 
 import java.io.IOException;
 import java.nio.ByteBuffer;
@@ -82,45 +84,47 @@
     }
 
     @Override
-    protected void decodeBody(Asn1Header header) throws IOException {
+    protected void decodeBody(Asn1ParsingResult parsingResult) throws IOException {
         checkAndInitFields();
 
-        Asn1Collection coll = createCollection();
-        coll.setLazy(true);
-        coll.decodeBody(header);
+        Asn1Container container = (Asn1Container) parsingResult;
+        List<Asn1ParsingResult> parsingResults = container.getChildren();
 
         int lastPos = -1, foundPos = -1;
-        List<Asn1Type> decodedItems = coll.getValue();
-        for (Asn1Type itemObj : decodedItems) {
+
+        for (Asn1ParsingResult parsingItem : parsingResults) {
+            if (parsingItem.isEOC()) {
+                continue;
+            }
+
             foundPos = -1;
-            Asn1Item item = (Asn1Item) itemObj;
             for (int i = lastPos + 1; i < fieldInfos.length; ++i) {
-                if (item.isContextSpecific()) {
-                    if (fieldInfos[i].getTagNo() == item.tagNo()) {
+                if (parsingItem.isContextSpecific()) {
+                    if (fieldInfos[i].getTagNo() == parsingItem.tagNo()) {
                         foundPos = i;
                         break;
                     }
-                } else if (fields[i].tag().equals(item.tag())) {
+                } else if (fields[i].tag().equals(parsingItem.tag())) {
                     foundPos = i;
                     break;
                 }
             }
             if (foundPos == -1) {
-                throw new IOException("Unexpected item with tag: " + item.tag());
+                throw new IOException("Unexpected item with tag: " + parsingItem.tag());
             }
             lastPos = foundPos;
 
             AbstractAsn1Type<?> fieldValue = (AbstractAsn1Type<?>) fields[foundPos];
             if (fieldValue instanceof Asn1Any) {
                 Asn1Any any = (Asn1Any) fieldValue;
-                any.setField(item);
+                any.setField(parsingItem);
                 any.setFieldInfo(fieldInfos[foundPos]);
             } else {
-                if (item.isContextSpecific()) {
-                    item.decodeValueWith(fieldValue,
+                if (parsingItem.isContextSpecific()) {
+                    DecodingUtil.decodeValueWith(parsingItem, fieldValue,
                         fieldInfos[foundPos].getTaggingOption());
                 } else {
-                    item.decodeValueWith(fieldValue);
+                    DecodingUtil.decodeValueWith(parsingItem, fieldValue);
                 }
             }
         }
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 dcb51fc..605b445 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
@@ -21,8 +21,10 @@
 
 import org.apache.kerby.asn1.Asn1Dumpable;
 import org.apache.kerby.asn1.Asn1Dumper;
-import org.apache.kerby.asn1.Asn1Header;
+import org.apache.kerby.asn1.DecodingUtil;
 import org.apache.kerby.asn1.Tag;
+import org.apache.kerby.asn1.parse.Asn1Container;
+import org.apache.kerby.asn1.parse.Asn1ParsingResult;
 
 import java.io.IOException;
 import java.nio.ByteBuffer;
@@ -35,6 +37,8 @@
 public class Asn1Constructed
     extends AbstractAsn1Type<List<Asn1Type>> implements Asn1Dumpable {
 
+    protected Asn1Container container;
+
     private boolean lazy = false;
 
     public Asn1Constructed(Tag tag) {
@@ -43,6 +47,11 @@
         usePrimitive(false);
     }
 
+
+    public Asn1Container getContainer() {
+        return container;
+    }
+
     public void setLazy(boolean lazy) {
         this.lazy = lazy;
     }
@@ -82,20 +91,23 @@
     }
 
     @Override
-    protected void decodeBody(Asn1Header header) throws IOException {
-        Asn1ParsingContainer container = new Asn1ParsingContainer(tag());
-        container.decode(header);
+    protected void decodeBody(Asn1ParsingResult parsingResult) throws IOException {
+        Asn1Container container = (Asn1Container) parsingResult;
+        this.container = container;
 
-        for (Asn1ParsingResult result : container.getParsingResults()) {
-            if (!result.isEOC()) {
-                Asn1Item item = new Asn1Item(result);
-                if (item.isSimple() && !isLazy()) {
-                    item.decodeValueAsSimple();
-                    addItem(item.getValue());
-                } else {
-                    addItem(item);
-                }
+        if (!isLazy()) {
+            decodeElements();
+        }
+    }
+
+    protected void decodeElements() throws IOException {
+        for (Asn1ParsingResult parsingItem : getContainer().getChildren()) {
+            if (parsingItem.isEOC()) {
+                continue;
             }
+
+            Asn1Type tmpValue = DecodingUtil.decodeValue(parsingItem);
+            addItem(tmpValue);
         }
     }
 
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 f8610ea..ec8c280 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,8 +19,8 @@
  */
 package org.apache.kerby.asn1.type;
 
-import org.apache.kerby.asn1.Asn1Header;
 import org.apache.kerby.asn1.UniversalTag;
+import org.apache.kerby.asn1.parse.Asn1ParsingResult;
 
 import java.io.IOException;
 
@@ -46,8 +46,8 @@
     }
 
     @Override
-    protected void decodeBody(Asn1Header header) throws IOException {
-        if (header.getLength() != 0) {
+    protected void decodeBody(Asn1ParsingResult parsingResult) throws IOException {
+        if (parsingResult.getBodyLength() != 0) {
             throw new IOException("Unexpected bytes found for EOC");
         }
     }
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
deleted file mode 100644
index b3994ff..0000000
--- a/kerby-asn1/src/main/java/org/apache/kerby/asn1/type/Asn1Item.java
+++ /dev/null
@@ -1,140 +0,0 @@
-/**
- *  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.Asn1Factory;
-import org.apache.kerby.asn1.Asn1Header;
-import org.apache.kerby.asn1.Tag;
-import org.apache.kerby.asn1.TaggingOption;
-
-import java.io.IOException;
-import java.nio.ByteBuffer;
-
-/**
- * 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, like implicit encoded value for
- * tagged value.
- *
- * For not fully decoded value, you tell your case using isSimple/isCollection/
- * 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 final Asn1ParsingResult parsingResult;
-
-    public Asn1Item(Asn1ParsingResult parsingResult) {
-        super(parsingResult.tag());
-        this.parsingResult = parsingResult;
-    }
-
-    public Asn1Item(Tag tag, Asn1ParsingResult parsingResult) {
-        super(tag);
-        this.parsingResult = parsingResult;
-    }
-
-    public Asn1ParsingResult getParsingResult() {
-        return parsingResult;
-    }
-
-    @Override
-    protected int encodingBodyLength() {
-        if (getValue() != null) {
-            return ((AbstractAsn1Type<?>) getValue()).encodingBodyLength();
-        }
-        return parsingResult.encodingBodyLength();
-    }
-
-    @Override
-    protected void encodeBody(ByteBuffer buffer) {
-        if (getValue() != null) {
-            ((Asn1Object) getValue()).encodeBody(buffer);
-        }
-    }
-
-    @Override
-    protected void decodeBody(Asn1Header header) throws IOException {
-        this.parsingResult.decodeBody(header);
-    }
-
-    public boolean isFullyDecoded() {
-        return getValue() != null;
-    }
-
-    public void decodeValueAsSimple() throws IOException {
-        if (getValue() != null) {
-            return;
-        }
-        if (!isSimple()) {
-            throw new IllegalArgumentException(
-                "Attempting to decode non-simple value as simple");
-        }
-
-        Asn1Object value = (Asn1Object) Asn1Factory.create(tagNo());
-        value.useDefinitiveLength(isDefinitiveLength());
-        decodeValueWith(value);
-    }
-
-    public void decodeValueAsCollection() throws IOException {
-        if (getValue() != null) {
-            return;
-        }
-        if (!isCollection()) {
-            throw new IllegalArgumentException(
-                "Attempting to decode non-collection value as collection");
-        }
-
-        Asn1Type value = Asn1Factory.create(tagNo());
-        value.useDefinitiveLength(isDefinitiveLength());
-        decodeValueWith(value);
-    }
-
-    public void decodeValueAs(Class<? extends Asn1Type> type) throws IOException {
-        Asn1Type value;
-        try {
-            value = type.newInstance();
-        } catch (Exception e) {
-            throw new RuntimeException("Invalid type: "
-                + type.getCanonicalName(), e);
-        }
-        decodeValueWith(value);
-    }
-
-    public void decodeValueWith(Asn1Type value) throws IOException {
-        setValue(value);
-        value.useDefinitiveLength(isDefinitiveLength());
-        ((Asn1Object) value).decode(parsingResult.getHeader());
-    }
-
-    public void decodeValueWith(Asn1Type value, TaggingOption taggingOption) throws IOException {
-        if (!isTagSpecific()) {
-            throw new IllegalArgumentException(
-                "Attempting to decode non-tagged value using tagging way");
-        }
-        ((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 6affb95..87a80b8 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,8 +19,8 @@
  */
 package org.apache.kerby.asn1.type;
 
-import org.apache.kerby.asn1.Asn1Header;
 import org.apache.kerby.asn1.UniversalTag;
+import org.apache.kerby.asn1.parse.Asn1ParsingResult;
 
 import java.io.IOException;
 
@@ -46,8 +46,8 @@
     }
 
     @Override
-    protected void decodeBody(Asn1Header header) throws IOException {
-        if (header.getLength() != 0) {
+    protected void decodeBody(Asn1ParsingResult parsingResult) throws IOException {
+        if (parsingResult.getHeader().getLength() != 0) {
             throw new IOException("Unexpected bytes found for 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 6b4a20c..5bb9854 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
@@ -19,12 +19,12 @@
  */
 package org.apache.kerby.asn1.type;
 
-import org.apache.kerby.asn1.Asn1Header;
 import org.apache.kerby.asn1.Tag;
 import org.apache.kerby.asn1.TaggingOption;
 import org.apache.kerby.asn1.UniversalTag;
-import org.apache.kerby.asn1.util.Asn1Reader;
-import org.apache.kerby.asn1.util.Asn1Reader2;
+import org.apache.kerby.asn1.parse.Asn1Container;
+import org.apache.kerby.asn1.parse.Asn1Parser;
+import org.apache.kerby.asn1.parse.Asn1ParsingResult;
 import org.apache.kerby.asn1.util.Asn1Util;
 
 import java.io.IOException;
@@ -199,7 +199,7 @@
         return tag.isSpecific();
     }
 
-    protected boolean isEOC() {
+    public boolean isEOC() {
         return tag().isEOC();
     }
 
@@ -215,23 +215,20 @@
 
     @Override
     public void decode(ByteBuffer content) throws IOException {
-        Asn1Reader reader = new Asn1Reader2(content);
-        Asn1Header header = reader.readHeader();
-
-        useDefinitiveLength(header.isDefinitiveLength());
-        decode(header);
+        Asn1ParsingResult parsingResult = Asn1Parser.parse(content);
+        decode(parsingResult);
     }
 
-    public void decode(Asn1Header header) throws IOException {
-        if (!tag().equals(header.getTag())) {
-            throw new IOException("Unexpected tag " + header.getTag()
+    public void decode(Asn1ParsingResult parsingResult) throws IOException {
+        if (!tag().equals(parsingResult.tag())) {
+            throw new IOException("Unexpected tag " + parsingResult.tag()
                 + ", expecting " + tag());
         }
 
-        decodeBody(header);
+        decodeBody(parsingResult);
     }
 
-    protected abstract void decodeBody(Asn1Header header) throws IOException;
+    protected abstract void decodeBody(Asn1ParsingResult parsingResult) throws IOException;
 
     protected int taggedEncodingLength(TaggingOption taggingOption) {
         int taggingTagNo = taggingOption.getTagNo();
@@ -242,8 +239,10 @@
         return taggingEncodingLen;
     }
 
+    @Override
     public byte[] taggedEncode(TaggingOption taggingOption) {
-        ByteBuffer byteBuffer = ByteBuffer.allocate(taggedEncodingLength(taggingOption));
+        int len = taggedEncodingLength(taggingOption);
+        ByteBuffer byteBuffer = ByteBuffer.allocate(len);
         taggedEncode(byteBuffer, taggingOption);
         byteBuffer.flip();
         return byteBuffer.array();
@@ -263,6 +262,7 @@
         }
     }
 
+    @Override
     public void taggedDecode(byte[] content,
                              TaggingOption taggingOption) throws IOException {
         taggedDecode(ByteBuffer.wrap(content), taggingOption);
@@ -271,25 +271,24 @@
     @Override
     public void taggedDecode(ByteBuffer content,
                              TaggingOption taggingOption) throws IOException {
-        Asn1Reader reader = new Asn1Reader2(content);
-        Asn1Header header = reader.readHeader();
-
-        useDefinitiveLength(header.isDefinitiveLength());
-        taggedDecode(header, taggingOption);
+        Asn1ParsingResult parsingResult = Asn1Parser.parse(content);
+        taggedDecode(parsingResult, taggingOption);
     }
 
-    protected void taggedDecode(Asn1Header header,
+    public void taggedDecode(Asn1ParsingResult parsingResult,
                                 TaggingOption taggingOption) throws IOException {
         Tag expectedTaggingTagFlags = taggingOption.getTag(!isPrimitive());
-        if (!expectedTaggingTagFlags.equals(header.getTag())) {
-            throw new IOException("Unexpected tag " + header.getTag()
+        if (!expectedTaggingTagFlags.equals(parsingResult.tag())) {
+            throw new IOException("Unexpected tag " + parsingResult.tag()
                     + ", expecting " + expectedTaggingTagFlags);
         }
 
         if (taggingOption.isImplicit()) {
-            decodeBody(header);
+            decodeBody(parsingResult);
         } else {
-            decode(header.getBodyBuffer());
+            Asn1Container container = (Asn1Container) parsingResult;
+            Asn1ParsingResult body = container.getChildren().get(0);
+            decode(body);
         }
     }
 }
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 88c4f46..3b91459 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,8 +19,8 @@
  */
 package org.apache.kerby.asn1.type;
 
-import org.apache.kerby.asn1.Asn1Header;
 import org.apache.kerby.asn1.UniversalTag;
+import org.apache.kerby.asn1.parse.Asn1ParsingResult;
 
 import java.io.IOException;
 
@@ -44,8 +44,8 @@
     }
 
     @Override
-    protected void decodeBody(Asn1Header header) throws IOException {
-        setValue(header.readBodyBytes());
+    protected void decodeBody(Asn1ParsingResult parsingResult) throws IOException {
+        setValue(parsingResult.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
deleted file mode 100644
index 3bd5a7f..0000000
--- a/kerby-asn1/src/main/java/org/apache/kerby/asn1/type/Asn1ParsingContainer.java
+++ /dev/null
@@ -1,165 +0,0 @@
-/**
- *  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/Asn1ParsingResult.java b/kerby-asn1/src/main/java/org/apache/kerby/asn1/type/Asn1ParsingResult.java
deleted file mode 100644
index 004fd02..0000000
--- a/kerby-asn1/src/main/java/org/apache/kerby/asn1/type/Asn1ParsingResult.java
+++ /dev/null
@@ -1,84 +0,0 @@
-/**
- *  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 6d08f70..1472bc6 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,9 +19,9 @@
  */
 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.parse.Asn1ParsingResult;
 import org.apache.kerby.asn1.util.Asn1Util;
 
 import java.io.IOException;
@@ -93,8 +93,8 @@
     }
 
     @Override
-    protected void decodeBody(Asn1Header header) throws IOException {
-        byte[] leftBytes = header.readBodyBytes();
+    protected void decodeBody(Asn1ParsingResult parsingResult) throws IOException {
+        byte[] leftBytes = parsingResult.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 8a8ee3b..4a5bddd 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,8 +21,9 @@
 
 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.parse.Asn1Container;
+import org.apache.kerby.asn1.parse.Asn1ParsingResult;
 
 import java.io.IOException;
 import java.lang.reflect.ParameterizedType;
@@ -81,19 +82,21 @@
     }
 
     @Override
-    protected void decodeBody(Asn1Header header) throws IOException {
+    protected void decodeBody(Asn1ParsingResult parsingResult) throws IOException {
         Asn1Object value = (Asn1Object) getValue();
         if (isImplicit()) {
-            value.decodeBody(header);
+            value.decodeBody(parsingResult);
         } else {
-            value.decode(header.getBodyBuffer());
+            Asn1Container container = (Asn1Container) parsingResult;
+            Asn1ParsingResult body = container.getChildren().get(0);
+            value.decode(body);
         }
     }
 
     private void initValue() {
         Class<? extends Asn1Type> valueType = (Class<T>) ((ParameterizedType)
                 getClass().getGenericSuperclass()).getActualTypeArguments()[0];
-        AbstractAsn1Type<?> value = null;
+        AbstractAsn1Type<?> value;
         try {
             value = (AbstractAsn1Type<?>) valueType.newInstance();
         } catch (Exception e) {
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 4d3cbe2..27e0898 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,9 +22,9 @@
 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;
+import org.apache.kerby.asn1.parse.Asn1ParsingResult;
 
 import java.io.IOException;
 import java.nio.ByteBuffer;
@@ -141,8 +141,8 @@
     }
 
     @Override
-    protected void decodeBody(Asn1Header header) throws IOException {
-        tagging.decodeBody(header);
+    protected void decodeBody(Asn1ParsingResult parsingResult) throws IOException {
+        tagging.decodeBody(parsingResult);
     }
 
     protected <T extends Asn1Type> T getFieldAs(EnumType index, Class<T> t) {
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
deleted file mode 100644
index 503507f..0000000
--- a/kerby-asn1/src/main/java/org/apache/kerby/asn1/util/Asn1Reader1.java
+++ /dev/null
@@ -1,62 +0,0 @@
-/**
- *  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.util;
-
-import java.io.IOException;
-import java.nio.ByteBuffer;
-
-/**
- * ASN1 reader for stateful reading.
- */
-public final class Asn1Reader1 extends Asn1Reader {
-
-    public Asn1Reader1(ByteBuffer buffer) {
-        super(buffer);
-    }
-
-    @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
-    public ByteBuffer getValueBuffer(int valueLength) {
-        ByteBuffer result = buffer.duplicate();
-        result.limit(buffer.position() + valueLength);
-        buffer.position(buffer.position() + valueLength);
-
-        return result;
-    }
-}
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
deleted file mode 100644
index 0136db4..0000000
--- a/kerby-asn1/src/main/java/org/apache/kerby/asn1/util/Asn1Reader2.java
+++ /dev/null
@@ -1,65 +0,0 @@
-/**
- *  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.util;
-
-import java.io.IOException;
-import java.nio.ByteBuffer;
-
-/**
- * ASN1 reader for positional reading.
- */
-public final class Asn1Reader2 extends Asn1Reader {
-    private int position;
-
-    public Asn1Reader2(ByteBuffer buffer, int position) {
-        super(buffer);
-        this.position = position;
-    }
-
-    public Asn1Reader2(ByteBuffer buffer) {
-        super(buffer);
-        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
-    public ByteBuffer getValueBuffer(int valueLength) {
-        return buffer;
-    }
-
-    @Override
-    protected byte readByte() throws IOException {
-        return buffer.get(position++);
-    }
-}
diff --git a/kerby-asn1/src/test/java/org/apache/kerby/asn1/TestAsn1ConstructedOctetString.java b/kerby-asn1/src/test/java/org/apache/kerby/asn1/TestAsn1ConstructedOctetString.java
new file mode 100644
index 0000000..3d66cb8
--- /dev/null
+++ b/kerby-asn1/src/test/java/org/apache/kerby/asn1/TestAsn1ConstructedOctetString.java
@@ -0,0 +1,35 @@
+/**
+ *  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;
+
+import org.apache.kerby.asn1.type.Asn1OctetString;
+
+import java.io.IOException;
+
+public class TestAsn1ConstructedOctetString {
+
+    //@Test
+    public void testDecoding() throws IOException {
+        byte[] data = TestUtil.readBytesFromTxtFile("/constructed-octet-string.txt");
+        Asn1OctetString octetString = new Asn1OctetString();
+        octetString.decode(data);
+        Asn1.dump(octetString);
+    }
+}
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 d564e1b..bbaed2a 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,12 +19,10 @@
  */
 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 {
 
@@ -35,7 +33,8 @@
             Asn1.dump(pr);
 
             byte[] data = TestData.createSammplePersonnelEncodingData();
-            Asn1.dump(data);
+            Asn1.dump(data, true);
+            //Asn1.dump(data, false);
         } catch (Exception e) {
             e.printStackTrace();
             Assert.fail();
@@ -44,9 +43,10 @@
 
     @Test
     public void testDump1WithCompressedData() throws IOException {
-        String hexStr = readTxtFile("/compressed-data.txt");
+        String hexStr = TestUtil.readStringFromTxtFile("/compressed-data.txt");
         try {
-            Asn1.dump(hexStr);
+            Asn1.dump(hexStr, true);
+            //Asn1.dump(hexStr, false);
         } catch (Exception e) {
             e.printStackTrace();
             Assert.fail();
@@ -55,9 +55,10 @@
 
     @Test
     public void testDump1WithSignedData() throws IOException {
-        String hexStr = readTxtFile("/signed-data.txt");
+        String hexStr = TestUtil.readStringFromTxtFile("/signed-data.txt");
         try {
-            Asn1.dump(hexStr);
+            Asn1.dump(hexStr, true);
+            //Asn1.dump(hexStr, false);
         } catch (Exception e) {
             e.printStackTrace();
             Assert.fail();
@@ -66,22 +67,13 @@
 
     @Test
     public void testDump1WithDerData() throws IOException {
-        byte[] data = readBinFile("/der-data.dat");
+        byte[] data = TestUtil.readBytesFromBinFile("/der-data.dat");
         try {
-            Asn1.dump(data);
+            Asn1.dump(data, true);
+            //Asn1.dump(data, false);
         } 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 c7f4fde..a8948d4 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
@@ -76,8 +76,8 @@
     public void testDecoding() throws IOException {
         PersonnelRecord expected = TestData.createSamplePersonnel();
         byte[] data = TestData.createSammplePersonnelEncodingData();
+        Asn1.dump(data, true);
         PersonnelRecord decoded = new PersonnelRecord();
-        Asn1.dump(data);
         decoded.decode(data);
         Asn1.dump(decoded);
 
diff --git a/kerby-asn1/src/test/java/org/apache/kerby/asn1/TestUtil.java b/kerby-asn1/src/test/java/org/apache/kerby/asn1/TestUtil.java
new file mode 100644
index 0000000..619af7b
--- /dev/null
+++ b/kerby-asn1/src/test/java/org/apache/kerby/asn1/TestUtil.java
@@ -0,0 +1,47 @@
+/**
+ *  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;
+
+import org.apache.kerby.asn1.util.HexUtil;
+import org.apache.kerby.asn1.util.IOUtil;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+public final class TestUtil {
+    private TestUtil() {
+
+    }
+
+    static byte[] readBytesFromTxtFile(String resource) throws IOException {
+        String hexStr = readStringFromTxtFile(resource);
+        return HexUtil.hex2bytes(hexStr);
+    }
+
+    static String readStringFromTxtFile(String resource) throws IOException {
+        InputStream is = TestUtil.class.getResourceAsStream(resource);
+        return IOUtil.readInput(is);
+    }
+
+    static byte[] readBytesFromBinFile(String resource) throws IOException {
+        InputStream is = TestUtil.class.getResourceAsStream(resource);
+        return IOUtil.readInputStream(is);
+    }
+}
diff --git a/kerby-asn1/src/test/resources/constructed-octet-string.txt b/kerby-asn1/src/test/resources/constructed-octet-string.txt
new file mode 100644
index 0000000..66f28f1
--- /dev/null
+++ b/kerby-asn1/src/test/resources/constructed-octet-string.txt
@@ -0,0 +1 @@
+24800482021E789CED945D6FDA301486EF2BF93FE4FA9560F15718AD76618C9BA15140492A75BD0B591A22D1002644E3DFD71E8516ED2FF4B573FC11FBB17D4E62BD69DAB2697BD9715BDE06F976BBAE8BBCAD37CD37339EF49E28BB0B9AFCB5FC11DBCD61CBFA7F292337FA3CC7E6CDFEA5B43DD3149B3F7553DD06CBBAC9EDF163C8B8DE6F37FBDA036F83BA59D74D7917BCD4EBF23F28B999A40A6188FD45BE652FC2F333F46F3D9DCF4CF02EDFB550493633C9A56B18D1301C808594E3D111988384EFA2089101244EB19823A5570909BD4A57A43341E0C9D57928439266F82E4F6CE1D9646462BFE39101CB2138678271D149C9F805C5DB427447263AB6E29DCFFE041333453844629C25FA3101A5C8921964242344D29511260FF78E1DBD834862EE1129BF88A505B75CB85C704ED9AE68AB420A24A58B4BD91465303BBC2E4B4B16268152F8E92216C487D605077A01DAE78CB987F78513BC6C5589B2EA442B48A69E20DAAA5DB6A2B52E239EA25A552B9C154393FBF908D9025AE1D1C78F613C7F80D66E07D5619DDB60BA397D4FFB605BDA202BEDEB9E6837616CA09D3B85E460D2F90FE32152E709C61915CE8A93FF9CF34C0AC198F075922A0D85911FAF0CB814125CB8176178F6DC201A60E04D24C9624E5D17EDE5790E7A0A20548A61D8F7711A43FDC2BF2564C72517BBE8C5179DF056F26EC7779CE1438AE2D8DAD517F58BFA4575FF70064A5277D9B14F77606C403F2E4B3231EA539B923773F797340000
\ No newline at end of file
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 43d4540..4693d05 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
@@ -20,8 +20,8 @@
 package org.apache.kerby.kerberos.kerb;
 
 import org.apache.kerby.asn1.Asn1;
-import org.apache.kerby.asn1.Asn1Header;
 import org.apache.kerby.asn1.Tag;
+import org.apache.kerby.asn1.parse.Asn1ParsingResult;
 import org.apache.kerby.asn1.type.Asn1Type;
 import org.apache.kerby.kerberos.kerb.type.ap.ApReq;
 import org.apache.kerby.kerberos.kerb.type.base.KrbError;
@@ -65,8 +65,8 @@
     }
 
     public static KrbMessage decodeMessage(ByteBuffer buffer) throws IOException {
-        Asn1Header header = Asn1.decodeHeader(buffer);
-        Tag tag = header.getTag();
+        Asn1ParsingResult parsingResult = Asn1.parse(buffer);
+        Tag tag = parsingResult.tag();
 
         KrbMessage msg;
         KrbMessageType msgType = KrbMessageType.fromValue(tag.tagNo());
@@ -88,7 +88,7 @@
             throw new IOException("To be supported krb message type with tag: " + tag);
         }
 
-        msg.decode(header);
+        msg.decode(parsingResult);
         return msg;
     }
 
diff --git a/kerby-pkix/src/main/java/org/apache/kerby/x509/type/GeneralName.java b/kerby-pkix/src/main/java/org/apache/kerby/x509/type/GeneralName.java
index 65c1143..6bae5f4 100644
--- a/kerby-pkix/src/main/java/org/apache/kerby/x509/type/GeneralName.java
+++ b/kerby-pkix/src/main/java/org/apache/kerby/x509/type/GeneralName.java
@@ -19,15 +19,16 @@
  */
 package org.apache.kerby.x509.type;
 
-import org.apache.kerby.asn1.EnumType;
-import org.apache.kerby.asn1.type.Asn1Choice;
 import org.apache.kerby.asn1.Asn1FieldInfo;
+import org.apache.kerby.asn1.EnumType;
+import org.apache.kerby.asn1.ExplicitField;
+import org.apache.kerby.asn1.type.Asn1Any;
+import org.apache.kerby.asn1.type.Asn1Choice;
 import org.apache.kerby.asn1.type.Asn1IA5String;
-import org.apache.kerby.asn1.type.Asn1Item;
 import org.apache.kerby.asn1.type.Asn1ObjectIdentifier;
 import org.apache.kerby.asn1.type.Asn1OctetString;
-import org.apache.kerby.asn1.ExplicitField;
 import org.apache.kerby.x500.type.Name;
+
 import static org.apache.kerby.x509.type.GeneralName.MyEnum.*;
 
 /**
@@ -74,7 +75,7 @@
         new ExplicitField(RFC822_NAME, Asn1IA5String.class),
         new ExplicitField(DNS_NAME, Asn1IA5String.class),
         // ORAddress is to be defined.
-        new ExplicitField(X400_ADDRESS, Asn1Item.class),
+        new ExplicitField(X400_ADDRESS, Asn1Any.class),
         new ExplicitField(DIRECTORY_NAME, Name.class),
         new ExplicitField(EDI_PARTY_NAME, EDIPartyName.class),
         new ExplicitField(UNIFORM_RESOURCE_IDENTIFIER, Asn1IA5String.class),
@@ -110,11 +111,11 @@
         setFieldAs(DNS_NAME, dnsName);
     }
 
-    public Asn1Item getX400Address() {
-        return getFieldAs(X400_ADDRESS, Asn1Item.class);
+    public Asn1Any getX400Address() {
+        return getFieldAs(X400_ADDRESS, Asn1Any.class);
     }
 
-    public void setX400Address(Asn1Item x400Address) {
+    public void setX400Address(Asn1Any x400Address) {
         setFieldAs(X400_ADDRESS, x400Address);
     }
 
diff --git a/kerby-pkix/src/test/java/org/apache/kerby/cms/TestCompressedData.java b/kerby-pkix/src/test/java/org/apache/kerby/cms/TestCompressedData.java
index a662182..5331380 100644
--- a/kerby-pkix/src/test/java/org/apache/kerby/cms/TestCompressedData.java
+++ b/kerby-pkix/src/test/java/org/apache/kerby/cms/TestCompressedData.java
@@ -32,7 +32,7 @@
     public void testDump1WithCompressedData() throws IOException {
         byte[] data = readDataFile("/compressed-data.txt");
         try {
-            Asn1.dump(data);
+            Asn1.dump(data, true);
 
             ContentInfo contentInfo = new ContentInfo();
             contentInfo.decode(data);
diff --git a/kerby-pkix/src/test/java/org/apache/kerby/cms/TestSignedData.java b/kerby-pkix/src/test/java/org/apache/kerby/cms/TestSignedData.java
index 92b7d13..d4e1a75 100644
--- a/kerby-pkix/src/test/java/org/apache/kerby/cms/TestSignedData.java
+++ b/kerby-pkix/src/test/java/org/apache/kerby/cms/TestSignedData.java
@@ -32,7 +32,7 @@
     public void testDump1WithSignedData() throws IOException {
         byte[] data = readDataFile("/signed-data.txt");
         try {
-            Asn1.dump(data);
+            Asn1.dump(data, true);
 
             ContentInfo contentInfo = new ContentInfo();
             contentInfo.decode(data);