Merge branch 'master' of https://git-wip-us.apache.org/repos/asf/directory-kerby
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
index 456d6ea..a0e27ca 100644
--- 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
@@ -22,6 +22,7 @@
 import org.apache.kerby.asn1.Asn1Dumpable;
 import org.apache.kerby.asn1.Asn1Dumper;
 
+import java.nio.ByteBuffer;
 import java.util.ArrayList;
 import java.util.List;
 
@@ -33,12 +34,8 @@
 
     private List<Asn1ParseResult> children = new ArrayList<>();
 
-    public Asn1Container(Asn1Header header) {
-        super(header);
-    }
-
-    public Asn1Container(Asn1ParseResult parseResult) {
-        super(parseResult.header);
+    public Asn1Container(Asn1Header header, int bodyStart, ByteBuffer buffer) {
+        super(header, bodyStart, buffer);
     }
 
     public List<Asn1ParseResult> getChildren() {
diff --git a/kerby-asn1/src/main/java/org/apache/kerby/asn1/parse/Asn1DerivedItem.java b/kerby-asn1/src/main/java/org/apache/kerby/asn1/parse/Asn1DerivedItem.java
new file mode 100644
index 0000000..41db80e
--- /dev/null
+++ b/kerby-asn1/src/main/java/org/apache/kerby/asn1/parse/Asn1DerivedItem.java
@@ -0,0 +1,86 @@
+/**
+ *  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.nio.ByteBuffer;
+
+/**
+ * Combine multiple parts in a container into a single item, for primitive types
+ * that use constructed encoding.
+ */
+public class Asn1DerivedItem extends Asn1Item {
+
+    private final Asn1Container container;
+    private final Tag newTag;
+    private int newBodyLength;
+    private ByteBuffer newBodyBuffer;
+
+    public Asn1DerivedItem(Tag newTag, Asn1Container container) {
+        super(container.getHeader(), container.getBodyStart(),
+            container.getBuffer());
+
+        this.newTag = newTag;
+        this.container = container;
+        this.newBodyLength = -1;
+    }
+
+    @Override
+    public Tag tag() {
+        return newTag;
+    }
+
+    private int computeBodyLength() {
+        int totalLen = 0;
+        for (Asn1ParseResult parseItem : container.getChildren()) {
+            totalLen += parseItem.getBodyLength();
+        }
+
+        return totalLen;
+    }
+
+    private ByteBuffer makeBodyBuffer() {
+        ByteBuffer tmpBuffer = ByteBuffer.allocate(getBodyLength());
+        for (Asn1ParseResult parseItem : container.getChildren()) {
+            tmpBuffer.put(parseItem.getBodyBuffer());
+        }
+        tmpBuffer.flip();
+
+        return tmpBuffer;
+    }
+
+    @Override
+    public ByteBuffer getBodyBuffer() {
+        if (newBodyBuffer == null) {
+            newBodyBuffer = makeBodyBuffer();
+        }
+        return newBodyBuffer;
+    }
+
+    @Override
+    public int getBodyLength() {
+        if (newBodyLength == -1) {
+            newBodyLength = computeBodyLength();
+        }
+
+        return newBodyLength;
+    }
+}
diff --git a/kerby-asn1/src/main/java/org/apache/kerby/asn1/parse/Asn1Header.java b/kerby-asn1/src/main/java/org/apache/kerby/asn1/parse/Asn1Header.java
index bf3177b..8d90ac1 100644
--- a/kerby-asn1/src/main/java/org/apache/kerby/asn1/parse/Asn1Header.java
+++ b/kerby-asn1/src/main/java/org/apache/kerby/asn1/parse/Asn1Header.java
@@ -21,70 +21,23 @@
 
 import org.apache.kerby.asn1.Tag;
 
-import java.nio.ByteBuffer;
-
 public class Asn1Header {
     private Tag tag;
     private int length;
-    private int bodyStart;
-    private int bodyEnd;
-    private ByteBuffer buffer;
 
-    public Asn1Header(Tag tag, int length,
-                      int bodyStart, ByteBuffer buffer) {
+    public Asn1Header(Tag tag, int length) {
         this.tag = tag;
         this.length = length;
-        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 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() {
         return length == 0 && tag.isEOC();
     }
@@ -92,8 +45,4 @@
     public boolean isDefinitiveLength() {
         return length != -1;
     }
-
-    public boolean checkBodyFinished(int pos) {
-        return getBodyEnd() != -1 && pos >= getBodyEnd();
-    }
 }
diff --git a/kerby-asn1/src/main/java/org/apache/kerby/asn1/parse/Asn1Item.java b/kerby-asn1/src/main/java/org/apache/kerby/asn1/parse/Asn1Item.java
index 2e2722d..7548629 100644
--- a/kerby-asn1/src/main/java/org/apache/kerby/asn1/parse/Asn1Item.java
+++ b/kerby-asn1/src/main/java/org/apache/kerby/asn1/parse/Asn1Item.java
@@ -19,10 +19,19 @@
  */
 package org.apache.kerby.asn1.parse;
 
+import java.nio.ByteBuffer;
+
 public class Asn1Item extends Asn1ParseResult {
 
-    public Asn1Item(Asn1Header header) {
-        super(header);
+    public Asn1Item(Asn1Header header, int bodyStart, ByteBuffer buffer) {
+        super(header, bodyStart, buffer);
+    }
+
+    public byte[] readBodyBytes() {
+        ByteBuffer bodyBuffer = getBodyBuffer();
+        byte[] result = new byte[bodyBuffer.remaining()];
+        bodyBuffer.get(result);
+        return result;
     }
 
     @Override
diff --git a/kerby-asn1/src/main/java/org/apache/kerby/asn1/parse/Asn1ParseResult.java b/kerby-asn1/src/main/java/org/apache/kerby/asn1/parse/Asn1ParseResult.java
index be549ff..6aebeae 100644
--- a/kerby-asn1/src/main/java/org/apache/kerby/asn1/parse/Asn1ParseResult.java
+++ b/kerby-asn1/src/main/java/org/apache/kerby/asn1/parse/Asn1ParseResult.java
@@ -25,22 +25,58 @@
 import java.nio.ByteBuffer;
 
 public abstract class Asn1ParseResult extends Asn1Object {
-    protected Asn1Header header;
+    private Asn1Header header;
+    private int bodyStart;
+    private int bodyEnd;
+    private ByteBuffer buffer;
 
-    public Asn1ParseResult(Asn1Header header) {
+    public Asn1ParseResult(Asn1Header header,
+                           int bodyStart, ByteBuffer buffer) {
         super(header.getTag());
         this.header = header;
+        this.bodyStart = bodyStart;
+        this.buffer = buffer;
+
+        this.bodyEnd = isDefinitiveLength() ? bodyStart + header.getLength() : -1;
     }
 
     public Asn1Header getHeader() {
         return header;
     }
 
+    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 isDefinitiveLength() {
         return header.isDefinitiveLength();
     }
 
-    public int getAllLength() {
+    public int getEncodingLength() {
         return getHeaderLength() + getBodyLength();
     }
 
@@ -52,18 +88,21 @@
         return headerLen;
     }
 
-    public int getBodyLength() {
-        return header.getActualBodyLength();
-    }
-
     protected int getOffset() {
-        return header.getBodyStart() - getHeaderLength();
+        return getBodyStart() - getHeaderLength();
     }
 
-    public byte[] readBodyBytes() {
-        ByteBuffer bodyBuffer = header.getBodyBuffer();
-        byte[] result = new byte[bodyBuffer.remaining()];
-        bodyBuffer.get(result);
-        return result;
+    public int getBodyLength() {
+        if (isDefinitiveLength()) {
+            return header.getLength();
+        } else if (getBodyEnd() != -1) {
+            return getBodyEnd() - getBodyStart();
+        }
+        return -1;
+    }
+
+
+    public boolean checkBodyFinished(int pos) {
+        return getBodyEnd() != -1 && pos >= getBodyEnd();
     }
 }
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
index c81c8c1..8e07131 100644
--- 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
@@ -30,13 +30,8 @@
 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();
+        Asn1Reader reader = new Asn1Reader(container.getBuffer());
+        int pos = container.getBodyStart();
         while (true) {
             reader.setPosition(pos);
             Asn1ParseResult asn1Obj = parse(reader);
@@ -44,21 +39,19 @@
                 break;
             }
 
-            if (parent != null) {
-                parent.addItem(asn1Obj);
-            }
+            container.addItem(asn1Obj);
 
-            pos += asn1Obj.getAllLength();
+            pos += asn1Obj.getEncodingLength();
             if (asn1Obj.isEOC()) {
                 break;
             }
 
-            if (header.checkBodyFinished(pos)) {
+            if (container.checkBodyFinished(pos)) {
                 break;
             }
         }
 
-        header.setBodyEnd(pos);
+        container.setBodyEnd(pos);
     }
 
     public static Asn1ParseResult parse(ByteBuffer content) throws IOException {
@@ -73,12 +66,14 @@
 
         Asn1Header header = reader.readHeader();
         Tag tmpTag = header.getTag();
+        int bodyStart = reader.getPosition();
         Asn1ParseResult parseResult;
 
         if (tmpTag.isPrimitive()) {
-            parseResult = new Asn1Item(header);
+            parseResult = new Asn1Item(header, bodyStart, reader.getBuffer());
         } else {
-            Asn1Container container = new Asn1Container(header);
+            Asn1Container container = new Asn1Container(header,
+                bodyStart, reader.getBuffer());
             parse(container);
             parseResult = container;
         }
diff --git a/kerby-asn1/src/main/java/org/apache/kerby/asn1/parse/Asn1Reader.java b/kerby-asn1/src/main/java/org/apache/kerby/asn1/parse/Asn1Reader.java
index 555d7a8..89994f8 100644
--- a/kerby-asn1/src/main/java/org/apache/kerby/asn1/parse/Asn1Reader.java
+++ b/kerby-asn1/src/main/java/org/apache/kerby/asn1/parse/Asn1Reader.java
@@ -38,9 +38,7 @@
     public Asn1Header readHeader() throws IOException {
         Tag tag = readTag();
         int valueLength = readLength();
-        int bodyStart = getPosition();
-        Asn1Header header = new Asn1Header(tag, valueLength,
-            bodyStart, getValueBuffer(valueLength));
+        Asn1Header header = new Asn1Header(tag, valueLength);
         return header;
     }
 
@@ -61,10 +59,6 @@
         return position < buffer.limit();
     }
 
-    public ByteBuffer getValueBuffer(int valueLength) {
-        return buffer;
-    }
-
     protected byte readByte() throws IOException {
         return buffer.get(position++);
     }
diff --git a/kerby-asn1/src/main/java/org/apache/kerby/asn1/type/Asn1Encodeable.java b/kerby-asn1/src/main/java/org/apache/kerby/asn1/type/Asn1Encodeable.java
index 07ec4c9..ea85d95 100644
--- a/kerby-asn1/src/main/java/org/apache/kerby/asn1/type/Asn1Encodeable.java
+++ b/kerby-asn1/src/main/java/org/apache/kerby/asn1/type/Asn1Encodeable.java
@@ -23,6 +23,7 @@
 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.Asn1DerivedItem;
 import org.apache.kerby.asn1.parse.Asn1ParseResult;
 import org.apache.kerby.asn1.parse.Asn1Parser;
 import org.apache.kerby.asn1.util.Asn1Util;
@@ -179,8 +180,14 @@
 
     public void decode(Asn1ParseResult parseResult) throws IOException {
         if (!tag().equals(parseResult.tag())) {
-            throw new IOException("Unexpected tag " + parseResult.tag()
-                + ", expecting " + tag());
+            // Primitive but using constructed encoding
+            if (isPrimitive() && !parseResult.isPrimitive()) {
+                Asn1Container container = (Asn1Container) parseResult;
+                parseResult = new Asn1DerivedItem(tag(), container);
+            } else {
+                throw new IOException("Unexpected tag " + parseResult.tag()
+                    + ", expecting " + tag());
+            }
         }
 
         decodeBody(parseResult);
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 78cb1ff..a1348f8 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
@@ -20,6 +20,7 @@
 package org.apache.kerby.asn1.type;
 
 import org.apache.kerby.asn1.UniversalTag;
+import org.apache.kerby.asn1.parse.Asn1Item;
 import org.apache.kerby.asn1.parse.Asn1ParseResult;
 
 import java.io.IOException;
@@ -45,7 +46,8 @@
 
     @Override
     protected void decodeBody(Asn1ParseResult parseResult) throws IOException {
-        setValue(parseResult.readBodyBytes());
+        Asn1Item item = (Asn1Item) parseResult;
+        setValue(item.readBodyBytes());
     }
 
     @Override
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 d75f0a3..45165a8 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
@@ -21,6 +21,7 @@
 
 import org.apache.kerby.asn1.Tag;
 import org.apache.kerby.asn1.UniversalTag;
+import org.apache.kerby.asn1.parse.Asn1Item;
 import org.apache.kerby.asn1.parse.Asn1ParseResult;
 import org.apache.kerby.asn1.util.Asn1Util;
 
@@ -94,7 +95,8 @@
 
     @Override
     protected void decodeBody(Asn1ParseResult parseResult) throws IOException {
-        byte[] leftBytes = parseResult.readBodyBytes();
+        Asn1Item item = (Asn1Item) parseResult;
+        byte[] leftBytes = item.readBodyBytes();
         if (leftBytes.length > 0) {
             setBytes(leftBytes);
             toValue();
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
index 3d66cb8..e7b40be 100644
--- a/kerby-asn1/src/test/java/org/apache/kerby/asn1/TestAsn1ConstructedOctetString.java
+++ b/kerby-asn1/src/test/java/org/apache/kerby/asn1/TestAsn1ConstructedOctetString.java
@@ -20,12 +20,13 @@
 package org.apache.kerby.asn1;
 
 import org.apache.kerby.asn1.type.Asn1OctetString;
+import org.junit.Test;
 
 import java.io.IOException;
 
 public class TestAsn1ConstructedOctetString {
 
-    //@Test
+    @Test
     public void testDecoding() throws IOException {
         byte[] data = TestUtil.readBytesFromTxtFile("/constructed-octet-string.txt");
         Asn1OctetString octetString = new Asn1OctetString();