You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@qpid.apache.org by ro...@apache.org on 2026/07/31 16:35:07 UTC

(qpid-proton-j) branch main updated: various codec and engine improvements

This is an automated email from the ASF dual-hosted git repository.

asf-gitbox-commits pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/qpid-proton-j.git


The following commit(s) were added to refs/heads/main by this push:
     new 92047419 various codec and engine improvements
92047419 is described below

commit 92047419cc87bab6220e7adeb354958d8109618e
Author: Robbie Gemmell <ro...@apache.org>
AuthorDate: Fri Jul 31 16:09:07 2026 +0100

    various codec and engine improvements
---
 .../java/org/apache/qpid/proton/amqp/Symbol.java   |   72 +-
 .../qpid/proton/codec/AbstractSymbolDecoder.java   |   63 +
 .../org/apache/qpid/proton/codec/ArrayType.java    |  350 ++-
 .../org/apache/qpid/proton/codec/BinaryType.java   |   41 +-
 .../java/org/apache/qpid/proton/codec/Data.java    |    3 +
 .../java/org/apache/qpid/proton/codec/Decoder.java |   29 +
 .../org/apache/qpid/proton/codec/DecoderImpl.java  |  322 ++-
 .../qpid/proton/codec/DynamicTypeConstructor.java  |   11 +
 .../org/apache/qpid/proton/codec/EncoderImpl.java  |    9 +-
 .../org/apache/qpid/proton/codec/ListType.java     |  260 +-
 .../java/org/apache/qpid/proton/codec/MapType.java |  209 +-
 .../org/apache/qpid/proton/codec/StringType.java   |   14 +-
 .../org/apache/qpid/proton/codec/SymbolType.java   |   77 +-
 .../qpid/proton/codec/ValidationSupport.java       |  110 +
 .../apache/qpid/proton/codec/impl/DataDecoder.java |  756 ++++--
 .../apache/qpid/proton/codec/impl/DataImpl.java    |   49 +
 .../proton/codec/impl/DataValidationSupport.java   |  128 +
 .../codec/messaging/FastPathAcceptedType.java      |   24 +-
 .../codec/messaging/FastPathAmqpSequenceType.java  |   11 +-
 .../codec/messaging/FastPathAmqpValueType.java     |   29 +-
 .../FastPathApplicationPropertiesType.java         |  127 +-
 .../proton/codec/messaging/FastPathDataType.java   |   11 +-
 .../messaging/FastPathDeliveryAnnotationsType.java |  134 +-
 .../proton/codec/messaging/FastPathFooterType.java |   11 +-
 .../proton/codec/messaging/FastPathHeaderType.java |  109 +-
 .../messaging/FastPathMessageAnnotationsType.java  |  134 +-
 .../codec/messaging/FastPathPropertiesType.java    |  128 +-
 .../codec/transport/FastPathDispositionType.java   |   92 +-
 .../proton/codec/transport/FastPathFlowType.java   |   92 +-
 .../codec/transport/FastPathTransferType.java      |   92 +-
 .../org/apache/qpid/proton/engine/Transport.java   |   10 +
 .../proton/engine/TransportConditionException.java |   47 +
 .../qpid/proton/engine/impl/FrameParser.java       |   37 +-
 .../qpid/proton/engine/impl/FrameWriter.java       |    4 +
 .../apache/qpid/proton/engine/impl/SaslImpl.java   |    9 +-
 .../qpid/proton/engine/impl/TransportImpl.java     |  113 +-
 .../proton/engine/impl/TransportOutputAdaptor.java |    5 +-
 .../qpid/proton/engine/impl/TransportSession.java  |  145 +-
 .../org/apache/qpid/proton/message/Message.java    |   16 +
 .../qpid/proton/message/MessageDecodeOptions.java  |   85 +
 .../qpid/proton/message/impl/MessageImpl.java      |   34 +-
 .../org/apache/qpid/proton/amqp/SymbolTest.java    |  395 +++
 .../qpid/proton/codec/AmqpValueTypeCodecTest.java  |  145 +-
 .../codec/ApplicationPropertiesTypeTest.java       |  199 ++
 .../qpid/proton/codec/ArrayTypeCodecTest.java      | 2526 +++++++++++++++++-
 .../qpid/proton/codec/BinaryTypeCodecTest.java     |   77 +
 .../apache/qpid/proton/codec/CodecTestSupport.java |    8 +-
 .../apache/qpid/proton/codec/DecoderImplTest.java  |  221 ++
 .../proton/codec/DeliveryAnnotationsCodecTest.java |  295 +++
 .../qpid/proton/codec/DispositionCodecTest.java    |  245 ++
 .../apache/qpid/proton/codec/FlowCodecTest.java    |  263 ++
 .../apache/qpid/proton/codec/FooterCodecTest.java  |  245 ++
 .../qpid/proton/codec/HeaderTypeCodecTest.java     |  144 ++
 .../qpid/proton/codec/ListTypeCodecTest.java       |  333 +++
 .../apache/qpid/proton/codec/MapTypeCodecTest.java |  374 +++
 .../codec/MessageAnnotationsTypeCodecTest.java     |  195 ++
 .../qpid/proton/codec/PropertiesCodecTest.java     |  196 ++
 .../apache/qpid/proton/codec/StringTypeTest.java   |  108 +-
 .../apache/qpid/proton/codec/SymbolTypeTest.java   |  318 +++
 .../apache/qpid/proton/codec/TransferTypeTest.java |  166 ++
 .../codec/UnknownDescribedTypeCodecTest.java       |  178 ++
 .../qpid/proton/codec/impl/DataImplTest.java       | 2726 +++++++++++++++++++-
 .../apache/qpid/proton/engine/impl/AmqpFramer.java |   46 +
 .../qpid/proton/engine/impl/MockTransportImpl.java |   46 +
 .../proton/engine/impl/SaslFrameParserTest.java    |    6 +-
 .../engine/impl/TransportDeliveryTestAccessor.java |   29 +
 .../qpid/proton/engine/impl/TransportImplTest.java |  256 +-
 .../engine/impl/TransportLinkTestAccessor.java     |   30 +
 .../engine/impl/TransportSessionTestAccessor.java  |   34 +
 .../qpid/proton/message/impl/MessageImplTest.java  |  194 ++
 .../systemtests/DefaultDeliveryStateTest.java      |   61 -
 .../qpid/proton/systemtests/DeliveryTest.java      |   85 -
 .../qpid/proton/systemtests/EngineTestBase.java    |  154 +-
 .../apache/qpid/proton/systemtests/LinkTest.java   |  645 ++++-
 .../apache/qpid/proton/systemtests/SaslTest.java   |  513 ++++
 .../qpid/proton/systemtests/SessionTest.java       |  419 +++
 tests/interop/described.amqp                       |  Bin 33 -> 33 bytes
 tests/java/org/apache/qpid/proton/InteropTest.java |    3 +-
 tests/python/interop-generate                      |    2 +-
 tests/python/proton_tests/interop.py               |    2 +-
 tests/python/proton_tests/sasl.py                  |    2 +-
 81 files changed, 14511 insertions(+), 1375 deletions(-)

diff --git a/proton-j/src/main/java/org/apache/qpid/proton/amqp/Symbol.java b/proton-j/src/main/java/org/apache/qpid/proton/amqp/Symbol.java
index 4e3d4761..a3a09951 100644
--- a/proton-j/src/main/java/org/apache/qpid/proton/amqp/Symbol.java
+++ b/proton-j/src/main/java/org/apache/qpid/proton/amqp/Symbol.java
@@ -29,10 +29,17 @@ import org.apache.qpid.proton.codec.WritableBuffer;
 
 public final class Symbol implements Comparable<Symbol>, CharSequence
 {
+    private static final int MAX_CACHE_SIZE = 4096;
+    private static final int MAX_CACHED_SYMBOL_LENGTH = 64;
+
+    private static final int MAX_SASL_CACHE_SIZE = 64;
+    private static final int MAX_CACHED_SASL_SYMBOL_LENGTH = 32;
+
     private final String _underlying;
     private final byte[] _underlyingBytes;
 
     private static final ConcurrentHashMap<String, Symbol> _symbols = new ConcurrentHashMap<String, Symbol>(2048);
+    private static final ConcurrentHashMap<String, Symbol> _saslSymbols = new ConcurrentHashMap<String, Symbol>(32);
 
     private Symbol(String underlying)
     {
@@ -72,6 +79,23 @@ public final class Symbol implements Comparable<Symbol>, CharSequence
         return _underlying.hashCode();
     }
 
+    @Override
+    public boolean equals(Object other) {
+        if (this == other) {
+            return true;
+        }
+
+        if (other == null) {
+            return false;
+        }
+
+        if (other instanceof Symbol) {
+            return _underlying.equals(((Symbol) other)._underlying);
+        }
+
+        return false;
+    }
+
     public static Symbol valueOf(String symbolVal)
     {
         return getSymbol(symbolVal);
@@ -86,12 +110,14 @@ public final class Symbol implements Comparable<Symbol>, CharSequence
         Symbol symbol = _symbols.get(symbolVal);
         if(symbol == null)
         {
-            symbolVal = symbolVal.intern();
             symbol = new Symbol(symbolVal);
-            Symbol existing;
-            if((existing = _symbols.putIfAbsent(symbolVal, symbol)) != null)
+            if(symbol.length() <= MAX_CACHED_SYMBOL_LENGTH && _symbols.size() < MAX_CACHE_SIZE)
             {
-                symbol = existing;
+                Symbol existing;
+                if((existing = _symbols.putIfAbsent(symbolVal, symbol)) != null)
+                {
+                    symbol = existing;
+                }
             }
         }
         return symbol;
@@ -106,4 +132,42 @@ public final class Symbol implements Comparable<Symbol>, CharSequence
     {
         buffer.put(_underlyingBytes, 0, _underlyingBytes.length);
     }
+
+    public static Symbol getSaslSymbol(String symbolVal)
+    {
+        if(symbolVal == null)
+        {
+            return null;
+        }
+
+        // Lookup main cache first, then SASL-specific, to reuse where possible
+        Symbol symbol = _symbols.get(symbolVal);
+        if(symbol == null)
+        {
+            symbol = _saslSymbols.get(symbolVal);
+        }
+
+        // If no match, populate into the SASL-specific cache
+        if(symbol == null)
+        {
+            symbol = new Symbol(symbolVal);
+            if(symbol.length() <= MAX_CACHED_SASL_SYMBOL_LENGTH)
+            {
+                synchronized (_saslSymbols)
+                {
+                    final Symbol existing = _saslSymbols.get(symbolVal);
+                    if (existing != null)
+                    {
+                        return existing;
+                    }
+
+                    if (_saslSymbols.size() < MAX_SASL_CACHE_SIZE)
+                    {
+                        _saslSymbols.put(symbolVal, symbol);
+                    }
+                }
+            }
+        }
+        return symbol;
+    }
 }
diff --git a/proton-j/src/main/java/org/apache/qpid/proton/codec/AbstractSymbolDecoder.java b/proton-j/src/main/java/org/apache/qpid/proton/codec/AbstractSymbolDecoder.java
new file mode 100644
index 00000000..853e986d
--- /dev/null
+++ b/proton-j/src/main/java/org/apache/qpid/proton/codec/AbstractSymbolDecoder.java
@@ -0,0 +1,63 @@
+package org.apache.qpid.proton.codec;
+
+import java.nio.charset.StandardCharsets;
+import java.util.concurrent.ConcurrentMap;
+
+import org.apache.qpid.proton.amqp.Symbol;
+import org.apache.qpid.proton.codec.ReadableBuffer.ByteBufferReader;
+
+abstract class AbstractSymbolDecoder implements DecoderImpl.TypeDecoder<Symbol>
+{
+    private final int _maxCacheSize;
+    private final int _maxCachedSymbolSize;
+    private final ConcurrentMap<ReadableBuffer, Symbol> _symbolCache;
+
+    public AbstractSymbolDecoder(final int maxCacheSize, final int maxCachedSymbolSize, final ConcurrentMap<ReadableBuffer, Symbol> symbolCache)
+    {
+        _maxCacheSize = maxCacheSize;
+        _maxCachedSymbolSize = maxCachedSymbolSize;
+        _symbolCache = symbolCache;
+    }
+
+    @Override
+    public Symbol decode(DecoderImpl decoder, ReadableBuffer buffer)
+    {
+        final int remaining = buffer.remaining();
+        final boolean canCache = remaining <= _maxCachedSymbolSize;
+
+        Symbol symbol = canCache ? _symbolCache.get(buffer) : null;
+        if (symbol != null)
+        {
+            buffer.position(buffer.limit());
+            return symbol;
+        }
+
+        final byte[] bytes = new byte[remaining];
+        buffer.get(bytes);
+
+        final String symString = new String(bytes, StandardCharsets.US_ASCII);
+        symbol = createSymbol(symString);
+
+        if (canCache)
+        {
+            final ByteBufferReader keyBuffer = ReadableBuffer.ByteBufferReader.wrap(bytes);
+            synchronized (_symbolCache)
+            {
+                final Symbol existing = _symbolCache.get(keyBuffer);
+                if (existing != null)
+                {
+                    return existing;
+                }
+
+                if (_symbolCache.size() < _maxCacheSize)
+                {
+                    _symbolCache.put(keyBuffer, symbol);
+                }
+            }
+        }
+
+        return symbol;
+    }
+
+    public abstract Symbol createSymbol(String string);
+}
\ No newline at end of file
diff --git a/proton-j/src/main/java/org/apache/qpid/proton/codec/ArrayType.java b/proton-j/src/main/java/org/apache/qpid/proton/codec/ArrayType.java
index 31044867..6ec34bee 100644
--- a/proton-j/src/main/java/org/apache/qpid/proton/codec/ArrayType.java
+++ b/proton-j/src/main/java/org/apache/qpid/proton/codec/ArrayType.java
@@ -111,6 +111,10 @@ public class ArrayType implements PrimitiveType<Object[]>
         }
         else
         {
+            if (val[0] == null) {
+                throw new IllegalArgumentException("Cannot encode array with null first element");
+            }
+
             AMQPType underlyingType = encoder.getTypeFromClass(val.getClass().getComponentType());
             boolean checkTypes = false;
 
@@ -161,8 +165,26 @@ public class ArrayType implements PrimitiveType<Object[]>
                     checkTypes = true;
                     underlyingType = encoder.getType(val[0]);
                 }
-                TypeEncoding underlyingEncoding = underlyingType.getEncoding(val[0]);
+
                 TypeEncoding canonicalEncoding = underlyingType.getCanonicalEncoding();
+                if (checkTypes && canonicalEncoding != null)
+                {
+                    for (int i = 0; i < val.length; i++)
+                    {
+                        if (encoder.getType(val[i]) != underlyingType)
+                        {
+                            throw new IllegalArgumentException("Non matching types " + underlyingType + " and "
+                                    + encoder.getType(val[i]) + " in array");
+                        }
+                    }
+                }
+
+                if (canonicalEncoding != null)
+                {
+                    return canonicalEncoding;
+                }
+
+                TypeEncoding underlyingEncoding = underlyingType.getEncoding(val[0]);
 
                 for(int i = 0; i < val.length && (checkTypes || underlyingEncoding != canonicalEncoding); i++)
                 {
@@ -281,6 +303,27 @@ public class ArrayType implements PrimitiveType<Object[]>
         return size;
     }
 
+    private static void validateSizeInitial(int size, DecoderImpl decoder)
+    {
+        if (size < 0)
+        {
+            throw new DecodeException("Array size " + size + " cant be negative");
+        }
+
+        if (size > decoder.getByteBufferRemaining())
+        {
+            throw new DecodeException("Array size " + size + " cant be more than remaining bytes " + decoder.getByteBufferRemaining());
+        }
+    }
+
+    private static void validateCountInitial(int count)
+    {
+        if (count < 0)
+        {
+            throw new DecodeException("Array element count " + count + " cant be negative");
+        }
+    }
+
     @Override
     public ArrayEncoding getCanonicalEncoding()
     {
@@ -310,20 +353,7 @@ public class ArrayType implements PrimitiveType<Object[]>
 
     private ArrayEncoding getEncoding(final boolean[] a)
     {
-        return a.length < 254 || a.length <= 255 && allSameValue(a) ? _shortArrayEncoding : _arrayEncoding;
-    }
-
-    private boolean allSameValue(final boolean[] a)
-    {
-        boolean val = a[0];
-        for(int i = 1; i < a.length; i++)
-        {
-            if(val != a[i])
-            {
-                return false;
-            }
-        }
-        return true;
+        return a.length < 254 ? _shortArrayEncoding : _arrayEncoding;
     }
 
     public void write(byte[] a)
@@ -359,19 +389,7 @@ public class ArrayType implements PrimitiveType<Object[]>
 
     private ArrayEncoding getEncoding(final int[] a)
     {
-        return a.length < 63 || (a.length < 254 && allSmallInts(a)) ? _shortArrayEncoding : _arrayEncoding;
-    }
-
-    private boolean allSmallInts(final int[] a)
-    {
-        for(int i = 0; i < a.length; i++)
-        {
-            if(a[i] < -128 || a[i] > 127)
-            {
-                return false;
-            }
-        }
-        return true;
+        return a.length < 64 ? _shortArrayEncoding : _arrayEncoding;
     }
 
     public void write(long[] a)
@@ -383,19 +401,7 @@ public class ArrayType implements PrimitiveType<Object[]>
 
     private ArrayEncoding getEncoding(final long[] a)
     {
-        return a.length < 31 || (a.length < 254 && allSmallLongs(a)) ? _shortArrayEncoding : _arrayEncoding;
-    }
-
-    private boolean allSmallLongs(final long[] a)
-    {
-        for(int i = 0; i < a.length; i++)
-        {
-            if(a[i] < -128L || a[i] > 127L)
-            {
-                return false;
-            }
-        }
-        return true;
+        return a.length < 32 ? _shortArrayEncoding : _arrayEncoding;
     }
 
     public void write(float[] a)
@@ -407,7 +413,7 @@ public class ArrayType implements PrimitiveType<Object[]>
 
     private ArrayEncoding getEncoding(final float[] a)
     {
-        return a.length < 63 ? _shortArrayEncoding : _arrayEncoding;
+        return a.length < 64 ? _shortArrayEncoding : _arrayEncoding;
     }
 
     public void write(double[] a)
@@ -419,7 +425,7 @@ public class ArrayType implements PrimitiveType<Object[]>
 
     private ArrayEncoding getEncoding(final double[] a)
     {
-        return a.length < 31 ? _shortArrayEncoding : _arrayEncoding;
+        return a.length < 32 ? _shortArrayEncoding : _arrayEncoding;
     }
 
     public void write(char[] a)
@@ -431,7 +437,7 @@ public class ArrayType implements PrimitiveType<Object[]>
 
     private ArrayEncoding getEncoding(final char[] a)
     {
-        return a.length < 63 ? _shortArrayEncoding : _arrayEncoding;
+        return a.length < 64 ? _shortArrayEncoding : _arrayEncoding;
     }
 
     private class AllArrayEncoding
@@ -648,18 +654,82 @@ public class ArrayType implements PrimitiveType<Object[]>
         public Object[] readValue()
         {
             DecoderImpl decoder = getDecoder();
-            int size = decoder.readRawInt();
-            int count = decoder.readRawInt();
-            return decodeArray(decoder, count);
+            ReadableBuffer buffer = decoder.getBuffer();
+
+            decoder.increaseDecodeDepth();
+            try
+            {
+                int size = decoder.readRawInt();
+                validateSizeInitial(size, decoder);
+                final int oldLimit = buffer.limit();
+                final int newLimit = buffer.position() + size;
+
+                try
+                {
+                    buffer.limit(newLimit);
+
+                    int count = decoder.readRawInt();
+                    validateCountInitial(count);
+
+                    final Object[] decodedArray = decodeArray(decoder, count, size);
+
+                    if (buffer.position() != newLimit)
+                    {
+                        throw new DecodeException("Array incorrectly encoded, left " + (newLimit - buffer.position()) + " bytes from size indicated " + size);
+                    }
+
+                    return decodedArray;
+                }
+                finally
+                {
+                    buffer.limit(oldLimit);
+                }
+            }
+            finally
+            {
+                decoder.decreaseDecodeDepth();
+            }
         }
 
         @Override
         public Object readValueArray()
         {
             DecoderImpl decoder = getDecoder();
-            int size = decoder.readRawInt();
-            int count = decoder.readRawInt();
-            return decodeArrayAsObject(decoder, count);
+            ReadableBuffer buffer = decoder.getBuffer();
+
+            decoder.increaseDecodeDepth();
+            try
+            {
+                int size = decoder.readRawInt();
+                validateSizeInitial(size, decoder);
+                final int oldLimit = buffer.limit();
+                final int newLimit = buffer.position() + size;
+
+                try
+                {
+                    buffer.limit(newLimit);
+
+                    int count = decoder.readRawInt();
+                    validateCountInitial(count);
+
+                    final Object decodedArray = decodeArrayAsObject(decoder, count, size);
+
+                    if (buffer.position() != newLimit)
+                    {
+                        throw new DecodeException("Array incorrectly encoded, left " + (newLimit - buffer.position()) + " bytes from size indicated " + size);
+                    }
+
+                    return decodedArray;
+                }
+                finally
+                {
+                    buffer.limit(oldLimit);
+                }
+            }
+            finally
+            {
+                decoder.decreaseDecodeDepth();
+            }
         }
 
         @Override
@@ -668,6 +738,7 @@ public class ArrayType implements PrimitiveType<Object[]>
             DecoderImpl decoder = getDecoder();
             ReadableBuffer buffer = decoder.getBuffer();
             int size = decoder.readRawInt();
+            validateSizeInitial(size, decoder);
             buffer.position(buffer.position() + size);
         }
     }
@@ -941,18 +1012,80 @@ public class ArrayType implements PrimitiveType<Object[]>
         public Object[] readValue()
         {
             DecoderImpl decoder = getDecoder();
-            int size = ((int)decoder.readRawByte()) & 0xFF;
-            int count = ((int)decoder.readRawByte()) & 0xFF;
-            return decodeArray(decoder, count);
+            ReadableBuffer buffer = decoder.getBuffer();
+
+            decoder.increaseDecodeDepth();
+            try
+            {
+                int size = ((int)decoder.readRawByte()) & 0xFF;
+                validateSizeInitial(size, decoder);
+
+                final int oldLimit = buffer.limit();
+                final int newLimit = buffer.position() + size;
+
+                try
+                {
+                    buffer.limit(newLimit);
+                    int count = ((int)decoder.readRawByte()) & 0xFF;
+                    validateCountInitial(count);
+
+                    Object[] decodedArray = decodeArray(decoder, count, size);
+                    if (buffer.position() != newLimit)
+                    {
+                        throw new DecodeException("Array incorrectly encoded, left " + (newLimit - buffer.position()) + " bytes from size indicated " + size);
+                    }
+
+                    return decodedArray;
+                }
+                finally
+                {
+                    buffer.limit(oldLimit);
+                }
+            }
+            finally
+            {
+                decoder.decreaseDecodeDepth();
+            }
         }
 
         @Override
         public Object readValueArray()
         {
             DecoderImpl decoder = getDecoder();
-            int size = ((int)decoder.readRawByte()) & 0xFF;
-            int count = ((int)decoder.readRawByte()) & 0xFF;
-            return decodeArrayAsObject(decoder, count);
+            ReadableBuffer buffer = decoder.getBuffer();
+
+            decoder.increaseDecodeDepth();
+            try
+            {
+                int size = ((int)decoder.readRawByte()) & 0xFF;
+                validateSizeInitial(size, decoder);
+
+                final int oldLimit = buffer.limit();
+                final int newLimit = buffer.position() + size;
+
+                try
+                {
+                    buffer.limit(newLimit);
+                    int count = ((int)decoder.readRawByte()) & 0xFF;
+                    validateCountInitial(count);
+
+                    Object decodedArray = decodeArrayAsObject(decoder, count, size);
+                    if (buffer.position() != newLimit)
+                    {
+                        throw new DecodeException("Array incorrectly encoded, left " + (newLimit - buffer.position()) + " bytes from size indicated " + size);
+                    }
+
+                    return decodedArray;
+                }
+                finally
+                {
+                    buffer.limit(oldLimit);
+                }
+            }
+            finally
+            {
+                decoder.decreaseDecodeDepth();
+            }
         }
 
         @Override
@@ -961,28 +1094,14 @@ public class ArrayType implements PrimitiveType<Object[]>
             DecoderImpl decoder = getDecoder();
             ReadableBuffer buffer = decoder.getBuffer();
             int size = ((int)decoder.readRawByte()) & 0xFF;
+            validateSizeInitial(size, decoder);
             buffer.position(buffer.position() + size);
         }
     }
 
     private BooleanType.BooleanEncoding getUnderlyingEncoding(final boolean[] a)
     {
-        if(a.length == 0)
-        {
-            return _booleanType.getCanonicalEncoding();
-        }
-        else
-        {
-            boolean val = a[0];
-            for(int i = 1; i < a.length; i++)
-            {
-                if(val != a[i])
-                {
-                    return _booleanType.getCanonicalEncoding();
-                }
-            }
-            return _booleanType.getEncoding(val);
-        }
+        return _booleanType.getCanonicalEncoding();
     }
 
     private ByteType.ByteEncoding getUnderlyingEncoding(final byte[] a)
@@ -997,26 +1116,12 @@ public class ArrayType implements PrimitiveType<Object[]>
 
     private IntegerType.IntegerEncoding getUnderlyingEncoding(final int[] a)
     {
-        if(a.length == 0 || !allSmallInts(a))
-        {
-            return _integerType.getCanonicalEncoding();
-        }
-        else
-        {
-            return _integerType.getEncoding(a[0]);
-        }
+        return _integerType.getCanonicalEncoding();
     }
 
     private LongType.LongEncoding getUnderlyingEncoding(final long[] a)
     {
-        if(a.length == 0 || !allSmallLongs(a))
-        {
-            return _longType.getCanonicalEncoding();
-        }
-        else
-        {
-            return _longType.getEncoding(a[0]);
-        }
+        return _longType.getCanonicalEncoding();
     }
 
     private FloatType.FloatEncoding getUnderlyingEncoding(final float[] a)
@@ -1034,20 +1139,18 @@ public class ArrayType implements PrimitiveType<Object[]>
         return _characterType.getCanonicalEncoding();
     }
 
-    private static Object[] decodeArray(final DecoderImpl decoder, final int count)
+    private static Object[] decodeArray(final DecoderImpl decoder, final int count, final int size)
     {
         TypeConstructor constructor = decoder.readConstructor(true);
-        return decodeNonPrimitive(decoder, constructor, count);
+        return decodeNonPrimitive(decoder, constructor, count, size);
     }
 
     private static Object[] decodeNonPrimitive(final DecoderImpl decoder,
                                                final TypeConstructor constructor,
-                                               final int count)
+                                               final int count,
+                                               final int size)
     {
-        if (count > decoder.getByteBufferRemaining()) {
-            throw new IllegalArgumentException("Array element count "+count+" is specified to be greater than the amount of data available ("+
-                                               decoder.getByteBufferRemaining()+")");
-        }
+        checkSizeCountConstructor(decoder, constructor, count, size);
 
         if(constructor instanceof ArrayEncoding)
         {
@@ -1074,15 +1177,60 @@ public class ArrayType implements PrimitiveType<Object[]>
         }
     }
 
-    private static Object decodeArrayAsObject(final DecoderImpl decoder, final int count)
+    private static void checkSizeCountConstructor(final DecoderImpl decoder, final TypeConstructor constructor, final int count, final int size)
+    {
+        TypeConstructor constructorToCheck = constructor;
+
+        if(!constructor.encodesJavaPrimitive() && constructor instanceof DynamicTypeConstructor)
+        {
+            constructorToCheck = ((DynamicTypeConstructor) constructor).getUnderlyingEncoding();
+        }
+
+        if(constructorToCheck instanceof PrimitiveTypeEncoding<?>)
+        {
+            PrimitiveTypeEncoding<?> primitiveType = (PrimitiveTypeEncoding<?>) constructorToCheck;
+
+            final byte encodingCode = primitiveType.getEncodingCode();
+            if (isZeroWidthEncoding(encodingCode))
+            {
+                if (count > decoder.getZeroWidthArrayElementLimit())
+                {
+                    throw new DecodeException("Array element count " + count + " is specified to be greater than the allowed 0-width-element limit for arrays ("
+                                                                                                                + decoder.getZeroWidthArrayElementLimit() + ")");
+                }
+                else
+                {
+                    // Remaining checks are not valid for these cases, return
+                    return;
+                }
+            }
+        }
+
+        if (count > size)
+        {
+            throw new DecodeException("Array element count " + count + " is specified to be greater than the size of array (" + size +")");
+        }
+
+        if (count > decoder.getByteBufferRemaining())
+        {
+            throw new DecodeException("Array element count " + count + " is specified to be greater than the amount of data available ("+
+                    decoder.getByteBufferRemaining()+")");
+        }
+    }
+
+    private static boolean isZeroWidthEncoding(final byte encodingCode)
+    {
+        return encodingCode == EncodingCodes.LIST0 || encodingCode == EncodingCodes.ULONG0
+                || encodingCode == EncodingCodes.UINT0 || encodingCode == EncodingCodes.NULL
+                || encodingCode == EncodingCodes.BOOLEAN_FALSE || encodingCode == EncodingCodes.BOOLEAN_TRUE;
+    }
+
+    private static Object decodeArrayAsObject(final DecoderImpl decoder, final int count, final int size)
     {
         TypeConstructor constructor = decoder.readConstructor(true);
         if(constructor.encodesJavaPrimitive())
         {
-            if (count > decoder.getByteBufferRemaining()) {
-                throw new IllegalArgumentException("Array element count "+count+" is specified to be greater than the amount of data available ("+
-                                                   decoder.getByteBufferRemaining()+")");
-            }
+            checkSizeCountConstructor(decoder, constructor, count, size);
 
             if(constructor instanceof BooleanType.BooleanEncoding)
             {
@@ -1123,7 +1271,7 @@ public class ArrayType implements PrimitiveType<Object[]>
         }
         else
         {
-            return decodeNonPrimitive(decoder, constructor, count);
+            return decodeNonPrimitive(decoder, constructor, count, size);
         }
     }
 
diff --git a/proton-j/src/main/java/org/apache/qpid/proton/codec/BinaryType.java b/proton-j/src/main/java/org/apache/qpid/proton/codec/BinaryType.java
index c9f6d49f..3498996b 100644
--- a/proton-j/src/main/java/org/apache/qpid/proton/codec/BinaryType.java
+++ b/proton-j/src/main/java/org/apache/qpid/proton/codec/BinaryType.java
@@ -27,6 +27,8 @@ import org.apache.qpid.proton.amqp.Binary;
 
 public class BinaryType extends AbstractPrimitiveType<Binary>
 {
+    private static final String TYPE_NAME = "binary";
+
     private final BinaryEncoding _binaryEncoding;
     private final BinaryEncoding _shortBinaryEncoding;
 
@@ -132,12 +134,11 @@ public class BinaryType extends AbstractPrimitiveType<Binary>
         public Binary readValue()
         {
             final DecoderImpl decoder = getDecoder();
-            int size = decoder.readRawInt();
-            if (size > decoder.getByteBufferRemaining()) {
-                throw new IllegalArgumentException("Binary data size "+size+" is specified to be greater than the amount of data available ("+
-                                                   decoder.getByteBufferRemaining()+")");
-            }
-            byte[] data = new byte[size];
+            final int size = decoder.readRawInt();
+
+            ValidationSupport.validateLength(TYPE_NAME, size, decoder.getByteBufferRemaining());
+
+            final byte[] data = new byte[size];
             decoder.readRaw(data, 0, size);
             return new Binary(data);
         }
@@ -145,9 +146,12 @@ public class BinaryType extends AbstractPrimitiveType<Binary>
         @Override
         public void skipValue()
         {
-            DecoderImpl decoder = getDecoder();
-            ReadableBuffer buffer = decoder.getBuffer();
-            int size = decoder.readRawInt();
+            final DecoderImpl decoder = getDecoder();
+            final ReadableBuffer buffer = decoder.getBuffer();
+            final int size = decoder.readRawInt();
+
+            ValidationSupport.validateLength(TYPE_NAME, size, buffer.remaining());
+
             buffer.position(buffer.position() + size);
         }
     }
@@ -196,18 +200,25 @@ public class BinaryType extends AbstractPrimitiveType<Binary>
         @Override
         public Binary readValue()
         {
-            int size = ((int)getDecoder().readRawByte()) & 0xff;
-            byte[] data = new byte[size];
-            getDecoder().readRaw(data, 0, size);
+            final DecoderImpl decoder = getDecoder();
+            final int size = ((int) decoder.readRawByte()) & 0xff;
+
+            ValidationSupport.validateLength(TYPE_NAME, size, decoder.getByteBufferRemaining());
+
+            final byte[] data = new byte[size];
+            decoder.readRaw(data, 0, size);
             return new Binary(data);
         }
 
         @Override
         public void skipValue()
         {
-            DecoderImpl decoder = getDecoder();
-            ReadableBuffer buffer = decoder.getBuffer();
-            int size = ((int)getDecoder().readRawByte()) & 0xff;
+            final DecoderImpl decoder = getDecoder();
+            final ReadableBuffer buffer = decoder.getBuffer();
+            final int size = ((int) decoder.readRawByte()) & 0xff;
+
+            ValidationSupport.validateLength(TYPE_NAME, size, buffer.remaining());
+
             buffer.position(buffer.position() + size);
         }
     }
diff --git a/proton-j/src/main/java/org/apache/qpid/proton/codec/Data.java b/proton-j/src/main/java/org/apache/qpid/proton/codec/Data.java
index c3fc1e50..27de50ec 100644
--- a/proton-j/src/main/java/org/apache/qpid/proton/codec/Data.java
+++ b/proton-j/src/main/java/org/apache/qpid/proton/codec/Data.java
@@ -161,4 +161,7 @@ public interface Data
     DescribedType getDescribedType();
 
     String format();
+
+    int getZeroWidthElementLimit();
+    void setZeroWidthElementLimit(int zeroWidthElementLimit);
 }
diff --git a/proton-j/src/main/java/org/apache/qpid/proton/codec/Decoder.java b/proton-j/src/main/java/org/apache/qpid/proton/codec/Decoder.java
index b1f47e08..751221f5 100644
--- a/proton-j/src/main/java/org/apache/qpid/proton/codec/Decoder.java
+++ b/proton-j/src/main/java/org/apache/qpid/proton/codec/Decoder.java
@@ -143,6 +143,35 @@ public interface Decoder
     Object readObject();
     Object readObject(Object defaultValue);
 
+    /**
+     * Returns the maximum number of array elements allowed while decoding arrays of zero-width
+     * elements (ulong0, uint0, list0, boolean-true, boolean-false, null) before a decode exception.
+     *
+     * @return the maximum number of zero-width elements allowed in an array.
+     */
+    int getZeroWidthArrayElementLimit();
+
+    /**
+     * Configures the maximum number of array elements allowed while decoding arrays of zero-width
+     * elements (ulong0, uint0, list0, boolean-true, boolean-false, null) before a decode exception.
+     * Default is 0, meaning only the empty array is permitted.
+     */
+    void setZeroWidthArrayElementLimit(int zeroWidthElementLimit);
+
+    /**
+     * Returns the maximum decode depth allowed before error when decoding Maps, Lists,
+     * Arrays, and certain related DescribedTypes from an encoded payload.
+     *
+     * @return the maximum depth limit before error for Maps, Lists, Arrays and related DescribedTypes.
+     */
+    int getMaxDecodeDepth();
+
+    /**
+     * Configures the maximum decode depth allowed before error when decoding Maps, Lists,
+     * Arrays, and certain related DescribedTypes from an encoded payload. Default is 50.
+     */
+    void setMaxDecodeDepth(int maxDecodeDepth);
+
     void register(final Object descriptor, final DescribedTypeConstructor dtc);
 
     void register(final Object descriptor, final FastPathDescribedTypeConstructor<?> dtc);
diff --git a/proton-j/src/main/java/org/apache/qpid/proton/codec/DecoderImpl.java b/proton-j/src/main/java/org/apache/qpid/proton/codec/DecoderImpl.java
index 58dcfd1e..8447e984 100644
--- a/proton-j/src/main/java/org/apache/qpid/proton/codec/DecoderImpl.java
+++ b/proton-j/src/main/java/org/apache/qpid/proton/codec/DecoderImpl.java
@@ -25,7 +25,6 @@ import java.nio.ByteBuffer;
 import java.nio.charset.CharsetDecoder;
 import java.nio.charset.StandardCharsets;
 import java.util.Collection;
-import java.util.Collections;
 import java.util.Date;
 import java.util.HashMap;
 import java.util.List;
@@ -45,6 +44,15 @@ import org.apache.qpid.proton.amqp.UnsignedShort;
 
 public class DecoderImpl implements ByteBufferDecoder
 {
+    private static final int UNKNOWN_DT_CONSTRUCTOR_CACHE_SIZE = 16;
+    private static final int DT_DESCRIPTOR_MAX_CACHED_SYMBOL_LENGTH = 64;
+
+    /**
+     * The default maximum depth the decoder will allow before triggering a {@link DecodeException}
+     * when the depth is increased during decode of complex types that can nest objects.
+     */
+    private static final int DEFAULT_MAX_DECODE_DEPTH = 32;
+
     private ReadableBuffer _buffer;
 
     private final CharsetDecoder _charsetDecoder = StandardCharsets.UTF_8.newDecoder();
@@ -56,15 +64,68 @@ public class DecoderImpl implements ByteBufferDecoder
     private final Map<Object, FastPathDescribedTypeConstructor<?>> _fastPathTypeConstructors =
         new HashMap<Object, FastPathDescribedTypeConstructor<?>>();
 
+    private Map<Object, UnknownDescribedTypeConstructor> _unknownDescribedTypeConstructors = new HashMap<>();
+
+    private int _zeroWidthElementLimit = 0;
+    private boolean _sasl;
+
+    private int _maxDecodeDepth = DEFAULT_MAX_DECODE_DEPTH;
+    private int _decodeDepth;
+
     public DecoderImpl()
     {
+        this(false);
+    }
+
+    public DecoderImpl(boolean sasl)
+    {
+        _sasl = sasl;
     }
 
     DecoderImpl(final ByteBuffer buffer)
+    {
+        this(buffer, false);
+    }
+
+    DecoderImpl(final ByteBuffer buffer, boolean sasl)
     {
         _buffer = new ReadableBuffer.ByteBufferReader(buffer);
     }
 
+    /**
+     * Returns the maximum decode depth allowed before error when decoding Maps, Lists,
+     * Arrays, and certain related DescribedTypes from an encoded payload.
+     *
+     * @return the maximum depth limit before error for Maps, Lists, Arrays and related DescribedTypes.
+     */
+    @Override
+    public int getMaxDecodeDepth() {
+        return _maxDecodeDepth;
+    }
+
+    /**
+     * Configures the maximum decode depth allowed before error when decoding Maps, Lists,
+     * Arrays, and certain related DescribedTypes from an encoded payload. Default is 32.
+     */
+    @Override
+    public void setMaxDecodeDepth(int maxDecodeDepth) {
+        this._maxDecodeDepth = maxDecodeDepth;
+    }
+
+    public void increaseDecodeDepth() {
+        if (++_decodeDepth > _maxDecodeDepth) {
+            --_decodeDepth; // Reset so the unwind goes back to zero
+            throw new DecodeException("The nesting of types in the object being decoded exceeded the configured limit: " + _maxDecodeDepth);
+        }
+    }
+
+    public void decreaseDecodeDepth() {
+        if(--_decodeDepth < 0) {
+            ++_decodeDepth; // Reset so the unwind goes back to zero
+            throw new IllegalStateException("Decode depth cannot go negative");
+        }
+    }
+
     public TypeConstructor<?> peekConstructor()
     {
         _buffer.mark();
@@ -81,17 +142,28 @@ public class DecoderImpl implements ByteBufferDecoder
     @SuppressWarnings("rawtypes")
     public TypeConstructor readConstructor()
     {
-        return readConstructor(false);
+        return readConstructor(false, false);
     }
 
     @SuppressWarnings("rawtypes")
     public TypeConstructor readConstructor(boolean excludeFastPathConstructors)
+    {
+        return readConstructor(excludeFastPathConstructors, false);
+    }
+
+    @SuppressWarnings("rawtypes")
+    private TypeConstructor readConstructor(boolean excludeFastPathConstructors, boolean decodingDescribedTypePrimitiveNestedEncoding)
     {
         int code = ((int)readRawByte()) & 0xff;
         if(code == EncodingCodes.DESCRIBED_TYPE_INDICATOR)
         {
+            if(decodingDescribedTypePrimitiveNestedEncoding) {
+                throw new DecodeException("DescribedType value encoding cannot be another DescribedType, must be a primitive type");
+            }
+
             final byte encoding = _buffer.get(_buffer.position());
             final Object descriptor;
+            boolean cachable = true;
 
             if (EncodingCodes.SMALLULONG == encoding || EncodingCodes.ULONG == encoding)
             {
@@ -99,11 +171,22 @@ public class DecoderImpl implements ByteBufferDecoder
             }
             else if (EncodingCodes.SYM8 == encoding || EncodingCodes.SYM32 == encoding)
             {
-                descriptor = readSymbol(null);
+                final Symbol sym = readSymbol(null);
+                if (sym.length() > DT_DESCRIPTOR_MAX_CACHED_SYMBOL_LENGTH)
+                {
+                    cachable = false;
+                }
+                descriptor = sym;
             }
             else
             {
-                descriptor = readObject();
+                if (_sasl)
+                {
+                    throw new DecodeException("Unexpected described type descriptor encoding during SASL: " + EncodingCodes.toString(encoding));
+                }
+
+                descriptor = readReservedDescriptor();
+                cachable = false;
             }
 
             if (!excludeFastPathConstructors)
@@ -115,26 +198,13 @@ public class DecoderImpl implements ByteBufferDecoder
                 }
             }
 
-            TypeConstructor<?> nestedEncoding = readConstructor(false);
+            TypeConstructor<?> nestedEncoding = readConstructor(false, true);
             DescribedTypeConstructor<?> dtc = _dynamicTypeConstructors.get(descriptor);
             if(dtc == null)
             {
-                dtc = new DescribedTypeConstructor()
-                {
-                    @Override
-                    public DescribedType newInstance(final Object described)
-                    {
-                        return new UnknownDescribedType(descriptor, described);
-                    }
-
-                    @Override
-                    public Class<?> getTypeClass()
-                    {
-                        return UnknownDescribedType.class;
-                    }
-                };
-                register(descriptor, dtc);
+                dtc = handleUnknownDescribedType(descriptor, cachable);
             }
+
             return new DynamicTypeConstructor(dtc, nestedEncoding);
         }
         else
@@ -143,6 +213,25 @@ public class DecoderImpl implements ByteBufferDecoder
         }
     }
 
+    private UnknownDescribedTypeConstructor handleUnknownDescribedType(final Object descriptor, final boolean cacheAllowed) {
+        if (_sasl)
+        {
+            throw new DecodeException("Cannot decode unknown described types during SASL");
+        }
+
+        UnknownDescribedTypeConstructor udtc = cacheAllowed ? _unknownDescribedTypeConstructors.get(descriptor): null;
+
+        if (udtc == null)
+        {
+            udtc = new UnknownDescribedTypeConstructor(descriptor);
+            if (cacheAllowed && _unknownDescribedTypeConstructors.size() < UNKNOWN_DT_CONSTRUCTOR_CACHE_SIZE)
+            {
+                _unknownDescribedTypeConstructors.put(descriptor, udtc);
+            }
+        }
+        return udtc;
+    }
+
     @Override
     public void register(final Object descriptor, final FastPathDescribedTypeConstructor<?> btc)
     {
@@ -236,22 +325,15 @@ public class DecoderImpl implements ByteBufferDecoder
     @Override
     public byte readByte(final byte defaultVal)
     {
-        TypeConstructor<?> constructor = readConstructor();
-        if(constructor instanceof ByteType.ByteEncoding)
-        {
-            return ((ByteType.ByteEncoding)constructor).readPrimitiveValue();
-        }
-        else
-        {
-            Object val = constructor.readValue();
-            if(val == null)
-            {
+        byte encodingCode = _buffer.get();
+
+        switch (encodingCode) {
+            case EncodingCodes.BYTE:
+                return readRawByte();
+            case EncodingCodes.NULL:
                 return defaultVal;
-            }
-            else
-            {
-                throw unexpectedType(val, Byte.class);
-            }
+            default:
+                throw new DecodeException("Expected Byte type but found encoding: " + EncodingCodes.toString(encodingCode));
         }
     }
 
@@ -280,22 +362,16 @@ public class DecoderImpl implements ByteBufferDecoder
     @Override
     public short readShort(final short defaultVal)
     {
-        TypeConstructor<?> constructor = readConstructor();
-        if(constructor instanceof ShortType.ShortEncoding)
-        {
-            return ((ShortType.ShortEncoding)constructor).readPrimitiveValue();
-        }
-        else
+        byte encodingCode = _buffer.get();
+
+        switch (encodingCode)
         {
-            Object val = constructor.readValue();
-            if(val == null)
-            {
+            case EncodingCodes.SHORT:
+                return readRawShort();
+            case EncodingCodes.NULL:
                 return defaultVal;
-            }
-            else
-            {
-                throw unexpectedType(val, Short.class);
-            }
+            default:
+                throw new DecodeException("Expected Short type but found encoding: " + EncodingCodes.toString(encodingCode));
         }
     }
 
@@ -326,22 +402,18 @@ public class DecoderImpl implements ByteBufferDecoder
     @Override
     public int readInteger(final int defaultVal)
     {
-        TypeConstructor<?> constructor = readConstructor();
-        if(constructor instanceof IntegerType.IntegerEncoding)
-        {
-            return ((IntegerType.IntegerEncoding)constructor).readPrimitiveValue();
-        }
-        else
+        byte encodingCode = _buffer.get();
+
+        switch (encodingCode)
         {
-            Object val = constructor.readValue();
-            if(val == null)
-            {
+            case EncodingCodes.SMALLINT:
+                return readRawByte();
+            case EncodingCodes.INT:
+                return readRawInt();
+            case EncodingCodes.NULL:
                 return defaultVal;
-            }
-            else
-            {
-                throw unexpectedType(val, Integer.class);
-            }
+            default:
+                throw new DecodeException("Expected Integer type but found encoding: " + EncodingCodes.toString(encodingCode));
         }
     }
 
@@ -372,22 +444,18 @@ public class DecoderImpl implements ByteBufferDecoder
     @Override
     public long readLong(final long defaultVal)
     {
-        TypeConstructor<?> constructor = readConstructor();
-        if(constructor instanceof LongType.LongEncoding)
-        {
-            return ((LongType.LongEncoding)constructor).readPrimitiveValue();
-        }
-        else
+        byte encodingCode = _buffer.get();
+
+        switch (encodingCode)
         {
-            Object val = constructor.readValue();
-            if(val == null)
-            {
+            case EncodingCodes.SMALLLONG:
+                return readRawByte();
+            case EncodingCodes.LONG:
+                return readRawLong();
+            case EncodingCodes.NULL:
                 return defaultVal;
-            }
-            else
-            {
-                throw unexpectedType(val, Long.class);
-            }
+            default:
+                throw new DecodeException("Expected Long type but found encoding: " + EncodingCodes.toString(encodingCode));
         }
     }
 
@@ -550,22 +618,16 @@ public class DecoderImpl implements ByteBufferDecoder
     @Override
     public float readFloat(final float defaultVal)
     {
-        TypeConstructor<?> constructor = readConstructor();
-        if(constructor instanceof FloatType.FloatEncoding)
-        {
-            return ((FloatType.FloatEncoding)constructor).readPrimitiveValue();
-        }
-        else
+        byte encodingCode = _buffer.get();
+
+        switch (encodingCode)
         {
-            Object val = constructor.readValue();
-            if(val == null)
-            {
+            case EncodingCodes.FLOAT:
+                return readRawFloat();
+            case EncodingCodes.NULL:
                 return defaultVal;
-            }
-            else
-            {
-                throw unexpectedType(val, Float.class);
-            }
+            default:
+                throw new DecodeException("Expected Float type but found encoding: " + EncodingCodes.toString(encodingCode));
         }
     }
 
@@ -594,22 +656,16 @@ public class DecoderImpl implements ByteBufferDecoder
     @Override
     public double readDouble(final double defaultVal)
     {
-        TypeConstructor<?> constructor = readConstructor();
-        if(constructor instanceof DoubleType.DoubleEncoding)
-        {
-            return ((DoubleType.DoubleEncoding)constructor).readPrimitiveValue();
-        }
-        else
+        byte encodingCode = _buffer.get();
+
+        switch (encodingCode)
         {
-            Object val = constructor.readValue();
-            if(val == null)
-            {
+            case EncodingCodes.DOUBLE:
+                return readRawDouble();
+            case EncodingCodes.NULL:
                 return defaultVal;
-            }
-            else
-            {
-                throw unexpectedType(val, Double.class);
-            }
+            default:
+                throw new DecodeException("Expected Double type but found encoding: " + EncodingCodes.toString(encodingCode));
         }
     }
 
@@ -804,7 +860,7 @@ public class DecoderImpl implements ByteBufferDecoder
         switch (encodingCode)
         {
             case EncodingCodes.LIST0:
-                return Collections.EMPTY_LIST;
+                return (List) _constructors[EncodingCodes.LIST0 & 0xff].readValue();
             case EncodingCodes.LIST8:
                 return (List) _constructors[EncodingCodes.LIST8 & 0xff].readValue();
             case EncodingCodes.LIST32:
@@ -1027,6 +1083,11 @@ public class DecoderImpl implements ByteBufferDecoder
         return val == null ? defaultValue : val;
     }
 
+    protected Object readReservedDescriptor()
+    {
+        throw new DecodeException("Reserved non-ulong/non-symbol described-type descriptors not supported by this decoder");
+    }
+
     <V> void register(PrimitiveType<V> type)
     {
         Collection<? extends PrimitiveTypeEncoding<V>> encodings = type.getAllEncodings();
@@ -1073,17 +1134,26 @@ public class DecoderImpl implements ByteBufferDecoder
         _buffer.get(data, offset, length);
     }
 
-    <V> V readRaw(TypeDecoder<V> decoder, int size)
+    @Deprecated
+    <V> V readRaw(final TypeDecoder<V> decoder, final  int size)
+    {
+        return readRaw(decoder, size, "unspecified-type");
+    }
+
+    <V> V readRaw(final TypeDecoder<V> decoder, final int size, final String typeName)
     {
+        ValidationSupport.validateLength(typeName, size, _buffer.remaining());
+
         final int oldLimit = _buffer.limit();
-        final int oldPosition = _buffer.position();
+        final int newLimit = _buffer.position() + size;
 
         final V decode;
-
         try {
-            decode = decoder.decode(this, _buffer.limit(_buffer.position() + size));
+            decode = decoder.decode(this, _buffer.limit(newLimit));
+            if (_buffer.position() != newLimit) {
+                throw new DecodeException(typeName + " decode unexpectedly left " + (newLimit - _buffer.position()) + " bytes from size indicated " + size);
+            }
         } finally {
-            _buffer.position(oldPosition + size);
             _buffer.limit(oldLimit);
         }
 
@@ -1121,6 +1191,26 @@ public class DecoderImpl implements ByteBufferDecoder
         V decode(DecoderImpl decoder, ReadableBuffer buf);
     }
 
+    private static final class UnknownDescribedTypeConstructor implements DescribedTypeConstructor<Object> {
+        private final Object descriptor;
+
+        private UnknownDescribedTypeConstructor(Object descriptor) {
+            this.descriptor = descriptor;
+        }
+
+        @Override
+        public DescribedType newInstance(final Object described)
+        {
+            return new UnknownDescribedType(descriptor, described);
+        }
+
+        @Override
+        public Class<?> getTypeClass()
+        {
+            return UnknownDescribedType.class;
+        }
+    }
+
     private static class UnknownDescribedType implements DescribedType
     {
         private final Object _descriptor;
@@ -1163,4 +1253,14 @@ public class DecoderImpl implements ByteBufferDecoder
     public int getByteBufferRemaining() {
         return _buffer.remaining();
     }
+
+    @Override
+    public int getZeroWidthArrayElementLimit() {
+        return _zeroWidthElementLimit;
+    }
+
+    @Override
+    public void setZeroWidthArrayElementLimit(int zeroWidthElementLimit) {
+        this._zeroWidthElementLimit = zeroWidthElementLimit;
+    }
 }
diff --git a/proton-j/src/main/java/org/apache/qpid/proton/codec/DynamicTypeConstructor.java b/proton-j/src/main/java/org/apache/qpid/proton/codec/DynamicTypeConstructor.java
index 2f8b9f91..873d8dc1 100644
--- a/proton-j/src/main/java/org/apache/qpid/proton/codec/DynamicTypeConstructor.java
+++ b/proton-j/src/main/java/org/apache/qpid/proton/codec/DynamicTypeConstructor.java
@@ -62,4 +62,15 @@ public class DynamicTypeConstructor implements TypeConstructor
     {
         return _describedTypeConstructor.getTypeClass();
     }
+
+    public TypeConstructor getUnderlyingEncoding()
+    {
+        return _underlyingEncoding;
+    }
+
+    public DescribedTypeConstructor getDescribedTypeConstructor()
+    {
+        return _describedTypeConstructor;
+    }
+
 }
diff --git a/proton-j/src/main/java/org/apache/qpid/proton/codec/EncoderImpl.java b/proton-j/src/main/java/org/apache/qpid/proton/codec/EncoderImpl.java
index 164a77d6..76f475cd 100644
--- a/proton-j/src/main/java/org/apache/qpid/proton/codec/EncoderImpl.java
+++ b/proton-j/src/main/java/org/apache/qpid/proton/codec/EncoderImpl.java
@@ -80,7 +80,12 @@ public final class EncoderImpl implements ByteBufferEncoder
 
     private final ArrayType             _arrayType;
 
-    public EncoderImpl(DecoderImpl decoder)
+    public EncoderImpl(final DecoderImpl decoder)
+    {
+        this(decoder, false);
+    }
+
+    public EncoderImpl(final DecoderImpl decoder, final boolean sasl)
     {
         _decoder                = decoder;
         _nullType               = new NullType(this, decoder);
@@ -107,7 +112,7 @@ public final class EncoderImpl implements ByteBufferEncoder
 
 
         _binaryType             = new BinaryType(this, decoder);
-        _symbolType             = new SymbolType(this, decoder);
+        _symbolType             = new SymbolType(this, decoder, sasl);
         _stringType             = new StringType(this, decoder);
 
         _listType               = new ListType(this, decoder);
diff --git a/proton-j/src/main/java/org/apache/qpid/proton/codec/ListType.java b/proton-j/src/main/java/org/apache/qpid/proton/codec/ListType.java
index 265063cb..14e02584 100644
--- a/proton-j/src/main/java/org/apache/qpid/proton/codec/ListType.java
+++ b/proton-j/src/main/java/org/apache/qpid/proton/codec/ListType.java
@@ -20,6 +20,9 @@
  */
 package org.apache.qpid.proton.codec;
 
+import static org.apache.qpid.proton.codec.ValidationSupport.validateListCount;
+import static org.apache.qpid.proton.codec.ValidationSupport.validateListSize;
+
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collection;
@@ -160,76 +163,98 @@ public class ListType extends AbstractPrimitiveType<List>
             DecoderImpl decoder = getDecoder();
             ReadableBuffer buffer = decoder.getBuffer();
 
-            int size = decoder.readRawInt();
-            // todo - limit the decoder with size
-            int count = decoder.readRawInt();
-            // Ensure we do not allocate an array of size greater then the available data, otherwise there is a risk for an OOM error
-            if (count > decoder.getByteBufferRemaining()) {
-                throw new IllegalArgumentException("List element count "+count+" is specified to be greater than the amount of data available ("+
-                                                   decoder.getByteBufferRemaining()+")");
-            }
+            decoder.increaseDecodeDepth();
+            try
+            {
+                final int size = decoder.readRawInt();
+                validateListSize(size, decoder.getByteBufferRemaining());
 
-            TypeConstructor<?> typeConstructor = null;
+                final int oldLimit = buffer.limit();
+                final int newLimit = buffer.position() + size;
 
-            List<Object> list = new ArrayList<>(count);
-            for (int i = 0; i < count; i++)
-            {
-                boolean arrayType = false;
-                byte encodingCode = buffer.get(buffer.position());
-                switch (encodingCode)
+                List<Object> list = null;
+                try
                 {
-                    case EncodingCodes.ARRAY8:
-                    case EncodingCodes.ARRAY32:
-                        arrayType = true;
-                }
+                    buffer.limit(newLimit);
 
-                // Whenever we can just reuse the previously used TypeDecoder instead
-                // of spending time looking up the same one again.
-                if (typeConstructor == null)
-                {
-                    typeConstructor = getDecoder().readConstructor();
-                }
-                else
-                {
-                    if (encodingCode == EncodingCodes.DESCRIBED_TYPE_INDICATOR || !(typeConstructor instanceof PrimitiveTypeEncoding<?>))
-                    {
-                        typeConstructor = getDecoder().readConstructor();
-                    }
-                    else
+                    final int count = decoder.readRawInt();
+                    validateListCount(size, count, decoder.getByteBufferRemaining());
+
+                    TypeConstructor<?> typeConstructor = null;
+
+                    list = new ArrayList<>(count);
+                    for (int i = 0; i < count; i++)
                     {
-                        PrimitiveTypeEncoding<?> primitiveConstructor = (PrimitiveTypeEncoding<?>) typeConstructor;
-                        if (encodingCode != primitiveConstructor.getEncodingCode())
+                        boolean arrayType = false;
+                        byte encodingCode = buffer.get(buffer.position());
+                        switch (encodingCode)
+                        {
+                        case EncodingCodes.ARRAY8:
+                        case EncodingCodes.ARRAY32:
+                            arrayType = true;
+                        }
+
+                        // Whenever we can just reuse the previously used TypeDecoder instead
+                        // of spending time looking up the same one again.
+                        if (typeConstructor == null)
                         {
                             typeConstructor = getDecoder().readConstructor();
                         }
                         else
                         {
-                            // consume the encoding code byte for real
-                            encodingCode = buffer.get();
+                            if (encodingCode == EncodingCodes.DESCRIBED_TYPE_INDICATOR || !(typeConstructor instanceof PrimitiveTypeEncoding<?>))
+                            {
+                                typeConstructor = getDecoder().readConstructor();
+                            }
+                            else
+                            {
+                                PrimitiveTypeEncoding<?> primitiveConstructor = (PrimitiveTypeEncoding<?>) typeConstructor;
+                                if (encodingCode != primitiveConstructor.getEncodingCode())
+                                {
+                                    typeConstructor = getDecoder().readConstructor();
+                                }
+                                else
+                                {
+                                    // consume the encoding code byte for real
+                                    encodingCode = buffer.get();
+                                }
+                            }
                         }
-                    }
-                }
 
-                if(typeConstructor == null)
-                {
-                    throw new DecodeException("Unknown constructor");
-                }
+                        if(typeConstructor == null)
+                        {
+                            throw new DecodeException("Unknown constructor");
+                        }
 
-                final Object value;
+                        final Object value;
 
-                if (arrayType)
-                {
-                    value = ((ArrayType.ArrayEncoding) typeConstructor).readValueArray();
+                        if (arrayType)
+                        {
+                            value = ((ArrayType.ArrayEncoding) typeConstructor).readValueArray();
+                        }
+                        else
+                        {
+                            value = typeConstructor.readValue();
+                        }
+
+                        list.add(value);
+                    }
+
+                    if (buffer.position() != newLimit) {
+                        throw new DecodeException("List incorrectly encoded, left " + (newLimit - buffer.position()) + " bytes from size indicated " + size);
+                    }
                 }
-                else
+                finally
                 {
-                    value = typeConstructor.readValue();
+                    buffer.limit(oldLimit);
                 }
 
-                list.add(value);
+                return list;
+            }
+            finally
+            {
+                decoder.decreaseDecodeDepth();
             }
-
-            return list;
         }
 
         @Override
@@ -238,6 +263,7 @@ public class ListType extends AbstractPrimitiveType<List>
             DecoderImpl decoder = getDecoder();
             ReadableBuffer buffer = decoder.getBuffer();
             int size = decoder.readRawInt();
+            validateListSize(size, decoder.getByteBufferRemaining());
             buffer.position(buffer.position() + size);
         }
 
@@ -310,71 +336,99 @@ public class ListType extends AbstractPrimitiveType<List>
             DecoderImpl decoder = getDecoder();
             ReadableBuffer buffer = decoder.getBuffer();
 
-            int size = ((int)decoder.readRawByte()) & 0xff;
-            // todo - limit the decoder with size
-            int count = ((int)decoder.readRawByte()) & 0xff;
+            decoder.increaseDecodeDepth();
+            try
+            {
+                int size = ((int)decoder.readRawByte()) & 0xff;
+                validateListSize(size, decoder.getByteBufferRemaining());
 
-            TypeConstructor<?> typeConstructor = null;
+                final int oldLimit = buffer.limit();
+                final int newLimit = buffer.position() + size;
 
-            List<Object> list = new ArrayList<>(count);
-            for (int i = 0; i < count; i++)
-            {
-                boolean arrayType = false;
-                byte encodingCode = buffer.get(buffer.position());
-                switch (encodingCode)
+                List<Object> list = null;
+                try
                 {
-                    case EncodingCodes.ARRAY8:
-                    case EncodingCodes.ARRAY32:
-                        arrayType = true;
-                }
+                    buffer.limit(newLimit);
 
-                // Whenever we can just reuse the previously used TypeDecoder instead
-                // of spending time looking up the same one again.
-                if (typeConstructor == null)
-                {
-                    typeConstructor = getDecoder().readConstructor();
-                }
-                else
-                {
-                    if (encodingCode == EncodingCodes.DESCRIBED_TYPE_INDICATOR || !(typeConstructor instanceof PrimitiveTypeEncoding<?>))
-                    {
-                        typeConstructor = getDecoder().readConstructor();
-                    }
-                    else
+                    int count = ((int)decoder.readRawByte()) & 0xff;
+                    validateListCount(size, count, decoder.getByteBufferRemaining());
+
+                    TypeConstructor<?> typeConstructor = null;
+
+                    list = new ArrayList<>(count);
+                    for (int i = 0; i < count; i++)
                     {
-                        PrimitiveTypeEncoding<?> primitiveConstructor = (PrimitiveTypeEncoding<?>) typeConstructor;
-                        if (encodingCode != primitiveConstructor.getEncodingCode())
+                        boolean arrayType = false;
+                        byte encodingCode = buffer.get(buffer.position());
+                        switch (encodingCode)
+                        {
+                        case EncodingCodes.ARRAY8:
+                        case EncodingCodes.ARRAY32:
+                            arrayType = true;
+                        }
+
+                        // Whenever we can just reuse the previously used TypeDecoder instead
+                        // of spending time looking up the same one again.
+                        if (typeConstructor == null)
                         {
                             typeConstructor = getDecoder().readConstructor();
                         }
                         else
                         {
-                            // consume the encoding code byte for real
-                            encodingCode = buffer.get();
+                            if (encodingCode == EncodingCodes.DESCRIBED_TYPE_INDICATOR || !(typeConstructor instanceof PrimitiveTypeEncoding<?>))
+                            {
+                                typeConstructor = getDecoder().readConstructor();
+                            }
+                            else
+                            {
+                                PrimitiveTypeEncoding<?> primitiveConstructor = (PrimitiveTypeEncoding<?>) typeConstructor;
+                                if (encodingCode != primitiveConstructor.getEncodingCode())
+                                {
+                                    typeConstructor = getDecoder().readConstructor();
+                                }
+                                else
+                                {
+                                    // consume the encoding code byte for real
+                                    encodingCode = buffer.get();
+                                }
+                            }
                         }
-                    }
-                }
 
-                if (typeConstructor == null)
-                {
-                    throw new DecodeException("Unknown constructor");
-                }
+                        if (typeConstructor == null)
+                        {
+                            throw new DecodeException("Unknown constructor");
+                        }
 
-                final Object value;
+                        final Object value;
 
-                if (arrayType)
-                {
-                    value = ((ArrayType.ArrayEncoding) typeConstructor).readValueArray();
+                        if (arrayType)
+                        {
+                            value = ((ArrayType.ArrayEncoding) typeConstructor).readValueArray();
+                        }
+                        else
+                        {
+                            value = typeConstructor.readValue();
+                        }
+
+                        list.add(value);
+                    }
+
+                    if (buffer.position() != newLimit)
+                    {
+                        throw new DecodeException("List incorrectly encoded, left " + (newLimit - buffer.position()) + " bytes from size indicated " + size);
+                    }
                 }
-                else
+                finally
                 {
-                    value = typeConstructor.readValue();
+                    buffer.limit(oldLimit);
                 }
 
-                list.add(value);
+                return list;
+            }
+            finally
+            {
+                decoder.decreaseDecodeDepth();
             }
-
-            return list;
         }
 
         @Override
@@ -383,6 +437,7 @@ public class ListType extends AbstractPrimitiveType<List>
             DecoderImpl decoder = getDecoder();
             ReadableBuffer buffer = decoder.getBuffer();
             int size = ((int)decoder.readRawByte()) & 0xff;
+            validateListSize(size, decoder.getByteBufferRemaining());
             buffer.position(buffer.position() + size);
         }
 
@@ -441,7 +496,16 @@ public class ListType extends AbstractPrimitiveType<List>
         @Override
         public List readValue()
         {
-            return Collections.EMPTY_LIST;
+            DecoderImpl decoder = getDecoder();
+            decoder.increaseDecodeDepth();
+            try
+            {
+                return Collections.EMPTY_LIST;
+            }
+            finally
+            {
+                decoder.decreaseDecodeDepth();
+            }
         }
     }
 }
diff --git a/proton-j/src/main/java/org/apache/qpid/proton/codec/MapType.java b/proton-j/src/main/java/org/apache/qpid/proton/codec/MapType.java
index d73351e0..ed996585 100644
--- a/proton-j/src/main/java/org/apache/qpid/proton/codec/MapType.java
+++ b/proton-j/src/main/java/org/apache/qpid/proton/codec/MapType.java
@@ -20,6 +20,9 @@
  */
 package org.apache.qpid.proton.codec;
 
+import static org.apache.qpid.proton.codec.ValidationSupport.validateMapCount;
+import static org.apache.qpid.proton.codec.ValidationSupport.validateMapSize;
+
 import java.util.Arrays;
 import java.util.Collection;
 import java.util.Iterator;
@@ -246,58 +249,82 @@ public class MapType extends AbstractPrimitiveType<Map>
             final DecoderImpl decoder = getDecoder();
             final ReadableBuffer buffer = decoder.getBuffer();
 
-            final int size = decoder.readRawInt();
-            // todo - limit the decoder with size
-            final int count = decoder.readRawInt();
-            if (count > decoder.getByteBufferRemaining()) {
-                throw new IllegalArgumentException("Map element count "+count+" is specified to be greater than the amount of data available ("+
-                                                   decoder.getByteBufferRemaining()+")");
-            }
-
-            TypeConstructor<?> keyConstructor = null;
-            TypeConstructor<?> valueConstructor = null;
-
-            Map<Object, Object> map = new LinkedHashMap<>(count);
-            for(int i = 0; i < count / 2; i++)
+            decoder.increaseDecodeDepth();
+            try
             {
-                keyConstructor = findNextDecoder(decoder, buffer, keyConstructor);
-                if(keyConstructor == null)
-                {
-                    throw new DecodeException("Unknown constructor");
-                }
+                final int size = decoder.readRawInt();
+                validateMapSize(size, decoder.getByteBufferRemaining());
 
-                final Object key = keyConstructor.readValue();
+                final int oldLimit = buffer.limit();
+                final int newLimit = buffer.position() + size;
 
-                boolean arrayType = false;
-                final byte code = buffer.get(buffer.position());
-                switch (code)
+                Map<Object, Object> map = null;
+                try
                 {
-                    case EncodingCodes.ARRAY8:
-                    case EncodingCodes.ARRAY32:
-                        arrayType = true;
-                }
+                    buffer.limit(newLimit);
 
-                valueConstructor = findNextDecoder(decoder, buffer, valueConstructor);
-                if (valueConstructor == null)
-                {
-                    throw new DecodeException("Unknown constructor");
-                }
+                    final int count = decoder.readRawInt();
+                    validateMapCount(size, count, decoder.getByteBufferRemaining());
 
-                final Object value;
+                    TypeConstructor<?> keyConstructor = null;
+                    TypeConstructor<?> valueConstructor = null;
 
-                if (arrayType)
-                {
-                    value = ((ArrayType.ArrayEncoding) valueConstructor).readValueArray();
+                    map = new LinkedHashMap<>(count);
+                    for(int i = 0; i < count / 2; i++)
+                    {
+                        keyConstructor = findNextDecoder(decoder, buffer, keyConstructor);
+                        if(keyConstructor == null)
+                        {
+                            throw new DecodeException("Unknown constructor");
+                        }
+
+                        final Object key = keyConstructor.readValue();
+
+                        boolean arrayType = false;
+                        final byte code = buffer.get(buffer.position());
+                        switch (code)
+                        {
+                        case EncodingCodes.ARRAY8:
+                        case EncodingCodes.ARRAY32:
+                            arrayType = true;
+                        }
+
+                        valueConstructor = findNextDecoder(decoder, buffer, valueConstructor);
+                        if (valueConstructor == null)
+                        {
+                            throw new DecodeException("Unknown constructor");
+                        }
+
+                        final Object value;
+
+                        if (arrayType)
+                        {
+                            value = ((ArrayType.ArrayEncoding) valueConstructor).readValueArray();
+                        }
+                        else
+                        {
+                            value = valueConstructor.readValue();
+                        }
+
+                        map.put(key, value);
+                    }
+
+                    if (buffer.position() != newLimit)
+                    {
+                        throw new DecodeException("Map incorrectly encoded, left " + (newLimit - buffer.position()) + " bytes from size indicated " + size);
+                    }
                 }
-                else
+                finally
                 {
-                    value = valueConstructor.readValue();
+                    buffer.limit(oldLimit);
                 }
 
-                map.put(key, value);
+                return map;
+            }
+            finally
+            {
+                decoder.decreaseDecodeDepth();
             }
-
-            return map;
         }
 
         @Override
@@ -306,6 +333,7 @@ public class MapType extends AbstractPrimitiveType<Map>
             final DecoderImpl decoder = getDecoder();
             final ReadableBuffer buffer = decoder.getBuffer();
             final int size = decoder.readRawInt();
+            validateMapSize(size, decoder.getByteBufferRemaining());
             buffer.position(buffer.position() + size);
         }
 
@@ -398,54 +426,82 @@ public class MapType extends AbstractPrimitiveType<Map>
             final DecoderImpl decoder = getDecoder();
             final ReadableBuffer buffer = decoder.getBuffer();
 
-            final int size = (decoder.readRawByte()) & 0xff;
-            // todo - limit the decoder with size
-            final int count = (decoder.readRawByte()) & 0xff;
-
-            TypeConstructor<?> keyConstructor = null;
-            TypeConstructor<?> valueConstructor = null;
-
-            final Map<Object, Object> map = new LinkedHashMap<>(count);
-            for(int i = 0; i < count / 2; i++)
+            decoder.increaseDecodeDepth();
+            try
             {
-                keyConstructor = findNextDecoder(decoder, buffer, keyConstructor);
-                if(keyConstructor == null)
-                {
-                    throw new DecodeException("Unknown constructor");
-                }
+                final int size = (decoder.readRawByte()) & 0xff;
+                validateMapSize(size, decoder.getByteBufferRemaining());
 
-                final Object key = keyConstructor.readValue();
+                final int oldLimit = buffer.limit();
+                final int newLimit = buffer.position() + size;
 
-                boolean arrayType = false;
-                final byte code = buffer.get(buffer.position());
-                switch (code)
+                Map<Object, Object> map = null;
+                try
                 {
-                    case EncodingCodes.ARRAY8:
-                    case EncodingCodes.ARRAY32:
-                        arrayType = true;
-                }
+                    buffer.limit(newLimit);
 
-                valueConstructor = findNextDecoder(decoder, buffer, valueConstructor);
-                if(valueConstructor== null)
-                {
-                    throw new DecodeException("Unknown constructor");
-                }
+                    final int count = (decoder.readRawByte()) & 0xff;
+                    validateMapCount(size, count, decoder.getByteBufferRemaining());
 
-                final Object value;
+                    TypeConstructor<?> keyConstructor = null;
+                    TypeConstructor<?> valueConstructor = null;
 
-                if (arrayType)
-                {
-                    value = ((ArrayType.ArrayEncoding) valueConstructor).readValueArray();
+                    map = new LinkedHashMap<>(count);
+                    for(int i = 0; i < count / 2; i++)
+                    {
+                        keyConstructor = findNextDecoder(decoder, buffer, keyConstructor);
+                        if(keyConstructor == null)
+                        {
+                            throw new DecodeException("Unknown constructor");
+                        }
+
+                        final Object key = keyConstructor.readValue();
+
+                        boolean arrayType = false;
+                        final byte code = buffer.get(buffer.position());
+                        switch (code)
+                        {
+                        case EncodingCodes.ARRAY8:
+                        case EncodingCodes.ARRAY32:
+                            arrayType = true;
+                        }
+
+                        valueConstructor = findNextDecoder(decoder, buffer, valueConstructor);
+                        if(valueConstructor== null)
+                        {
+                            throw new DecodeException("Unknown constructor");
+                        }
+
+                        final Object value;
+
+                        if (arrayType)
+                        {
+                            value = ((ArrayType.ArrayEncoding) valueConstructor).readValueArray();
+                        }
+                        else
+                        {
+                            value = valueConstructor.readValue();
+                        }
+
+                        map.put(key, value);
+                    }
+
+                    if (buffer.position() != newLimit)
+                    {
+                        throw new DecodeException("Map incorrectly encoded, left " + (newLimit - buffer.position()) + " bytes from size indicated " + size);
+                    }
                 }
-                else
+                finally
                 {
-                    value = valueConstructor.readValue();
+                    buffer.limit(oldLimit);
                 }
 
-                map.put(key, value);
+                return map;
+            }
+            finally
+            {
+                decoder.decreaseDecodeDepth();
             }
-
-            return map;
         }
 
         @Override
@@ -454,6 +510,7 @@ public class MapType extends AbstractPrimitiveType<Map>
             final DecoderImpl decoder = getDecoder();
             final ReadableBuffer buffer = decoder.getBuffer();
             final int size = ((int)decoder.readRawByte()) & 0xff;
+            validateMapSize(size, decoder.getByteBufferRemaining());
             buffer.position(buffer.position() + size);
         }
 
diff --git a/proton-j/src/main/java/org/apache/qpid/proton/codec/StringType.java b/proton-j/src/main/java/org/apache/qpid/proton/codec/StringType.java
index e6184b4e..20fe23bf 100644
--- a/proton-j/src/main/java/org/apache/qpid/proton/codec/StringType.java
+++ b/proton-j/src/main/java/org/apache/qpid/proton/codec/StringType.java
@@ -27,6 +27,8 @@ import java.util.Collection;
 
 public class StringType extends AbstractPrimitiveType<String>
 {
+    private static final String TYPE_NAME = "string";
+
     private static final DecoderImpl.TypeDecoder<String> _stringCreator =
         new DecoderImpl.TypeDecoder<String>()
         {
@@ -180,9 +182,10 @@ public class StringType extends AbstractPrimitiveType<String>
         @Override
         public String readValue()
         {
-            DecoderImpl decoder = getDecoder();
-            int size = decoder.readRawInt();
-            return size == 0 ? "" : decoder.readRaw(_stringCreator, size);
+            final DecoderImpl decoder = getDecoder();
+            final int size = decoder.readRawInt();
+
+            return size == 0 ? "" : decoder.readRaw(_stringCreator, size, TYPE_NAME);
         }
 
         @Override
@@ -198,6 +201,7 @@ public class StringType extends AbstractPrimitiveType<String>
             DecoderImpl decoder = getDecoder();
             ReadableBuffer buffer = decoder.getBuffer();
             int size = decoder.readRawInt();
+            ValidationSupport.validateLength(TYPE_NAME, size, buffer.remaining());
             buffer.position(buffer.position() + size);
         }
     }
@@ -250,7 +254,8 @@ public class StringType extends AbstractPrimitiveType<String>
         {
             DecoderImpl decoder = getDecoder();
             int size = ((int)decoder.readRawByte()) & 0xff;
-            return size == 0 ? "" : decoder.readRaw(_stringCreator, size);
+
+            return size == 0 ? "" : decoder.readRaw(_stringCreator, size, TYPE_NAME);
         }
 
         @Override
@@ -266,6 +271,7 @@ public class StringType extends AbstractPrimitiveType<String>
             DecoderImpl decoder = getDecoder();
             ReadableBuffer buffer = decoder.getBuffer();
             int size = ((int)decoder.readRawByte()) & 0xff;
+            ValidationSupport.validateLength(TYPE_NAME, size, buffer.remaining());
             buffer.position(buffer.position() + size);
         }
     }
diff --git a/proton-j/src/main/java/org/apache/qpid/proton/codec/SymbolType.java b/proton-j/src/main/java/org/apache/qpid/proton/codec/SymbolType.java
index faee7b0b..84546260 100644
--- a/proton-j/src/main/java/org/apache/qpid/proton/codec/SymbolType.java
+++ b/proton-j/src/main/java/org/apache/qpid/proton/codec/SymbolType.java
@@ -20,44 +20,48 @@
  */
 package org.apache.qpid.proton.codec;
 
-import java.nio.charset.Charset;
 import java.util.Arrays;
 import java.util.Collection;
-import java.util.HashMap;
-import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
 
 import org.apache.qpid.proton.amqp.Symbol;
 
+
 public class SymbolType extends AbstractPrimitiveType<Symbol>
 {
-    private static final Charset ASCII_CHARSET = Charset.forName("US-ASCII");
+    private static final int MAX_SYMBOL_CACHE_SIZE = 4096;
+    private static final int MAX_CACHED_SYMBOL_LENGTH = 64;
+
+    private static final int MAX_SASL_SYMBOL_CACHE_SIZE = 64;
+    private static final int MAX_CACHED_SASL_SYMBOL_LENGTH = 32;
+
+    private static final ConcurrentMap<ReadableBuffer, Symbol> _symbolCache = new ConcurrentHashMap<ReadableBuffer, Symbol>(2048);
+    private static final ConcurrentMap<ReadableBuffer, Symbol> _saslSymbolCache = new ConcurrentHashMap<ReadableBuffer, Symbol>(32);
+
+    private static final String TYPE_NAME = "symbol";
+
+    private static final DecoderImpl.TypeDecoder<Symbol> _symbolDecoder = new AbstractSymbolDecoder(MAX_SYMBOL_CACHE_SIZE, MAX_CACHED_SYMBOL_LENGTH, _symbolCache)
+    {
+        @Override
+        public Symbol createSymbol(final String symbolVal)
+        {
+            return Symbol.getSymbol(symbolVal);
+        }
+    };
+
+    private static final DecoderImpl.TypeDecoder<Symbol> _saslSymbolDecoder = new AbstractSymbolDecoder(MAX_SASL_SYMBOL_CACHE_SIZE, MAX_CACHED_SASL_SYMBOL_LENGTH, _saslSymbolCache)
+    {
+        @Override
+        public Symbol createSymbol(final String symbolVal)
+        {
+            return Symbol.getSaslSymbol(symbolVal);
+        }
+    };
+
     private final SymbolEncoding _symbolEncoding;
     private final SymbolEncoding _shortSymbolEncoding;
-
-    private final Map<ReadableBuffer, Symbol> _symbolCache = new HashMap<ReadableBuffer, Symbol>();
-    private DecoderImpl.TypeDecoder<Symbol> _symbolCreator =
-        new DecoderImpl.TypeDecoder<Symbol>()
-        {
-            @Override
-            public Symbol decode(DecoderImpl decoder, ReadableBuffer buffer)
-            {
-                Symbol symbol = _symbolCache.get(buffer);
-                if (symbol == null)
-                {
-                    final byte[] bytes = new byte[buffer.remaining()];
-                    buffer.get(bytes);
-
-                    symbol = Symbol.getSymbol(new String(bytes, ASCII_CHARSET));
-
-                    _symbolCache.put(ReadableBuffer.ByteBufferReader.wrap(bytes), symbol);
-                }
-                else
-                {
-                    buffer.position(buffer.limit());
-                }
-                return symbol;
-            }
-        };
+    private DecoderImpl.TypeDecoder<Symbol> _symbolCreator;
 
     public static interface SymbolEncoding extends PrimitiveTypeEncoding<Symbol>
     {
@@ -65,13 +69,20 @@ public class SymbolType extends AbstractPrimitiveType<Symbol>
     }
 
     SymbolType(final EncoderImpl encoder, final DecoderImpl decoder)
+    {
+        this(encoder, decoder, false);
+    }
+
+    SymbolType(final EncoderImpl encoder, final DecoderImpl decoder, final boolean sasl)
     {
         _symbolEncoding =  new LongSymbolEncoding(encoder, decoder);
         _shortSymbolEncoding = new ShortSymbolEncoding(encoder, decoder);
+        _symbolCreator = sasl ? _saslSymbolDecoder : _symbolDecoder;
         encoder.register(Symbol.class, this);
         decoder.register(this);
     }
 
+
     @Override
     public Class<Symbol> getTypeClass()
     {
@@ -162,7 +173,8 @@ public class SymbolType extends AbstractPrimitiveType<Symbol>
         {
             DecoderImpl decoder = getDecoder();
             int size = decoder.readRawInt();
-            return decoder.readRaw(_symbolCreator, size);
+
+            return decoder.readRaw(_symbolCreator, size, TYPE_NAME);
         }
 
         @Override
@@ -171,6 +183,7 @@ public class SymbolType extends AbstractPrimitiveType<Symbol>
             DecoderImpl decoder = getDecoder();
             ReadableBuffer buffer = decoder.getBuffer();
             int size = decoder.readRawInt();
+            ValidationSupport.validateLength(TYPE_NAME, size, buffer.remaining());
             buffer.position(buffer.position() + size);
         }
     }
@@ -221,7 +234,8 @@ public class SymbolType extends AbstractPrimitiveType<Symbol>
         {
             DecoderImpl decoder = getDecoder();
             int size = ((int)decoder.readRawByte()) & 0xff;
-            return decoder.readRaw(_symbolCreator, size);
+
+            return decoder.readRaw(_symbolCreator, size, TYPE_NAME);
         }
 
         @Override
@@ -230,6 +244,7 @@ public class SymbolType extends AbstractPrimitiveType<Symbol>
             DecoderImpl decoder = getDecoder();
             ReadableBuffer buffer = decoder.getBuffer();
             int size = ((int)decoder.readRawByte()) & 0xff;
+            ValidationSupport.validateLength(TYPE_NAME, size, buffer.remaining());
             buffer.position(buffer.position() + size);
         }
     }
diff --git a/proton-j/src/main/java/org/apache/qpid/proton/codec/ValidationSupport.java b/proton-j/src/main/java/org/apache/qpid/proton/codec/ValidationSupport.java
new file mode 100644
index 00000000..9a958882
--- /dev/null
+++ b/proton-j/src/main/java/org/apache/qpid/proton/codec/ValidationSupport.java
@@ -0,0 +1,110 @@
+/*
+ *
+ * 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.qpid.proton.codec;
+
+public class ValidationSupport {
+
+    private ValidationSupport()
+    {
+        // No instances
+    }
+
+    public static void validateMapSize(final int size, final int remaining)
+    {
+        if (size < 0)
+        {
+            throw new DecodeException("Map size " + size + " cannot be negative");
+        }
+
+        if (size > remaining)
+        {
+            throw new DecodeException("Map size " + size + " cannot be more than remaining bytes " + remaining);
+        }
+    }
+
+    public static void validateMapCount(final int size, final int count, final int remaining)
+    {
+        if (count < 0)
+        {
+            throw new DecodeException("Map element count " + count + " cannot be negative");
+        }
+
+        if (count >= size)
+        {
+            throw new DecodeException("Map element count " + count + " must be less than size (" + size + ")");
+        }
+
+        if (count > remaining)
+        {
+            throw new DecodeException("Map element count " + count + " is specified to be greater than the amount of data available (" + remaining + ")");
+        }
+
+        if (count % 2 != 0)
+        {
+            throw new DecodeException("Map element count " + count + " cannot be odd");
+        }
+    }
+
+
+    public static void validateListSize(final int size, final int remaining)
+    {
+        if (size < 0)
+        {
+            throw new DecodeException("List size " + size + " cannot be negative");
+        }
+
+        if (size > remaining)
+        {
+            throw new DecodeException("List size " + size + " cannot be more than remaining bytes " + remaining);
+        }
+    }
+
+    public static void validateListCount(final int size, final int count, final int remaining)
+    {
+        if (count < 0)
+        {
+            throw new DecodeException("List element count " + count + " cannot be negative");
+        }
+
+        if (count >= size)
+        {
+            throw new DecodeException("List element count " + count + " must be less than size (" + size + ")");
+        }
+
+        if (count > remaining)
+        {
+            throw new DecodeException("List element count " + count + " is specified to be greater than the amount of data available (" + remaining + ")");
+        }
+    }
+
+    public static void validateLength(final String type, final int length, final int remaining)
+    {
+        if (length < 0)
+        {
+            throw new DecodeException(type + " length " + length + " cannot be negative");
+        }
+
+        if (length > remaining)
+        {
+            throw new DecodeException(type + " length " + length + " cannot be more than remaining bytes " + remaining);
+        }
+    }
+}
diff --git a/proton-j/src/main/java/org/apache/qpid/proton/codec/impl/DataDecoder.java b/proton-j/src/main/java/org/apache/qpid/proton/codec/impl/DataDecoder.java
index 71199bb3..e03097b1 100644
--- a/proton-j/src/main/java/org/apache/qpid/proton/codec/impl/DataDecoder.java
+++ b/proton-j/src/main/java/org/apache/qpid/proton/codec/impl/DataDecoder.java
@@ -21,13 +21,23 @@
 
 package org.apache.qpid.proton.codec.impl;
 
+import static org.apache.qpid.proton.codec.impl.DataValidationSupport.validateConstructorSize;
+import static org.apache.qpid.proton.codec.impl.DataValidationSupport.validateListCount;
+import static org.apache.qpid.proton.codec.impl.DataValidationSupport.validateListSize;
+import static org.apache.qpid.proton.codec.impl.DataValidationSupport.validateMapCount;
+import static org.apache.qpid.proton.codec.impl.DataValidationSupport.validateMapSize;
+import static org.apache.qpid.proton.codec.impl.DataValidationSupport.validateSizeInitial;
+
 import java.nio.ByteBuffer;
 import java.nio.charset.Charset;
+import java.nio.charset.StandardCharsets;
+import java.util.Arrays;
 import java.util.Date;
 import java.util.UUID;
 
 import org.apache.qpid.proton.amqp.*;
 import org.apache.qpid.proton.codec.Data;
+import org.apache.qpid.proton.codec.EncodingCodes;
 
 class DataDecoder
 {
@@ -35,6 +45,13 @@ class DataDecoder
     private static final Charset ASCII = Charset.forName("US-ASCII");
     private static final Charset UTF_8 = Charset.forName("UTF-8");
 
+    private static final long AMQP_VALUE_NUMERIC_CODE = 0x0000000000000077L;
+    private static final String AMQP_VALUE_SYMBOLIC_NAME = "amqp:amqp-value:*";
+    private static final int AMQP_VALUE_SYMBOLIC_NAME_LENGTH = AMQP_VALUE_SYMBOLIC_NAME.length();
+    private static final byte[] AMQP_VALUE_SYMBOLIC_NAME_BYTES =AMQP_VALUE_SYMBOLIC_NAME.getBytes(StandardCharsets.US_ASCII);
+    private static final Symbol AMQP_VALUE_SYMBOLIC_DESCRIPTOR = Symbol.valueOf(AMQP_VALUE_SYMBOLIC_NAME);
+    private static final UnsignedLong AMQP_VALUE_NUMERIC_DESCRIPTOR = UnsignedLong.valueOf(AMQP_VALUE_NUMERIC_CODE);
+
     private static final TypeConstructor[] _constructors = new TypeConstructor[256];
 
     static
@@ -99,19 +116,20 @@ class DataDecoder
     {
         Data.DataType getType();
 
-        int size(ByteBuffer b);
+        int size(ByteBuffer b, DataImpl data);
 
-        void parse(ByteBuffer b, Data data);
+        void parse(ByteBuffer b, DataImpl data);
     }
 
 
-    static int decode(ByteBuffer b, Data data)
+    static int decode(ByteBuffer b, DataImpl data)
     {
         if(b.hasRemaining())
         {
             int position = b.position();
             TypeConstructor c = readConstructor(b);
-            final int size = c.size(b);
+            final int size = c.size(b, data);
+
             if(b.remaining() >= size)
             {
                 c.parse(b, data);
@@ -120,12 +138,44 @@ class DataDecoder
             else
             {
                 b.position(position);
-                return -4;
+                return -4; // PN_UNDERFLOW
             }
         }
         return 0;
     }
 
+    private static TypeConstructor readDescriptorConstructor(ByteBuffer b)
+    {
+        final byte type = b.get();
+        if (EncodingCodes.SMALLULONG == type || EncodingCodes.ULONG == type || EncodingCodes.SYM8 == type || EncodingCodes.SYM32 == type)
+        {
+            final int index = type & 0xff;
+            TypeConstructor tc = _constructors[index];
+            if(tc == null)
+            {
+                throw new IllegalArgumentException("No constructor for type " + index);
+            }
+
+            return tc;
+        }
+        else
+        {
+            throw new IllegalArgumentException("Reserved non-ulong/non-symbol described-type descriptors not supported by this decoder");
+        }
+    }
+
+    private static TypeConstructor readDescribedConstructor(ByteBuffer buf, boolean isAmqpValue)
+    {
+        final TypeConstructor constructor = readConstructor(buf);
+
+        if (!isAmqpValue && Data.DataType.DESCRIBED == constructor.getType())
+        {
+            throw new IllegalArgumentException("DescribedType value encoding cannot be another DescribedType, must be a primitive type");
+        }
+
+        return constructor;
+    }
+
     private static TypeConstructor readConstructor(ByteBuffer b)
     {
         int index = b.get() & 0xff;
@@ -137,8 +187,7 @@ class DataDecoder
         return tc;
     }
 
-
-    private static class NullConstructor implements TypeConstructor
+    private static class NullConstructor extends Fixed0SizeConstructor
     {
         @Override
         public Data.DataType getType()
@@ -147,19 +196,13 @@ class DataDecoder
         }
 
         @Override
-        public int size(ByteBuffer b)
-        {
-            return 0;
-        }
-
-        @Override
-        public void parse(ByteBuffer b, Data data)
+        public void parse(ByteBuffer b, DataImpl data)
         {
             data.putNull();
         }
     }
 
-    private static class TrueConstructor implements TypeConstructor
+    private static class TrueConstructor extends Fixed0SizeConstructor
     {
         @Override
         public Data.DataType getType()
@@ -168,20 +211,14 @@ class DataDecoder
         }
 
         @Override
-        public int size(ByteBuffer b)
-        {
-            return 0;
-        }
-
-        @Override
-        public void parse(ByteBuffer b, Data data)
+        public void parse(ByteBuffer b, DataImpl data)
         {
             data.putBoolean(true);
         }
     }
 
 
-    private static class FalseConstructor implements TypeConstructor
+    private static class FalseConstructor extends Fixed0SizeConstructor
     {
 
         @Override
@@ -191,19 +228,13 @@ class DataDecoder
         }
 
         @Override
-        public int size(ByteBuffer b)
-        {
-            return 0;
-        }
-
-        @Override
-        public void parse(ByteBuffer b, Data data)
+        public void parse(ByteBuffer b, DataImpl data)
         {
             data.putBoolean(false);
         }
     }
 
-    private static class UInt0Constructor implements TypeConstructor
+    private static class UInt0Constructor extends Fixed0SizeConstructor
     {
 
         @Override
@@ -213,19 +244,13 @@ class DataDecoder
         }
 
         @Override
-        public int size(ByteBuffer b)
-        {
-            return 0;
-        }
-
-        @Override
-        public void parse(ByteBuffer b, Data data)
+        public void parse(ByteBuffer b, DataImpl data)
         {
             data.putUnsignedInteger(UnsignedInteger.ZERO);
         }
     }
 
-    private static class ULong0Constructor implements TypeConstructor
+    private static class ULong0Constructor extends Fixed0SizeConstructor
     {
 
         @Override
@@ -235,20 +260,14 @@ class DataDecoder
         }
 
         @Override
-        public int size(ByteBuffer b)
-        {
-            return 0;
-        }
-
-        @Override
-        public void parse(ByteBuffer b, Data data)
+        public void parse(ByteBuffer b, DataImpl data)
         {
             data.putUnsignedLong(UnsignedLong.ZERO);
         }
     }
 
 
-    private static class EmptyListConstructor implements TypeConstructor
+    private static class EmptyListConstructor extends Fixed0SizeConstructor
     {
 
         @Override
@@ -258,15 +277,31 @@ class DataDecoder
         }
 
         @Override
-        public int size(ByteBuffer b)
+        public void parse(ByteBuffer b, DataImpl data)
         {
-            return 0;
+            data.increaseDecodeDepth();
+            try
+            {
+                data.putList();
+            }
+            finally
+            {
+                data.decreaseDecodeDepth();
+            }
         }
 
         @Override
-        public void parse(ByteBuffer b, Data data)
+        public int size(ByteBuffer b, DataImpl data)
         {
-            data.putList();
+            data.increaseDecodeDepth();
+            try
+            {
+                return super.size(b, data);
+            }
+            finally
+            {
+                data.decreaseDecodeDepth();
+            }
         }
     }
 
@@ -274,7 +309,7 @@ class DataDecoder
     private static abstract class Fixed0SizeConstructor implements TypeConstructor
     {
         @Override
-        public final int size(ByteBuffer b)
+        public int size(ByteBuffer b, DataImpl data)
         {
             return 0;
         }
@@ -283,7 +318,7 @@ class DataDecoder
     private static abstract class Fixed1SizeConstructor implements TypeConstructor
     {
         @Override
-        public int size(ByteBuffer b)
+        public int size(ByteBuffer b, DataImpl data)
         {
             return 1;
         }
@@ -292,7 +327,7 @@ class DataDecoder
     private static abstract class Fixed2SizeConstructor implements TypeConstructor
     {
         @Override
-        public int size(ByteBuffer b)
+        public int size(ByteBuffer b, DataImpl data)
         {
             return 2;
         }
@@ -301,7 +336,7 @@ class DataDecoder
     private static abstract class Fixed4SizeConstructor implements TypeConstructor
     {
         @Override
-        public int size(ByteBuffer b)
+        public int size(ByteBuffer b, DataImpl data)
         {
             return 4;
         }
@@ -310,7 +345,7 @@ class DataDecoder
     private static abstract class Fixed8SizeConstructor implements TypeConstructor
     {
         @Override
-        public int size(ByteBuffer b)
+        public int size(ByteBuffer b, DataImpl data)
         {
             return 8;
         }
@@ -319,7 +354,7 @@ class DataDecoder
     private static abstract class Fixed16SizeConstructor implements TypeConstructor
     {
         @Override
-        public int size(ByteBuffer b)
+        public int size(ByteBuffer b, DataImpl data)
         {
             return 16;
         }
@@ -335,7 +370,7 @@ class DataDecoder
         }
 
         @Override
-        public void parse(ByteBuffer b, Data data)
+        public void parse(ByteBuffer b, DataImpl data)
         {
             data.putUnsignedByte(UnsignedByte.valueOf(b.get()));
         }
@@ -351,7 +386,7 @@ class DataDecoder
         }
 
         @Override
-        public void parse(ByteBuffer b, Data data)
+        public void parse(ByteBuffer b, DataImpl data)
         {
             data.putByte(b.get());
         }
@@ -367,7 +402,7 @@ class DataDecoder
         }
 
         @Override
-        public void parse(ByteBuffer b, Data data)
+        public void parse(ByteBuffer b, DataImpl data)
         {
             data.putUnsignedInteger(UnsignedInteger.valueOf(((int) b.get()) & 0xff));
         }
@@ -383,7 +418,7 @@ class DataDecoder
         }
 
         @Override
-        public void parse(ByteBuffer b, Data data)
+        public void parse(ByteBuffer b, DataImpl data)
         {
             data.putInt(b.get());
         }
@@ -399,7 +434,7 @@ class DataDecoder
         }
 
         @Override
-        public void parse(ByteBuffer b, Data data)
+        public void parse(ByteBuffer b, DataImpl data)
         {
             data.putUnsignedLong(UnsignedLong.valueOf(((int) b.get()) & 0xff));
         }
@@ -415,7 +450,7 @@ class DataDecoder
         }
 
         @Override
-        public void parse(ByteBuffer b, Data data)
+        public void parse(ByteBuffer b, DataImpl data)
         {
             data.putLong(b.get());
         }
@@ -431,7 +466,7 @@ class DataDecoder
         }
 
         @Override
-        public void parse(ByteBuffer b, Data data)
+        public void parse(ByteBuffer b, DataImpl data)
         {
             int i = b.get();
             if(i != 0 && i != 1)
@@ -452,7 +487,7 @@ class DataDecoder
         }
 
         @Override
-        public void parse(ByteBuffer b, Data data)
+        public void parse(ByteBuffer b, DataImpl data)
         {
             data.putUnsignedShort(UnsignedShort.valueOf(b.getShort()));
         }
@@ -468,7 +503,7 @@ class DataDecoder
         }
 
         @Override
-        public void parse(ByteBuffer b, Data data)
+        public void parse(ByteBuffer b, DataImpl data)
         {
             data.putShort(b.getShort());
         }
@@ -484,7 +519,7 @@ class DataDecoder
         }
 
         @Override
-        public void parse(ByteBuffer b, Data data)
+        public void parse(ByteBuffer b, DataImpl data)
         {
             data.putUnsignedInteger(UnsignedInteger.valueOf(b.getInt()));
         }
@@ -500,7 +535,7 @@ class DataDecoder
         }
 
         @Override
-        public void parse(ByteBuffer b, Data data)
+        public void parse(ByteBuffer b, DataImpl data)
         {
             data.putInt(b.getInt());
         }
@@ -516,7 +551,7 @@ class DataDecoder
         }
 
         @Override
-        public void parse(ByteBuffer b, Data data)
+        public void parse(ByteBuffer b, DataImpl data)
         {
             data.putFloat(b.getFloat());
         }
@@ -532,7 +567,7 @@ class DataDecoder
         }
 
         @Override
-        public void parse(ByteBuffer b, Data data)
+        public void parse(ByteBuffer b, DataImpl data)
         {
             data.putChar(b.getInt());
         }
@@ -548,7 +583,7 @@ class DataDecoder
         }
 
         @Override
-        public void parse(ByteBuffer b, Data data)
+        public void parse(ByteBuffer b, DataImpl data)
         {
             data.putDecimal32(new Decimal32(b.getInt()));
         }
@@ -564,7 +599,7 @@ class DataDecoder
         }
 
         @Override
-        public void parse(ByteBuffer b, Data data)
+        public void parse(ByteBuffer b, DataImpl data)
         {
             data.putUnsignedLong(UnsignedLong.valueOf(b.getLong()));
         }
@@ -580,7 +615,7 @@ class DataDecoder
         }
 
         @Override
-        public void parse(ByteBuffer b, Data data)
+        public void parse(ByteBuffer b, DataImpl data)
         {
             data.putLong(b.getLong());
         }
@@ -596,7 +631,7 @@ class DataDecoder
         }
 
         @Override
-        public void parse(ByteBuffer b, Data data)
+        public void parse(ByteBuffer b, DataImpl data)
         {
             data.putDouble(b.getDouble());
         }
@@ -612,7 +647,7 @@ class DataDecoder
         }
 
         @Override
-        public void parse(ByteBuffer b, Data data)
+        public void parse(ByteBuffer b, DataImpl data)
         {
             data.putTimestamp(new Date(b.getLong()));
         }
@@ -628,7 +663,7 @@ class DataDecoder
         }
 
         @Override
-        public void parse(ByteBuffer b, Data data)
+        public void parse(ByteBuffer b, DataImpl data)
         {
             data.putDecimal64(new Decimal64(b.getLong()));
         }
@@ -644,7 +679,7 @@ class DataDecoder
         }
 
         @Override
-        public void parse(ByteBuffer b, Data data)
+        public void parse(ByteBuffer b, DataImpl data)
         {
             data.putDecimal128(new Decimal128(b.getLong(), b.getLong()));
         }
@@ -660,7 +695,7 @@ class DataDecoder
         }
 
         @Override
-        public void parse(ByteBuffer b, Data data)
+        public void parse(ByteBuffer b, DataImpl data)
         {
             data.putUUID(new UUID(b.getLong(), b.getLong()));
         }
@@ -670,15 +705,16 @@ class DataDecoder
     {
 
         @Override
-        public int size(ByteBuffer b)
+        public int size(ByteBuffer b, DataImpl data)
         {
             int position = b.position();
             if(b.hasRemaining())
             {
                 int size = b.get() & 0xff;
+                validateTypeSize(size);
                 b.position(position);
 
-                return size+1;
+                return validateConstructorSize(size+1);
             }
             else
             {
@@ -686,21 +722,23 @@ class DataDecoder
             }
         }
 
+        public abstract void validateTypeSize(int size);
     }
 
     private static abstract class VariableConstructor implements TypeConstructor
     {
 
         @Override
-        public int size(ByteBuffer b)
+        public int size(ByteBuffer b, DataImpl data)
         {
             int position = b.position();
             if(b.remaining()>=4)
             {
                 int size = b.getInt();
+                validateTypeSize(size);
                 b.position(position);
 
-                return size+4;
+                return validateConstructorSize(size+4);
             }
             else
             {
@@ -708,11 +746,12 @@ class DataDecoder
             }
         }
 
+        public abstract void validateTypeSize(int size);
     }
 
-
     private static class SmallBinaryConstructor extends SmallVariableConstructor
     {
+        private static final String TYPE = "Binary";
 
         @Override
         public Data.DataType getType()
@@ -721,17 +760,24 @@ class DataDecoder
         }
 
         @Override
-        public void parse(ByteBuffer b, Data data)
+        public void parse(ByteBuffer b, DataImpl data)
         {
             int size = b.get() & 0xff;
+            DataValidationSupport.validateSize(TYPE, size, b.remaining());
             byte[] bytes = new byte[size];
             b.get(bytes);
             data.putBinary(bytes);
         }
+
+        @Override
+        public void validateTypeSize(int size) {
+            validateSizeInitial(TYPE, size);
+        }
     }
 
     private static class SmallSymbolConstructor extends SmallVariableConstructor
     {
+        private static final String TYPE = "Symbol";
 
         @Override
         public Data.DataType getType()
@@ -740,18 +786,25 @@ class DataDecoder
         }
 
         @Override
-        public void parse(ByteBuffer b, Data data)
+        public void parse(ByteBuffer b, DataImpl data)
         {
             int size = b.get() & 0xff;
+            DataValidationSupport.validateSize(TYPE, size, b.remaining());
             byte[] bytes = new byte[size];
             b.get(bytes);
             data.putSymbol(Symbol.valueOf(new String(bytes, ASCII)));
         }
+
+        @Override
+        public void validateTypeSize(int size) {
+            validateSizeInitial(TYPE, size);
+        }
     }
 
 
     private static class SmallStringConstructor extends SmallVariableConstructor
     {
+        private static final String TYPE = "String";
 
         @Override
         public Data.DataType getType()
@@ -760,17 +813,24 @@ class DataDecoder
         }
 
         @Override
-        public void parse(ByteBuffer b, Data data)
+        public void parse(ByteBuffer b, DataImpl data)
         {
             int size = b.get() & 0xff;
+            DataValidationSupport.validateSize(TYPE, size, b.remaining());
             byte[] bytes = new byte[size];
             b.get(bytes);
             data.putString(new String(bytes, UTF_8));
         }
+
+        @Override
+        public void validateTypeSize(int size) {
+            validateSizeInitial(TYPE, size);
+        }
     }
 
     private static class BinaryConstructor extends VariableConstructor
     {
+        private static final String TYPE = "Binary";
 
         @Override
         public Data.DataType getType()
@@ -779,17 +839,24 @@ class DataDecoder
         }
 
         @Override
-        public void parse(ByteBuffer b, Data data)
+        public void parse(ByteBuffer b, DataImpl data)
         {
             int size = b.getInt();
+            DataValidationSupport.validateSize(TYPE, size, b.remaining());
             byte[] bytes = new byte[size];
             b.get(bytes);
             data.putBinary(bytes);
         }
+
+        @Override
+        public void validateTypeSize(int size) {
+            DataValidationSupport.validateSizeInitial(TYPE, size);
+        }
     }
 
     private static class SymbolConstructor extends VariableConstructor
     {
+        private static final String TYPE = "Symbol";
 
         @Override
         public Data.DataType getType()
@@ -798,18 +865,25 @@ class DataDecoder
         }
 
         @Override
-        public void parse(ByteBuffer b, Data data)
+        public void parse(ByteBuffer b, DataImpl data)
         {
             int size = b.getInt();
+            DataValidationSupport.validateSize(TYPE, size, b.remaining());
             byte[] bytes = new byte[size];
             b.get(bytes);
             data.putSymbol(Symbol.valueOf(new String(bytes, ASCII)));
         }
+
+        @Override
+        public void validateTypeSize(int size) {
+            DataValidationSupport.validateSizeInitial(TYPE, size);
+        }
     }
 
 
     private static class StringConstructor extends VariableConstructor
     {
+        private static final String TYPE = "String";
 
         @Override
         public Data.DataType getType()
@@ -818,18 +892,25 @@ class DataDecoder
         }
 
         @Override
-        public void parse(ByteBuffer b, Data data)
+        public void parse(ByteBuffer b, DataImpl data)
         {
             int size = b.getInt();
+            DataValidationSupport.validateSize(TYPE, size, b.remaining());
             byte[] bytes = new byte[size];
             b.get(bytes);
             data.putString(new String(bytes, UTF_8));
         }
+
+        @Override
+        public void validateTypeSize(int size) {
+            DataValidationSupport.validateSizeInitial(TYPE, size);
+        }
     }
 
 
     private static class SmallListConstructor extends SmallVariableConstructor
     {
+        private static final String TYPE = "List";
 
         @Override
         public Data.DataType getType()
@@ -838,21 +919,51 @@ class DataDecoder
         }
 
         @Override
-        public void parse(ByteBuffer b, Data data)
+        public void parse(ByteBuffer b, DataImpl data)
         {
-            int size = b.get() & 0xff;
-            ByteBuffer buf = b.slice();
-            buf.limit(size);
-            b.position(b.position()+size);
-            int count = buf.get() & 0xff;
-            data.putList();
-            parseChildren(data, buf, count);
+            data.increaseDecodeDepth();
+            try
+            {
+                int size = b.get() & 0xff;
+                validateListSize(size, b.remaining());
+                ByteBuffer buf = b.slice();
+                buf.limit(size);
+                b.position(b.position()+size);
+                int count = buf.get() & 0xff;
+                validateListCount(size, count, buf.remaining());
+                data.putList();
+                parseChildren(data, buf, count, TYPE, size);
+            }
+            finally
+            {
+                data.decreaseDecodeDepth();
+            }
+        }
+
+        @Override
+        public int size(ByteBuffer b, DataImpl data)
+        {
+            data.increaseDecodeDepth();
+            try
+            {
+                return super.size(b, data);
+            }
+            finally
+            {
+                data.decreaseDecodeDepth();
+            }
+        }
+
+        @Override
+        public void validateTypeSize(int size) {
+            validateSizeInitial(TYPE, size);
         }
     }
 
 
     private static class SmallMapConstructor extends SmallVariableConstructor
     {
+        private static final String TYPE = "Map";
 
         @Override
         public Data.DataType getType()
@@ -861,21 +972,51 @@ class DataDecoder
         }
 
         @Override
-        public void parse(ByteBuffer b, Data data)
+        public void parse(ByteBuffer b, DataImpl data)
         {
-            int size = b.get() & 0xff;
-            ByteBuffer buf = b.slice();
-            buf.limit(size);
-            b.position(b.position()+size);
-            int count = buf.get() & 0xff;
-            data.putMap();
-            parseChildren(data, buf, count);
+            data.increaseDecodeDepth();
+            try
+            {
+                int size = b.get() & 0xff;
+                validateMapSize(size, b.remaining());
+                ByteBuffer buf = b.slice();
+                buf.limit(size);
+                b.position(b.position()+size);
+                int count = buf.get() & 0xff;
+                validateMapCount(size, count, buf.remaining());
+                data.putMap();
+                parseChildren(data, buf, count, TYPE, size);
+            }
+            finally
+            {
+                data.decreaseDecodeDepth();
+            }
+        }
+
+        @Override
+        public int size(ByteBuffer b, DataImpl data)
+        {
+            data.increaseDecodeDepth();
+            try
+            {
+                return super.size(b, data);
+            }
+            finally
+            {
+                data.decreaseDecodeDepth();
+            }
+        }
+
+        @Override
+        public void validateTypeSize(int size) {
+            validateSizeInitial(TYPE, size);
         }
     }
 
 
     private static class ListConstructor extends VariableConstructor
     {
+        private static final String TYPE = "List";
 
         @Override
         public Data.DataType getType()
@@ -884,21 +1025,51 @@ class DataDecoder
         }
 
         @Override
-        public void parse(ByteBuffer b, Data data)
+        public void parse(ByteBuffer b, DataImpl data)
         {
-            int size = b.getInt();
-            ByteBuffer buf = b.slice();
-            buf.limit(size);
-            b.position(b.position()+size);
-            int count = buf.getInt();
-            data.putList();
-            parseChildren(data, buf, count);
+            data.increaseDecodeDepth();
+            try
+            {
+                int size = b.getInt();
+                validateListSize(size, b.remaining());
+                ByteBuffer buf = b.slice();
+                buf.limit(size);
+                b.position(b.position()+size);
+                int count = buf.getInt();
+                validateListCount(size, count, buf.remaining());
+                data.putList();
+                parseChildren(data, buf, count, TYPE, size);
+            }
+            finally
+            {
+                data.decreaseDecodeDepth();
+            }
+        }
+
+        @Override
+        public int size(ByteBuffer b, DataImpl data)
+        {
+            data.increaseDecodeDepth();
+            try
+            {
+                return super.size(b, data);
+            }
+            finally
+            {
+                data.decreaseDecodeDepth();
+            }
+        }
+
+        @Override
+        public void validateTypeSize(int size) {
+            DataValidationSupport.validateSizeInitial(TYPE, size);
         }
     }
 
 
     private static class MapConstructor extends VariableConstructor
     {
+        private static final String TYPE = "Map";
 
         @Override
         public Data.DataType getType()
@@ -907,26 +1078,55 @@ class DataDecoder
         }
 
         @Override
-        public void parse(ByteBuffer b, Data data)
+        public void parse(ByteBuffer b, DataImpl data)
         {
-            int size = b.getInt();
-            ByteBuffer buf = b.slice();
-            buf.limit(size);
-            b.position(b.position()+size);
-            int count = buf.getInt();
-            data.putMap();
-            parseChildren(data, buf, count);
+            data.increaseDecodeDepth();
+            try
+            {
+                int size = b.getInt();
+                validateMapSize(size, b.remaining());
+                ByteBuffer buf = b.slice();
+                buf.limit(size);
+                b.position(b.position()+size);
+                int count = buf.getInt();
+                validateMapCount(size, count, buf.remaining());
+                data.putMap();
+                parseChildren(data, buf, count, TYPE, size);
+            }
+            finally
+            {
+                data.decreaseDecodeDepth();
+            }
+        }
+
+        @Override
+        public int size(ByteBuffer b, DataImpl data)
+        {
+            data.increaseDecodeDepth();
+            try
+            {
+                return super.size(b, data);
+            }
+            finally
+            {
+                data.decreaseDecodeDepth();
+            }
+        }
+
+        @Override
+        public void validateTypeSize(int size) {
+            DataValidationSupport.validateSizeInitial(TYPE, size);
         }
     }
 
 
-    private static void parseChildren(Data data, ByteBuffer buf, int count)
+    private static void parseChildren(DataImpl data, ByteBuffer buf, int count, String type, int parentSize)
     {
         data.enter();
         for(int i = 0; i < count; i++)
         {
             TypeConstructor c = readConstructor(buf);
-            final int size = c.size(buf);
+            final int size = c.size(buf, data);
             final int remaining = buf.remaining();
             if(size <= remaining)
             {
@@ -936,14 +1136,18 @@ class DataDecoder
             {
                 throw new IllegalArgumentException("Malformed data");
             }
+        }
 
+        if (buf.position() != parentSize)
+        {
+            throw new IllegalArgumentException(type + " incorrectly encoded, left " + (buf.remaining()) + " bytes from size indicated " + parentSize);
         }
+
         data.exit();
     }
 
     private static class DescribedTypeConstructor implements TypeConstructor
     {
-
         @Override
         public Data.DataType getType()
         {
@@ -951,46 +1155,97 @@ class DataDecoder
         }
 
         @Override
-        public int size(ByteBuffer b)
+        public int size(ByteBuffer b, DataImpl data)
         {
             ByteBuffer buf = b.slice();
             if(buf.hasRemaining())
             {
-                TypeConstructor c = readConstructor(buf);
-                int size = c.size(buf);
-                if(buf.remaining()>size)
+                TypeConstructor c = readDescriptorConstructor(buf);
+                int descriptorConstructorSize = c.size(buf, data);
+                long dtConstructorSize = 1L + descriptorConstructorSize; // 1 more for the type code in readDescriptorConstructor
+
+                if(buf.remaining() > descriptorConstructorSize)
                 {
-                    buf.position(size + 1);
-                    c = readConstructor(buf);
-                    return size + 2 + c.size(buf);
+                    buf.position(buf.position() + descriptorConstructorSize);
+
+                    final ByteBuffer descriptorCheckBuf = b.slice(); // slice of original buffer b, not buf!
+                    final boolean isAmqpValue = isAmqpValueDescriptor(descriptorCheckBuf);
+
+                    c = readDescribedConstructor(buf, isAmqpValue);
+
+                    final boolean countOwnDepth = isAmqpValue && Data.DataType.DESCRIBED.equals(c.getType());
+                    if (countOwnDepth)
+                    {
+                        data.increaseDecodeDepth();
+                    }
+                    try
+                    {
+                        int describedConstructorSize = c.size(buf, data);
+                        dtConstructorSize += 1L + describedConstructorSize; // 1  more for the type code in readDescribedConstructor
+                    }
+                    finally
+                    {
+                        if (countOwnDepth)
+                        {
+                            data.decreaseDecodeDepth();
+                        }
+                    }
                 }
                 else
                 {
-                    return size + 2;
+                    dtConstructorSize += 1; // another 1 more for the described constructor type code not yet read
+                }
+
+                if (dtConstructorSize > Integer.MAX_VALUE) {
+                    throw new IllegalArgumentException("DescribedTypeConstructor total size cannot exceed " + Integer.MAX_VALUE + " but was: " + dtConstructorSize);
                 }
+
+                return validateConstructorSize((int) dtConstructorSize);
             }
             else
             {
                 return 1;
             }
-
         }
 
         @Override
-        public void parse(ByteBuffer b, Data data)
+        public void parse(ByteBuffer b, DataImpl data)
         {
             data.putDescribed();
             data.enter();
-            TypeConstructor c = readConstructor(b);
-            c.parse(b, data);
-            c = readConstructor(b);
+
+            TypeConstructor c = readDescriptorConstructor(b);
+
             c.parse(b, data);
-            data.exit();
+
+            Object current = data.getObject();
+            boolean isAmqpValue = AMQP_VALUE_NUMERIC_DESCRIPTOR.equals(current) || AMQP_VALUE_SYMBOLIC_DESCRIPTOR.equals(current);
+
+            c = readDescribedConstructor(b, isAmqpValue);
+
+            boolean countOwnDepth = isAmqpValue && Data.DataType.DESCRIBED.equals(c.getType());
+            if (countOwnDepth)
+            {
+                data.increaseDecodeDepth();
+            }
+            try
+            {
+                c.parse(b, data);
+                data.exit();
+            }
+            finally
+            {
+                if (countOwnDepth)
+                {
+                    data.decreaseDecodeDepth();
+                }
+            }
         }
     }
 
     private static class SmallArrayConstructor extends SmallVariableConstructor
     {
+        private static final String TYPE = "Array";
 
         @Override
         public Data.DataType getType()
@@ -999,21 +1254,50 @@ class DataDecoder
         }
 
         @Override
-        public void parse(ByteBuffer b, Data data)
+        public void parse(ByteBuffer b, DataImpl data)
         {
+            data.increaseDecodeDepth();
+            try
+            {
+                int size = b.get() & 0xff;
+                validateArraySizeInitial(size, b.remaining());
+                ByteBuffer buf = b.slice();
+                buf.limit(size);
+                b.position(b.position()+size);
+                int count = buf.get() & 0xff;
+                validateArrayCountInitial(count);
+
+                parseArray(data, buf, count, size);
+            }
+            finally
+            {
+                data.decreaseDecodeDepth();
+            }
+        }
 
-            int size = b.get() & 0xff;
-            ByteBuffer buf = b.slice();
-            buf.limit(size);
-            b.position(b.position()+size);
-            int count = buf.get() & 0xff;
-            parseArray(data, buf, count);
+        @Override
+        public int size(ByteBuffer b, DataImpl data)
+        {
+            data.increaseDecodeDepth();
+            try
+            {
+                return super.size(b, data);
+            }
+            finally
+            {
+                data.decreaseDecodeDepth();
+            }
         }
 
+        @Override
+        public void validateTypeSize(int size) {
+            validateSizeInitial(TYPE, size);
+        }
     }
 
     private static class ArrayConstructor extends VariableConstructor
     {
+        private static final String TYPE = "Array";
 
         @Override
         public Data.DataType getType()
@@ -1023,52 +1307,198 @@ class DataDecoder
 
 
         @Override
-        public void parse(ByteBuffer b, Data data)
+        public void parse(ByteBuffer b, DataImpl data)
+        {
+            data.increaseDecodeDepth();
+            try
+            {
+                int size = b.getInt();
+                validateArraySizeInitial(size, b.remaining());
+                ByteBuffer buf = b.slice();
+                buf.limit(size);
+                b.position(b.position()+size);
+                int count = buf.getInt();
+                validateArrayCountInitial(count);
+
+                parseArray(data, buf, count, size);
+            }
+            finally
+            {
+                data.decreaseDecodeDepth();
+            }
+        }
+
+        @Override
+        public int size(ByteBuffer b, DataImpl data)
         {
+            data.increaseDecodeDepth();
+            try
+            {
+                return super.size(b, data);
+            }
+            finally
+            {
+                data.decreaseDecodeDepth();
+            }
+        }
 
-            int size = b.getInt();
-            ByteBuffer buf = b.slice();
-            buf.limit(size);
-            b.position(b.position()+size);
-            int count = buf.getInt();
-            parseArray(data, buf, count);
+        @Override
+        public void validateTypeSize(int size) {
+            validateSizeInitial(TYPE, size);
         }
     }
 
-    private static void parseArray(Data data, ByteBuffer buf, int count)
+    private static void parseArray(DataImpl data, ByteBuffer buf, int count, int size)
     {
+        // element constructor
         byte type = buf.get();
-        boolean isDescribed = type == (byte)0x00;
+        boolean isDescribed = isTypeCodeDescribedTypeIndicator(type);
         int descriptorPosition = buf.position();
         if(isDescribed)
         {
-            TypeConstructor descriptorTc = readConstructor(buf);
-            buf.position(buf.position()+descriptorTc.size(buf));
+            TypeConstructor descriptorTc = readDescriptorConstructor(buf);
+            buf.position(buf.position() + descriptorTc.size(buf, data));
             type = buf.get();
-            if(type == (byte)0x00)
+            if(isTypeCodeDescribedTypeIndicator(type))
             {
-                throw new IllegalArgumentException("Malformed array data");
+                throw new IllegalArgumentException("DescribedType value encoding cannot be another DescribedType, must be a primitive type");
             }
-
         }
-        TypeConstructor tc = _constructors[type&0xff];
 
+        // read array / elements
+        TypeConstructor tc = _constructors[type&0xff];
+        checkArraySizeCountConstructor(data, buf.remaining(), tc, count, size);
         data.putArray(isDescribed, tc.getType());
         data.enter();
         if(isDescribed)
         {
             int position = buf.position();
             buf.position(descriptorPosition);
-            TypeConstructor descriptorTc = readConstructor(buf);
-            descriptorTc.parse(buf,data);
+            TypeConstructor descriptorTc = readDescriptorConstructor(buf);
+            descriptorTc.parse(buf, data);
             buf.position(position);
         }
         for(int i = 0; i<count; i++)
         {
-            tc.parse(buf,data);
+            tc.parse(buf, data);
+        }
+
+        if (buf.position() != size)
+        {
+            throw new IllegalArgumentException("Array incorrectly encoded, left " + (buf.remaining()) + " bytes from size indicated " + size);
         }
 
         data.exit();
     }
 
+    private static void validateArraySizeInitial(int size, int remaining)
+    {
+        if (size < 0)
+        {
+            throw new IllegalArgumentException("Array size " + size + " cannot be negative");
+        }
+
+        if (size > remaining)
+        {
+            throw new IllegalArgumentException("Array size " + size + " cannot be more than remaining bytes " + remaining);
+        }
+    }
+
+    private static void validateArrayCountInitial(int count)
+    {
+        if (count < 0)
+        {
+            throw new IllegalArgumentException("Array element count " + count + " cannot be negative");
+        }
+    }
+
+    private static void checkArraySizeCountConstructor(Data data, int remaining, final TypeConstructor constructor, final int count, final int size)
+    {
+        if(constructor instanceof Fixed0SizeConstructor)
+        {
+            if (count > data.getZeroWidthElementLimit())
+            {
+                throw new IllegalArgumentException("Array element count " + count + " is specified to be greater than the allowed 0-width-element limit for arrays ("
+                        + data.getZeroWidthElementLimit() + ")");
+            }
+            else
+            {
+                // Remaining checks are not valid for these cases, return
+                return;
+            }
+        }
+
+        if (count > size)
+        {
+            throw new IllegalArgumentException("Array element count " + count + " is specified to be greater than the size of array (" + size + ")");
+        }
+
+        if (count > remaining)
+        {
+            throw new IllegalArgumentException("Array element count " + count + " is specified to be greater than the amount of data available (" + remaining + ")");
+        }
+    }
+
+    private static boolean isTypeCodeDescribedTypeIndicator(byte type)
+    {
+        return type == (byte)0x00;
+    }
+
+    private static boolean isAmqpValueDescriptor(ByteBuffer buf)
+    {
+        if(buf.hasRemaining())
+        {
+            byte type = buf.get();
+            switch (type) {
+                case EncodingCodes.SMALLULONG:
+                    if(buf.hasRemaining())
+                    {
+                        byte value = buf.get();
+
+                        return value == AMQP_VALUE_NUMERIC_CODE;
+                    }
+                    break;
+                case EncodingCodes.ULONG:
+                    if (buf.remaining() >= 8)
+                    {
+                        long value = buf.getLong();
+
+                        return value == AMQP_VALUE_NUMERIC_CODE;
+                    }
+                    break;
+                case EncodingCodes.SYM8:
+                    if(buf.hasRemaining())
+                    {
+                        int length = buf.get() & 0xFF;
+
+                        return checkSymbolContentIsAmqpValue(buf, length);
+                    }
+                    break;
+                case EncodingCodes.SYM32:
+                    if (buf.remaining() >= 4)
+                    {
+                        int length = buf.getInt();
+                        validateSizeInitial("Symbol", length);
+
+                        return checkSymbolContentIsAmqpValue(buf, length);
+                    }
+                    break;
+                default:
+                    throw new IllegalArgumentException("Reserved non-ulong/non-symbol described-type descriptors not supported by this decoder");
+            }
+        }
+
+        return false;
+    }
+
+    private static boolean checkSymbolContentIsAmqpValue(ByteBuffer buf, int length) {
+        if (length == AMQP_VALUE_SYMBOLIC_NAME_LENGTH && length <= buf.remaining()) {
+            byte[] bytes = new byte[AMQP_VALUE_SYMBOLIC_NAME_LENGTH];
+            buf.get(bytes);
+
+            return Arrays.equals(AMQP_VALUE_SYMBOLIC_NAME_BYTES, bytes);
+        }
+
+        return false;
+    }
 }
\ No newline at end of file
diff --git a/proton-j/src/main/java/org/apache/qpid/proton/codec/impl/DataImpl.java b/proton-j/src/main/java/org/apache/qpid/proton/codec/impl/DataImpl.java
index 5c397297..509affb1 100644
--- a/proton-j/src/main/java/org/apache/qpid/proton/codec/impl/DataImpl.java
+++ b/proton-j/src/main/java/org/apache/qpid/proton/codec/impl/DataImpl.java
@@ -30,11 +30,15 @@ import org.apache.qpid.proton.codec.Data;
 
 public class DataImpl implements Data
 {
+    private static final int DEFAULT_MAX_DECODE_DEPTH = 50;
 
     private Element _first;
     private Element _current;
     private Element _parent;
 
+    private int zeroWidthElementLimit = 0;
+    private int maxDecodeDepth = DEFAULT_MAX_DECODE_DEPTH;
+    private int decodeDepth = 0;
 
     public DataImpl()
     {
@@ -867,4 +871,49 @@ public class DataImpl implements Data
                              System.identityHashCode(_parent),
                              sb);
     }
+
+    @Override
+    public int getZeroWidthElementLimit() {
+        return zeroWidthElementLimit;
+    }
+
+    @Override
+    public void setZeroWidthElementLimit(int zeroWidthElementLimit) {
+        this.zeroWidthElementLimit = zeroWidthElementLimit;
+    }
+
+    /**
+     * Returns the maximum decode depth allowed before error when decoding Maps, Lists,
+     * Arrays, and certain related DescribedTypes from an encoded payload.
+     *
+     * @return the maximum depth limit before error for Maps, Lists, Arrays and related DescribedTypes.
+     */
+    public int getMaxDecodeDepth() {
+        return maxDecodeDepth;
+    }
+
+    /**
+     * Configures the maximum decode depth allowed before error when decoding Maps, Lists,
+     * Arrays, and certain related DescribedTypes from an encoded payload. Default is 50.
+     *
+     * It would be unusual to set this to less than 3 due to nesting of types that make up
+     * the base protocol itself.
+     */
+    public void setMaxDecodeDepth(int maxDecodeDepth) {
+        this.maxDecodeDepth = maxDecodeDepth;
+    }
+
+    void increaseDecodeDepth() {
+        if (++decodeDepth > maxDecodeDepth) {
+            --decodeDepth; // Reset so the unwind goes back to zero
+            throw new IllegalArgumentException("The nesting of types in the object being decoded exceeded the configured limit: " + maxDecodeDepth);
+        }
+    }
+
+    void decreaseDecodeDepth() {
+        if(--decodeDepth < 0) {
+            ++decodeDepth; // Reset so the unwind goes back to zero
+            throw new IllegalStateException("Decode depth cannot go negative");
+        }
+    }
 }
diff --git a/proton-j/src/main/java/org/apache/qpid/proton/codec/impl/DataValidationSupport.java b/proton-j/src/main/java/org/apache/qpid/proton/codec/impl/DataValidationSupport.java
new file mode 100644
index 00000000..b37a757c
--- /dev/null
+++ b/proton-j/src/main/java/org/apache/qpid/proton/codec/impl/DataValidationSupport.java
@@ -0,0 +1,128 @@
+/*
+ *
+ * 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.qpid.proton.codec.impl;
+
+public class DataValidationSupport {
+
+    private DataValidationSupport()
+    {
+        // No instances
+    }
+
+    public static void validateMapSize(final int size, final int remaining)
+    {
+        if (size < 0)
+        {
+            throw new IllegalArgumentException("Map size " + size + " cannot be negative");
+        }
+
+        if (size > remaining)
+        {
+            throw new IllegalArgumentException("Map size " + size + " cannot be more than remaining bytes " + remaining);
+        }
+    }
+
+    public static void validateMapCount(final int size, final int count, final int remaining)
+    {
+        if (count < 0)
+        {
+            throw new IllegalArgumentException("Map element count " + count + " cannot be negative");
+        }
+
+        if (count >= size)
+        {
+            throw new IllegalArgumentException("Map element count " + count + " must be less than size (" + size + ")");
+        }
+
+        if (count > remaining)
+        {
+            throw new IllegalArgumentException("Map element count " + count + " is specified to be greater than the amount of data available (" + remaining + ")");
+        }
+
+        if (count % 2 != 0)
+        {
+            throw new IllegalArgumentException("Map element count " + count + " cannot be odd");
+        }
+    }
+
+
+    public static void validateListSize(final int size, final int remaining)
+    {
+        if (size < 0)
+        {
+            throw new IllegalArgumentException("List size " + size + " cannot be negative");
+        }
+
+        if (size > remaining)
+        {
+            throw new IllegalArgumentException("List size " + size + " cannot be more than remaining bytes " + remaining);
+        }
+    }
+
+    public static void validateListCount(final int size, final int count, final int remaining)
+    {
+        if (count < 0)
+        {
+            throw new IllegalArgumentException("List element count " + count + " cannot be negative");
+        }
+
+        if (count >= size)
+        {
+            throw new IllegalArgumentException("List element count " + count + " must be less than size (" + size + ")");
+        }
+
+        if (count > remaining)
+        {
+            throw new IllegalArgumentException("List element count " + count + " is specified to be greater than the amount of data available (" + remaining + ")");
+        }
+    }
+
+    public static void validateSize(final String type, final int size, final int remaining)
+    {
+        if (size < 0)
+        {
+            throw new IllegalArgumentException(type + " length " + size + " cannot be negative");
+        }
+
+        if (size > remaining)
+        {
+            throw new IllegalArgumentException(type + " length " + size + " cannot be more than remaining bytes " + remaining);
+        }
+    }
+
+    public static void validateSizeInitial(final String type, final int size)
+    {
+        if (size < 0)
+        {
+            throw new IllegalArgumentException(type + " size " + size + " cannot be negative");
+        }
+    }
+
+    public static int  validateConstructorSize(final int size)
+    {
+        if (size < 0)
+        {
+            throw new IllegalArgumentException("constructor size cannot be negative: " + size);
+        }
+
+        return size;
+    }
+}
diff --git a/proton-j/src/main/java/org/apache/qpid/proton/codec/messaging/FastPathAcceptedType.java b/proton-j/src/main/java/org/apache/qpid/proton/codec/messaging/FastPathAcceptedType.java
index 9e5e96db..a29c9d0b 100644
--- a/proton-j/src/main/java/org/apache/qpid/proton/codec/messaging/FastPathAcceptedType.java
+++ b/proton-j/src/main/java/org/apache/qpid/proton/codec/messaging/FastPathAcceptedType.java
@@ -89,9 +89,12 @@ public class FastPathAcceptedType implements AMQPType<Accepted>, FastPathDescrib
     @Override
     public Accepted readValue() {
         DecoderImpl decoder = getDecoder();
-        byte typeCode = decoder.getBuffer().get();
+        decoder.increaseDecodeDepth();
 
-        switch (typeCode) {
+        try {
+            byte typeCode = decoder.getBuffer().get();
+
+            switch (typeCode) {
             case EncodingCodes.LIST0:
                 break;
             case EncodingCodes.LIST8:
@@ -104,14 +107,25 @@ public class FastPathAcceptedType implements AMQPType<Accepted>, FastPathDescrib
                 break;
             default:
                 throw new DecodeException("Incorrect type found in Accepted type encoding: " + typeCode);
-        }
+            }
 
-        return Accepted.getInstance();
+            return Accepted.getInstance();
+        } finally {
+            decoder.decreaseDecodeDepth();
+        }
     }
 
     @Override
     public void skipValue() {
-        getDecoder().readConstructor().skipValue();
+        final DecoderImpl decoder = getDecoder();
+
+        decoder.increaseDecodeDepth();
+        try {
+            decoder.readConstructor().skipValue();
+        }
+        finally {
+            decoder.decreaseDecodeDepth();
+        }
     }
 
     @Override
diff --git a/proton-j/src/main/java/org/apache/qpid/proton/codec/messaging/FastPathAmqpSequenceType.java b/proton-j/src/main/java/org/apache/qpid/proton/codec/messaging/FastPathAmqpSequenceType.java
index 6a4ebd4e..6f2aad85 100644
--- a/proton-j/src/main/java/org/apache/qpid/proton/codec/messaging/FastPathAmqpSequenceType.java
+++ b/proton-j/src/main/java/org/apache/qpid/proton/codec/messaging/FastPathAmqpSequenceType.java
@@ -80,12 +80,21 @@ public class FastPathAmqpSequenceType implements AMQPType<AmqpSequence>, FastPat
 
     @Override
     public AmqpSequence readValue() {
+        // Depth handled by list type readValue
         return new AmqpSequence(getDecoder().readList());
     }
 
     @Override
     public void skipValue() {
-        getDecoder().readConstructor().skipValue();
+        final DecoderImpl decoder = getDecoder();
+
+        decoder.increaseDecodeDepth();
+        try {
+            decoder.readConstructor().skipValue();
+        }
+        finally {
+            decoder.decreaseDecodeDepth();
+        }
     }
 
     @Override
diff --git a/proton-j/src/main/java/org/apache/qpid/proton/codec/messaging/FastPathAmqpValueType.java b/proton-j/src/main/java/org/apache/qpid/proton/codec/messaging/FastPathAmqpValueType.java
index b9d4ee78..74c28f6b 100644
--- a/proton-j/src/main/java/org/apache/qpid/proton/codec/messaging/FastPathAmqpValueType.java
+++ b/proton-j/src/main/java/org/apache/qpid/proton/codec/messaging/FastPathAmqpValueType.java
@@ -27,6 +27,7 @@ import org.apache.qpid.proton.codec.DecoderImpl;
 import org.apache.qpid.proton.codec.EncoderImpl;
 import org.apache.qpid.proton.codec.EncodingCodes;
 import org.apache.qpid.proton.codec.FastPathDescribedTypeConstructor;
+import org.apache.qpid.proton.codec.ReadableBuffer;
 import org.apache.qpid.proton.codec.TypeEncoding;
 import org.apache.qpid.proton.codec.WritableBuffer;
 
@@ -80,12 +81,36 @@ public class FastPathAmqpValueType implements AMQPType<AmqpValue>, FastPathDescr
 
     @Override
     public AmqpValue readValue() {
-        return new AmqpValue(getDecoder().readObject());
+        final DecoderImpl decoder = getDecoder();
+        final ReadableBuffer buffer = decoder.getBuffer();
+
+        // Peeks ahead, doesnt read the byte
+        final byte nestedEncodingCode = buffer.get(buffer.position());
+        final boolean countOwnDepth = nestedEncodingCode == EncodingCodes.DESCRIBED_TYPE_INDICATOR;
+
+        if(countOwnDepth) {
+            decoder.increaseDecodeDepth();
+        }
+        try {
+            return new AmqpValue(decoder.readObject());
+        } finally {
+            if(countOwnDepth) {
+                decoder.decreaseDecodeDepth();
+            }
+        }
     }
 
     @Override
     public void skipValue() {
-        getDecoder().readConstructor().skipValue();
+        final DecoderImpl decoder = getDecoder();
+
+        decoder.increaseDecodeDepth();
+        try {
+            decoder.readConstructor().skipValue();
+        }
+        finally {
+            decoder.decreaseDecodeDepth();
+        }
     }
 
     @Override
diff --git a/proton-j/src/main/java/org/apache/qpid/proton/codec/messaging/FastPathApplicationPropertiesType.java b/proton-j/src/main/java/org/apache/qpid/proton/codec/messaging/FastPathApplicationPropertiesType.java
index 82ed7a22..0622027e 100644
--- a/proton-j/src/main/java/org/apache/qpid/proton/codec/messaging/FastPathApplicationPropertiesType.java
+++ b/proton-j/src/main/java/org/apache/qpid/proton/codec/messaging/FastPathApplicationPropertiesType.java
@@ -16,6 +16,9 @@
  */
 package org.apache.qpid.proton.codec.messaging;
 
+import static org.apache.qpid.proton.codec.ValidationSupport.validateMapCount;
+import static org.apache.qpid.proton.codec.ValidationSupport.validateMapSize;
+
 import java.util.Collection;
 import java.util.LinkedHashMap;
 import java.util.Map;
@@ -94,68 +97,96 @@ public class FastPathApplicationPropertiesType implements AMQPType<ApplicationPr
         DecoderImpl decoder = getDecoder();
         ReadableBuffer buffer = decoder.getBuffer();
 
-        final int size;
-        final int count;
-
-        byte encodingCode = buffer.get();
-
-        switch (encodingCode) {
-            case EncodingCodes.MAP8:
-                size = buffer.get() & 0xFF;
-                count = buffer.get() & 0xFF;
-                break;
-            case EncodingCodes.MAP32:
-                size = buffer.getInt();
-                count = buffer.getInt();
-                break;
-            case EncodingCodes.NULL:
-                return new ApplicationProperties(null);
-            default:
-                throw new ProtonException("Expected Map type but found encoding: " + encodingCode);
-        }
+        decoder.increaseDecodeDepth();
+        try {
+            final int size;
+            final int count;
+
+            byte encodingCode = buffer.get();
+
+            final int oldLimit = buffer.limit();
+            final int newLimit;
+
+            switch (encodingCode) {
+                case EncodingCodes.MAP8:
+                    size = buffer.get() & 0xFF;
+                    validateMapSize(size, buffer.remaining());
+                    newLimit = buffer.position() + size;
+                    count = buffer.get() & 0xFF;
+                    validateMapCount(size, count, buffer.remaining());
+                    break;
+                case EncodingCodes.MAP32:
+                    size = buffer.getInt();
+                    validateMapSize(size, buffer.remaining());
+                    newLimit = buffer.position() + size;
+                    count = buffer.getInt();
+                    validateMapCount(size, count, buffer.remaining());
+                    break;
+                case EncodingCodes.NULL:
+                    return new ApplicationProperties(null);
+                default:
+                    throw new ProtonException("Expected Map type but found encoding: " + encodingCode);
+            }
 
-        if (count > buffer.remaining()) {
-            throw new IllegalArgumentException("Map element count " + count + " is specified to be greater than the " +
-                                               "amount of data available ("+ buffer.remaining() + ")");
-        }
+            final Map<String, Object> map;
+            try {
+                buffer.limit(newLimit);
 
-        TypeConstructor<?> valueConstructor = null;
+                TypeConstructor<?> valueConstructor = null;
 
-        Map<String, Object> map = new LinkedHashMap<>(count);
-        for (int i = 0; i < count / 2; i++) {
-            String key = decoder.readString(null);
-            if (key == null) {
-                throw new DecodeException("String key in ApplicationProperties cannot be null");
-            }
+                map = new LinkedHashMap<>(count);
+                for (int i = 0; i < count / 2; i++) {
+                    String key = decoder.readString(null);
+                    if (key == null) {
+                        throw new DecodeException("String key in ApplicationProperties cannot be null");
+                    }
 
-            boolean arrayType = false;
-            byte code = buffer.get(buffer.position());
-            switch (code)
-            {
-                case EncodingCodes.ARRAY8:
-                case EncodingCodes.ARRAY32:
-                    arrayType = true;
-            }
+                    boolean arrayType = false;
+                    byte code = buffer.get(buffer.position());
+                    switch (code)
+                    {
+                        case EncodingCodes.ARRAY8:
+                        case EncodingCodes.ARRAY32:
+                            arrayType = true;
+                    }
 
-            valueConstructor = findNextDecoder(decoder, buffer, valueConstructor);
+                    valueConstructor = findNextDecoder(decoder, buffer, valueConstructor);
 
-            final Object value;
+                    final Object value;
 
-            if (arrayType) {
-                value = ((ArrayType.ArrayEncoding) valueConstructor).readValueArray();
-            } else {
-                value = valueConstructor.readValue();
+                    if (arrayType) {
+                        value = ((ArrayType.ArrayEncoding) valueConstructor).readValueArray();
+                    } else {
+                        value = valueConstructor.readValue();
+                    }
+
+                    map.put(key, value);
+                }
+
+                if (buffer.position() != newLimit) {
+                    throw new DecodeException("ApplicationProperties incorrectly encoded, left " + (newLimit - buffer.position()) + " bytes from size indicated " + size);
+                }
+            } finally {
+                buffer.limit(oldLimit);
             }
 
-            map.put(key, value);
+            return new ApplicationProperties(map);
+        } finally {
+            decoder.decreaseDecodeDepth();
         }
-
-        return new ApplicationProperties(map);
     }
 
     @Override
     public void skipValue() {
-        getDecoder().readConstructor().skipValue();
+        final DecoderImpl decoder = getDecoder();
+
+        decoder.increaseDecodeDepth();
+        try {
+            decoder.readConstructor().skipValue();
+        }
+        finally {
+            decoder.decreaseDecodeDepth();
+        }
     }
 
     @Override
diff --git a/proton-j/src/main/java/org/apache/qpid/proton/codec/messaging/FastPathDataType.java b/proton-j/src/main/java/org/apache/qpid/proton/codec/messaging/FastPathDataType.java
index acc6d77d..06bc268f 100644
--- a/proton-j/src/main/java/org/apache/qpid/proton/codec/messaging/FastPathDataType.java
+++ b/proton-j/src/main/java/org/apache/qpid/proton/codec/messaging/FastPathDataType.java
@@ -83,6 +83,7 @@ public class FastPathDataType implements AMQPType<Data>, FastPathDescribedTypeCo
 
     @Override
     public Data readValue() {
+        // No depth as this is a [described] binary primitive read with fixed decoding
         ReadableBuffer buffer = getDecoder().getBuffer();
         byte encodingCode = buffer.get();
 
@@ -114,7 +115,15 @@ public class FastPathDataType implements AMQPType<Data>, FastPathDescribedTypeCo
 
     @Override
     public void skipValue() {
-        getDecoder().readConstructor().skipValue();
+        final DecoderImpl decoder = getDecoder();
+
+        decoder.increaseDecodeDepth();
+        try {
+            decoder.readConstructor().skipValue();
+        }
+        finally {
+            decoder.decreaseDecodeDepth();
+        }
     }
 
     @Override
diff --git a/proton-j/src/main/java/org/apache/qpid/proton/codec/messaging/FastPathDeliveryAnnotationsType.java b/proton-j/src/main/java/org/apache/qpid/proton/codec/messaging/FastPathDeliveryAnnotationsType.java
index 778f30e4..08abb496 100644
--- a/proton-j/src/main/java/org/apache/qpid/proton/codec/messaging/FastPathDeliveryAnnotationsType.java
+++ b/proton-j/src/main/java/org/apache/qpid/proton/codec/messaging/FastPathDeliveryAnnotationsType.java
@@ -16,6 +16,9 @@
  */
 package org.apache.qpid.proton.codec.messaging;
 
+import static org.apache.qpid.proton.codec.ValidationSupport.validateMapCount;
+import static org.apache.qpid.proton.codec.ValidationSupport.validateMapSize;
+
 import java.util.Collection;
 import java.util.LinkedHashMap;
 import java.util.Map;
@@ -94,68 +97,95 @@ public class FastPathDeliveryAnnotationsType implements AMQPType<DeliveryAnnotat
         DecoderImpl decoder = getDecoder();
         ReadableBuffer buffer = decoder.getBuffer();
 
-        final int size;
-        final int count;
-
-        byte encodingCode = buffer.get();
-
-        switch (encodingCode) {
-            case EncodingCodes.MAP8:
-                size = buffer.get() & 0xFF;
-                count = buffer.get() & 0xFF;
-                break;
-            case EncodingCodes.MAP32:
-                size = buffer.getInt();
-                count = buffer.getInt();
-                break;
-            case EncodingCodes.NULL:
-                return new DeliveryAnnotations(null);
-            default:
-                throw new ProtonException("Expected Map type but found encoding: " + encodingCode);
-        }
-
-        if (count > buffer.remaining()) {
-            throw new IllegalArgumentException("Map element count " + count + " is specified to be greater than the " +
-                                               "amount of data available (" + buffer.remaining() + ")");
-        }
-
-        TypeConstructor<?> valueConstructor = null;
-
-        Map<Symbol, Object> map = new LinkedHashMap<>(count);
-        for(int i = 0; i < count / 2; i++) {
-            Symbol key = decoder.readSymbol(null);
-            if (key == null) {
-                throw new DecodeException("String key in DeliveryAnnotations cannot be null");
-            }
-
-            boolean arrayType = false;
-            byte code = buffer.get(buffer.position());
-            switch (code)
-            {
-                case EncodingCodes.ARRAY8:
-                case EncodingCodes.ARRAY32:
-                    arrayType = true;
+        decoder.increaseDecodeDepth();
+        try {
+            final int size;
+            final int count;
+
+            byte encodingCode = buffer.get();
+
+            final int oldLimit = buffer.limit();
+            final int newLimit;
+
+            switch (encodingCode) {
+                case EncodingCodes.MAP8:
+                    size = buffer.get() & 0xFF;
+                    validateMapSize(size, buffer.remaining());
+                    newLimit = buffer.position() + size;
+                    count = buffer.get() & 0xFF;
+                    validateMapCount(size, count, buffer.remaining());
+                    break;
+                case EncodingCodes.MAP32:
+                    size = buffer.getInt();
+                    validateMapSize(size, buffer.remaining());
+                    newLimit = buffer.position() + size;
+                    count = buffer.getInt();
+                    validateMapCount(size, count, buffer.remaining());
+                    break;
+                case EncodingCodes.NULL:
+                    return new DeliveryAnnotations(null);
+                default:
+                    throw new ProtonException("Expected Map type but found encoding: " + encodingCode);
             }
 
-            valueConstructor = findNextDecoder(decoder, buffer, valueConstructor);
-
-            final Object value;
+            final Map<Symbol, Object> map;
+            try {
+                buffer.limit(newLimit);
+                TypeConstructor<?> valueConstructor = null;
+
+                map = new LinkedHashMap<>(count);
+                for(int i = 0; i < count / 2; i++) {
+                    Symbol key = decoder.readSymbol(null);
+                    if (key == null) {
+                        throw new DecodeException("Symbol key in DeliveryAnnotations cannot be null");
+                    }
+
+                    boolean arrayType = false;
+                    byte code = buffer.get(buffer.position());
+                    switch (code)
+                    {
+                        case EncodingCodes.ARRAY8:
+                        case EncodingCodes.ARRAY32:
+                            arrayType = true;
+                    }
+
+                    valueConstructor = findNextDecoder(decoder, buffer, valueConstructor);
+
+                    final Object value;
+
+                    if (arrayType) {
+                        value = ((ArrayType.ArrayEncoding) valueConstructor).readValueArray();
+                    } else {
+                        value = valueConstructor.readValue();
+                    }
+
+                    map.put(key, value);
+                }
 
-            if (arrayType) {
-                value = ((ArrayType.ArrayEncoding) valueConstructor).readValueArray();
-            } else {
-                value = valueConstructor.readValue();
+                if (buffer.position() != newLimit) {
+                    throw new DecodeException("DeliveryAnnotations incorrectly encoded, left " + (newLimit - buffer.position()) + " bytes from size indicated " + size);
+                }
+            } finally {
+                buffer.limit(oldLimit);
             }
 
-            map.put(key, value);
+            return new DeliveryAnnotations(map);
+        } finally {
+            decoder.decreaseDecodeDepth();
         }
-
-        return new DeliveryAnnotations(map);
     }
 
     @Override
     public void skipValue() {
-        getDecoder().readConstructor().skipValue();
+        final DecoderImpl decoder = getDecoder();
+
+        decoder.increaseDecodeDepth();
+        try {
+            decoder.readConstructor().skipValue();
+        }
+        finally {
+            decoder.decreaseDecodeDepth();
+        }
     }
 
     @Override
diff --git a/proton-j/src/main/java/org/apache/qpid/proton/codec/messaging/FastPathFooterType.java b/proton-j/src/main/java/org/apache/qpid/proton/codec/messaging/FastPathFooterType.java
index 88f690f4..e46e6ae4 100644
--- a/proton-j/src/main/java/org/apache/qpid/proton/codec/messaging/FastPathFooterType.java
+++ b/proton-j/src/main/java/org/apache/qpid/proton/codec/messaging/FastPathFooterType.java
@@ -81,12 +81,21 @@ public class FastPathFooterType implements AMQPType<Footer>, FastPathDescribedTy
 
     @Override
     public Footer readValue() {
+        // Depth handled by map type readValue
         return new Footer(getDecoder().readMap());
     }
 
     @Override
     public void skipValue() {
-        getDecoder().readConstructor().skipValue();
+        final DecoderImpl decoder = getDecoder();
+
+        decoder.increaseDecodeDepth();
+        try {
+            decoder.readConstructor().skipValue();
+        }
+        finally {
+            decoder.decreaseDecodeDepth();
+        }
     }
 
     @Override
diff --git a/proton-j/src/main/java/org/apache/qpid/proton/codec/messaging/FastPathHeaderType.java b/proton-j/src/main/java/org/apache/qpid/proton/codec/messaging/FastPathHeaderType.java
index b60d53e5..f46718fd 100644
--- a/proton-j/src/main/java/org/apache/qpid/proton/codec/messaging/FastPathHeaderType.java
+++ b/proton-j/src/main/java/org/apache/qpid/proton/codec/messaging/FastPathHeaderType.java
@@ -16,6 +16,9 @@
  */
 package org.apache.qpid.proton.codec.messaging;
 
+import static org.apache.qpid.proton.codec.ValidationSupport.validateListCount;
+import static org.apache.qpid.proton.codec.ValidationSupport.validateListSize;
+
 import java.util.Collection;
 
 import org.apache.qpid.proton.amqp.Symbol;
@@ -59,57 +62,89 @@ public class FastPathHeaderType implements AMQPType<Header>, FastPathDescribedTy
     public Header readValue() {
         DecoderImpl decoder = getDecoder();
         ReadableBuffer buffer = decoder.getBuffer();
-        byte typeCode = decoder.getBuffer().get();
 
-        @SuppressWarnings("unused")
-        int size = 0;
-        int count = 0;
+        decoder.increaseDecodeDepth();
+        try {
+            byte typeCode = decoder.getBuffer().get();
 
-        switch (typeCode) {
-            case EncodingCodes.LIST0:
-                break;
-            case EncodingCodes.LIST8:
-                size = buffer.get() & 0xff;
-                count = buffer.get() & 0xff;
-                break;
-            case EncodingCodes.LIST32:
-                size = buffer.getInt();
-                count = buffer.getInt();
-                break;
-            default:
-                throw new DecodeException("Incorrect type found in Header encoding: " + typeCode);
-        }
+            int size = 0;
+            int count = 0;
 
-        Header header = new Header();
+            final int oldLimit = buffer.limit();
+            final int newLimit;
 
-        for (int index = 0; index < count; ++index) {
-            switch (index) {
-                case 0:
-                    header.setDurable(decoder.readBoolean(null));
-                    break;
-                case 1:
-                    header.setPriority(decoder.readUnsignedByte(null));
-                    break;
-                case 2:
-                    header.setTtl(decoder.readUnsignedInteger(null));
+            switch (typeCode) {
+                case EncodingCodes.LIST0:
+                    newLimit = buffer.position();
                     break;
-                case 3:
-                    header.setFirstAcquirer(decoder.readBoolean(null));
+                case EncodingCodes.LIST8:
+                    size = buffer.get() & 0xff;
+                    validateListSize(size, buffer.remaining());
+                    newLimit = buffer.position() + size;
+                    count = buffer.get() & 0xff;
+                    validateListCount(size, count, buffer.remaining());
                     break;
-                case 4:
-                    header.setDeliveryCount(decoder.readUnsignedInteger(null));
+                case EncodingCodes.LIST32:
+                    size = buffer.getInt();
+                    validateListSize(size, buffer.remaining());
+                    newLimit = buffer.position() + size;
+                    count = buffer.getInt();
+                    validateListCount(size, count, buffer.remaining());
                     break;
                 default:
-                    throw new IllegalStateException("To many entries in Header encoding");
+                    throw new DecodeException("Incorrect type found in Header encoding: " + typeCode);
+            }
+
+            Header header = new Header();
+            try {
+                buffer.limit(newLimit);
+
+                for (int index = 0; index < count; ++index) {
+                    switch (index) {
+                        case 0:
+                            header.setDurable(decoder.readBoolean(null));
+                            break;
+                        case 1:
+                            header.setPriority(decoder.readUnsignedByte(null));
+                            break;
+                        case 2:
+                            header.setTtl(decoder.readUnsignedInteger(null));
+                            break;
+                        case 3:
+                            header.setFirstAcquirer(decoder.readBoolean(null));
+                            break;
+                        case 4:
+                            header.setDeliveryCount(decoder.readUnsignedInteger(null));
+                            break;
+                        default:
+                            throw new IllegalStateException("To many entries in Header encoding");
+                    }
+                }
+
+                if (buffer.position() != newLimit) {
+                    throw new DecodeException("Header incorrectly encoded, left " + (newLimit - buffer.position()) + " bytes from size indicated " + size);
+                }
+            } finally {
+                buffer.limit(oldLimit);
             }
-        }
 
-        return header;
+            return header;
+        } finally {
+            decoder.decreaseDecodeDepth();
+        }
     }
 
     @Override
     public void skipValue() {
-        getDecoder().readConstructor().skipValue();
+        final DecoderImpl decoder = getDecoder();
+
+        decoder.increaseDecodeDepth();
+        try {
+            decoder.readConstructor().skipValue();
+        }
+        finally {
+            decoder.decreaseDecodeDepth();
+        }
     }
 
     @Override
diff --git a/proton-j/src/main/java/org/apache/qpid/proton/codec/messaging/FastPathMessageAnnotationsType.java b/proton-j/src/main/java/org/apache/qpid/proton/codec/messaging/FastPathMessageAnnotationsType.java
index d9148333..15b59bfe 100644
--- a/proton-j/src/main/java/org/apache/qpid/proton/codec/messaging/FastPathMessageAnnotationsType.java
+++ b/proton-j/src/main/java/org/apache/qpid/proton/codec/messaging/FastPathMessageAnnotationsType.java
@@ -16,6 +16,9 @@
  */
 package org.apache.qpid.proton.codec.messaging;
 
+import static org.apache.qpid.proton.codec.ValidationSupport.validateMapCount;
+import static org.apache.qpid.proton.codec.ValidationSupport.validateMapSize;
+
 import java.util.Collection;
 import java.util.LinkedHashMap;
 import java.util.Map;
@@ -94,68 +97,95 @@ public class FastPathMessageAnnotationsType implements AMQPType<MessageAnnotatio
         DecoderImpl decoder = getDecoder();
         ReadableBuffer buffer = decoder.getBuffer();
 
-        final int size;
-        final int count;
-
-        byte encodingCode = buffer.get();
-
-        switch (encodingCode) {
-            case EncodingCodes.MAP8:
-                size = buffer.get() & 0xFF;
-                count = buffer.get() & 0xFF;
-                break;
-            case EncodingCodes.MAP32:
-                size = buffer.getInt();
-                count = buffer.getInt();
-                break;
-            case EncodingCodes.NULL:
-                return new MessageAnnotations(null);
-            default:
-                throw new ProtonException("Expected Map type but found encoding: " + encodingCode);
-        }
-
-        if (count > buffer.remaining()) {
-            throw new IllegalArgumentException("Map element count "+count+" is specified to be greater than the amount of data available ("+
-                                               decoder.getByteBufferRemaining()+")");
-        }
-
-        TypeConstructor<?> valueConstructor = null;
-
-        Map<Symbol, Object> map = new LinkedHashMap<>(count);
-        for(int i = 0; i < count / 2; i++) {
-            Symbol key = decoder.readSymbol(null);
-            if (key == null) {
-                throw new DecodeException("String key in DeliveryAnnotations cannot be null");
-            }
-
-            boolean arrayType = false;
-            byte code = buffer.get(buffer.position());
-            switch (code)
-            {
-                case EncodingCodes.ARRAY8:
-                case EncodingCodes.ARRAY32:
-                    arrayType = true;
+        decoder.increaseDecodeDepth();
+        try {
+            final int size;
+            final int count;
+
+            byte encodingCode = buffer.get();
+
+            final int oldLimit = buffer.limit();
+            final int newLimit;
+
+            switch (encodingCode) {
+                case EncodingCodes.MAP8:
+                    size = buffer.get() & 0xFF;
+                    validateMapSize(size, buffer.remaining());
+                    newLimit = buffer.position() + size;
+                    count = buffer.get() & 0xFF;
+                    validateMapCount(size, count, buffer.remaining());
+                    break;
+                case EncodingCodes.MAP32:
+                    size = buffer.getInt();
+                    validateMapSize(size, buffer.remaining());
+                    newLimit = buffer.position() + size;
+                    count = buffer.getInt();
+                    validateMapCount(size, count, buffer.remaining());
+                    break;
+                case EncodingCodes.NULL:
+                    return new MessageAnnotations(null);
+                default:
+                    throw new ProtonException("Expected Map type but found encoding: " + encodingCode);
             }
 
-            valueConstructor = findNextDecoder(decoder, buffer, valueConstructor);
-
-            final Object value;
+            final Map<Symbol, Object> map;
+            try {
+                buffer.limit(newLimit);
+                TypeConstructor<?> valueConstructor = null;
+
+                map = new LinkedHashMap<>(count);
+                for(int i = 0; i < count / 2; i++) {
+                    Symbol key = decoder.readSymbol(null);
+                    if (key == null) {
+                        throw new DecodeException("Symbol key in MessageAnnotations cannot be null");
+                    }
+
+                    boolean arrayType = false;
+                    byte code = buffer.get(buffer.position());
+                    switch (code)
+                    {
+                        case EncodingCodes.ARRAY8:
+                        case EncodingCodes.ARRAY32:
+                            arrayType = true;
+                    }
+
+                    valueConstructor = findNextDecoder(decoder, buffer, valueConstructor);
+
+                    final Object value;
+
+                    if (arrayType) {
+                        value = ((ArrayType.ArrayEncoding) valueConstructor).readValueArray();
+                    } else {
+                        value = valueConstructor.readValue();
+                    }
+
+                    map.put(key, value);
+                }
 
-            if (arrayType) {
-                value = ((ArrayType.ArrayEncoding) valueConstructor).readValueArray();
-            } else {
-                value = valueConstructor.readValue();
+                if (buffer.position() != newLimit) {
+                    throw new DecodeException("MessageAnnotations incorrectly encoded, left " + (newLimit - buffer.position()) + " bytes from size indicated " + size);
+                }
+            } finally {
+                buffer.limit(oldLimit);
             }
 
-            map.put(key, value);
+            return new MessageAnnotations(map);
+        } finally {
+            decoder.decreaseDecodeDepth();
         }
-
-        return new MessageAnnotations(map);
     }
 
     @Override
     public void skipValue() {
-        getDecoder().readConstructor().skipValue();
+        final DecoderImpl decoder = getDecoder();
+
+        decoder.increaseDecodeDepth();
+        try {
+            decoder.readConstructor().skipValue();
+        }
+        finally {
+            decoder.decreaseDecodeDepth();
+        }
     }
 
     @Override
diff --git a/proton-j/src/main/java/org/apache/qpid/proton/codec/messaging/FastPathPropertiesType.java b/proton-j/src/main/java/org/apache/qpid/proton/codec/messaging/FastPathPropertiesType.java
index 22b30d14..b72e302e 100644
--- a/proton-j/src/main/java/org/apache/qpid/proton/codec/messaging/FastPathPropertiesType.java
+++ b/proton-j/src/main/java/org/apache/qpid/proton/codec/messaging/FastPathPropertiesType.java
@@ -16,6 +16,9 @@
  */
 package org.apache.qpid.proton.codec.messaging;
 
+import static org.apache.qpid.proton.codec.ValidationSupport.validateListCount;
+import static org.apache.qpid.proton.codec.ValidationSupport.validateListSize;
+
 import java.util.Collection;
 
 import org.apache.qpid.proton.amqp.Symbol;
@@ -61,79 +64,106 @@ public class FastPathPropertiesType implements AMQPType<Properties>, FastPathDes
         ReadableBuffer buffer = decoder.getBuffer();
         byte typeCode = decoder.getBuffer().get();
 
-        @SuppressWarnings("unused")
         int size = 0;
         int count = 0;
 
+        final int oldLimit = buffer.limit();
+        final int newLimit;
+
         switch (typeCode) {
             case EncodingCodes.LIST0:
+                newLimit = buffer.position();
                 break;
             case EncodingCodes.LIST8:
                 size = buffer.get() & 0xff;
+                validateListSize(size, buffer.remaining());
+                newLimit = buffer.position() + size;
                 count = buffer.get() & 0xff;
+                validateListCount(size, count, buffer.remaining());
                 break;
             case EncodingCodes.LIST32:
                 size = buffer.getInt();
+                validateListSize(size, buffer.remaining());
+                newLimit = buffer.position() + size;
                 count = buffer.getInt();
+                validateListCount(size, count, buffer.remaining());
                 break;
             default:
                 throw new DecodeException("Incorrect type found in Properties encoding: " + typeCode);
         }
 
-        Properties properties = new Properties();
-
-        for (int index = 0; index < count; ++index) {
-            switch (index) {
-                case 0:
-                    properties.setMessageId(decoder.readObject());
-                    break;
-                case 1:
-                    properties.setUserId(decoder.readBinary(null));
-                    break;
-                case 2:
-                    properties.setTo(decoder.readString(null));
-                    break;
-                case 3:
-                    properties.setSubject(decoder.readString(null));
-                    break;
-                case 4:
-                    properties.setReplyTo(decoder.readString(null));
-                    break;
-                case 5:
-                    properties.setCorrelationId(decoder.readObject());
-                    break;
-                case 6:
-                    properties.setContentType(decoder.readSymbol(null));
-                    break;
-                case 7:
-                    properties.setContentEncoding(decoder.readSymbol(null));
-                    break;
-                case 8:
-                    properties.setAbsoluteExpiryTime(decoder.readTimestamp(null));
-                    break;
-                case 9:
-                    properties.setCreationTime(decoder.readTimestamp(null));
-                    break;
-                case 10:
-                    properties.setGroupId(decoder.readString(null));
-                    break;
-                case 11:
-                    properties.setGroupSequence(decoder.readUnsignedInteger(null));
-                    break;
-                case 12:
-                    properties.setReplyToGroupId(decoder.readString(null));
-                    break;
-                default:
-                    throw new IllegalStateException("To many entries in Properties encoding");
+        try {
+            buffer.limit(newLimit);
+
+            Properties properties = new Properties();
+
+            for (int index = 0; index < count; ++index) {
+                switch (index) {
+                    case 0:
+                        properties.setMessageId(decoder.readObject());
+                        break;
+                    case 1:
+                        properties.setUserId(decoder.readBinary(null));
+                        break;
+                    case 2:
+                        properties.setTo(decoder.readString(null));
+                        break;
+                    case 3:
+                        properties.setSubject(decoder.readString(null));
+                        break;
+                    case 4:
+                        properties.setReplyTo(decoder.readString(null));
+                        break;
+                    case 5:
+                        properties.setCorrelationId(decoder.readObject());
+                        break;
+                    case 6:
+                        properties.setContentType(decoder.readSymbol(null));
+                        break;
+                    case 7:
+                        properties.setContentEncoding(decoder.readSymbol(null));
+                        break;
+                    case 8:
+                        properties.setAbsoluteExpiryTime(decoder.readTimestamp(null));
+                        break;
+                    case 9:
+                        properties.setCreationTime(decoder.readTimestamp(null));
+                        break;
+                    case 10:
+                        properties.setGroupId(decoder.readString(null));
+                        break;
+                    case 11:
+                        properties.setGroupSequence(decoder.readUnsignedInteger(null));
+                        break;
+                    case 12:
+                        properties.setReplyToGroupId(decoder.readString(null));
+                        break;
+                    default:
+                        throw new IllegalStateException("To many entries in Properties encoding");
+                }
+            }
+
+            if (buffer.position() != newLimit) {
+                throw new DecodeException("Properties incorrectly encoded, left " + (newLimit - buffer.position()) + " bytes from size indicated " + size);
             }
-        }
 
-        return properties;
+            return properties;
+        } finally {
+            buffer.limit(oldLimit);
+        }
     }
 
     @Override
     public void skipValue() {
-        getDecoder().readConstructor().skipValue();
+        final DecoderImpl decoder = getDecoder();
+
+        decoder.increaseDecodeDepth();
+        try {
+            decoder.readConstructor().skipValue();
+        }
+        finally {
+            decoder.decreaseDecodeDepth();
+        }
     }
 
     @Override
diff --git a/proton-j/src/main/java/org/apache/qpid/proton/codec/transport/FastPathDispositionType.java b/proton-j/src/main/java/org/apache/qpid/proton/codec/transport/FastPathDispositionType.java
index e2ed8d79..38137e2d 100644
--- a/proton-j/src/main/java/org/apache/qpid/proton/codec/transport/FastPathDispositionType.java
+++ b/proton-j/src/main/java/org/apache/qpid/proton/codec/transport/FastPathDispositionType.java
@@ -16,6 +16,9 @@
  */
 package org.apache.qpid.proton.codec.transport;
 
+import static org.apache.qpid.proton.codec.ValidationSupport.validateListCount;
+import static org.apache.qpid.proton.codec.ValidationSupport.validateListSize;
+
 import java.util.Collection;
 
 import org.apache.qpid.proton.amqp.Symbol;
@@ -32,6 +35,7 @@ import org.apache.qpid.proton.codec.DecoderImpl;
 import org.apache.qpid.proton.codec.EncoderImpl;
 import org.apache.qpid.proton.codec.EncodingCodes;
 import org.apache.qpid.proton.codec.FastPathDescribedTypeConstructor;
+import org.apache.qpid.proton.codec.ReadableBuffer;
 import org.apache.qpid.proton.codec.TypeEncoding;
 import org.apache.qpid.proton.codec.WritableBuffer;
 
@@ -94,36 +98,62 @@ public class FastPathDispositionType implements AMQPType<Disposition>, FastPathD
     @Override
     public Disposition readValue() {
         DecoderImpl decoder = getDecoder();
-        byte typeCode = decoder.getBuffer().get();
+        ReadableBuffer buffer = decoder.getBuffer();
 
-        @SuppressWarnings("unused")
-        int size = 0;
-        int count = 0;
+        decoder.increaseDecodeDepth();
+        try {
+            byte typeCode = buffer.get();
 
-        switch (typeCode) {
-            case EncodingCodes.LIST0:
-                // TODO - Technically invalid however old decoder also allowed this.
-                break;
-            case EncodingCodes.LIST8:
-                size = ((int)decoder.getBuffer().get()) & 0xff;
-                count = ((int)decoder.getBuffer().get()) & 0xff;
-                break;
-            case EncodingCodes.LIST32:
-                size = decoder.getBuffer().getInt();
-                count = decoder.getBuffer().getInt();
-                break;
-            default:
-                throw new DecodeException("Incorrect type found in Disposition encoding: " + typeCode);
-        }
+            int size = 0;
+            int count = 0;
 
-        if (count < 2) {
-            throw new DecodeException("The 'first' field cannot be omitted");
-        }
+            final int oldLimit = buffer.limit();
+            final int newLimit;
 
-        try {
-            return readFields(decoder, count);
-        } catch (NullPointerException npe) {
-            throw new DecodeException("Unexpected null value - mandatory field not set? (" + npe.getMessage() + ")", npe);
+            switch (typeCode) {
+                case EncodingCodes.LIST0:
+                    // Technically invalid however old decoder also allowed this. Is rejected by later field check.
+                    newLimit = buffer.position();
+                    break;
+                case EncodingCodes.LIST8:
+                    size = ((int) buffer.get()) & 0xff;
+                    validateListSize(size, buffer.remaining());
+                    newLimit = buffer.position() + size;
+                    count = ((int) buffer.get()) & 0xff;
+                    validateListCount(size, count, buffer.remaining());
+                    break;
+                case EncodingCodes.LIST32:
+                    size = buffer.getInt();
+                    validateListSize(size, buffer.remaining());
+                    newLimit = buffer.position() + size;
+                    count = buffer.getInt();
+                    validateListCount(size, count, buffer.remaining());
+                    break;
+                default:
+                    throw new DecodeException("Incorrect type found in Disposition encoding: " + typeCode);
+            }
+
+            if (count < 2) {
+                throw new DecodeException("The 'first' field cannot be omitted");
+            }
+
+            try {
+                buffer.limit(newLimit);
+
+                final Disposition disposition = readFields(decoder, count);
+
+                if (buffer.position() != newLimit) {
+                    throw new DecodeException("Disposition incorrectly encoded, left " + (newLimit - buffer.position()) + " bytes from size indicated " + size);
+                }
+
+                return disposition;
+            } catch (NullPointerException npe) {
+                throw new DecodeException("Unexpected null value - mandatory field not set? (" + npe.getMessage() + ")", npe);
+            } finally {
+                buffer.limit(oldLimit);
+            }
+        } finally {
+            decoder.decreaseDecodeDepth();
         }
     }
 
@@ -160,7 +190,15 @@ public class FastPathDispositionType implements AMQPType<Disposition>, FastPathD
 
     @Override
     public void skipValue() {
-        getDecoder().readConstructor().skipValue();
+        final DecoderImpl decoder = getDecoder();
+
+        decoder.increaseDecodeDepth();
+        try {
+            decoder.readConstructor().skipValue();
+        }
+        finally {
+            decoder.decreaseDecodeDepth();
+        }
     }
 
     @Override
diff --git a/proton-j/src/main/java/org/apache/qpid/proton/codec/transport/FastPathFlowType.java b/proton-j/src/main/java/org/apache/qpid/proton/codec/transport/FastPathFlowType.java
index 4d17f2e8..9f9b5c54 100644
--- a/proton-j/src/main/java/org/apache/qpid/proton/codec/transport/FastPathFlowType.java
+++ b/proton-j/src/main/java/org/apache/qpid/proton/codec/transport/FastPathFlowType.java
@@ -16,6 +16,9 @@
  */
 package org.apache.qpid.proton.codec.transport;
 
+import static org.apache.qpid.proton.codec.ValidationSupport.validateListCount;
+import static org.apache.qpid.proton.codec.ValidationSupport.validateListSize;
+
 import java.util.Collection;
 
 import org.apache.qpid.proton.amqp.Symbol;
@@ -28,6 +31,7 @@ import org.apache.qpid.proton.codec.DecoderImpl;
 import org.apache.qpid.proton.codec.EncoderImpl;
 import org.apache.qpid.proton.codec.EncodingCodes;
 import org.apache.qpid.proton.codec.FastPathDescribedTypeConstructor;
+import org.apache.qpid.proton.codec.ReadableBuffer;
 import org.apache.qpid.proton.codec.TypeEncoding;
 import org.apache.qpid.proton.codec.WritableBuffer;
 
@@ -82,36 +86,62 @@ public class FastPathFlowType implements AMQPType<Flow>, FastPathDescribedTypeCo
     @Override
     public Flow readValue() {
         DecoderImpl decoder = getDecoder();
-        byte typeCode = decoder.getBuffer().get();
+        ReadableBuffer buffer = decoder.getBuffer();
 
-        @SuppressWarnings("unused")
-        int size = 0;
-        int count = 0;
+        decoder.increaseDecodeDepth();
+        try {
+            byte typeCode = buffer.get();
 
-        switch (typeCode) {
-            case EncodingCodes.LIST0:
-                // TODO - Technically invalid however old decoder also allowed this.
-                break;
-            case EncodingCodes.LIST8:
-                size = ((int)decoder.getBuffer().get()) & 0xff;
-                count = ((int)decoder.getBuffer().get()) & 0xff;
-                break;
-            case EncodingCodes.LIST32:
-                size = decoder.getBuffer().getInt();
-                count = decoder.getBuffer().getInt();
-                break;
-            default:
-                throw new DecodeException("Incorrect type found in Flow encoding: " + typeCode);
-        }
+            int size = 0;
+            int count = 0;
 
-        if (count < 4) {
-            throw new DecodeException("The outgoing-window field cannot be omitted");
-        }
+            final int oldLimit = buffer.limit();
+            final int newLimit;
 
-        try {
-            return readFields(decoder, count);
-        } catch (NullPointerException npe) {
-            throw new DecodeException("Unexpected null value - mandatory field not set? (" + npe.getMessage() + ")", npe);
+            switch (typeCode) {
+                case EncodingCodes.LIST0:
+                    // Technically invalid however old decoder also allowed this. Is rejected by later field check.
+                    newLimit = buffer.position();
+                    break;
+                case EncodingCodes.LIST8:
+                    size = ((int)buffer.get()) & 0xff;
+                    validateListSize(size, buffer.remaining());
+                    newLimit = buffer.position() + size;
+                    count = ((int)buffer.get()) & 0xff;
+                    validateListCount(size, count, buffer.remaining());
+                    break;
+                case EncodingCodes.LIST32:
+                    size = buffer.getInt();
+                    validateListSize(size, buffer.remaining());
+                    newLimit = buffer.position() + size;
+                    count = buffer.getInt();
+                    validateListCount(size, count, buffer.remaining());
+                    break;
+                default:
+                    throw new DecodeException("Incorrect type found in Flow encoding: " + typeCode);
+            }
+
+            if (count < 4) {
+                throw new DecodeException("The outgoing-window field cannot be omitted");
+            }
+
+            try {
+                buffer.limit(newLimit);
+
+                final Flow flow = readFields(decoder, count);
+
+                if (buffer.position() != newLimit) {
+                    throw new DecodeException("Flow incorrectly encoded, left " + (newLimit - buffer.position()) + " bytes from size indicated " + size);
+                }
+
+                return flow;
+            } catch (NullPointerException npe) {
+                throw new DecodeException("Unexpected null value - mandatory field not set? (" + npe.getMessage() + ")", npe);
+            } finally {
+                buffer.limit(oldLimit);
+            }
+        } finally {
+            decoder.decreaseDecodeDepth();
         }
     }
 
@@ -163,7 +193,15 @@ public class FastPathFlowType implements AMQPType<Flow>, FastPathDescribedTypeCo
 
     @Override
     public void skipValue() {
-        getDecoder().readConstructor().skipValue();
+        final DecoderImpl decoder = getDecoder();
+
+        decoder.increaseDecodeDepth();
+        try {
+            decoder.readConstructor().skipValue();
+        }
+        finally {
+            decoder.decreaseDecodeDepth();
+        }
     }
 
     @Override
diff --git a/proton-j/src/main/java/org/apache/qpid/proton/codec/transport/FastPathTransferType.java b/proton-j/src/main/java/org/apache/qpid/proton/codec/transport/FastPathTransferType.java
index b5408853..04d54b66 100644
--- a/proton-j/src/main/java/org/apache/qpid/proton/codec/transport/FastPathTransferType.java
+++ b/proton-j/src/main/java/org/apache/qpid/proton/codec/transport/FastPathTransferType.java
@@ -16,6 +16,9 @@
  */
 package org.apache.qpid.proton.codec.transport;
 
+import static org.apache.qpid.proton.codec.ValidationSupport.validateListCount;
+import static org.apache.qpid.proton.codec.ValidationSupport.validateListSize;
+
 import java.util.Collection;
 
 import org.apache.qpid.proton.amqp.Symbol;
@@ -31,6 +34,7 @@ import org.apache.qpid.proton.codec.DecoderImpl;
 import org.apache.qpid.proton.codec.EncoderImpl;
 import org.apache.qpid.proton.codec.EncodingCodes;
 import org.apache.qpid.proton.codec.FastPathDescribedTypeConstructor;
+import org.apache.qpid.proton.codec.ReadableBuffer;
 import org.apache.qpid.proton.codec.TypeEncoding;
 import org.apache.qpid.proton.codec.WritableBuffer;
 
@@ -63,36 +67,62 @@ public class FastPathTransferType implements AMQPType<Transfer>, FastPathDescrib
     @Override
     public Transfer readValue() {
         DecoderImpl decoder = getDecoder();
-        byte typeCode = decoder.getBuffer().get();
+        ReadableBuffer buffer = decoder.getBuffer();
 
-        @SuppressWarnings("unused")
-        int size = 0;
-        int count = 0;
+        decoder.increaseDecodeDepth();
+        try {
+            byte typeCode = buffer.get();
 
-        switch (typeCode) {
-            case EncodingCodes.LIST0:
-                // TODO - Technically invalid however old decoder also allowed this.
-                break;
-            case EncodingCodes.LIST8:
-                size = ((int)decoder.getBuffer().get()) & 0xff;
-                count = ((int)decoder.getBuffer().get()) & 0xff;
-                break;
-            case EncodingCodes.LIST32:
-                size = decoder.getBuffer().getInt();
-                count = decoder.getBuffer().getInt();
-                break;
-            default:
-                throw new DecodeException("Incorrect type found in Transfer encoding: " + typeCode);
-        }
+            int size = 0;
+            int count = 0;
 
-        if (count < 1) {
-            throw new DecodeException("The handle field cannot be omitted");
-        }
+            final int oldLimit = buffer.limit();
+            final int newLimit;
 
-        try {
-            return readFields(decoder, count);
-        } catch (NullPointerException npe) {
-            throw new DecodeException("Unexpected null value - mandatory field not set? (" + npe.getMessage() + ")", npe);
+            switch (typeCode) {
+                case EncodingCodes.LIST0:
+                    // Technically invalid however old decoder also allowed this. Is rejected by later field check.
+                    newLimit = buffer.position();
+                    break;
+                case EncodingCodes.LIST8:
+                    size = ((int) buffer.get()) & 0xff;
+                    validateListSize(size, buffer.remaining());
+                    newLimit = buffer.position() + size;
+                    count = ((int) buffer.get()) & 0xff;
+                    validateListCount(size, count, buffer.remaining());
+                    break;
+                case EncodingCodes.LIST32:
+                    size = buffer.getInt();
+                    validateListSize(size, buffer.remaining());
+                    newLimit = buffer.position() + size;
+                    count = buffer.getInt();
+                    validateListCount(size, count, buffer.remaining());
+                    break;
+                default:
+                    throw new DecodeException("Incorrect type found in Transfer encoding: " + typeCode);
+            }
+
+            if (count < 1) {
+                throw new DecodeException("The handle field cannot be omitted");
+            }
+
+            try {
+                buffer.limit(newLimit);
+
+                final Transfer transfer = readFields(decoder, count);
+
+                if (buffer.position() != newLimit) {
+                    throw new DecodeException("Transfer incorrectly encoded, left " + (newLimit - buffer.position()) + " bytes from size indicated " + size);
+                }
+
+                return transfer;
+            } catch (NullPointerException npe) {
+                throw new DecodeException("Unexpected null value - mandatory field not set? (" + npe.getMessage() + ")", npe);
+            } finally {
+                buffer.limit(oldLimit);
+            }
+        } finally {
+            decoder.decreaseDecodeDepth();
         }
     }
 
@@ -145,7 +175,15 @@ public class FastPathTransferType implements AMQPType<Transfer>, FastPathDescrib
 
     @Override
     public void skipValue() {
-        getDecoder().readConstructor().skipValue();
+        final DecoderImpl decoder = getDecoder();
+
+        decoder.increaseDecodeDepth();
+        try {
+            decoder.readConstructor().skipValue();
+        }
+        finally {
+            decoder.decreaseDecodeDepth();
+        }
     }
 
     @Override
diff --git a/proton-j/src/main/java/org/apache/qpid/proton/engine/Transport.java b/proton-j/src/main/java/org/apache/qpid/proton/engine/Transport.java
index 66cde843..86d189f8 100644
--- a/proton-j/src/main/java/org/apache/qpid/proton/engine/Transport.java
+++ b/proton-j/src/main/java/org/apache/qpid/proton/engine/Transport.java
@@ -327,4 +327,14 @@ public interface Transport extends Endpoint
     void setOutboundFrameSizeLimit(int size);
 
     int getOutboundFrameSizeLimit();
+
+    /**
+     * Set an upper bound on the number of transfer frames per incoming delivery that
+     * are permitted. Defaults to 65535. 0 means no limit.
+     *
+     * @param maxTransfersPerDelivery the max number of transfers per delivery
+     */
+    void setMaxTransfersPerDelivery(int maxTransfersPerDelivery);
+
+    int getMaxTransfersPerDelivery();
 }
diff --git a/proton-j/src/main/java/org/apache/qpid/proton/engine/TransportConditionException.java b/proton-j/src/main/java/org/apache/qpid/proton/engine/TransportConditionException.java
new file mode 100644
index 00000000..73277abd
--- /dev/null
+++ b/proton-j/src/main/java/org/apache/qpid/proton/engine/TransportConditionException.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.qpid.proton.engine;
+
+import java.util.Objects;
+
+import org.apache.qpid.proton.amqp.Symbol;
+
+public class TransportConditionException extends TransportException {
+
+    private static final long serialVersionUID = 828725563575892233L;
+    private Symbol _condition;
+
+    public TransportConditionException(String message, Symbol condition) {
+        this(message, condition, null);
+    }
+
+    public TransportConditionException(String message, Symbol condition, Throwable cause) {
+        super(message, cause);
+        Objects.requireNonNull(message, "A message must be provided");
+        Objects.requireNonNull(condition, "A condition must be provided");
+        _condition = condition;
+    }
+
+    public Symbol getCondition() {
+        return _condition;
+    }
+}
diff --git a/proton-j/src/main/java/org/apache/qpid/proton/engine/impl/FrameParser.java b/proton-j/src/main/java/org/apache/qpid/proton/engine/impl/FrameParser.java
index d0df4994..02ec3bbf 100644
--- a/proton-j/src/main/java/org/apache/qpid/proton/engine/impl/FrameParser.java
+++ b/proton-j/src/main/java/org/apache/qpid/proton/engine/impl/FrameParser.java
@@ -73,6 +73,7 @@ class FrameParser implements TransportInput
 
     private ByteBuffer _inputBuffer = null;
     private boolean _tail_closed = false;
+    private boolean _tail_closed_seen = false;
 
     private State _state = State.HEADER0;
 
@@ -114,7 +115,7 @@ class FrameParser implements TransportInput
         State state = _state;
         ByteBuffer oldIn = null;
 
-        boolean transportAccepting = true;
+        boolean transportAccepting = !_tail_closed_seen;
 
         while(in.hasRemaining() && state != State.ERROR && transportAccepting)
         {
@@ -392,8 +393,21 @@ class FrameParser implements TransportInput
                         if (frameBodySize > 0)
                         {
                             _decoder.setByteBuffer(in);
-                            val = _decoder.readObject();
-                            _decoder.setByteBuffer(null);
+                            try
+                            {
+                                val = _decoder.readObject();
+                            }
+                            catch(DecodeException de)
+                            {
+                                throw de;
+                            }
+                            catch (RuntimeException re)
+                            {
+                                throw new DecodeException("Exception decoding frame performative: " + re.getMessage(), re);
+                            }
+                            finally {
+                                _decoder.setByteBuffer(null);
+                            }
 
                             if(in.hasRemaining())
                             {
@@ -423,6 +437,9 @@ class FrameParser implements TransportInput
                             if(_frameHandler.isHandlingFrames())
                             {
                                 _tail_closed = _frameHandler.handleFrame(frame);
+                                if (_tail_closed) {
+                                    transportAccepting = false;
+                                }
                             }
                             else
                             {
@@ -457,8 +474,9 @@ class FrameParser implements TransportInput
 
         }
 
-        if (_tail_closed)
+        if (_tail_closed && !_tail_closed_seen)
         {
+            _tail_closed_seen = true;
             if (in.hasRemaining()) {
                 state = State.ERROR;
                 frameParsingError = new TransportException("framing error");
@@ -476,6 +494,7 @@ class FrameParser implements TransportInput
         if(_state == State.ERROR)
         {
             _tail_closed = true;
+            _tail_closed_seen = true;
             if(frameParsingError != null)
             {
                 _parsingError = frameParsingError;
@@ -544,6 +563,10 @@ class FrameParser implements TransportInput
                 } else {
                     _inputBuffer.clear();
                 }
+
+                if(_tail_closed && _tail_closed_seen) {
+                    _inputBuffer = null;
+                }
             }
         }
         else
@@ -555,8 +578,10 @@ class FrameParser implements TransportInput
     @Override
     public void close_tail()
     {
-        _tail_closed = true;
-        process();
+        if (!_tail_closed) {
+            _tail_closed = true;
+            process();
+        }
     }
 
     /**
diff --git a/proton-j/src/main/java/org/apache/qpid/proton/engine/impl/FrameWriter.java b/proton-j/src/main/java/org/apache/qpid/proton/engine/impl/FrameWriter.java
index 6b689f90..64ed4514 100644
--- a/proton-j/src/main/java/org/apache/qpid/proton/engine/impl/FrameWriter.java
+++ b/proton-j/src/main/java/org/apache/qpid/proton/engine/impl/FrameWriter.java
@@ -73,6 +73,10 @@ class FrameWriter {
         return frameBuffer.transferTo(dst);
     }
 
+    int getFrameBufferPosition() {
+        return frameBuffer.position();
+    }
+
     long getFramesOutput() {
         return framesOutput;
     }
diff --git a/proton-j/src/main/java/org/apache/qpid/proton/engine/impl/SaslImpl.java b/proton-j/src/main/java/org/apache/qpid/proton/engine/impl/SaslImpl.java
index eea40536..01c34d12 100644
--- a/proton-j/src/main/java/org/apache/qpid/proton/engine/impl/SaslImpl.java
+++ b/proton-j/src/main/java/org/apache/qpid/proton/engine/impl/SaslImpl.java
@@ -53,8 +53,8 @@ public class SaslImpl implements Sasl, SaslFrameBody.SaslFrameBodyHandler<Void>,
     public static final byte SASL_FRAME_TYPE = (byte) 1;
     private static final String HEADER_DESCRIPTION = "SASL";
 
-    private final DecoderImpl _decoder = new DecoderImpl();
-    private final EncoderImpl _encoder = new EncoderImpl(_decoder);
+    private final DecoderImpl _decoder = new DecoderImpl(true);
+    private final EncoderImpl _encoder = new EncoderImpl(_decoder, true);
 
     private final TransportImpl _transport;
 
@@ -98,7 +98,10 @@ public class SaslImpl implements Sasl, SaslFrameBody.SaslFrameBodyHandler<Void>,
         _transport = transport;
         _maxFrameSize = maxFrameSize;
 
-        AMQPDefinedTypes.registerAllTypes(_decoder,_encoder);
+        _decoder.setZeroWidthArrayElementLimit(0);
+        _decoder.setMaxDecodeDepth(2);
+
+        AMQPDefinedTypes.registerSecurityTypes(_decoder,_encoder);
         _frameParser = new SaslFrameParser(this, _decoder, maxFrameSize, _transport);
         _frameWriter = new FrameWriter(_encoder, maxFrameSize, FrameWriter.SASL_FRAME_TYPE, _transport);
     }
diff --git a/proton-j/src/main/java/org/apache/qpid/proton/engine/impl/TransportImpl.java b/proton-j/src/main/java/org/apache/qpid/proton/engine/impl/TransportImpl.java
index b9f07dc0..222ffd77 100644
--- a/proton-j/src/main/java/org/apache/qpid/proton/engine/impl/TransportImpl.java
+++ b/proton-j/src/main/java/org/apache/qpid/proton/engine/impl/TransportImpl.java
@@ -17,6 +17,7 @@
 
 package org.apache.qpid.proton.engine.impl;
 
+import static org.apache.qpid.proton.amqp.transport.ConnectionError.FRAMING_ERROR;
 import static org.apache.qpid.proton.engine.impl.ByteBufferUtils.pourArrayToBuffer;
 import static org.apache.qpid.proton.engine.impl.ByteBufferUtils.pourBufferToArray;
 
@@ -58,6 +59,7 @@ import org.apache.qpid.proton.engine.Sasl;
 import org.apache.qpid.proton.engine.Ssl;
 import org.apache.qpid.proton.engine.SslDomain;
 import org.apache.qpid.proton.engine.SslPeerDetails;
+import org.apache.qpid.proton.engine.TransportConditionException;
 import org.apache.qpid.proton.engine.TransportDecodeException;
 import org.apache.qpid.proton.engine.TransportException;
 import org.apache.qpid.proton.engine.TransportResult;
@@ -73,6 +75,7 @@ public class TransportImpl extends EndpointImpl
 {
     static final int BUFFER_RELEASE_THRESHOLD = Integer.getInteger("proton.transport_buffer_release_threshold", 2 * 1024 * 1024);
     private static final int CHANNEL_MAX_LIMIT = 65535;
+    private static final int TRANSFERS_PER_DELIVERY_MAX_LIMIT = 65535;
 
     private static final boolean getBooleanEnv(String name)
     {
@@ -111,6 +114,7 @@ public class TransportImpl extends EndpointImpl
     private int _outboundFrameSizeLimit = 0;
     private int _channelMax       = CHANNEL_MAX_LIMIT;
     private int _remoteChannelMax = CHANNEL_MAX_LIMIT;
+    private int _maxTransfersPerDelivery = TRANSFERS_PER_DELIVERY_MAX_LIMIT;
 
     private final FrameWriter _frameWriter;
 
@@ -171,6 +175,10 @@ public class TransportImpl extends EndpointImpl
     {
         AMQPDefinedTypes.registerAllTypes(_decoder, _encoder);
 
+        if (maxFrameSize > 0 && maxFrameSize < MIN_MAX_FRAME_SIZE)
+        {
+            throw new IllegalArgumentException("Cannot set max frame size under 512 (MIN_MAX_FRAME_SIZE)");
+        }
         _maxFrameSize = maxFrameSize;
         _frameWriter = new FrameWriter(_encoder, _remoteMaxFrameSize,
                                        FrameWriter.AMQP_FRAME_TYPE,
@@ -213,12 +221,22 @@ public class TransportImpl extends EndpointImpl
             throw new IllegalStateException("Cannot set initial remote max frame size after transport has been initialised");
         }
 
+        if (remoteMaxFrameSize < MIN_MAX_FRAME_SIZE)
+        {
+            throw new IllegalArgumentException("Cannot set initial remote max frame size under 512 (MIN_MAX_FRAME_SIZE)");
+        }
+
         _remoteMaxFrameSize = remoteMaxFrameSize;
     }
 
     @Override
     public void setMaxFrameSize(int maxFrameSize)
     {
+        if (maxFrameSize > 0 && maxFrameSize < MIN_MAX_FRAME_SIZE)
+        {
+            throw new IllegalArgumentException("Cannot set max frame size under 512 (MIN_MAX_FRAME_SIZE)");
+        }
+
         if(_init)
         {
             throw new IllegalStateException("Cannot set max frame size after transport has been initialised");
@@ -360,22 +378,31 @@ public class TransportImpl extends EndpointImpl
     {
         processHeader();
         processOpen();
-        processBegin();
-        processAttach();
-        processReceiverFlow();
-        // we process transport work twice intentionally, the first
-        // pass may end up settling deliveries that the second pass
-        // can clean up
-        processTransportWork();
-        processTransportWork();
-        processSenderFlow();
-        processDetach();
-        processEnd();
+        if (!_conditionSet) {
+            processBegin();
+            processAttach();
+            processReceiverFlow();
+            // we process transport work twice intentionally, the first
+            // pass may end up settling deliveries that the second pass
+            // can clean up
+            processTransportWork();
+            processTransportWork();
+            processSenderFlow();
+            processDetach();
+            processEnd();
+        }
         processClose();
 
         _frameWriter.readBytes(outputBuffer);
 
-        return _isCloseSent || _head_closed;
+        if(_frameWriter.getFrameBufferPosition() > 0)
+        {
+            return false;
+        }
+        else
+        {
+            return _isCloseSent || _head_closed;
+        }
     }
 
     @Override
@@ -783,7 +810,7 @@ public class TransportImpl extends EndpointImpl
 
                     if(session.getLocalState() == EndpointState.ACTIVE && transportSession.isLocalChannelSet())
                     {
-                        if(transportSession.getIncomingWindowSize().equals(UnsignedInteger.ZERO))
+                        if(transportSession.getIncomingWindowSize().equals(UnsignedInteger.ZERO) && !_conditionSet)
                         {
                             writeFlow(transportSession, null);
                         }
@@ -1115,7 +1142,7 @@ public class TransportImpl extends EndpointImpl
              (_connectionEndpoint != null &&
               _connectionEndpoint.getLocalState() == EndpointState.CLOSED)) &&
             !_isCloseSent) {
-            if(!hasSendableMessages(null))
+            if(!hasSendableMessages(null) || _conditionSet)
             {
                 Close close = new Close();
 
@@ -1171,25 +1198,21 @@ public class TransportImpl extends EndpointImpl
     @Override
     public void handleOpen(Open open, Binary payload, Integer channel)
     {
-        setRemoteState(EndpointState.ACTIVE);
-        if(_connectionEndpoint != null)
-        {
-            _connectionEndpoint.handleOpen(open);
-        }
-        else
-        {
-            _open = open;
-        }
+        int effectiveMaxFrameSize = MIN_MAX_FRAME_SIZE;
+        final UnsignedInteger openMaxFrameSize = open.getMaxFrameSize();
 
-        int effectiveMaxFrameSize = _remoteMaxFrameSize;
-        if(open.getMaxFrameSize().longValue() > 0)
+        if (openMaxFrameSize.longValue() >= MIN_MAX_FRAME_SIZE)
+        {
+            _remoteMaxFrameSize = openMaxFrameSize.intValue();
+            effectiveMaxFrameSize = (int) Math.min(openMaxFrameSize.longValue(), Integer.MAX_VALUE);
+        } else
         {
-            _remoteMaxFrameSize = (int) open.getMaxFrameSize().longValue();
-            effectiveMaxFrameSize = (int) Math.min(open.getMaxFrameSize().longValue(), Integer.MAX_VALUE);
+            closed(new TransportConditionException("Requested max-frame-size " + openMaxFrameSize.longValue() + " is below the minimum of 512 (MIN-MAX-FRAME-SIZE)", FRAMING_ERROR));
+            return;
         }
 
         if(_outboundFrameSizeLimit > 0) {
-            effectiveMaxFrameSize = (int) Math.min(open.getMaxFrameSize().longValue(), _outboundFrameSizeLimit);
+            effectiveMaxFrameSize = Math.min(effectiveMaxFrameSize, _outboundFrameSizeLimit);
         }
 
         _frameWriter.setMaxFrameSize(effectiveMaxFrameSize);
@@ -1203,6 +1226,16 @@ public class TransportImpl extends EndpointImpl
         {
             _remoteIdleTimeout = open.getIdleTimeOut().intValue();
         }
+
+        setRemoteState(EndpointState.ACTIVE);
+        if(_connectionEndpoint != null)
+        {
+            _connectionEndpoint.handleOpen(open);
+        }
+        else
+        {
+            _open = open;
+        }
     }
 
     @Override
@@ -1470,7 +1503,7 @@ public class TransportImpl extends EndpointImpl
         }
 
         frame.getBody().invoke(this,frame.getPayload(), frame.getChannel());
-        return _closeReceived;
+        return _closeReceived || _conditionSet;
     }
 
     void put(Event.Type type, Object context) {
@@ -1494,6 +1527,10 @@ public class TransportImpl extends EndpointImpl
             if(!_conditionSet) {
                 if(error instanceof TransportDecodeException) {
                     setCondition(new ErrorCondition(AmqpError.DECODE_ERROR, error.getMessage()));
+                } else if (error instanceof TransportConditionException) {
+                    final Symbol condition = ((TransportConditionException) error).getCondition();
+                    ErrorCondition errorCondition = new ErrorCondition(condition, error.getMessage());
+                    setCondition(errorCondition);
                 } else {
                     String description =  error == null ? "connection aborted" : error.toString();
 
@@ -1510,6 +1547,7 @@ public class TransportImpl extends EndpointImpl
         }
 
         if (!postedTailClosed) {
+            // The FrameParser (_inputProcessor) sets _tail_closed when handleFrame returns _conditionSet.
             put(Event.Type.TRANSPORT_TAIL_CLOSED, this);
             postedTailClosed = true;
             maybePostClosed();
@@ -1875,6 +1913,9 @@ public class TransportImpl extends EndpointImpl
 
     @Override
     public void setOutboundFrameSizeLimit(int limit) {
+        if (limit > 0 && limit < MIN_MAX_FRAME_SIZE) {
+            throw new IllegalArgumentException("Outbound frame size limit should be at least 512 (MIN_MAX_FRAME_SIZE)");
+        }
         _outboundFrameSizeLimit = limit;
     }
 
@@ -1882,4 +1923,18 @@ public class TransportImpl extends EndpointImpl
     public int getOutboundFrameSizeLimit() {
         return _outboundFrameSizeLimit;
     }
+
+    @Override
+    public int getMaxTransfersPerDelivery() {
+        return _maxTransfersPerDelivery;
+    }
+
+    @Override
+    public void setMaxTransfersPerDelivery(final int maxTransfersPerDelivery) {
+        if (maxTransfersPerDelivery < 0) {
+            throw new IllegalArgumentException("maxTransfersPerDelivery must not be negative");
+        }
+
+        _maxTransfersPerDelivery = maxTransfersPerDelivery;
+    }
 }
diff --git a/proton-j/src/main/java/org/apache/qpid/proton/engine/impl/TransportOutputAdaptor.java b/proton-j/src/main/java/org/apache/qpid/proton/engine/impl/TransportOutputAdaptor.java
index f43d7e0b..c25d0d74 100644
--- a/proton-j/src/main/java/org/apache/qpid/proton/engine/impl/TransportOutputAdaptor.java
+++ b/proton-j/src/main/java/org/apache/qpid/proton/engine/impl/TransportOutputAdaptor.java
@@ -56,7 +56,10 @@ class TransportOutputAdaptor implements TransportOutput
             init_buffers();
         }
 
-        _output_done = _transportOutputWriter.writeInto(_outputBuffer);
+        if (!_output_done)
+        {
+            _output_done = _transportOutputWriter.writeInto(_outputBuffer);
+        }
         _head.limit(_outputBuffer.position());
 
         if (_outputBuffer.position() == 0 && _outputBuffer.capacity() > TransportImpl.BUFFER_RELEASE_THRESHOLD)
diff --git a/proton-j/src/main/java/org/apache/qpid/proton/engine/impl/TransportSession.java b/proton-j/src/main/java/org/apache/qpid/proton/engine/impl/TransportSession.java
index d657ba95..8f5ea6ae 100644
--- a/proton-j/src/main/java/org/apache/qpid/proton/engine/impl/TransportSession.java
+++ b/proton-j/src/main/java/org/apache/qpid/proton/engine/impl/TransportSession.java
@@ -21,16 +21,23 @@
 
 package org.apache.qpid.proton.engine.impl;
 
+import static org.apache.qpid.proton.amqp.transport.SessionError.WINDOW_VIOLATION;
+
 import java.util.HashMap;
+import java.util.Iterator;
 import java.util.Map;
+import java.util.Set;
 
 import org.apache.qpid.proton.amqp.Binary;
 import org.apache.qpid.proton.amqp.UnsignedInteger;
+import org.apache.qpid.proton.amqp.transport.AmqpError;
 import org.apache.qpid.proton.amqp.transport.Disposition;
 import org.apache.qpid.proton.amqp.transport.Flow;
+import org.apache.qpid.proton.amqp.transport.LinkError;
 import org.apache.qpid.proton.amqp.transport.Role;
 import org.apache.qpid.proton.amqp.transport.Transfer;
 import org.apache.qpid.proton.engine.Event;
+import org.apache.qpid.proton.engine.TransportConditionException;
 
 class TransportSession
 {
@@ -71,6 +78,7 @@ class TransportSession
     private boolean _endReceived;
     private boolean _beginSent;
     private boolean _endSent;
+    private boolean _transferLimitBlown;
 
     TransportSession(TransportImpl transport, SessionImpl session)
     {
@@ -279,6 +287,10 @@ class TransportSession
 
     public void handleTransfer(Transfer transfer, Binary payload)
     {
+        if(checkIncomingWindow()) {
+            return;
+        }
+
         DeliveryImpl delivery;
         incrementNextIncomingId(); // The conceptual/non-wire transfer-id, for the session window.
 
@@ -290,6 +302,10 @@ class TransportSession
         {
             delivery = _unsettledIncomingDeliveriesById.get(linkIncomingDeliveryId);
             delivery.getTransportDelivery().incrementSessionSize();
+
+            if(checkTransfersPerDeliveryLimit(delivery)) {
+                return;
+            }
         }
         else
         {
@@ -298,6 +314,7 @@ class TransportSession
             _incomingDeliveryId = deliveryId;
 
             ReceiverImpl receiver = transportReceiver.getReceiver();
+
             Binary deliveryTag = transfer.getDeliveryTag();
             delivery = receiver.delivery(deliveryTag.getArray(), deliveryTag.getArrayOffset(),
                                                       deliveryTag.getLength());
@@ -355,6 +372,41 @@ class TransportSession
         getSession().getConnection().put(Event.Type.DELIVERY, delivery);
     }
 
+    private boolean checkTransfersPerDeliveryLimit(DeliveryImpl delivery) {
+        if (_transferLimitBlown)
+        {
+            return true;
+        }
+
+        if (_transport.getMaxTransfersPerDelivery() > 0)
+        {
+            if (delivery.getTransportDelivery().getSessionSize() > _transport.getMaxTransfersPerDelivery())
+            {
+                _transport.closed(new TransportConditionException("Max transfers per delivery limit exceeded", LinkError.TRANSFER_LIMIT_EXCEEDED));
+                _transferLimitBlown = true;
+                return true;
+            }
+        }
+
+        return false;
+    }
+
+    private boolean checkIncomingWindow() {
+        if (_transferLimitBlown)
+        {
+            return true;
+        }
+
+        if (UnsignedInteger.ZERO.equals(_incomingWindowSize))
+        {
+            _transferLimitBlown = true;
+            _transport.closed(new TransportConditionException("Session incoming window exceeded", WINDOW_VIOLATION));
+            return true;
+        }
+
+        return false;
+    }
+
     private void verifyNewDeliveryIdSequence(UnsignedInteger previousId, UnsignedInteger linkIncomingId, UnsignedInteger newDeliveryId) {
         if(newDeliveryId == null) {
             throw new IllegalStateException("No delivery-id specified on first Transfer of new delivery");
@@ -434,13 +486,96 @@ class TransportSession
 
     void handleDisposition(Disposition disposition)
     {
-        UnsignedInteger id = disposition.getFirst();
-        UnsignedInteger last = disposition.getLast() == null ? id : disposition.getLast();
+        final UnsignedInteger first = disposition.getFirst();
         final Map<UnsignedInteger, DeliveryImpl> unsettledDeliveries =
                 disposition.getRole() == Role.RECEIVER ? _unsettledOutgoingDeliveriesById
                         : _unsettledIncomingDeliveriesById;
 
-        while(id.compareTo(last)<=0)
+        if (disposition.getLast() == null || first.equals(disposition.getLast())) {
+            applyDispositionSingle(first, disposition, unsettledDeliveries);
+            return;
+        }
+
+        final UnsignedInteger last = disposition.getLast();
+
+        final long range = getSequenceRange(first, last);
+        if (range > Integer.MAX_VALUE) {
+            _transport.closed(new TransportConditionException("Invalid disposition sequence range: " + first.longValue() + " to " + last.longValue(), AmqpError.NOT_ALLOWED));
+            return;
+        }
+
+        final long count = range + 1;
+        if (count > unsettledDeliveries.size())
+        {
+            applyDispositionByEntries(first, range, disposition, unsettledDeliveries);
+        }
+        else
+        {
+            applyDispositionByRange(first, range, disposition, unsettledDeliveries);
+        }
+    }
+
+    private void applyDispositionSingle(final UnsignedInteger id, final Disposition disposition,
+                                        final Map<UnsignedInteger, DeliveryImpl> unsettledDeliveries)
+    {
+        DeliveryImpl delivery = unsettledDeliveries.get(id);
+        if(delivery != null)
+        {
+            if(disposition.getState() != null)
+            {
+                delivery.setRemoteDeliveryState(disposition.getState());
+            }
+            if(disposition.getSettled())
+            {
+                delivery.setRemoteSettled(true);
+                unsettledDeliveries.remove(id);
+            }
+            delivery.updateWork();
+
+            getSession().getConnection().put(Event.Type.DELIVERY, delivery);
+        }
+    }
+
+    private long getSequenceRange(final UnsignedInteger first, final UnsignedInteger last) {
+        return (last.intValue() - first.intValue()) & 0xFFFFFFFFL;
+    }
+
+    private void applyDispositionByEntries(final UnsignedInteger first, final long range, final Disposition disposition,
+                                           final Map<UnsignedInteger, DeliveryImpl> unsettledDeliveries)
+    {
+        Set<UnsignedInteger> deliveryIDs = unsettledDeliveries.keySet();
+        Iterator<UnsignedInteger> idIterator = deliveryIDs.iterator();
+
+        while (idIterator.hasNext())
+        {
+            final UnsignedInteger id = idIterator.next();
+
+            if (getSequenceRange(first, id) <= range)
+            {
+                final DeliveryImpl delivery = unsettledDeliveries.get(id);
+                if (disposition.getState() != null)
+                {
+                    delivery.setRemoteDeliveryState(disposition.getState());
+                }
+                if (disposition.getSettled())
+                {
+                    delivery.setRemoteSettled(true);
+
+                    // Remove it from the map, via the key iterator
+                    idIterator.remove();
+                }
+                delivery.updateWork();
+
+                getSession().getConnection().put(Event.Type.DELIVERY, delivery);
+            }
+        }
+    }
+
+    private void applyDispositionByRange(final UnsignedInteger first, final long range, final Disposition disposition,
+                                         final Map<UnsignedInteger, DeliveryImpl> unsettledDeliveries)
+    {
+        UnsignedInteger id = first;
+        for (long i = 0L; i <= range; i++)
         {
             DeliveryImpl delivery = unsettledDeliveries.get(id);
             if(delivery != null)
@@ -449,7 +584,7 @@ class TransportSession
                 {
                     delivery.setRemoteDeliveryState(disposition.getState());
                 }
-                if(Boolean.TRUE.equals(disposition.getSettled()))
+                if(disposition.getSettled())
                 {
                     delivery.setRemoteSettled(true);
                     unsettledDeliveries.remove(id);
@@ -458,9 +593,9 @@ class TransportSession
 
                 getSession().getConnection().put(Event.Type.DELIVERY, delivery);
             }
+
             id = id.add(UnsignedInteger.ONE);
         }
-        //TODO - Implement.
     }
 
     void addUnsettledOutgoing(UnsignedInteger deliveryId, DeliveryImpl delivery)
diff --git a/proton-j/src/main/java/org/apache/qpid/proton/message/Message.java b/proton-j/src/main/java/org/apache/qpid/proton/message/Message.java
index db290e3c..adbe76c4 100644
--- a/proton-j/src/main/java/org/apache/qpid/proton/message/Message.java
+++ b/proton-j/src/main/java/org/apache/qpid/proton/message/Message.java
@@ -169,6 +169,8 @@ public interface Message
      */
     int decode(byte[] data, int offset, int length);
 
+    int decode(byte[] data, int offset, int length, MessageDecodeOptions options);
+
     /**
      * Decodes the Message from the given {@link ReadableBuffer}.
      * <p>
@@ -181,6 +183,20 @@ public interface Message
      */
     void decode(ReadableBuffer buffer);
 
+    /**
+     * Decodes the Message from the given {@link ReadableBuffer}.
+     * <p>
+     * If the buffer given does not contain the fully encoded Message bytes for decode
+     * this method will throw an exception to indicate the buffer underflow condition and
+     * the message object will be left in an undefined state.
+     *
+     * @param buffer
+     *      A {@link ReadableBuffer} that contains the complete message bytes.
+     * @param options
+     *      A {@link MessageDecodeOptions} that configures certain decode settings.
+     */
+    void decode(ReadableBuffer buffer, MessageDecodeOptions options);
+
     /**
      * Encodes up to {@code length} bytes of the message into the provided byte array,
      * starting at position {@code offset}.
diff --git a/proton-j/src/main/java/org/apache/qpid/proton/message/MessageDecodeOptions.java b/proton-j/src/main/java/org/apache/qpid/proton/message/MessageDecodeOptions.java
new file mode 100644
index 00000000..e2c2b2c1
--- /dev/null
+++ b/proton-j/src/main/java/org/apache/qpid/proton/message/MessageDecodeOptions.java
@@ -0,0 +1,85 @@
+/*
+ * 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.qpid.proton.message;
+
+/**
+ * Configuration options that can be passed to the {@link Message} decode APIs
+ * to govern certain behaviours of the AMQP decoder.
+ */
+public final class MessageDecodeOptions {
+
+    private static final int DEFAULT_MAX_DECODE_DEPTH = 32;
+
+    private int _maxDecodeDepth = DEFAULT_MAX_DECODE_DEPTH;
+    private int _zeroWidthElementLimit = 0;
+
+    /**
+     * New decode options with default values.
+     */
+    public MessageDecodeOptions() {
+    }
+
+    /**
+     * Gets the configured maximum number of elements that can be decoded from an array
+     * encoded with the zero-width elements (ulong0, uint0, list0, boolean-true,
+     * boolean-false, null).
+     *
+     * @return the max number of zero-width array elements allowed.
+     */
+    public int getZeroWidthArrayElementLimit() {
+        return _zeroWidthElementLimit;
+    }
+
+    /**
+     * Configures the maximum number of array elements allowed while decoding arrays
+     * of zero-width elements (ulong0, uint0, list0, boolean-true, boolean-false, null)
+     * before a decode exception.
+     *
+     * Default is 0, meaning only the empty array is permitted.
+     *
+     * @param zeroWidthElementLimit The configured max zero-width elements allowed.
+     *
+     * @return this {@link MessageDecodeOptions} instance.
+     */
+    public MessageDecodeOptions setZeroWidthArrayElementLimit(int zeroWidthElementLimit) {
+        _zeroWidthElementLimit = zeroWidthElementLimit;
+        return this;
+    }
+
+    /**
+     * Returns the maximum decode depth allowed before error when decoding Maps, Lists,
+     * Arrays, and certain related DescribedTypes from an encoded payload.
+     *
+     * @return the maximum depth limit before error for Maps, Lists, Arrays and related DescribedTypes.
+     */
+    public int getMaxDecodeDepth() {
+        return _maxDecodeDepth;
+    }
+
+    /**
+     * Configures the maximum decode depth allowed before error when decoding Maps, Lists,
+     * Arrays, and certain related DescribedTypes from an encoded payload. Default is 32.
+     *
+     * @param maxDecodeDepth The configured max decode depth allowed.
+     *
+     * @return this {@link MessageDecodeOptions} instance.
+     */
+    public MessageDecodeOptions setMaxDecodeDepth(int maxDecodeDepth) {
+        this._maxDecodeDepth = maxDecodeDepth;
+        return this;
+    }
+}
\ No newline at end of file
diff --git a/proton-j/src/main/java/org/apache/qpid/proton/message/impl/MessageImpl.java b/proton-j/src/main/java/org/apache/qpid/proton/message/impl/MessageImpl.java
index a31c169c..76a52eb7 100644
--- a/proton-j/src/main/java/org/apache/qpid/proton/message/impl/MessageImpl.java
+++ b/proton-j/src/main/java/org/apache/qpid/proton/message/impl/MessageImpl.java
@@ -23,6 +23,8 @@ package org.apache.qpid.proton.message.impl;
 
 import java.nio.ByteBuffer;
 import java.util.Date;
+import java.util.Objects;
+
 import org.apache.qpid.proton.amqp.Binary;
 import org.apache.qpid.proton.amqp.Symbol;
 import org.apache.qpid.proton.amqp.UnsignedByte;
@@ -33,6 +35,8 @@ import org.apache.qpid.proton.message.*;
 
 public class MessageImpl implements ProtonJMessage
 {
+    private static final MessageDecodeOptions DEFAULT_OPTIONS = new MessageDecodeOptions();
+
     private Header _header;
     private DeliveryAnnotations _deliveryAnnotations;
     private MessageAnnotations _messageAnnotations;
@@ -568,21 +572,47 @@ public class MessageImpl implements ProtonJMessage
     @Override
     public int decode(byte[] data, int offset, int length)
     {
+        return decode(data, offset, length, DEFAULT_OPTIONS);
+    }
+
+    @Override
+    public int decode(byte[] data, int offset, int length, MessageDecodeOptions options)
+    {
+        Objects.requireNonNull(options, "Options must not be null");
+
         final ByteBuffer buffer = ByteBuffer.wrap(data, offset, length);
-        decode(buffer);
+        decode(buffer, options);
 
         return length-buffer.remaining();
     }
 
     public void decode(ByteBuffer buffer)
     {
-        decode(ReadableBuffer.ByteBufferReader.wrap(buffer));
+        decode(ReadableBuffer.ByteBufferReader.wrap(buffer), DEFAULT_OPTIONS);
     }
 
+    public void decode(ByteBuffer buffer, MessageDecodeOptions options)
+    {
+        Objects.requireNonNull(options, "Options must not be null");
+
+        decode(ReadableBuffer.ByteBufferReader.wrap(buffer), options);
+    }
+
+    @Override
     public void decode(ReadableBuffer buffer)
     {
+        decode(buffer, DEFAULT_OPTIONS);
+    }
+
+    @Override
+    public void decode(ReadableBuffer buffer, MessageDecodeOptions options)
+    {
+        Objects.requireNonNull(options, "Options must not be null");
+
         DecoderImpl decoder = tlsCodec.get().decoder;
         decoder.setBuffer(buffer);
+        decoder.setZeroWidthArrayElementLimit(options.getZeroWidthArrayElementLimit());
+        decoder.setMaxDecodeDepth(options.getMaxDecodeDepth());
 
         _header = null;
         _deliveryAnnotations = null;
diff --git a/proton-j/src/test/java/org/apache/qpid/proton/amqp/SymbolTest.java b/proton-j/src/test/java/org/apache/qpid/proton/amqp/SymbolTest.java
new file mode 100644
index 00000000..2d210a41
--- /dev/null
+++ b/proton-j/src/test/java/org/apache/qpid/proton/amqp/SymbolTest.java
@@ -0,0 +1,395 @@
+/*
+ * 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.qpid.proton.amqp;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNotSame;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertTrue;
+
+import org.junit.Ignore;
+import org.junit.Test;
+
+/**
+ * Test the {@link Symbol} values.
+ */
+public class SymbolTest {
+
+    private final String LARGE_SYMBOL_VALUE = "Large String: " +
+        "The quick brown fox jumps over the lazy dog. " +
+        "The quick brown fox jumps over the lazy dog. " +
+        "The quick brown fox jumps over the lazy dog. " +
+        "The quick brown fox jumps over the lazy dog. " +
+        "The quick brown fox jumps over the lazy dog. " +
+        "The quick brown fox jumps over the lazy dog. " +
+        "The quick brown fox jumps over the lazy dog. " +
+        "The quick brown fox jumps over the lazy dog.";
+
+    private final String MEDIUM_SYMBOL_VALUE = "Medium String: " +
+         "The quick brown fox jumps over the lazy dog.";
+
+    @Test
+    public void testGetSymbolWithNullString() {
+        assertNull(Symbol.getSymbol((String) null));
+    }
+
+    @Test
+    public void testGetSaslSymbolWithNullString() {
+        assertNull(Symbol.getSaslSymbol((String) null));
+    }
+
+    @Test
+    public void testGetSymbolWithEmptyString() {
+        assertNotNull(Symbol.getSymbol(""));
+        assertSame(Symbol.getSymbol(""), Symbol.getSymbol(""));
+    }
+
+    @Test
+    public void testGetSaslSymbolWithEmptyString() {
+        assertNotNull(Symbol.getSaslSymbol(""));
+        assertSame(Symbol.getSaslSymbol(""), Symbol.getSaslSymbol(""));
+    }
+
+    @Test
+    public void testCompareTo() {
+        String symbolString1 = "Symbol-1";
+        String symbolString2 = "Symbol-2";
+        String symbolString3 = "Symbol-3";
+
+        Symbol symbol1 = Symbol.getSymbol(symbolString1);
+        Symbol symbol2 = Symbol.getSymbol(symbolString2);
+        Symbol symbol3 = Symbol.getSymbol(symbolString3);
+
+        assertEquals(0, symbol1.compareTo(symbol1));
+        assertEquals(0, symbol2.compareTo(symbol2));
+        assertEquals(0, symbol3.compareTo(symbol3));
+
+        assertTrue(symbol2.compareTo(symbol1) > 0);
+        assertTrue(symbol3.compareTo(symbol1) > 0);
+        assertTrue(symbol3.compareTo(symbol2) > 0);
+
+        assertTrue(symbol1.compareTo(symbol2) < 0);
+        assertTrue(symbol1.compareTo(symbol3) < 0);
+        assertTrue(symbol2.compareTo(symbol3) < 0);
+    }
+
+    @Test
+    public void testEquals() {
+        final String symbolString1 = "Symbol-1";
+        final String symbolString2 = "Symbol-2";
+        final String symbolString3 = "Symbol-3";
+
+        final Symbol symbol1 = Symbol.getSymbol(symbolString1);
+        final Symbol symbol2 = Symbol.getSymbol(symbolString2);
+        final Symbol symbol3 = Symbol.getSymbol(symbolString3);
+
+        assertEquals(symbolString1, symbol1.toString());
+        assertEquals(symbolString2, symbol2.toString());
+        assertEquals(symbolString3, symbol3.toString());
+
+        assertNotEquals(symbol1, symbol2);
+        assertNotEquals(symbol2, symbol3);
+        assertNotEquals(symbol3, symbol1);
+
+        assertNotEquals(symbol1, symbolString1);
+        assertNotEquals(symbol2, symbolString2);
+        assertNotEquals(symbol3, symbolString3);
+
+        assertNotEquals(symbol1, null);
+        assertNotEquals(symbol2, null);
+        assertNotEquals(symbol3, null);
+
+        final Symbol symbol1B = Symbol.getSymbol(symbolString1);
+
+        assertEquals(symbol1, symbol1B);
+        assertEquals(symbol1, symbol1); // Itself
+
+        final Symbol largeSymbolA = Symbol.getSymbol(LARGE_SYMBOL_VALUE);
+        final Symbol largeSymbolB = Symbol.getSymbol(LARGE_SYMBOL_VALUE);
+        final Symbol largeSymbolC = Symbol.getSymbol(LARGE_SYMBOL_VALUE + "C");
+
+        assertNotEquals(symbol1, largeSymbolA);
+        assertNotEquals(largeSymbolA, symbol1);
+        assertNotEquals(largeSymbolA, largeSymbolC);
+        assertNotEquals(largeSymbolC, largeSymbolA);
+
+        assertEquals(largeSymbolA, largeSymbolB);
+        assertEquals(largeSymbolB, largeSymbolA);
+
+        assertEquals(largeSymbolA, largeSymbolA); // Itself
+
+        assertNotEquals(largeSymbolA, null);
+
+        final Symbol saslSymbol1 = Symbol.getSymbol(symbolString1);
+        final Symbol saslSymbol2 = Symbol.getSymbol(symbolString2);
+
+        assertEquals(symbol1, saslSymbol1);
+        assertEquals(saslSymbol1, symbol1);
+
+        assertNotEquals(symbol2, saslSymbol1);
+        assertNotEquals(saslSymbol1, symbol2);
+
+        assertNotEquals(saslSymbol1, saslSymbol2);
+        assertNotEquals(saslSymbol2, saslSymbol1);
+    }
+
+    @Test
+    public void testHashcode() {
+        final String symbolString1 = "Symbol-1";
+        final String symbolString2 = "Symbol-2";
+
+        final Symbol symbol1 = Symbol.getSymbol(symbolString1);
+        final Symbol symbol2 = Symbol.getSymbol(symbolString2);
+
+        assertNotEquals(symbol1, symbol2);
+        assertNotEquals(symbol1.hashCode(), symbol2.hashCode());
+
+        assertEquals(symbol1.hashCode(), Symbol.getSymbol(symbolString1).hashCode());
+        assertEquals(symbol2.hashCode(), Symbol.getSymbol(symbolString2).hashCode());
+        assertEquals(symbol1.hashCode(), Symbol.getSaslSymbol(symbolString1).hashCode());
+        assertEquals(symbol2.hashCode(), Symbol.getSaslSymbol(symbolString2).hashCode());
+
+        final Symbol largeSymbolA = Symbol.getSymbol(LARGE_SYMBOL_VALUE);
+        final Symbol largeSymbolB = Symbol.getSymbol(LARGE_SYMBOL_VALUE);
+
+        assertEquals(largeSymbolA, largeSymbolB);
+        assertNotSame(largeSymbolA, largeSymbolB);
+        assertEquals(largeSymbolA.hashCode(), largeSymbolB.hashCode());
+    }
+
+    @Test
+    public void testValueOf() {
+        String symbolString1 = "Symbol-1";
+        String symbolString2 = "Symbol-2";
+
+        Symbol symbol1 = Symbol.valueOf(symbolString1);
+        Symbol symbol2 = Symbol.getSymbol(symbolString2);
+
+        assertNotEquals(symbol1, symbol2);
+
+        assertEquals(symbolString1, symbol1.toString());
+        assertEquals(symbolString2, symbol2.toString());
+    }
+
+    @Test
+    public void testValueOfProducesCachedSingleton() {
+        String symbolString = "Symbol-String";
+
+        Symbol symbol1 = Symbol.valueOf(symbolString);
+        Symbol symbol2 = Symbol.getSymbol(symbolString);
+
+        assertEquals(symbolString, symbol1.toString());
+        assertEquals(symbolString, symbol2.toString());
+
+        assertSame(symbol1, symbol2);
+    }
+
+    @Test
+    public void testGetSymbol() {
+        String symbolString1 = "Symbol-1";
+        String symbolString2 = "Symbol-2";
+
+        Symbol symbol1 = Symbol.getSymbol(symbolString1);
+        Symbol symbol2 = Symbol.getSymbol(symbolString2);
+
+        assertNotEquals(symbol1, symbol2);
+
+        assertEquals(symbolString1, symbol1.toString());
+        assertEquals(symbolString2, symbol2.toString());
+    }
+
+    @Test
+    public void testGetSymbolProducesCachedSingleton() {
+        String symbolString = "Symbol-String";
+
+        Symbol symbol1 = Symbol.getSymbol(symbolString);
+        Symbol symbol2 = Symbol.getSymbol(symbolString);
+
+        assertEquals(symbolString, symbol1.toString());
+        assertEquals(symbolString, symbol2.toString());
+
+        assertSame(symbol1, symbol2);
+    }
+
+    @Test
+    public void testGetSymbolAndValueOfProduceCachedSingleton() {
+        String symbolString = "Symbol-String";
+
+        Symbol symbol1 = Symbol.valueOf(symbolString);
+        Symbol symbol2 = Symbol.getSymbol(symbolString);
+
+        assertEquals(symbolString, symbol1.toString());
+        assertEquals(symbolString, symbol2.toString());
+
+        assertSame(symbol1, symbol2);
+    }
+
+    @Test
+    public void testToStringProducesCachedSingleton() {
+        String symbolString = "Symbol-String";
+
+        Symbol symbol1 = Symbol.getSymbol(symbolString);
+        Symbol symbol2 = Symbol.getSymbol(symbolString);
+
+        assertEquals(symbolString, symbol1.toString());
+        assertEquals(symbolString, symbol2.toString());
+
+        assertSame(symbol1, symbol2);
+        assertSame(symbol1.toString(), symbol2.toString());
+    }
+
+    @Test
+    public void testLargeSymbolNotCached() {
+        Symbol symbol1 = Symbol.getSymbol(LARGE_SYMBOL_VALUE);
+        Symbol symbol2 = Symbol.getSymbol(LARGE_SYMBOL_VALUE);
+
+        assertNotSame(symbol1, symbol2);
+    }
+
+    @Test
+    public void testLargeSaslSymbolNotCached() {
+        Symbol symbol1 = Symbol.getSaslSymbol(LARGE_SYMBOL_VALUE);
+        Symbol symbol2 = Symbol.getSaslSymbol(LARGE_SYMBOL_VALUE);
+
+        assertNotSame(symbol1, symbol2);
+    }
+
+    @Test
+    @Ignore("Cache is static so running has side effects.")
+    public void testGetSymbolCachingMaxesOut() {
+        boolean notSame = false;
+        int attempts = 0;
+        final String prefix = "getSymbol";
+
+        for (attempts = 0; attempts <= Short.MAX_VALUE; attempts++) {
+            final String name = prefix + String.valueOf(attempts);
+
+            final Symbol symbol1 = Symbol.getSymbol(name);
+            final Symbol symbol2 = Symbol.getSymbol(name);
+
+            if (symbol1 != symbol2) {
+                notSame = true;
+                break;
+            }
+        }
+
+        assertTrue(notSame);
+        assertTrue(attempts > 500);
+        assertTrue(attempts < 8193);
+    }
+
+    @Test
+    @Ignore("Cache is static so running has side effects.")
+    public void testGetSaslSymbolCachingMaxesOut() {
+        boolean notSame = false;
+        int attempts = 0;
+        final String prefix = "getSaslSymbol";
+
+        for (attempts = 0; attempts <= Short.MAX_VALUE; attempts++) {
+            final String name = prefix + String.valueOf(attempts);
+
+            final Symbol symbol1 = Symbol.getSaslSymbol(name);
+            final Symbol symbol2 = Symbol.getSaslSymbol(name);
+
+            if (symbol1 != symbol2) {
+                notSame = true;
+                break;
+            }
+        }
+
+        assertTrue(notSame);
+        assertTrue(attempts > 5);
+        assertTrue(attempts < 256);
+    }
+
+    @Test
+    public void testGetSymbolAndGetSaslSymbolUseDifferentCachesBothCache() {
+        final String symbolString = "Test-Sasl-Symbol-String";
+
+        // Try SASL first, so it caches
+        final Symbol saslSymbol1 = Symbol.getSaslSymbol(symbolString);
+        final Symbol saslSymbol2 = Symbol.getSaslSymbol(symbolString);
+
+        assertEquals(symbolString, saslSymbol1.toString());
+        assertEquals(symbolString, saslSymbol2.toString());
+
+        assertSame(saslSymbol1, saslSymbol2);
+
+        // Then try main, which never checks SASL cache.
+        final Symbol symbol3 = Symbol.getSymbol(symbolString);
+        final Symbol symbol4 = Symbol.getSymbol(symbolString);
+
+        assertEquals(symbolString, symbol3.toString());
+        assertEquals(symbolString, symbol4.toString());
+
+        assertSame(symbol3, symbol4);
+
+        assertNotSame(saslSymbol1, symbol3);
+        assertNotSame(saslSymbol2, symbol4);
+    }
+
+    @Test
+    public void testGetSymbolAndGetSaslSymbolUseDifferentCachesButSaslCanReuse() {
+        final String symbolString = "Test-Symbol-String";
+
+        // Try main first, so it caches
+        final Symbol symbol1 = Symbol.getSymbol(symbolString);
+        final Symbol symbol2 = Symbol.getSymbol(symbolString);
+
+        assertEquals(symbolString, symbol1.toString());
+        assertEquals(symbolString, symbol2.toString());
+
+        assertSame(symbol1, symbol2);
+
+        // Then try SASL, which will first check the main cache to avoid dup.
+        final Symbol saslSymbol3 = Symbol.getSaslSymbol(symbolString);
+        final Symbol saslSymbol4 = Symbol.getSaslSymbol(symbolString);
+
+        assertEquals(symbolString, saslSymbol3.toString());
+        assertEquals(symbolString, saslSymbol4.toString());
+
+        assertSame(saslSymbol3, saslSymbol4);
+
+        assertSame(symbol1, saslSymbol3);
+        assertSame(symbol2, saslSymbol4);
+    }
+
+    @Test
+    public void testGetSymbolAndGetSaslSymbolUseDifferentCachesOnlyMainCached() {
+        final Symbol saslSymbol1 = Symbol.getSaslSymbol(MEDIUM_SYMBOL_VALUE);
+        final Symbol saslSymbol2 = Symbol.getSaslSymbol(MEDIUM_SYMBOL_VALUE);
+
+        assertEquals(MEDIUM_SYMBOL_VALUE, saslSymbol1.toString());
+        assertEquals(MEDIUM_SYMBOL_VALUE, saslSymbol2.toString());
+
+        assertNotSame(saslSymbol1, saslSymbol2); // Exceeded length for SASL cache
+
+        final Symbol symbol3 = Symbol.getSymbol(MEDIUM_SYMBOL_VALUE);
+        final Symbol symbol4 = Symbol.getSymbol(MEDIUM_SYMBOL_VALUE);
+
+        assertEquals(MEDIUM_SYMBOL_VALUE, symbol3.toString());
+        assertEquals(MEDIUM_SYMBOL_VALUE, symbol4.toString());
+
+        assertSame(symbol3, symbol4);
+
+        assertNotSame(saslSymbol1, symbol3);
+        assertNotSame(saslSymbol2, symbol4);
+    }
+}
diff --git a/proton-j/src/test/java/org/apache/qpid/proton/codec/AmqpValueTypeCodecTest.java b/proton-j/src/test/java/org/apache/qpid/proton/codec/AmqpValueTypeCodecTest.java
index f2154e32..ddeaf993 100644
--- a/proton-j/src/test/java/org/apache/qpid/proton/codec/AmqpValueTypeCodecTest.java
+++ b/proton-j/src/test/java/org/apache/qpid/proton/codec/AmqpValueTypeCodecTest.java
@@ -19,10 +19,14 @@ package org.apache.qpid.proton.codec;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
 
 import java.io.IOException;
+import java.nio.charset.StandardCharsets;
 import java.util.UUID;
 
+import org.apache.qpid.proton.amqp.DescribedType;
+import org.apache.qpid.proton.amqp.Symbol;
 import org.apache.qpid.proton.amqp.messaging.AmqpValue;
 import org.junit.Test;
 
@@ -65,7 +69,7 @@ public class AmqpValueTypeCodecTest extends CodecTestSupport {
             encoder.writeObject(value);
         }
 
-        buffer.clear();
+        buffer.flip();
 
         for (int i = 0; i < size; ++i) {
             final Object result = decoder.readObject();
@@ -78,4 +82,143 @@ public class AmqpValueTypeCodecTest extends CodecTestSupport {
             assertEquals(value.getValue(), decoded.getValue());
         }
     }
+
+    @Test
+    public void testDecodeAmqpValueContainingDescribedType() throws IOException {
+        final String descriptorValue = "UNKNOWN";
+        final byte[] descriptorBytes = descriptorValue.getBytes(StandardCharsets.US_ASCII);
+        final String describedString = "testDecodeAmqpValueContainingDescribedType";
+        final byte[] describedStringBytes = describedString.getBytes(StandardCharsets.UTF_8);
+
+        buffer.put((byte) 0x00); // DescribedType
+        buffer.put((byte) EncodingCodes.SMALLULONG); // small-ulong constructor
+        buffer.put((byte) 0x77); // amqp-value ulong descriptor
+        buffer.put((byte) 0x00); // DescribedType
+        buffer.put(EncodingCodes.SYM8); // Symbol
+        buffer.put((byte) descriptorBytes.length); // descriptor length
+        buffer.put(descriptorBytes); // descriptor value
+        buffer.put(EncodingCodes.STR8); // String
+        buffer.put((byte) describedStringBytes.length); // string length
+        buffer.put(describedStringBytes); // string value
+
+        buffer.flip();
+
+        final Object result = decoder.readObject();
+
+        assertNotNull(result);
+        assertTrue(result instanceof AmqpValue);
+
+        AmqpValue decoded = (AmqpValue) result;
+
+        assertTrue(decoded.getValue() instanceof DescribedType);
+        final DescribedType decodedDescribed = (DescribedType) decoded.getValue();
+        assertEquals(Symbol.valueOf(descriptorValue), decodedDescribed.getDescriptor());
+        assertEquals(describedString, decodedDescribed.getDescribed());
+    }
+
+    @Test
+    public void testDecodeAmqpValueContainingDescribedTypeNested() throws IOException {
+        final String descriptor1Value = "UNKNOWN1";
+        final byte[] descriptor1Bytes = descriptor1Value.getBytes(StandardCharsets.US_ASCII);
+        final String descriptor2Value = "UNKNOWN2";
+        final byte[] descriptor2Bytes = descriptor2Value.getBytes(StandardCharsets.US_ASCII);
+
+        buffer.put((byte) 0x00); // DescribedType
+        buffer.put((byte) EncodingCodes.SMALLULONG); // small-ulong constructor
+        buffer.put((byte) 0x77); // amqp-value ulong descriptor
+        buffer.put((byte) 0x00); // DescribedType
+        buffer.put(EncodingCodes.SYM8); // Symbol
+        buffer.put((byte) descriptor1Bytes.length); // descriptor length
+        buffer.put(descriptor1Bytes); // descriptor value
+        buffer.put((byte) 0x00); // DescribedType
+        buffer.put(EncodingCodes.SYM8); // Symbol
+        buffer.put((byte) descriptor2Bytes.length); // descriptor2 length
+        buffer.put(descriptor2Bytes); // descriptor2 value
+        buffer.put((byte) 0x45); // list0 primitive type constructor
+
+        buffer.flip();
+
+        try {
+            decoder.readObject();
+            fail("Expected exception");
+        } catch(DecodeException de) {
+            assertEquals("DescribedType value encoding cannot be another DescribedType, must be a primitive type", de.getMessage());
+            //expected
+        }
+    }
+
+    @Test
+    public void testDecodeAmqpValueContainingDescribedTypeNestedAmqpValue() throws IOException {
+        final String descriptorValue = "UNKNOWN1";
+        final byte[] descriptorBytes = descriptorValue.getBytes(StandardCharsets.US_ASCII);
+
+        buffer.put((byte) 0x00); // DescribedType
+        buffer.put((byte) EncodingCodes.SMALLULONG); // small-ulong constructor
+        buffer.put((byte) 0x77); // amqp-value ulong descriptor
+        buffer.put((byte) 0x00); // DescribedType
+        buffer.put(EncodingCodes.SYM8); // Symbol
+        buffer.put((byte) descriptorBytes.length); // descriptor length
+        buffer.put(descriptorBytes); // descriptor value
+        buffer.put((byte) 0x00); // DescribedType
+        buffer.put((byte) EncodingCodes.SMALLULONG); // small-ulong constructor
+        buffer.put((byte) 0x77); // amqp-value ulong descriptor
+        buffer.put((byte) 0x45); // list0 primitive type constructor
+
+        buffer.flip();
+
+        try {
+            decoder.readObject();
+            fail("Expected exception");
+        } catch(DecodeException de) {
+            assertEquals("DescribedType value encoding cannot be another DescribedType, must be a primitive type", de.getMessage());
+            //expected
+        }
+    }
+
+    @Test
+    public void testDecodeAmqpValueContainingAmqpValueNested() throws IOException {
+        final String describedString = "testDecodeAmqpValueContainingAmqpValueNested";
+        final byte[] describedStringBytes = describedString.getBytes(StandardCharsets.UTF_8);
+
+        buffer.put((byte) 0x00); // DescribedType
+        buffer.put((byte) EncodingCodes.SMALLULONG); // small-ulong constructor
+        buffer.put((byte) 0x77); // amqp-value ulong descriptor
+        buffer.put((byte) 0x00); // DescribedType
+        buffer.put((byte) EncodingCodes.SMALLULONG); // small-ulong constructor
+        buffer.put((byte) 0x77); // amqp-value ulong descriptor
+        buffer.put((byte) 0x00); // DescribedType
+        buffer.put((byte) EncodingCodes.SMALLULONG); // small-ulong constructor
+        buffer.put((byte) 0x77); // amqp-value ulong descriptor
+        buffer.put(EncodingCodes.STR8); // String
+        buffer.put((byte) describedStringBytes.length); // string length
+        buffer.put(describedStringBytes); // string value
+
+        buffer.flip();
+        int limit = buffer.limit();
+
+        // We count amqp-value containing another described-type as having depth, so this is depth 2
+        decoder.setMaxDecodeDepth(1);
+        try {
+            decoder.readObject();
+            fail("Expected exception");
+        } catch(DecodeException de) {
+            assertEquals("The nesting of types in the object being decoded exceeded the configured limit: 1", de.getMessage());
+            //expected
+        }
+
+        buffer.clear();
+        buffer.limit(limit);
+
+        // Try again with permissive depth limit
+        decoder.setMaxDecodeDepth(2);
+        Object result = decoder.readObject();
+
+        assertNotNull(result);
+        assertTrue(result instanceof AmqpValue);
+        final Object resultNested1 = ((AmqpValue)result).getValue();
+        assertTrue(resultNested1 instanceof AmqpValue);
+        final Object resultNested2 = ((AmqpValue)resultNested1).getValue();
+        assertTrue(resultNested2 instanceof AmqpValue);
+        assertEquals(describedString, ((AmqpValue) resultNested2).getValue());
+    }
 }
diff --git a/proton-j/src/test/java/org/apache/qpid/proton/codec/ApplicationPropertiesTypeTest.java b/proton-j/src/test/java/org/apache/qpid/proton/codec/ApplicationPropertiesTypeTest.java
index 778b5090..6a8493ef 100644
--- a/proton-j/src/test/java/org/apache/qpid/proton/codec/ApplicationPropertiesTypeTest.java
+++ b/proton-j/src/test/java/org/apache/qpid/proton/codec/ApplicationPropertiesTypeTest.java
@@ -19,12 +19,18 @@ package org.apache.qpid.proton.codec;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
 
 import java.io.IOException;
+import java.nio.BufferUnderflowException;
+import java.nio.ByteBuffer;
 import java.util.Date;
+import java.util.Iterator;
 import java.util.LinkedHashMap;
 import java.util.Map;
+import java.util.Map.Entry;
 
+import org.apache.qpid.proton.amqp.UnsignedInteger;
 import org.apache.qpid.proton.amqp.messaging.ApplicationProperties;
 import org.junit.Test;
 import org.mockito.Mockito;
@@ -109,4 +115,197 @@ public class ApplicationPropertiesTypeTest extends CodecTestSupport {
         // Each key should also try and reserve space for the String data
         Mockito.verify(spy, Mockito.times(ENTRIES + 1)).ensureRemaining(Mockito.anyInt());
     }
+
+    @Test
+    public void testDecodeApplicationProperties() throws Exception {
+        Object o = doDecodeApplicationPropertiesWithSpecificSizeCountTestImpl(false, 25, 6);
+        assertDecodedApplicationProperties(o);
+
+        o = doDecodeApplicationPropertiesWithSpecificSizeCountTestImpl(true, 28, 6);
+        assertDecodedApplicationProperties(o);
+    }
+
+    @Test
+    public void testDecodeThrowsWithSizeBeyondAvailable() throws Exception {
+        try {
+            doDecodeApplicationPropertiesWithSpecificSizeCountTestImpl(false, 25 + 1, 6);
+            fail("Expected exception to be thrown");
+        } catch (DecodeException de) {
+            assertEquals("Map size 26 cannot be more than remaining bytes 25", de.getMessage());
+        }
+
+        try {
+            doDecodeApplicationPropertiesWithSpecificSizeCountTestImpl(true, 28 + 1, 6);
+            fail("Expected exception to be thrown");
+        } catch (DecodeException de) {
+            assertEquals("Map size 29 cannot be more than remaining bytes 28", de.getMessage());
+        }
+    }
+
+    @Test
+    public void testDecodeThrowsWithNegativeSize() throws Exception {
+        try {
+            doDecodeApplicationPropertiesWithSpecificSizeCountTestImpl(true, UnsignedInteger.MAX_VALUE.intValue(), 6);
+            fail("Expected exception to be thrown");
+        } catch (DecodeException de) {
+            assertEquals("Map size -1 cannot be negative", de.getMessage());
+        }
+    }
+
+    @Test
+    public void testDecodeThrowsWithNegativeCount() throws Exception {
+        try {
+            doDecodeApplicationPropertiesWithSpecificSizeCountTestImpl(true, 28, UnsignedInteger.MAX_VALUE.intValue());
+            fail("Expected exception to be thrown");
+        } catch (DecodeException de) {
+            assertEquals("Map element count -1 cannot be negative", de.getMessage());
+        }
+    }
+
+    @Test
+    public void testDecodeThrowsWithCountLargerThanSize() throws Exception {
+        try {
+            // Deliberately tries not to decode the full map, so the 'remaining' checks dont foul
+            doDecodeApplicationPropertiesWithSpecificSizeCountTestImpl(true, 20, 22);
+            fail("Expected exception to be thrown");
+        } catch (DecodeException de) {
+            assertEquals("Map element count 22 must be less than size (20)", de.getMessage());
+        }
+    }
+
+    @Test
+    public void testDecodeThrowsWithOddCount() throws Exception {
+        try {
+            doDecodeApplicationPropertiesWithSpecificSizeCountTestImpl(false, 25, 5);
+            fail("Expected exception to be thrown");
+        } catch (DecodeException de) {
+            assertEquals("Map element count 5 cannot be odd", de.getMessage());
+        }
+
+        try {
+            doDecodeApplicationPropertiesWithSpecificSizeCountTestImpl(true, 28, 5);
+            fail("Expected exception to be thrown");
+        } catch (DecodeException de) {
+            assertEquals("Map element count 5 cannot be odd", de.getMessage());
+        }
+    }
+
+    private Object doDecodeApplicationPropertiesWithSpecificSizeCountTestImpl(final boolean map32, int size, int count) {
+        ByteBuffer tmpBuf = ByteBuffer.allocate(1024);
+        tmpBuf.put((byte) 0x00); // DescribedType
+        tmpBuf.put((byte) 0x53); // small-ulong
+        tmpBuf.put((byte) 0x74); // application-properties ulong descriptor
+        if (map32) {
+            tmpBuf.put((byte) (byte) 0xD1); // map32 primitive type constructor
+            tmpBuf.putInt(size); // size
+            tmpBuf.putInt(count); // count
+        } else {
+            tmpBuf.put((byte) (byte) 0xC1); // map8 primitive type constructor
+            tmpBuf.put((byte) size); // size
+            tmpBuf.put((byte) count); // count
+        }
+
+        tmpBuf.put((byte) 0xA1); // str8
+        tmpBuf.put((byte) 1); // length
+        tmpBuf.put((byte) 0x33); // "3"
+        tmpBuf.put((byte) 0x71); // int
+        tmpBuf.putInt(3);
+
+        tmpBuf.put((byte) 0xA1); // str8
+        tmpBuf.put((byte) 1); // length
+        tmpBuf.put((byte) 0x32); // "2"
+        tmpBuf.put((byte) 0x71); // int
+        tmpBuf.putInt(2);
+
+        tmpBuf.put((byte) 0xA1); // str8
+        tmpBuf.put((byte) 1); // length
+        tmpBuf.put((byte) 0x31); // "1"
+        tmpBuf.put((byte) 0x71); // int
+        tmpBuf.putInt(1);
+
+        tmpBuf.flip();
+
+        int oldLimit = tmpBuf.limit();
+
+        decoder.setByteBuffer(tmpBuf);
+
+        final Object decoded;
+        try
+        {
+            decoded = decoder.readObject();
+        }
+        finally
+        {
+            assertEquals(oldLimit, tmpBuf.limit());
+        }
+        assertEquals(oldLimit, tmpBuf.position());
+
+        return decoded;
+    }
+
+    private void assertDecodedApplicationProperties(Object o) {
+        assertTrue(o instanceof ApplicationProperties);
+        Map<String,Object> map = ((ApplicationProperties)o).getValue();
+        assertTrue(map instanceof LinkedHashMap);
+
+        assertEquals(3, map.size());
+        assertEquals(Integer.valueOf(3), map.get("3"));
+        assertEquals(Integer.valueOf(2), map.get("2"));
+        assertEquals(Integer.valueOf(1), map.get("1"));
+
+        Iterator<Map.Entry<String,Object>> iterator = map.entrySet().iterator();
+        assertTrue(iterator.hasNext());
+
+        Entry<String, Object> entry = iterator.next();
+        assertEquals("3", entry.getKey());
+        assertEquals(Integer.valueOf(3), entry.getValue());
+
+        assertTrue(iterator.hasNext());
+        entry = iterator.next();
+        assertEquals("2", entry.getKey());
+        assertEquals(Integer.valueOf(2), entry.getValue());
+
+        assertTrue(iterator.hasNext());
+        entry = iterator.next();
+        assertEquals("1", entry.getKey());
+        assertEquals(Integer.valueOf(1), entry.getValue());
+    }
+
+    @Test
+    public void testDecodeOverflowThrows() throws Exception {
+        try {
+            // Deliberately pass size for part map, but count for full map...will try to decode count, overflow size
+            doDecodeApplicationPropertiesWithSpecificSizeCountTestImpl(false, 25 - 8, 6);
+            fail("Expected exception to be thrown");
+        } catch (BufferUnderflowException bue) {
+            // Expected
+        }
+
+        try {
+            // Deliberately pass size for part map, but count for full map...will try to decode count, overflow size
+            doDecodeApplicationPropertiesWithSpecificSizeCountTestImpl(true, 28 - 8, 6);
+            fail("Expected exception to be thrown");
+        } catch (BufferUnderflowException bue) {
+            // Expected
+        }
+    }
+
+    @Test
+    public void testDecodeUnderflowThrows() throws Exception {
+        try {
+            // Deliberately pass size for full map, but count for part map...will try to decode count, underflow size
+            doDecodeApplicationPropertiesWithSpecificSizeCountTestImpl(false, 25, 4);
+            fail("Expected exception to be thrown");
+        } catch (DecodeException de) {
+            assertEquals("ApplicationProperties incorrectly encoded, left 8 bytes from size indicated 25", de.getMessage());
+        }
+
+        try {
+            // Deliberately pass size for full map, but count for part map...will try to decode count, underflow size
+            doDecodeApplicationPropertiesWithSpecificSizeCountTestImpl(true, 28, 4);
+            fail("Expected exception to be thrown");
+        } catch (DecodeException de) {
+            assertEquals("ApplicationProperties incorrectly encoded, left 8 bytes from size indicated 28", de.getMessage());
+        }
+    }
 }
diff --git a/proton-j/src/test/java/org/apache/qpid/proton/codec/ArrayTypeCodecTest.java b/proton-j/src/test/java/org/apache/qpid/proton/codec/ArrayTypeCodecTest.java
index 9a85216e..5d31d9af 100644
--- a/proton-j/src/test/java/org/apache/qpid/proton/codec/ArrayTypeCodecTest.java
+++ b/proton-j/src/test/java/org/apache/qpid/proton/codec/ArrayTypeCodecTest.java
@@ -21,18 +21,25 @@ import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
 
 import java.io.IOException;
+import java.nio.BufferUnderflowException;
 import java.nio.ByteBuffer;
+import java.nio.charset.StandardCharsets;
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.Collections;
 import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.Random;
 import java.util.UUID;
 
+import org.apache.qpid.proton.amqp.DescribedType;
 import org.apache.qpid.proton.amqp.Symbol;
+import org.apache.qpid.proton.amqp.UnsignedInteger;
+import org.apache.qpid.proton.amqp.UnsignedLong;
 import org.junit.Test;
 import org.mockito.Mockito;
 
@@ -319,9 +326,18 @@ public class ArrayTypeCodecTest extends CodecTestSupport {
         doEncodeDecodeByteArrayTestImpl(192);
     }
 
+    @Test
+    public void testEncodeDecodeByteArray253() throws Throwable {
+        doEncodeDecodeByteArrayTestImpl(253);
+    }
+
+    @Test
+    public void testEncodeDecodeByteArray254() throws Throwable {
+        doEncodeDecodeByteArrayTestImpl(254);
+    }
+
     @Test
     public void testEncodeDecodeByteArray384() throws Throwable {
-        // byte array32
         doEncodeDecodeByteArrayTestImpl(384);
     }
 
@@ -338,7 +354,7 @@ public class ArrayTypeCodecTest extends CodecTestSupport {
             ByteBuffer expectedEncodingWrapper = ByteBuffer.wrap(expectedEncoding);
 
             // Write the array encoding code, array size, and element count
-            if(count < 254) {
+            if(encodingWidth == 1) {
                 expectedEncodingWrapper.put((byte) 0xE0); // 'array8' type descriptor code
                 expectedEncodingWrapper.put((byte) arrayPayloadSize);
                 expectedEncodingWrapper.put((byte) count);
@@ -397,38 +413,334 @@ public class ArrayTypeCodecTest extends CodecTestSupport {
         return payload;
     }
 
+    @Test
+    public void testEncodeDecodeBooleanArray0() throws Throwable {
+        // boolean array8 size 0
+        doEncodeDecodeBooleanArrayTestImpl(createPayloadArrayBooleans(0));
+    }
+
     @Test
     public void testEncodeDecodeBooleanArray100() throws Throwable {
         // boolean array8 less than 128 bytes
-        doEncodeDecodeBooleanArrayTestImpl(100);
+        doEncodeDecodeBooleanArrayTestImpl(createPayloadArrayBooleans(100));
     }
 
     @Test
     public void testEncodeDecodeBooleanArray192() throws Throwable {
         // boolean array8 greater than 128 bytes
-        doEncodeDecodeBooleanArrayTestImpl(192);
+        doEncodeDecodeBooleanArrayTestImpl(createPayloadArrayBooleans(192));
+    }
+
+    @Test
+    public void testEncodeDecodeBooleanArray253() throws Throwable {
+        doEncodeDecodeBooleanArrayTestImpl(createPayloadArrayBooleans(253));
+    }
+
+    @Test
+    public void testEncodeDecodeBooleanArray254() throws Throwable {
+        doEncodeDecodeBooleanArrayTestImpl(createPayloadArrayBooleans(254));
+    }
+
+    @Test
+    public void testEncodeDecodeBooleanArray255() throws Throwable {
+        doEncodeDecodeBooleanArrayTestImpl(createPayloadArrayBooleans(255));
     }
 
     @Test
     public void testEncodeDecodeBooleanArray384() throws Throwable {
-        // boolean array32
-        doEncodeDecodeBooleanArrayTestImpl(384);
+        doEncodeDecodeBooleanArrayTestImpl(createPayloadArrayBooleans(384));
+    }
+
+    @Test
+    public void testEncodeDecodeBooleanArrayTrue100() throws Throwable {
+        doEncodeDecodeBooleanArrayTestImpl(createPayloadArrayBoolean(true, 100));
+    }
+
+    @Test
+    public void testEncodeDecodeBooleanArrayTrue192() throws Throwable {
+        doEncodeDecodeBooleanArrayTestImpl(createPayloadArrayBoolean(true, 192));
+    }
+
+    @Test
+    public void testEncodeDecodeBooleanArrayTrue253() throws Throwable {
+        doEncodeDecodeBooleanArrayTestImpl(createPayloadArrayBoolean(true, 253));
+    }
+
+    @Test
+    public void testEncodeDecodeBooleanArrayTrue254() throws Throwable {
+        doEncodeDecodeBooleanArrayTestImpl(createPayloadArrayBoolean(true, 254));
+    }
+
+    @Test
+    public void testEncodeDecodeBooleanArrayTrue255() throws Throwable {
+        doEncodeDecodeBooleanArrayTestImpl(createPayloadArrayBoolean(true, 255));
+    }
+
+    @Test
+    public void testEncodeDecodeBooleanArrayTrue384() throws Throwable {
+        doEncodeDecodeBooleanArrayTestImpl(createPayloadArrayBoolean(true, 384));
+    }
+
+    @Test
+    public void testEncodeDecodeBooleanArrayFalse100() throws Throwable {
+        doEncodeDecodeBooleanArrayTestImpl(createPayloadArrayBoolean(false, 100));
+    }
+
+    @Test
+    public void testEncodeDecodeBooleanArrayFalse192() throws Throwable {
+        doEncodeDecodeBooleanArrayTestImpl(createPayloadArrayBoolean(false, 192));
+    }
+
+    @Test
+    public void testEncodeDecodeBooleanArrayFalse253() throws Throwable {
+        doEncodeDecodeBooleanArrayTestImpl(createPayloadArrayBoolean(false, 253));
+    }
+
+    @Test
+    public void testEncodeDecodeBooleanArrayFalse254() throws Throwable {
+        doEncodeDecodeBooleanArrayTestImpl(createPayloadArrayBoolean(false, 254));
     }
 
-    private void doEncodeDecodeBooleanArrayTestImpl(int count) throws Throwable {
-        boolean[] source = createPayloadArrayBooleans(count);
+    @Test
+    public void testEncodeDecodeBooleanArrayFalse255() throws Throwable {
+        doEncodeDecodeBooleanArrayTestImpl(createPayloadArrayBoolean(false, 255));
+    }
+
+    @Test
+    public void testEncodeDecodeBooleanArrayFalse384() throws Throwable {
+        doEncodeDecodeBooleanArrayTestImpl(createPayloadArrayBoolean(false, 384));
+    }
+
+    @Test
+    public void testDecodeBooleanArrayExplicitlyFalse0() throws Throwable {
+        Object decoded = doDecodeBooleanArrayTrueFalseOnlyTestImpl(true, 0, false);
+        assertNotNull(decoded);
+        assertTrue(decoded.getClass().isArray());
+        assertEquals(boolean.class, decoded.getClass().getComponentType());
+        assertArrayEquals(new boolean[0], (boolean[]) decoded);
+    }
 
+    @Test
+    public void testDecodeBooleanArrayExplicitlyFalse0Array32() throws Throwable {
+        Object decoded = doDecodeBooleanArrayTrueFalseOnlyTestImpl(false, 0, false);
+        assertNotNull(decoded);
+        assertTrue(decoded.getClass().isArray());
+        assertEquals(boolean.class, decoded.getClass().getComponentType());
+        assertArrayEquals(new boolean[0], (boolean[]) decoded);
+    }
+
+    @Test
+    public void testDecodeBooleanArrayExplicitlyFalse255() throws Throwable {
         try {
-            assertEquals("Unexpected source array length", count, source.length);
+            doDecodeBooleanArrayTrueFalseOnlyTestImpl(true, 255, false);
+            fail("Expected exception");
+        } catch (DecodeException e) {
+            // expected
+        }
+    }
 
-            int encodingWidth = count < 254 ? 1 : 4; // less than 254 and not 256, since we also need 1 byte for element count, and (in this case) 1 byte for primitive element type constructor.
+    @Test
+    public void testDecodeBooleanArrayExplicitlyFalse255WhenAllowed() throws Throwable {
+        doDecodeBooleanArrayTrueFalseOnlyWhenAllowedTestImpl(true, 255, false);
+    }
+
+    @Test
+    public void testDecodeBooleanArrayExplicitlyFalse255Array32() throws Throwable {
+        try {
+            doDecodeBooleanArrayTrueFalseOnlyTestImpl(false, 255, false);
+            fail("Expected exception");
+        } catch (DecodeException e) {
+            // expected
+        }
+    }
+
+    @Test
+    public void testDecodeBooleanArrayExplicitlyFalse255Array32WhenAllowed() throws Throwable {
+        doDecodeBooleanArrayTrueFalseOnlyWhenAllowedTestImpl(false, 255, false);
+    }
+
+    @Test
+    public void testDecodeBooleanArrayExplicitlyFalse256() throws Throwable {
+        try {
+            doDecodeBooleanArrayTrueFalseOnlyTestImpl(false, 256, false);
+            fail("Expected exception");
+        } catch (DecodeException e) {
+            // expected
+        }
+    }
+
+    @Test
+    public void testDecodeBooleanArrayExplicitlyFalse256WhenAllowed() throws Throwable {
+        doDecodeBooleanArrayTrueFalseOnlyWhenAllowedTestImpl(false, 256, false);
+    }
+
+    @Test
+    public void testDecodeBooleanArrayExplicitlyFalse384() throws Throwable {
+        try {
+            doDecodeBooleanArrayTrueFalseOnlyTestImpl(false, 384, false);
+            fail("Expected exception");
+        } catch (DecodeException e) {
+            // expected
+        }
+    }
+
+    @Test
+    public void testDecodeBooleanArrayExplicitlyTrue0() throws Throwable {
+        Object decoded = doDecodeBooleanArrayTrueFalseOnlyTestImpl(true, 0, true);
+        assertNotNull(decoded);
+        assertTrue(decoded.getClass().isArray());
+        assertEquals(boolean.class, decoded.getClass().getComponentType());
+        assertArrayEquals(new boolean[0], (boolean[]) decoded);
+    }
+
+    @Test
+    public void testDecodeBooleanArrayExplicitlyTrue0Array32() throws Throwable {
+        Object decoded = doDecodeBooleanArrayTrueFalseOnlyTestImpl(false, 0, true);
+        assertNotNull(decoded);
+        assertTrue(decoded.getClass().isArray());
+        assertEquals(boolean.class, decoded.getClass().getComponentType());
+        assertArrayEquals(new boolean[0], (boolean[]) decoded);
+    }
+
+    @Test
+    public void testDecodeBooleanArrayExplicitlyTrue10() throws Throwable {
+        try {
+            doDecodeBooleanArrayTrueFalseOnlyTestImpl(true, 10, true);
+            fail("Expected exception");
+        } catch (DecodeException e) {
+            // expected
+        }
+    }
+
+    @Test
+    public void testDecodeBooleanArrayExplicitlyTrue10WhenAllowed() throws Throwable {
+        doDecodeBooleanArrayTrueFalseOnlyWhenAllowedTestImpl(true, 10, true);
+    }
+
+    @Test
+    public void testDecodeBooleanArrayExplicitlyTrue10Array32() throws Throwable {
+        try {
+            doDecodeBooleanArrayTrueFalseOnlyTestImpl(false, 10, true);
+            fail("Expected exception");
+        } catch (DecodeException e) {
+            // expected
+        }
+    }
+
+    @Test
+    public void testDecodeBooleanArrayExplicitlyTrue10Array32WhenAllowed() throws Throwable {
+        doDecodeBooleanArrayTrueFalseOnlyWhenAllowedTestImpl(false, 10, true);
+    }
+
+    @Test
+    public void testDecodeBooleanArrayExplicitlyTrue255() throws Throwable {
+        try {
+            doDecodeBooleanArrayTrueFalseOnlyTestImpl(true, 255, true);
+            fail("Expected exception");
+        } catch (DecodeException e) {
+            // expected
+        }
+    }
+
+    @Test
+    public void testDecodeBooleanArrayExplicitlyTrue255WhenAllowed() throws Throwable {
+        doDecodeBooleanArrayTrueFalseOnlyWhenAllowedTestImpl(true, 255, true);
+    }
+
+    @Test
+    public void testDecodeBooleanArrayExplicitlyTrue255Array32() throws Throwable {
+        try {
+            doDecodeBooleanArrayTrueFalseOnlyTestImpl(false, 255, true);
+            fail("Expected exception");
+        } catch (DecodeException e) {
+            // expected
+        }
+    }
+
+    @Test
+    public void testDecodeBooleanArrayExplicitlyTrue255Array32WhenAllowed() throws Throwable {
+        doDecodeBooleanArrayTrueFalseOnlyWhenAllowedTestImpl(false, 255, true);
+    }
+
+    @Test
+    public void testDecodeBooleanArrayExplicitlyTrue256() throws Throwable {
+        try {
+            doDecodeBooleanArrayTrueFalseOnlyTestImpl(false, 256, true);
+            fail("Expected exception");
+        } catch (DecodeException e) {
+            // expected
+        }
+    }
+
+    @Test
+    public void testDecodeBooleanArrayExplicitlyTrue256WhenAllowed() throws Throwable {
+        doDecodeBooleanArrayTrueFalseOnlyWhenAllowedTestImpl(false, 256, true);
+    }
+
+    @Test
+    public void testDecodeBooleanArrayExplicitlyTrue384() throws Throwable {
+        try {
+            doDecodeBooleanArrayTrueFalseOnlyTestImpl(false, 384, true);
+            fail("Expected exception");
+        } catch (DecodeException e) {
+            // expected
+        }
+    }
+
+    private void doDecodeBooleanArrayTrueFalseOnlyWhenAllowedTestImpl(boolean array8, final int count, final boolean type) throws Throwable {
+        decoder.setZeroWidthArrayElementLimit(count);
+        Object decoded = doDecodeBooleanArrayTrueFalseOnlyTestImpl(array8, count, type);
+        assertNotNull(decoded);
+        assertTrue(decoded.getClass().isArray());
+        assertEquals(boolean.class, decoded.getClass().getComponentType());
+
+        final boolean[] expected = new boolean[count];
+        Arrays.fill(expected, type);
+
+        assertArrayEquals(expected, (boolean[]) decoded);
+    }
+
+    private Object doDecodeBooleanArrayTrueFalseOnlyTestImpl(boolean array8, final int count, final boolean type) throws Exception {
+        if (array8) {
+            assertTrue("Cant encode boolean-true/boolean-false array8 with count > 255", count <= 255);
+        }
+
+        int encodingWidth = array8 ? 1 : 4;
+        int arrayPayloadSize = encodingWidth + 1; // variable width for element count + byte type descriptor
+        int expectedEncodedArraySize = 1 + encodingWidth + arrayPayloadSize; // array type code + variable width for
+                                                                             // array size + other encoded payload
+        // Write the array encoding code, array size, and element count
+        if (array8) {
+            buffer.put((byte) 0xE0); // 'array8' type descriptor code
+            buffer.put((byte) arrayPayloadSize);
+            buffer.put((byte) count);
+        } else {
+            buffer.put((byte) 0xF0); // 'array32' type descriptor code
+            buffer.putInt(arrayPayloadSize);
+            buffer.putInt(count);
+        }
+        // Write the type descriptor
+        buffer.put(type ? (byte) EncodingCodes.BOOLEAN_TRUE : EncodingCodes.BOOLEAN_FALSE);
+
+        assertEquals(expectedEncodedArraySize, buffer.position());
+
+        buffer.flip();
+
+        return decoder.readObject();
+    }
+
+    private void doEncodeDecodeBooleanArrayTestImpl(final boolean[] source) throws Throwable {
+        final int count = source.length;
+
+        try {
+            int encodingWidth = source.length < 254 ? 1 : 4; // less than 254 and not 256, since we also need 1 byte for element count, and (in this case) 1 byte for primitive element type constructor.
             int arrayPayloadSize =  encodingWidth + 1 + count; // variable width for element count + byte type descriptor + number of elements
             int expectedEncodedArraySize = 1 + encodingWidth + arrayPayloadSize; // array type code +  variable width for array size + other encoded payload
             byte[] expectedEncoding = new byte[expectedEncodedArraySize];
             ByteBuffer expectedEncodingWrapper = ByteBuffer.wrap(expectedEncoding);
 
             // Write the array encoding code, array size, and element count
-            if(count < 254) {
+            if(encodingWidth == 1) {
                 expectedEncodingWrapper.put((byte) 0xE0); // 'array8' type descriptor code
                 expectedEncodingWrapper.put((byte) arrayPayloadSize);
                 expectedEncodingWrapper.put((byte) count);
@@ -449,18 +761,15 @@ public class ArrayTypeCodecTest extends CodecTestSupport {
 
             assertFalse("Should have filled expected encoding array", expectedEncodingWrapper.hasRemaining());
 
-            // Now verify against the actual encoding of the array
+            // Now produce actual encoding of the array for later comparison
             assertEquals("Unexpected buffer position", 0, buffer.position());
             encoder.writeArray(source);
-            assertEquals("Unexpected encoded payload length", expectedEncodedArraySize, buffer.position());
 
-            byte[] actualEncoding = new byte[expectedEncodedArraySize];
+            byte[] actualEncoding = new byte[buffer.position()];
             buffer.flip();
             buffer.get(actualEncoding);
             assertFalse("Should have drained the encoder buffer contents", buffer.hasRemaining());
 
-            assertArrayEquals("Unexpected actual array encoding", expectedEncoding, actualEncoding);
-
             // Now verify against the decoding
             buffer.flip();
             Object decoded = decoder.readObject();
@@ -470,6 +779,10 @@ public class ArrayTypeCodecTest extends CodecTestSupport {
             assertEquals(boolean.class, decoded.getClass().getComponentType());
 
             assertArrayEquals("Unexpected decoding", source, (boolean[]) decoded);
+
+            // Now verify expected encoding against the actual encoding of the array
+            assertEquals("Unexpected encoded payload length", expectedEncodedArraySize, actualEncoding.length);
+            assertArrayEquals("Unexpected actual array encoding", expectedEncoding, actualEncoding);
         }
         catch (Throwable t) {
             System.err.println("Error during test, source array: " + Arrays.toString(source));
@@ -488,6 +801,15 @@ public class ArrayTypeCodecTest extends CodecTestSupport {
         return payload;
     }
 
+    private static boolean[] createPayloadArrayBoolean(final boolean values, final int length) {
+        boolean[] payload = new boolean[length];
+        for (int i = 0; i < length; i++) {
+            payload[i] = values;
+        }
+
+        return payload;
+    }
+
     @Test
     public void testEncodeDecodeShortArray50() throws Throwable {
         // short array8 less than 128 bytes
@@ -500,9 +822,18 @@ public class ArrayTypeCodecTest extends CodecTestSupport {
         doEncodeDecodeShortArrayTestImpl(100);
     }
 
+    @Test
+    public void testEncodeDecodeShortArray126() throws Throwable {
+        doEncodeDecodeShortArrayTestImpl(126);
+    }
+
+    @Test
+    public void testEncodeDecodeShortArray127() throws Throwable {
+        doEncodeDecodeShortArrayTestImpl(127);
+    }
+
     @Test
     public void testEncodeDecodeShortArray384() throws Throwable {
-        // short array32
         doEncodeDecodeShortArrayTestImpl(384);
     }
 
@@ -519,7 +850,7 @@ public class ArrayTypeCodecTest extends CodecTestSupport {
             ByteBuffer expectedEncodingWrapper = ByteBuffer.wrap(expectedEncoding);
 
             // Write the array encoding code, array size, and element count
-            if(count < 254) {
+            if(encodingWidth == 1) {
                 expectedEncodingWrapper.put((byte) 0xE0); // 'array8' type descriptor code
                 expectedEncodingWrapper.put((byte) arrayPayloadSize);
                 expectedEncodingWrapper.put((byte) count);
@@ -590,45 +921,126 @@ public class ArrayTypeCodecTest extends CodecTestSupport {
         doEncodeDecodeIntArrayTestImpl(50, false);
     }
 
+    @Test
+    public void testEncodeDecodeIntArray63() throws Throwable {
+        doEncodeDecodeIntArrayTestImpl(63, false);
+    }
+
+    @Test
+    public void testEncodeDecodeIntArray64() throws Throwable {
+        doEncodeDecodeIntArrayTestImpl(64, false);
+    }
+
     @Test
     public void testEncodeDecodeIntArray384() throws Throwable {
-        // int array32
         doEncodeDecodeIntArrayTestImpl(384, false);
     }
 
+    @Test
+    public void testEncodeDecodeSmallIntArray63() throws Throwable {
+        doEncodeDecodeIntArrayTestImpl(63, true);
+    }
+
+    @Test
+    public void testEncodeDecodeSmallIntArray64() throws Throwable {
+        doEncodeDecodeIntArrayTestImpl(64, true);
+    }
+
     @Test
     public void testEncodeDecodeSmallIntArray100() throws Throwable {
-        // small-int array8 less than 128 bytes
         doEncodeDecodeIntArrayTestImpl(100, true);
     }
 
     @Test
     public void testEncodeDecodeSmallIntArray192() throws Throwable {
-        // small-int array8 greater than 128 bytes
         doEncodeDecodeIntArrayTestImpl(192, true);
     }
 
     @Test
     public void testEncodeDecodeSmallIntArray384() throws Throwable {
-        // small-int array32
         doEncodeDecodeIntArrayTestImpl(384, true);
     }
 
+    @Test
+    public void testDecodeSmallIntArray253() throws Throwable {
+        final int count = 253;
+        doDecodeSmallLongArrayTestImpl(true, count, createPayloadArrayInts(count, true));
+        buffer.clear();
+        doDecodeSmallLongArrayTestImpl(false, count, createPayloadArrayInts(count, true));
+    }
+
+    @Test
+    public void testDecodeSmallIntArray254() throws Throwable {
+        final int count = 254;
+        doDecodeSmallLongArrayTestImpl(false, count, createPayloadArrayInts(count, true));
+    }
+
+    @Test
+    public void testDecodeSmallIntArray255() throws Throwable {
+        final int count = 255;
+        doDecodeSmallLongArrayTestImpl(false, count, createPayloadArrayInts(count, true));
+    }
+
+    @Test
+    public void testDecodeSmallIntArray384() throws Throwable {
+        final int count = 384;
+        doDecodeSmallLongArrayTestImpl(false, count, createPayloadArrayInts(count, true));
+    }
+
+    private void doDecodeSmallLongArrayTestImpl(final boolean array8, final int count, int[] smallInts) throws Exception {
+        if (array8) {
+            assertTrue("Cant encode small-int array8 with count " + count, count <= 253);
+        }
+
+        int encodingWidth = array8 ? 1 : 4;
+        int arrayPayloadSize = encodingWidth + 1 + (count * 1); // variable width for element count + byte type descriptor + elements
+        int expectedEncodedArraySize = 1 + encodingWidth + arrayPayloadSize; // array type code + variable width for
+                                                                             // array size + other encoded payload
+
+        if (array8) {
+            assertTrue("Cant encode array8 with arrayPayloadSize " + count, arrayPayloadSize < 256);
+        }
+
+        // Write the array encoding code, array size, and element count
+        if (array8) {
+            buffer.put((byte) 0xE0); // 'array8' type descriptor code
+            buffer.put((byte) arrayPayloadSize);
+            buffer.put((byte) count);
+        } else {
+            buffer.put((byte) 0xF0); // 'array32' type descriptor code
+            buffer.putInt(arrayPayloadSize);
+            buffer.putInt(count);
+        }
+
+        buffer.put((byte) 0x54); // 'small-int' type descriptor code
+        for(int element : smallInts) {
+            assertTrue(element <= 127 && element >= -128);
+
+            buffer.put((byte) element);
+        }
+
+        assertEquals(expectedEncodedArraySize, buffer.position());
+
+        buffer.flip();
+
+        Object o = decoder.readObject();
+
+        assertNotNull(o);
+        assertTrue(o.getClass().isArray());
+        assertEquals(int.class, o.getClass().getComponentType());
+
+        int[] decoded = (int[]) o;
+        assertArrayEquals(smallInts, decoded);
+    }
+
     private void doEncodeDecodeIntArrayTestImpl(int count, boolean smallInt) throws Throwable {
         int[] source = createPayloadArrayInts(count, smallInt);
 
         try {
             assertEquals("Unexpected source array length", count, source.length);
 
-            int encodingWidth;
-            int elementWidth;
-            if(smallInt) {
-                elementWidth = 1;
-                encodingWidth = count < 254 ? 1 : 4; // less than 254 and not 256, since each element is 1 byte, but we also need 1 byte for element count, and (in this case) 1 byte for primitive element type constructor.
-            } else {
-                elementWidth = 4;
-                encodingWidth = count < 63 ? 1 : 4; // less than 63, since each element is 4 bytes, but we also need 1 byte for element count, and (in this case) 1 byte for primitive element type constructor.
-            }
+            int elementWidth = 4;
+            int encodingWidth = count < 64 ? 1 : 4; // less than 63, since each element is 4 bytes, but we also need 1 byte for element count, and (in this case) 1 byte for primitive element type constructor.
 
             int arrayPayloadSize =  encodingWidth + 1 + (count * elementWidth); // variable width for element count + byte type descriptor + (number of elements * size)
             int expectedEncodedArraySize = 1 + encodingWidth + arrayPayloadSize; // array type code +  variable width for array size + other encoded payload
@@ -636,7 +1048,7 @@ public class ArrayTypeCodecTest extends CodecTestSupport {
             ByteBuffer expectedEncodingWrapper = ByteBuffer.wrap(expectedEncoding);
 
             // Write the array encoding code, array size, and element count
-            if(count < 254) {
+            if(encodingWidth == 1) {
                 expectedEncodingWrapper.put((byte) 0xE0); // 'array8' type descriptor code
                 expectedEncodingWrapper.put((byte) arrayPayloadSize);
                 expectedEncodingWrapper.put((byte) count);
@@ -647,25 +1059,15 @@ public class ArrayTypeCodecTest extends CodecTestSupport {
             }
 
             // Write the type descriptor
-            if(smallInt) {
-                expectedEncodingWrapper.put((byte) 0x54); // 'small int' type descriptor code
-            } else {
-                expectedEncodingWrapper.put((byte) 0x71); // 'int' type descriptor code
-            }
+            expectedEncodingWrapper.put((byte) 0x71); // 'int' type descriptor code
 
             // Write the elements
             for (int i = 0; i < count; i++) {
                 int j = source[i];
-                if(smallInt) {
-                    // Using put as small int is 1 byte
-                    assertTrue("Unexpected value: + j", j <= 127 && j >=-128);
-                    expectedEncodingWrapper.put((byte) j);
-                } else {
-                    expectedEncodingWrapper.putInt(j);
-                }
+                expectedEncodingWrapper.putInt(j);
             }
 
-            assertFalse("Should have filled expected encoding array", expectedEncodingWrapper.hasRemaining());
+            assertEquals("Should have filled expected encoding array", 0, expectedEncodingWrapper.remaining());
 
             // Now verify against the actual encoding of the array
             assertEquals("Unexpected buffer position", 0, buffer.position());
@@ -712,66 +1114,140 @@ public class ArrayTypeCodecTest extends CodecTestSupport {
 
     @Test
     public void testEncodeDecodeLongArray10() throws Throwable {
-        // long array8 less than 128 bytes
         doEncodeDecodeLongArrayTestImpl(10, false);
     }
 
     @Test
-    public void testEncodeDecodeLongArray25() throws Throwable {
-        // long array8 greater than 128 bytes
-        doEncodeDecodeLongArrayTestImpl(25, false);
+    public void testEncodeDecodeLongArray31() throws Throwable {
+        doEncodeDecodeLongArrayTestImpl(31, false);
+    }
+
+    @Test
+    public void testEncodeDecodeLongArray32() throws Throwable {
+        doEncodeDecodeLongArrayTestImpl(32, false);
     }
 
     @Test
     public void testEncodeDecodeLongArray384() throws Throwable {
-        // long array32
         doEncodeDecodeLongArrayTestImpl(384, false);
     }
 
+    @Test
+    public void testEncodeDecodeSmallLongArray31() throws Throwable {
+        doEncodeDecodeLongArrayTestImpl(30, true);
+    }
+
+    @Test
+    public void testEncodeDecodeSmallLongArray32() throws Throwable {
+        doEncodeDecodeLongArrayTestImpl(30, true);
+    }
+
     @Test
     public void testEncodeDecodeSmallLongArray100() throws Throwable {
-        // small-long array8 less than 128 bytes
         doEncodeDecodeLongArrayTestImpl(100, true);
     }
 
     @Test
     public void testEncodeDecodeSmallLongArray192() throws Throwable {
-        // small-long array8 greater than 128 bytes
         doEncodeDecodeLongArrayTestImpl(192, true);
     }
 
     @Test
     public void testEncodeDecodeSmallLongArray384() throws Throwable {
-        // small-long array32
         doEncodeDecodeLongArrayTestImpl(384, true);
     }
 
-    private void doEncodeDecodeLongArrayTestImpl(int count, boolean smallLong) throws Throwable {
-        long[] source = createPayloadArrayLongs(count, smallLong);
+    @Test
+    public void testDecodeSmallLongArray253() throws Throwable {
+        final int count = 253;
+        doDecodeSmallLongArrayTestImpl(true, count, createPayloadArrayLongs(count, true));
+        buffer.clear();
+        doDecodeSmallLongArrayTestImpl(false, count, createPayloadArrayLongs(count, true));
+    }
 
-        try {
-            assertEquals("Unexpected source array length", count, source.length);
+    @Test
+    public void testDecodeSmallLongArray254() throws Throwable {
+        final int count = 254;
+        doDecodeSmallLongArrayTestImpl(false, count, createPayloadArrayLongs(count, true));
+    }
 
-            int encodingWidth;
-            int elementWidth;
-            if(smallLong) {
-                elementWidth = 1;
-                encodingWidth = count < 254 ? 1 : 4; // less than 254 and not 256, since each element is 1 byte, but we also need 1 byte for element count, and (in this case) 1 byte for primitive element type constructor.
-            } else {
-                elementWidth = 8;
-                encodingWidth = count < 31 ? 1 : 4; // less than 31, since each element is 8 bytes, but we also need 1 byte for element count, and (in this case) 1 byte for primitive element type constructor.
-            }
+    @Test
+    public void testDecodeSmallLongArray255() throws Throwable {
+        final int count = 255;
+        doDecodeSmallLongArrayTestImpl(false, count, createPayloadArrayLongs(count, true));
+    }
 
-            int arrayPayloadSize =  encodingWidth + 1 + (count * elementWidth); // variable width for element count + byte type descriptor + (number of elements * size)
-            int expectedEncodedArraySize = 1 + encodingWidth + arrayPayloadSize; // array type code +  variable width for array size + other encoded payload
-            byte[] expectedEncoding = new byte[expectedEncodedArraySize];
-            ByteBuffer expectedEncodingWrapper = ByteBuffer.wrap(expectedEncoding);
+    @Test
+    public void testDecodeSmallLongArray384() throws Throwable {
+        final int count = 384;
+        doDecodeSmallLongArrayTestImpl(false, count, createPayloadArrayLongs(count, true));
+    }
 
-            // Write the array encoding code, array size, and element count
-            if(count < 254) {
-                expectedEncodingWrapper.put((byte) 0xE0); // 'array8' type descriptor code
-                expectedEncodingWrapper.put((byte) arrayPayloadSize);
-                expectedEncodingWrapper.put((byte) count);
+    private void doDecodeSmallLongArrayTestImpl(final boolean array8, final int count, long[] smallLongs) throws Exception {
+        if (array8) {
+            assertTrue("Cant encode small-long array8 with count " + count, count <= 253);
+        }
+
+        int encodingWidth = array8 ? 1 : 4;
+        int arrayPayloadSize = encodingWidth + 1 + (count * 1); // variable width for element count + byte type descriptor + elements
+        int expectedEncodedArraySize = 1 + encodingWidth + arrayPayloadSize; // array type code + variable width for
+                                                                             // array size + other encoded payload
+
+        if (array8) {
+            assertTrue("Cant encode array8 with arrayPayloadSize " + count, arrayPayloadSize < 256);
+        }
+
+        // Write the array encoding code, array size, and element count
+        if (array8) {
+            buffer.put((byte) 0xE0); // 'array8' type descriptor code
+            buffer.put((byte) arrayPayloadSize);
+            buffer.put((byte) count);
+        } else {
+            buffer.put((byte) 0xF0); // 'array32' type descriptor code
+            buffer.putInt(arrayPayloadSize);
+            buffer.putInt(count);
+        }
+
+        buffer.put((byte) 0x55); // 'small-long' type descriptor code
+        for(long element : smallLongs) {
+            assertTrue(element <= 127 && element >= -128);
+
+            buffer.put((byte) element);
+        }
+
+        assertEquals(expectedEncodedArraySize, buffer.position());
+
+        buffer.flip();
+
+        Object o = decoder.readObject();
+
+        assertNotNull(o);
+        assertTrue(o.getClass().isArray());
+        assertEquals(long.class, o.getClass().getComponentType());
+
+        long[] decoded = (long[]) o;
+        assertArrayEquals(smallLongs, decoded);
+    }
+
+    private void doEncodeDecodeLongArrayTestImpl(int count, boolean smallLong) throws Throwable {
+        long[] source = createPayloadArrayLongs(count, smallLong);
+
+        try {
+            assertEquals("Unexpected source array length", count, source.length);
+
+            int elementWidth = 8;
+            int encodingWidth = count < 32 ? 1 : 4; // less than 32, since each element is 8 bytes, but we also need 1 for element count, and (in this case) 1 byte for primitive element type constructor.
+
+            int arrayPayloadSize =  encodingWidth + 1 + (count * elementWidth); // variable width for element count + byte type descriptor + (number of elements * size)
+            int expectedEncodedArraySize = 1 + encodingWidth + arrayPayloadSize; // array type code +  variable width for array size + other encoded payload
+            byte[] expectedEncoding = new byte[expectedEncodedArraySize];
+            ByteBuffer expectedEncodingWrapper = ByteBuffer.wrap(expectedEncoding);
+
+            // Write the array encoding code, array size, and element count
+            if(encodingWidth == 1) {
+                expectedEncodingWrapper.put((byte) 0xE0); // 'array8' type descriptor code
+                expectedEncodingWrapper.put((byte) arrayPayloadSize);
+                expectedEncodingWrapper.put((byte) count);
             } else {
                 expectedEncodingWrapper.put((byte) 0xF0); // 'array32' type descriptor code
                 expectedEncodingWrapper.putInt(arrayPayloadSize);
@@ -779,25 +1255,14 @@ public class ArrayTypeCodecTest extends CodecTestSupport {
             }
 
             // Write the type descriptor
-            if(smallLong) {
-                expectedEncodingWrapper.put((byte) 0x55); // 'small long' type descriptor code
-            } else {
-                expectedEncodingWrapper.put((byte) 0x81); // 'long' type descriptor code
-            }
+            expectedEncodingWrapper.put((byte) 0x81); // 'long' type descriptor code
 
             // Write the elements
             for (int i = 0; i < count; i++) {
-                long j = source[i];
-                if(smallLong) {
-                    // Using put as small long is 1 byte
-                    assertTrue("Unexpected value: + j", j <= 127 && j >=-128);
-                    expectedEncodingWrapper.put((byte) j);
-                } else {
-                    expectedEncodingWrapper.putLong(j);
-                }
+                expectedEncodingWrapper.putLong(source[i]);
             }
 
-            assertFalse("Should have filled expected encoding array", expectedEncodingWrapper.hasRemaining());
+            assertEquals("Should have filled expected encoding array", 0, expectedEncodingWrapper.remaining());
 
             // Now verify against the actual encoding of the array
             assertEquals("Unexpected buffer position", 0, buffer.position());
@@ -854,9 +1319,18 @@ public class ArrayTypeCodecTest extends CodecTestSupport {
         doEncodeDecodeFloatArrayTestImpl(50);
     }
 
+    @Test
+    public void testEncodeDecodeFloatArray63() throws Throwable {
+        doEncodeDecodeFloatArrayTestImpl(63);
+    }
+
+    @Test
+    public void testEncodeDecodeFloatArray64() throws Throwable {
+        doEncodeDecodeFloatArrayTestImpl(64);
+    }
+
     @Test
     public void testEncodeDecodeFloatArray384() throws Throwable {
-        // float array32
         doEncodeDecodeFloatArrayTestImpl(384);
     }
 
@@ -866,14 +1340,14 @@ public class ArrayTypeCodecTest extends CodecTestSupport {
         try {
             assertEquals("Unexpected source array length", count, source.length);
 
-            int encodingWidth = count < 63 ? 1 : 4; // less than 63, since each element is 4 bytes, but we also need 1 byte for element count, and (in this case) 1 byte for primitive element type constructor.
+            int encodingWidth = count < 64 ? 1 : 4; // less than 64, since each element is 4 bytes, but we also need 1 byte for element count, and (in this case) 1 byte for primitive element type constructor.
             int arrayPayloadSize =  encodingWidth + 1 + (count * 4); // variable width for element count + byte type descriptor + (number of elements * size)
             int expectedEncodedArraySize = 1 + encodingWidth + arrayPayloadSize; // array type code +  variable width for array size + other encoded payload
             byte[] expectedEncoding = new byte[expectedEncodedArraySize];
             ByteBuffer expectedEncodingWrapper = ByteBuffer.wrap(expectedEncoding);
 
             // Write the array encoding code, array size, and element count
-            if(count < 254) {
+            if(encodingWidth == 1) {
                 expectedEncodingWrapper.put((byte) 0xE0); // 'array8' type descriptor code
                 expectedEncodingWrapper.put((byte) arrayPayloadSize);
                 expectedEncodingWrapper.put((byte) count);
@@ -944,9 +1418,18 @@ public class ArrayTypeCodecTest extends CodecTestSupport {
         doEncodeDecodeDoubleArrayTestImpl(25);
     }
 
+    @Test
+    public void testEncodeDecodeDoubleArray31() throws Throwable {
+        doEncodeDecodeDoubleArrayTestImpl(31);
+    }
+
+    @Test
+    public void testEncodeDecodeDoubleArray32() throws Throwable {
+        doEncodeDecodeDoubleArrayTestImpl(32);
+    }
+
     @Test
     public void testEncodeDecodeDoubleArray384() throws Throwable {
-        // double array32
         doEncodeDecodeDoubleArrayTestImpl(384);
     }
 
@@ -956,14 +1439,14 @@ public class ArrayTypeCodecTest extends CodecTestSupport {
         try {
             assertEquals("Unexpected source array length", count, source.length);
 
-            int encodingWidth = count < 31 ? 1 : 4; // less than 31, since each element is 8 bytes, but we also need 1 byte for element count, and (in this case) 1 byte for primitive element type constructor.
+            int encodingWidth = count < 32 ? 1 : 4; // less than 32, since each element is 8 bytes, but we also need 1 byte for element count, and (in this case) 1 byte for primitive element type constructor.
             int arrayPayloadSize =  encodingWidth + 1 + (count * 8); // variable width for element count + byte type descriptor + (number of elements * size)
             int expectedEncodedArraySize = 1 + encodingWidth + arrayPayloadSize; // array type code +  variable width for array size + other encoded payload
             byte[] expectedEncoding = new byte[expectedEncodedArraySize];
             ByteBuffer expectedEncodingWrapper = ByteBuffer.wrap(expectedEncoding);
 
             // Write the array encoding code, array size, and element count
-            if(count < 254) {
+            if(encodingWidth == 1) {
                 expectedEncodingWrapper.put((byte) 0xE0); // 'array8' type descriptor code
                 expectedEncodingWrapper.put((byte) arrayPayloadSize);
                 expectedEncodingWrapper.put((byte) count);
@@ -1034,9 +1517,18 @@ public class ArrayTypeCodecTest extends CodecTestSupport {
         doEncodeDecodeCharArrayTestImpl(50);
     }
 
+    @Test
+    public void testEncodeDecodeCharArray63() throws Throwable {
+        doEncodeDecodeCharArrayTestImpl(63);
+    }
+
+    @Test
+    public void testEncodeDecodeCharArray64() throws Throwable {
+        doEncodeDecodeCharArrayTestImpl(64);
+    }
+
     @Test
     public void testEncodeDecodeCharArray384() throws Throwable {
-        // char array32
         doEncodeDecodeCharArrayTestImpl(384);
     }
 
@@ -1046,14 +1538,14 @@ public class ArrayTypeCodecTest extends CodecTestSupport {
         try {
             assertEquals("Unexpected source array length", count, source.length);
 
-            int encodingWidth = count < 63 ? 1 : 4; // less than 63, since each element is 4 bytes, but we also need 1 byte for element count, and (in this case) 1 byte for primitive element type constructor.
+            int encodingWidth = count < 64 ? 1 : 4; // less than 64, since each element is 4 bytes, but we also need 1 byte for element count, and (in this case) 1 byte for primitive element type constructor.
             int arrayPayloadSize =  encodingWidth + 1 + (count * 4); // variable width for element count + byte type descriptor + (number of elements * size)
             int expectedEncodedArraySize = 1 + encodingWidth + arrayPayloadSize; // array type code +  variable width for array size + other encoded payload
             byte[] expectedEncoding = new byte[expectedEncodedArraySize];
             ByteBuffer expectedEncodingWrapper = ByteBuffer.wrap(expectedEncoding);
 
             // Write the array encoding code, array size, and element count
-            if(count < 254) {
+            if(encodingWidth == 1) {
                 expectedEncodingWrapper.put((byte) 0xE0); // 'array8' type descriptor code
                 expectedEncodingWrapper.put((byte) arrayPayloadSize);
                 expectedEncodingWrapper.put((byte) count);
@@ -1113,37 +1605,125 @@ public class ArrayTypeCodecTest extends CodecTestSupport {
     }
 
     @Test
-    public void testEncodeDecodeSmallSymbolArray50() throws Throwable {
-        // sym8 array8 less than 128 bytes
-        doEncodeDecodeSmallSymbolArrayTestImpl(50);
+    public void testEncodeDecodeSymbolArray50() throws Throwable {
+        doEncodeDecodeSymbolArrayTestImpl(50);
+    }
+
+    @Test
+    public void testEncodeDecodeSymbolArray100() throws Throwable {
+        doEncodeDecodeSymbolArrayTestImpl(100);
+    }
+
+    @Test
+    public void testEncodeDecodeSymbolArray384() throws Throwable {
+        doEncodeDecodeSymbolArrayTestImpl(384);
+    }
+
+    @Test
+    public void testDecodeSmallSymbolArray126() throws Throwable {
+        //Using 126 with single-char symbol since element length bytes will use up the array size
+        final int count = 126;
+        doDecodeSmallSymbolArrayTestImpl(true, count, Symbol.valueOf("a"));
+        buffer.clear();
+        doDecodeSmallSymbolArrayTestImpl(false, count, Symbol.valueOf("a"));
+    }
+
+    @Test
+    public void testDecodeSmallSymbolArray253() throws Throwable {
+        //Using 253 with empty symbol since element length bytes will use up the array size
+        final int count = 253;
+        doDecodeSmallSymbolArrayTestImpl(true, count, Symbol.valueOf(""));
+        buffer.clear();
+        doDecodeSmallSymbolArrayTestImpl(false, count, Symbol.valueOf(""));
+    }
+
+    @Test
+    public void testDecodeSmallSymbolArray254() throws Throwable {
+        final int count = 254;
+        doDecodeSmallSymbolArrayTestImpl(false, count, Symbol.valueOf("a"));
     }
 
     @Test
-    public void testEncodeDecodeSmallSymbolArray100() throws Throwable {
-        // sym8 array8 greater than 128 bytes
-        doEncodeDecodeSmallSymbolArrayTestImpl(100);
+    public void testDecodeSmallSymbolArray255() throws Throwable {
+        final int count = 255;
+        doDecodeSmallSymbolArrayTestImpl(false, count, Symbol.valueOf("a"));
     }
 
     @Test
-    public void testEncodeDecodeSmallSymbolArray384() throws Throwable {
-        // sym8 array32
-        doEncodeDecodeSmallSymbolArrayTestImpl(384);
+    public void testDecodeSmallSymbolArray384() throws Throwable {
+        final int count = 384;
+        doDecodeSmallSymbolArrayTestImpl(false, count, Symbol.valueOf("a"));
+    }
+
+    private void doDecodeSmallSymbolArrayTestImpl(final boolean array8, final int count, Symbol smallSymbol) throws Exception {
+        if (array8) {
+            assertTrue("Cant encode sym8 array8 with count " + count, count <= 253);
+            assertTrue("Individual sym8 in array8 cant exceed 253 chars", smallSymbol.length() <= 253);
+        } else {
+            assertTrue("Individual sym8 in array32 cant exceed 255 chars", smallSymbol.length() <= 255);
+        }
+
+        int encodingWidth = array8 ? 1 : 4;
+        int arrayPayloadSize = encodingWidth + 1 + (count * (1 + smallSymbol.length())); // variable width for element count + byte type descriptor + (elements = count * (length-byte + characters))
+        int expectedEncodedArraySize = 1 + encodingWidth + arrayPayloadSize; // array type code + variable width for
+                                                                             // array size + other encoded payload
+
+        if (array8) {
+            assertTrue("Cant encode array8 with arrayPayloadSize " + arrayPayloadSize, arrayPayloadSize <= 256);
+        }
+
+        // Write the array encoding code, array size, and element count
+        if (array8) {
+            buffer.put((byte) 0xE0); // 'array8' type descriptor code
+            buffer.put((byte) arrayPayloadSize);
+            buffer.put((byte) count);
+        } else {
+            buffer.put((byte) 0xF0); // 'array32' type descriptor code
+            buffer.putInt(arrayPayloadSize);
+            buffer.putInt(count);
+        }
+
+        buffer.put((byte) 0xA3); // 'sym8' type descriptor code
+        for (int i = 0; i < count; i++) {
+            buffer.put((byte) smallSymbol.length());
+
+            buffer.put(smallSymbol.toString().getBytes(StandardCharsets.US_ASCII));
+        }
+
+        assertEquals(expectedEncodedArraySize, buffer.position());
+
+        buffer.flip();
+
+        Object o = decoder.readObject();
+
+        assertNotNull(o);
+        assertTrue(o.getClass().isArray());
+        assertEquals(Symbol.class, o.getClass().getComponentType());
+
+        Symbol[] decoded = (Symbol[]) o;
+
+        Symbol[] expected = new Symbol[count];
+        for(int i = 0; i < count; i++) {
+            expected[i] = smallSymbol;
+        }
+
+        assertArrayEquals(expected, decoded);
     }
 
-    private void doEncodeDecodeSmallSymbolArrayTestImpl(int count) throws Throwable {
+    private void doEncodeDecodeSymbolArrayTestImpl(int count) throws Throwable {
         Symbol[] source = createPayloadArraySmallSymbols(count);
 
         try {
             assertEquals("Unexpected source array length", count, source.length);
 
-            int encodingWidth = count < 127 ? 1 : 4; // less than 127, since each element is 2 bytes (1 length, 1 content char), but we also need 1 byte for element count, and (in this case) 1 byte for primitive element type constructor.
-            int arrayPayloadSize =  encodingWidth + 1 + (count * 2); // variable width for element count + byte type descriptor + (number of elements * size[=length+content-char])
+            int encodingWidth = count < 51 ? 1 : 4; // less than 51, since each element is 5 bytes (4 length, 1 content char), but we also need 1 byte for element count, and (in this case) 1 byte for primitive element type constructor.
+            int arrayPayloadSize =  encodingWidth + 1 + (count * 5); // variable width for element count + byte type descriptor + (number of elements * size[=length+content-char])
             int expectedEncodedArraySize = 1 + encodingWidth + arrayPayloadSize; // array type code +  variable width for array size + other encoded payload
             byte[] expectedEncoding = new byte[expectedEncodedArraySize];
             ByteBuffer expectedEncodingWrapper = ByteBuffer.wrap(expectedEncoding);
 
             // Write the array encoding code, array size, and element count
-            if(count < 254) {
+            if(encodingWidth == 1) {
                 expectedEncodingWrapper.put((byte) 0xE0); // 'array8' type descriptor code
                 expectedEncodingWrapper.put((byte) arrayPayloadSize);
                 expectedEncodingWrapper.put((byte) count);
@@ -1154,18 +1734,18 @@ public class ArrayTypeCodecTest extends CodecTestSupport {
             }
 
             // Write the type descriptor
-            expectedEncodingWrapper.put((byte) 0xA3); // 'sym8' type descriptor code
+            expectedEncodingWrapper.put((byte) 0xB3); // 'sym32' type descriptor code
 
             // Write the elements
             for (int i = 0; i < count; i++) {
                 Symbol symbol = source[i];
                 assertEquals("Unexpected length", 1, symbol.length());
 
-                expectedEncodingWrapper.put((byte) 1); // Length
+                expectedEncodingWrapper.putInt(1); // Length
                 expectedEncodingWrapper.put((byte) symbol.toString().charAt(0)); // Content
             }
 
-            assertFalse("Should have filled expected encoding array", expectedEncodingWrapper.hasRemaining());
+            assertEquals("Should have filled expected encoding array", 0, expectedEncodingWrapper.remaining());
 
             // Now verify against the actual encoding of the array
             assertEquals("Unexpected buffer position", 0, buffer.position());
@@ -1205,4 +1785,1722 @@ public class ArrayTypeCodecTest extends CodecTestSupport {
 
         return payload;
     }
+
+    @Test
+    public void testEncodeDecodeULongArray0() throws Throwable {
+        doEncodeDecodeULongArrayTestImpl(createPayloadArrayULongs(0));
+    }
+
+    @Test
+    public void testEncodeDecodeULongArray31() throws Throwable {
+        doEncodeDecodeULongArrayTestImpl(createPayloadArrayULongs(31));
+    }
+
+    @Test
+    public void testEncodeDecodeULongArray32() throws Throwable {
+        doEncodeDecodeULongArrayTestImpl(createPayloadArrayULongs(32));
+    }
+
+    @Test
+    public void testEncodeDecodeULongArray100() throws Throwable {
+        doEncodeDecodeULongArrayTestImpl(createPayloadArrayULongs(100));
+    }
+
+    @Test
+    public void testEncodeDecodeULong0Array0() throws Throwable {
+        doEncodeDecodeULongArrayTestImpl(createPayloadArrayULong(UnsignedLong.ZERO, 0));
+    }
+
+    @Test
+    public void testEncodeDecodeULong0Array100() throws Throwable {
+        doEncodeDecodeULongArrayTestImpl(createPayloadArrayULong(UnsignedLong.ZERO, 100));
+    }
+
+    @Test
+    public void testEncodeDecodeULongArray384() throws Throwable {
+        doEncodeDecodeULongArrayTestImpl(createPayloadArrayULongs(384));
+    }
+
+    private void doEncodeDecodeULongArrayTestImpl(final UnsignedLong[] source) throws Throwable {
+        final int count = source.length;
+
+        try {
+            final int encodingWidth = count < 32 ? 1 : 4;
+            final int arrayPayloadSize =  encodingWidth + 1 + (8 * count); // count + element type descriptor + (element-width * number of elements)
+            final int expectedEncodedArraySize = 1 + encodingWidth + arrayPayloadSize; // array type code + size + encoded payload
+            final byte[] expectedEncoding = new byte[expectedEncodedArraySize];
+            ByteBuffer expectedEncodingWrapper = ByteBuffer.wrap(expectedEncoding);
+
+            // Write the array encoding code, array size, and element count
+            if(encodingWidth == 1) {
+                expectedEncodingWrapper.put((byte) 0xE0); // 'array8' type descriptor code
+                expectedEncodingWrapper.put((byte) arrayPayloadSize);
+                expectedEncodingWrapper.put((byte) count);
+            } else {
+                expectedEncodingWrapper.put((byte) 0xF0); // 'array32' type descriptor code
+                expectedEncodingWrapper.putInt(arrayPayloadSize);
+                expectedEncodingWrapper.putInt(count);
+            }
+
+            // Write the type descriptor
+            expectedEncodingWrapper.put((byte) 0x80); // 'ulong' type descriptor code
+
+            // Write the elements
+            for (int i = 0; i < count; i++) {
+                expectedEncodingWrapper.putLong(source[i].longValue());
+            }
+
+            assertEquals("Should have filled expected encoding array", 0, expectedEncodingWrapper.remaining());
+
+            // Now produce actual encoding of the array for later comparison
+            assertEquals("Unexpected buffer position", 0, buffer.position());
+            encoder.writeArray(source);
+
+            byte[] actualEncoding = new byte[buffer.position()];
+            buffer.flip();
+            buffer.get(actualEncoding);
+            assertFalse("Should have drained the encoder buffer contents", buffer.hasRemaining());
+
+            // Now verify against the decoding
+            buffer.flip();
+            Object decoded = decoder.readObject();
+            assertNotNull(decoded);
+            assertTrue(decoded.getClass().isArray());
+            assertFalse(decoded.getClass().getComponentType().isPrimitive());
+            assertEquals(UnsignedLong.class, decoded.getClass().getComponentType());
+
+            assertArrayEquals("Unexpected decoding", source, (UnsignedLong[]) decoded);
+
+            // Now verify expected encoding against the actual encoding of the array
+            assertEquals("Unexpected encoded payload length", expectedEncoding.length, actualEncoding.length);
+            assertArrayEquals("Unexpected actual array encoding", expectedEncoding, actualEncoding);
+        }
+        catch (Throwable t) {
+            System.err.println("Error during test, source array: " + Arrays.toString(source));
+            throw t;
+        }
+    }
+
+    private static UnsignedLong[] createPayloadArrayULongs(int length) {
+        return createPayloadArrayULongs(length, false);
+    }
+
+    private static UnsignedLong[] createPayloadArrayULongs(int length, boolean smallUlongs) {
+        Random rand = new Random(System.currentTimeMillis());
+
+        UnsignedLong[] payload = new UnsignedLong[length];
+        for (int i = 0; i < length; i++) {
+            final long val;
+            if(smallUlongs) {
+                val = (long) rand.nextInt(256);
+            } else {
+                val = (long) rand.nextInt();
+            }
+
+            payload[i] = UnsignedLong.valueOf(val);
+        }
+
+        return payload;
+    }
+
+    private static UnsignedLong[] createPayloadArrayULong(final UnsignedLong value, final int length) {
+        UnsignedLong[] payload = new UnsignedLong[length];
+        for (int i = 0; i < length; i++) {
+            payload[i] = value;
+        }
+
+        return payload;
+    }
+
+    @Test
+    public void testDecodeULongArrayExplicitlyUlong0Only0() throws Throwable {
+        Object decoded = doDecodeULongArrayUlong0OnlyTestImpl(true, 0);
+        assertNotNull(decoded);
+        assertTrue(decoded.getClass().isArray());
+        assertEquals(UnsignedLong.class, decoded.getClass().getComponentType());
+        assertArrayEquals(new UnsignedLong[0], (UnsignedLong[]) decoded);
+    }
+
+    @Test
+    public void testDecodeULongArrayExplicitlyUlong0Only0Array32() throws Throwable {
+        Object decoded = doDecodeULongArrayUlong0OnlyTestImpl(false, 0);
+        assertNotNull(decoded);
+        assertTrue(decoded.getClass().isArray());
+        assertEquals(UnsignedLong.class, decoded.getClass().getComponentType());
+        assertArrayEquals(new UnsignedLong[0], (UnsignedLong[]) decoded);
+    }
+
+    @Test
+    public void testDecodeULongArrayExplicitlyUlong0Only255() throws Throwable {
+        try {
+            doDecodeULongArrayUlong0OnlyTestImpl(true, 255);
+            fail("Expected exception");
+        } catch (DecodeException e) {
+            // expected
+        }
+    }
+
+    @Test
+    public void testDecodeULongArrayExplicitlyUlong0Only255WhenAllowed() throws Throwable {
+        doDecodeULongArrayUlong0OnlyWhenAllowedTestImpl(true, 255);
+    }
+
+    @Test
+    public void testDecodeULongArrayExplicitlyUlong0Only255Array32() throws Throwable {
+        try {
+            doDecodeULongArrayUlong0OnlyTestImpl(false, 255);
+            fail("Expected exception");
+        } catch (DecodeException e) {
+            // expected
+        }
+    }
+
+    @Test
+    public void testDecodeULongArrayExplicitlyUlong0Only255Array32WhenAllowed() throws Throwable {
+        doDecodeULongArrayUlong0OnlyWhenAllowedTestImpl(false, 255);
+    }
+
+    @Test
+    public void testDecodeULongArrayExplicitlyUlong0Only256() throws Throwable {
+        try {
+            doDecodeULongArrayUlong0OnlyTestImpl(false, 256);
+            fail("Expected exception");
+        } catch (DecodeException e) {
+            // expected
+        }
+    }
+
+    @Test
+    public void testDecodeULongArrayExplicitlyUlong0Only256WhenAllowed() throws Throwable {
+        doDecodeULongArrayUlong0OnlyWhenAllowedTestImpl(false, 256);
+    }
+
+    @Test
+    public void testDecodeULongArrayExplicitlyUlong0Only384() throws Throwable {
+        try {
+            doDecodeULongArrayUlong0OnlyTestImpl(false, 384);
+            fail("Expected exception");
+        } catch (DecodeException e) {
+            // expected
+        }
+    }
+
+    @Test
+    public void testDecodeULongArrayExplicitlyUlong0Only384WhenAllowed() throws Throwable {
+        doDecodeULongArrayUlong0OnlyWhenAllowedTestImpl(false, 256);
+    }
+
+    private Object doDecodeULongArrayUlong0OnlyTestImpl(final boolean array8, final int count) throws Exception {
+        if (array8) {
+            assertTrue("Cant encode ulong0 array8 with count > 255", count <= 255);
+        }
+
+        int encodingWidth = array8 ? 1 : 4;
+        int arrayPayloadSize = encodingWidth + 1; // variable width for element count + byte type descriptor
+        int expectedEncodedArraySize = 1 + encodingWidth + arrayPayloadSize; // array type code + variable width for
+                                                                             // array size + other encoded payload
+        // Write the array encoding code, array size, and element count
+        if (array8) {
+            buffer.put((byte) 0xE0); // 'array8' type descriptor code
+            buffer.put((byte) arrayPayloadSize);
+            buffer.put((byte) count);
+        } else {
+            buffer.put((byte) 0xF0); // 'array32' type descriptor code
+            buffer.putInt(arrayPayloadSize);
+            buffer.putInt(count);
+        }
+
+        buffer.put((byte) 0x44); // 'ulong0' type descriptor code
+
+        assertEquals(expectedEncodedArraySize, buffer.position());
+
+        buffer.flip();
+
+        return decoder.readObject();
+    }
+
+    private void doDecodeULongArrayUlong0OnlyWhenAllowedTestImpl(final boolean array8, final int count) throws Exception {
+        decoder.setZeroWidthArrayElementLimit(count);
+        Object decoded = doDecodeULongArrayUlong0OnlyTestImpl(array8, count);
+
+        assertNotNull(decoded);
+        assertTrue(decoded.getClass().isArray());
+        assertEquals(UnsignedLong.class, decoded.getClass().getComponentType());
+
+        UnsignedLong[] expected = new UnsignedLong[count];
+        for (int i = 0; i < count; i++) {
+            expected[i] = UnsignedLong.ZERO;
+        }
+        assertArrayEquals(expected, (UnsignedLong[]) decoded);
+    }
+
+    @Test
+    public void testDecodeSmallULongArray253() throws Throwable {
+        final int count = 253;
+        doDecodeSmallULongArrayTestImpl(true, count, createPayloadArrayULongs(count, true));
+        buffer.clear();
+        doDecodeSmallULongArrayTestImpl(false, count, createPayloadArrayULongs(count, true));
+    }
+
+    @Test
+    public void testDecodeSmallULongArray254() throws Throwable {
+        final int count = 254;
+        doDecodeSmallULongArrayTestImpl(false, count, createPayloadArrayULongs(count, true));
+    }
+
+    @Test
+    public void testDecodeSmallULongArray255() throws Throwable {
+        final int count = 255;
+        doDecodeSmallULongArrayTestImpl(false, count, createPayloadArrayULongs(count, true));
+    }
+
+    @Test
+    public void testDecodeSmallULongArray384() throws Throwable {
+        final int count = 384;
+        doDecodeSmallULongArrayTestImpl(false, count, createPayloadArrayULongs(count, true));
+    }
+
+    private void doDecodeSmallULongArrayTestImpl(final boolean array8, final int count, UnsignedLong[] smallULongs) throws Exception {
+        if (array8) {
+            assertTrue("Cant encode small-ulong array8 with count " + count, count <= 253);
+        }
+
+        int encodingWidth = array8 ? 1 : 4;
+        int arrayPayloadSize = encodingWidth + 1 + (count * 1); // variable width for element count + byte type descriptor + elements
+        int expectedEncodedArraySize = 1 + encodingWidth + arrayPayloadSize; // array type code + variable width for
+                                                                             // array size + other encoded payload
+
+        if (array8) {
+            assertTrue("Cant encode array8 with arrayPayloadSize " + count, arrayPayloadSize < 256);
+        }
+
+        // Write the array encoding code, array size, and element count
+        if (array8) {
+            buffer.put((byte) 0xE0); // 'array8' type descriptor code
+            buffer.put((byte) arrayPayloadSize);
+            buffer.put((byte) count);
+        } else {
+            buffer.put((byte) 0xF0); // 'array32' type descriptor code
+            buffer.putInt(arrayPayloadSize);
+            buffer.putInt(count);
+        }
+
+        buffer.put((byte) 0x53); // 'small-ulong' type descriptor code
+        for(UnsignedLong element : smallULongs) {
+            assertTrue(element.longValue() <= 255 && element.longValue() >= 0);
+
+            buffer.put((byte) element.longValue());
+        }
+
+        assertEquals(expectedEncodedArraySize, buffer.position());
+
+        buffer.flip();
+
+        Object o = decoder.readObject();
+
+        assertNotNull(o);
+        assertTrue(o.getClass().isArray());
+        assertEquals(UnsignedLong.class, o.getClass().getComponentType());
+        final UnsignedLong[] decoded = (UnsignedLong[]) o;
+
+        assertArrayEquals(smallULongs, decoded);
+    }
+
+    @Test
+    public void testEncodeDecodeUIntArray0() throws Throwable {
+        doEncodeDecodeUIntArrayTestImpl(createPayloadArrayUInts(0));
+    }
+
+    @Test
+    public void testEncodeDecodeUIntArray63() throws Throwable {
+        doEncodeDecodeUIntArrayTestImpl(createPayloadArrayUInts(63));
+    }
+
+    @Test
+    public void testEncodeDecodeUIntArray64() throws Throwable {
+        doEncodeDecodeUIntArrayTestImpl(createPayloadArrayUInts(64));
+    }
+
+    @Test
+    public void testEncodeDecodeUIntArray100() throws Throwable {
+        doEncodeDecodeUIntArrayTestImpl(createPayloadArrayUInts(100));
+    }
+
+    @Test
+    public void testEncodeDecodeUInt0Array0() throws Throwable {
+        doEncodeDecodeUIntArrayTestImpl(createPayloadArrayUInt(UnsignedInteger.ZERO, 0));
+    }
+
+    @Test
+    public void testEncodeDecodeUInt0Array100() throws Throwable {
+        doEncodeDecodeUIntArrayTestImpl(createPayloadArrayUInt(UnsignedInteger.ZERO, 100));
+    }
+
+    @Test
+    public void testEncodeDecodeUIntArray384() throws Throwable {
+        doEncodeDecodeUIntArrayTestImpl(createPayloadArrayUInts(384));
+    }
+
+    @Test
+    public void testDecodeSmallUIntArray253() throws Throwable {
+        final int count = 253;
+        doDecodeSmallUIntArrayTestImpl(true, count, createPayloadArrayUInts(count, true));
+        buffer.clear();
+        doDecodeSmallUIntArrayTestImpl(false, count, createPayloadArrayUInts(count, true));
+    }
+
+    @Test
+    public void testDecodeSmallUIntArray254() throws Throwable {
+        final int count = 254;
+        doDecodeSmallUIntArrayTestImpl(false, count, createPayloadArrayUInts(count, true));
+    }
+
+    @Test
+    public void testDecodeSmallUIntArray255() throws Throwable {
+        final int count = 255;
+        doDecodeSmallUIntArrayTestImpl(false, count, createPayloadArrayUInts(count, true));
+    }
+
+    @Test
+    public void testDecodeSmallUIntArray384() throws Throwable {
+        final int count = 384;
+        doDecodeSmallUIntArrayTestImpl(false, count, createPayloadArrayUInts(count, true));
+    }
+
+    private void doDecodeSmallUIntArrayTestImpl(final boolean array8, final int count, UnsignedInteger[] smallUInts) throws Exception {
+        if (array8) {
+            assertTrue("Cant encode small-uint array8 with count " + count, count <= 253);
+        }
+
+        int encodingWidth = array8 ? 1 : 4;
+        int arrayPayloadSize = encodingWidth + 1 + (count * 1); // variable width for element count + byte type descriptor + elements
+        int expectedEncodedArraySize = 1 + encodingWidth + arrayPayloadSize; // array type code + variable width for
+                                                                             // array size + other encoded payload
+
+        if (array8) {
+            assertTrue("Cant encode array8 with arrayPayloadSize " + count, arrayPayloadSize < 256);
+        }
+
+        // Write the array encoding code, array size, and element count
+        if (array8) {
+            buffer.put((byte) 0xE0); // 'array8' type descriptor code
+            buffer.put((byte) arrayPayloadSize);
+            buffer.put((byte) count);
+        } else {
+            buffer.put((byte) 0xF0); // 'array32' type descriptor code
+            buffer.putInt(arrayPayloadSize);
+            buffer.putInt(count);
+        }
+
+        buffer.put((byte) 0x52); // 'small-uint' type descriptor code
+        for(UnsignedInteger element : smallUInts) {
+            assertTrue(element.intValue() <= 255 && element.intValue() >= 0);
+
+            buffer.put((byte) element.intValue());
+        }
+
+        assertEquals(expectedEncodedArraySize, buffer.position());
+
+        buffer.flip();
+
+        Object o = decoder.readObject();
+
+        assertNotNull(o);
+        assertTrue(o.getClass().isArray());
+        assertEquals(UnsignedInteger.class, o.getClass().getComponentType());
+        final UnsignedInteger[] decoded = (UnsignedInteger[]) o;
+
+        assertArrayEquals(smallUInts, decoded);
+    }
+
+    private void doEncodeDecodeUIntArrayTestImpl(final UnsignedInteger[] source) throws Throwable {
+        final int count = source.length;
+
+        try {
+            final int encodingWidth = count < 64 ? 1 : 4; // less than 64, since each element is 4 bytes, but we also need 1 byte for element count, and (in this case) 1 byte for primitive element type constructor.
+            final int arrayPayloadSize =  encodingWidth + 1 + (4 * count); // count + element type descriptor + (element-width * number of elements)
+            final int expectedEncodedArraySize = 1 + encodingWidth + arrayPayloadSize; // array type code + size + encoded payload
+            final byte[] expectedEncoding = new byte[expectedEncodedArraySize];
+            ByteBuffer expectedEncodingWrapper = ByteBuffer.wrap(expectedEncoding);
+
+            // Write the array encoding code, array size, and element count
+            if(encodingWidth == 1) {
+                expectedEncodingWrapper.put((byte) 0xE0); // 'array8' type descriptor code
+                expectedEncodingWrapper.put((byte) arrayPayloadSize);
+                expectedEncodingWrapper.put((byte) count);
+            } else {
+                expectedEncodingWrapper.put((byte) 0xF0); // 'array32' type descriptor code
+                expectedEncodingWrapper.putInt(arrayPayloadSize);
+                expectedEncodingWrapper.putInt(count);
+            }
+
+            // Write the type descriptor
+            expectedEncodingWrapper.put((byte) 0x70); // 'uint' type descriptor code
+
+            // Write the elements
+            for (int i = 0; i < count; i++) {
+                expectedEncodingWrapper.putInt(source[i].intValue());
+            }
+
+            assertEquals("Should have filled expected encoding array", 0, expectedEncodingWrapper.remaining());
+
+            // Now produce actual encoding of the array for later comparison
+            assertEquals("Unexpected buffer position", 0, buffer.position());
+            encoder.writeArray(source);
+
+            byte[] actualEncoding = new byte[buffer.position()];
+            buffer.flip();
+            buffer.get(actualEncoding);
+            assertFalse("Should have drained the encoder buffer contents", buffer.hasRemaining());
+
+            // Now verify against the decoding
+            buffer.flip();
+            Object decoded = decoder.readObject();
+            assertNotNull(decoded);
+            assertTrue(decoded.getClass().isArray());
+            assertFalse(decoded.getClass().getComponentType().isPrimitive());
+            assertEquals(UnsignedInteger.class, decoded.getClass().getComponentType());
+
+            assertArrayEquals("Unexpected decoding", source, (UnsignedInteger[]) decoded);
+
+            // Now verify expected encoding against the actual encoding of the array
+            assertEquals("Unexpected encoded payload length", expectedEncoding.length, actualEncoding.length);
+            assertArrayEquals("Unexpected actual array encoding", expectedEncoding, actualEncoding);
+        }
+        catch (Throwable t) {
+            System.err.println("Error during test, source array: " + Arrays.toString(source));
+            throw t;
+        }
+    }
+
+    private static UnsignedInteger[] createPayloadArrayUInts(int length) {
+        return createPayloadArrayUInts(length, false);
+    }
+
+    private static UnsignedInteger[] createPayloadArrayUInts(int length, boolean smallUints) {
+        Random rand = new Random(System.currentTimeMillis());
+
+        UnsignedInteger[] payload = new UnsignedInteger[length];
+        for (int i = 0; i < length; i++) {
+            final int val;
+            if(smallUints) {
+                val = rand.nextInt(256);
+            } else {
+                val = rand.nextInt();
+            }
+
+            payload[i] = UnsignedInteger.valueOf(val);
+        }
+
+        return payload;
+    }
+
+    private static UnsignedInteger[] createPayloadArrayUInt(final UnsignedInteger value, final int length) {
+        UnsignedInteger[] payload = new UnsignedInteger[length];
+        for (int i = 0; i < length; i++) {
+            payload[i] = value;
+        }
+
+        return payload;
+    }
+
+    @Test
+    public void testDecodeUIntArrayExplicitlyUInt0Only0() throws Throwable {
+        Object decoded = doDecodeUIntArrayUInt0OnlyTestImpl(true, 0);
+        assertNotNull(decoded);
+        assertTrue(decoded.getClass().isArray());
+        assertEquals(UnsignedInteger.class, decoded.getClass().getComponentType());
+        assertArrayEquals(new UnsignedInteger[0], (UnsignedInteger[]) decoded);
+    }
+
+    @Test
+    public void testDecodeUIntArrayExplicitlyUInt0Only0Array32() throws Throwable {
+        Object decoded = doDecodeUIntArrayUInt0OnlyTestImpl(false, 0);
+        assertNotNull(decoded);
+        assertTrue(decoded.getClass().isArray());
+        assertEquals(UnsignedInteger.class, decoded.getClass().getComponentType());
+        assertArrayEquals(new UnsignedInteger[0], (UnsignedInteger[]) decoded);
+    }
+
+    @Test
+    public void testDecodeUIntArrayExplicitlyUInt0Only255() throws Throwable {
+        try {
+            doDecodeUIntArrayUInt0OnlyTestImpl(true, 255);
+            fail("Expected exception");
+        } catch (DecodeException e) {
+            // expected
+        }
+    }
+
+    @Test
+    public void testDecodeUIntArrayExplicitlyUInt0Only255WhenAllowed() throws Throwable {
+        doDecodeUIntArrayUInt0OnlyWhenAllowedTestImpl(true, 255);
+    }
+
+    @Test
+    public void testDecodeUIntArrayExplicitlyUInt0Only255Array32() throws Throwable {
+        try {
+            doDecodeUIntArrayUInt0OnlyTestImpl(false, 255);
+            fail("Expected exception");
+        } catch (DecodeException e) {
+            // expected
+        }
+    }
+
+    @Test
+    public void testDecodeUIntArrayExplicitlyUInt0Only255Array32WhenAllowed() throws Throwable {
+        doDecodeUIntArrayUInt0OnlyWhenAllowedTestImpl(false, 255);
+    }
+
+    @Test
+    public void testDecodeUIntArrayExplicitlyUInt0Only256() throws Throwable {
+        try {
+            doDecodeUIntArrayUInt0OnlyTestImpl(false, 256);
+            fail("Expected exception");
+        } catch (DecodeException e) {
+            // expected
+        }
+    }
+
+    @Test
+    public void testDecodeUIntArrayExplicitlyUInt0Only256WhenAllowed() throws Throwable {
+        doDecodeUIntArrayUInt0OnlyWhenAllowedTestImpl(false, 256);
+    }
+
+    @Test
+    public void testDecodeUIntArrayExplicitlyUInt0Only384() throws Throwable {
+        try {
+            doDecodeUIntArrayUInt0OnlyTestImpl(false, 384);
+            fail("Expected exception");
+        } catch (DecodeException e) {
+            // expected
+        }
+    }
+
+    @Test
+    public void testDecodeUIntArrayExplicitlyUInt0Only384WhenAllowed() throws Throwable {
+        doDecodeUIntArrayUInt0OnlyWhenAllowedTestImpl(false, 384);
+    }
+
+    private Object doDecodeUIntArrayUInt0OnlyTestImpl(final boolean array8, final int count) throws Exception {
+        if (array8) {
+            assertTrue("Cant encode uint0 array8 with count > 255", count <= 255);
+        }
+
+        int encodingWidth = array8 ? 1 : 4;
+        int arrayPayloadSize = encodingWidth + 1; // variable width for element count + byte type descriptor
+        int expectedEncodedArraySize = 1 + encodingWidth + arrayPayloadSize; // array type code + variable width for
+                                                                             // array size + other encoded payload
+        // Write the array encoding code, array size, and element count
+        if (array8) {
+            buffer.put((byte) 0xE0); // 'array8' type descriptor code
+            buffer.put((byte) arrayPayloadSize);
+            buffer.put((byte) count);
+        } else {
+            buffer.put((byte) 0xF0); // 'array32' type descriptor code
+            buffer.putInt(arrayPayloadSize);
+            buffer.putInt(count);
+        }
+
+        buffer.put((byte) 0x43); // 'uint0' type descriptor code
+
+        assertEquals(expectedEncodedArraySize, buffer.position());
+
+        buffer.flip();
+
+        return decoder.readObject();
+    }
+
+
+    private void doDecodeUIntArrayUInt0OnlyWhenAllowedTestImpl(final boolean array8, final int count) throws Exception {
+        decoder.setZeroWidthArrayElementLimit(count);
+        Object decoded = doDecodeUIntArrayUInt0OnlyTestImpl(array8, count);
+
+        assertNotNull(decoded);
+        assertTrue(decoded.getClass().isArray());
+        assertEquals(UnsignedInteger.class, decoded.getClass().getComponentType());
+
+        UnsignedInteger[] expected = new UnsignedInteger[count];
+        for (int i = 0; i < count; i++) {
+            expected[i] = UnsignedInteger.ZERO;
+        }
+        assertArrayEquals(expected, (UnsignedInteger[]) decoded);
+    }
+
+    @Test
+    public void testEncodeDecodeListArray19() throws Throwable {
+        doEncodeDecodeListArrayTestImpl(createPayloadArrayLists(19, 1), 1);
+    }
+
+    @Test
+    public void testEncodeDecodeListArray20() throws Throwable {
+        doEncodeDecodeListArrayTestImpl(createPayloadArrayLists(20, 1), 1);
+    }
+
+    @Test
+    public void testEncodeDecodeListArray100() throws Throwable {
+        doEncodeDecodeListArrayTestImpl(createPayloadArrayLists(100, 1), 1);
+    }
+
+    @Test
+    public void testEncodeDecodeListArray384() throws Throwable {
+        doEncodeDecodeListArrayTestImpl(createPayloadArrayLists(384, 1), 1);
+    }
+
+    @Test
+    public void testEncodeDecodeListEmptyArray0() throws Throwable {
+        doEncodeDecodeListArrayTestImpl(createPayloadArrayEmptyList(0), 0);
+    }
+
+    @Test
+    public void testEncodeDecodeListEmptyArray31() throws Throwable {
+        doEncodeDecodeListArrayTestImpl(createPayloadArrayEmptyList(31), 0);
+    }
+
+    @Test
+    public void testEncodeDecodeListEmptyArray32() throws Throwable {
+        doEncodeDecodeListArrayTestImpl(createPayloadArrayEmptyList(32), 0);
+    }
+
+    @Test
+    public void testEncodeDecodeListEmptyArray100() throws Throwable {
+        doEncodeDecodeListArrayTestImpl(createPayloadArrayEmptyList(100), 0);
+    }
+
+    @Test
+    public void testEncodeDecodeListEmptyArray384() throws Throwable {
+        doEncodeDecodeListArrayTestImpl(createPayloadArrayEmptyList(384), 0);
+    }
+
+    private void doEncodeDecodeListArrayTestImpl(final List<Integer>[] source, final int expectedElementListLength) throws Throwable {
+        final int count = source.length;
+        final boolean emptyLists = expectedElementListLength == 0;
+
+        try {
+            final int encodingWidth;
+            if(emptyLists) {
+                encodingWidth = count < 32 ? 1 : 4; // less than 32, since each element is 8 bytes (list 4 size, 4 count), but we also need 1 for element count, and (in this case) 1 byte for primitive element type constructor.
+            } else {
+                encodingWidth = count < 20 ? 1 : 4; // less than 20, since each element is 13 bytes (list 4 size, 4 count, sub-value int with 1 element constructor, 4 value),
+                                                    // but we also need 1 for element count, and (in this case) 1 byte for primitive element type constructor.
+            }
+
+            final int arrayPayloadSize =  encodingWidth + 1 + ((4 + 4 + ((1+4) * expectedElementListLength)) * count); // count + element type descriptor + (element-width * number of elements)
+            final int expectedEncodedArraySize = 1 + encodingWidth + arrayPayloadSize; // array type code + size + encoded payload
+            final byte[] expectedEncoding = new byte[expectedEncodedArraySize];
+            ByteBuffer expectedEncodingWrapper = ByteBuffer.wrap(expectedEncoding);
+
+            // Write the array encoding code, array size, and element count
+            if(encodingWidth == 1) {
+                expectedEncodingWrapper.put((byte) 0xE0); // 'array8' type descriptor code
+                expectedEncodingWrapper.put((byte) arrayPayloadSize);
+                expectedEncodingWrapper.put((byte) count);
+            } else {
+                expectedEncodingWrapper.put((byte) 0xF0); // 'array32' type descriptor code
+                expectedEncodingWrapper.putInt(arrayPayloadSize);
+                expectedEncodingWrapper.putInt(count);
+            }
+
+            // Write the type descriptor
+            expectedEncodingWrapper.put((byte) 0xD0); // 'list32' type descriptor code
+
+            // Write the elements (lists)
+            for (int i = 0; i < count; i++) {
+                final List<Integer> list = source[i];
+                assertEquals(expectedElementListLength, list.size());
+
+                expectedEncodingWrapper.putInt(4 + ((1+4) * expectedElementListLength)); // size = count + elements
+                expectedEncodingWrapper.putInt(expectedElementListLength);
+                for(int j = 0; j < expectedElementListLength; j++) {
+                    expectedEncodingWrapper.put((byte) 0x71); // 'int' type descriptor code
+                    expectedEncodingWrapper.putInt(list.get(0));
+                }
+            }
+
+            assertEquals("Should have filled expected encoding array", 0, expectedEncodingWrapper.remaining());
+
+            // Now produce actual encoding of the array for later comparison
+            assertEquals("Unexpected buffer position", 0, buffer.position());
+            encoder.writeArray(source);
+
+            byte[] actualEncoding = new byte[buffer.position()];
+            buffer.flip();
+            buffer.get(actualEncoding);
+            assertFalse("Should have drained the encoder buffer contents", buffer.hasRemaining());
+
+            // Now verify against the decoding
+            buffer.flip();
+            Object decoded = decoder.readObject();
+            assertNotNull(decoded);
+            assertTrue(decoded.getClass().isArray());
+            assertFalse(decoded.getClass().getComponentType().isPrimitive());
+            assertEquals(List.class, decoded.getClass().getComponentType());
+
+            assertArrayEquals("Unexpected decoding", source, (List<Integer>[]) decoded);
+
+            // Now verify expected encoding against the actual encoding of the array
+            assertEquals("Unexpected encoded payload length", expectedEncoding.length, actualEncoding.length);
+            assertArrayEquals("Unexpected actual array encoding", expectedEncoding, actualEncoding);
+        }
+        catch (Throwable t) {
+            System.err.println("Error during test, source array: " + Arrays.toString(source));
+            throw t;
+        }
+    }
+
+    private static List<Integer>[] createPayloadArrayLists(int arrayLength, int listLength) {
+        Random rand = new Random(System.currentTimeMillis());
+
+        List<?>[] payload = new ArrayList<?>[arrayLength];
+        for (int i = 0; i < arrayLength; i++) {
+            ArrayList<Integer> l = new ArrayList<Integer>();
+            for (int j = 0; j < listLength; j++) {
+                l.add(256 + rand.nextInt(1024)); // adding 256 ensures it is a full size int every time, consistent sizing
+            }
+
+            payload[i] = l;
+        }
+
+        return (List<Integer>[]) payload;
+    }
+
+    private static List<Integer>[] createPayloadArrayEmptyList(final int length) {
+        List<?>[] payload = new List<?>[length];
+        for (int i = 0; i < length; i++) {
+            payload[i] = new ArrayList<Integer>(0);
+        }
+
+        return (List<Integer>[]) payload;
+    }
+
+    @Test
+    public void testDecodeListArrayExplicitlyList0Only0() throws Throwable {
+        Object decoded = doDecodeListArrayList0OnlyTestImpl(true, 0);
+        assertNotNull(decoded);
+        assertTrue(decoded.getClass().isArray());
+        assertEquals(List.class, decoded.getClass().getComponentType());
+        assertArrayEquals(new List[0], (List[]) decoded);
+    }
+
+    @Test
+    public void testDecodeListArrayExplicitlyList0Only0Array32() throws Throwable {
+        Object decoded = doDecodeListArrayList0OnlyTestImpl(false, 0);
+        assertNotNull(decoded);
+        assertTrue(decoded.getClass().isArray());
+        assertEquals(List.class, decoded.getClass().getComponentType());
+        assertArrayEquals(new ArrayList[0], (List[]) decoded);
+    }
+
+    @Test
+    public void testDecodeListArrayExplicitlyList0Only254() throws Throwable {
+        try {
+            doDecodeListArrayList0OnlyTestImpl(true, 254);
+            fail("Expected exception");
+        } catch (DecodeException e) {
+            assertEquals("Array element count 254 is specified to be greater than the allowed 0-width-element limit for arrays (0)", e.getMessage());
+        }
+    }
+
+    @Test
+    public void testDecodeListArrayExplicitlyList0Only254WhenAllowed() throws Throwable {
+        doDecodeListArrayList0OnlyWhenAllowedTestImpl(true, 254);
+    }
+
+    @Test
+    public void testDecodeListArrayExplicitlyList0Only254Array32() throws Throwable {
+        try {
+            doDecodeListArrayList0OnlyTestImpl(false, 254);
+            fail("Expected exception");
+        } catch (DecodeException e) {
+            assertEquals("Array element count 254 is specified to be greater than the allowed 0-width-element limit for arrays (0)", e.getMessage());
+        }
+    }
+
+    @Test
+    public void testDecodeListArrayExplicitlyList0Only254Array32WhenAllowed() throws Throwable {
+        doDecodeListArrayList0OnlyWhenAllowedTestImpl(false, 254);
+    }
+
+    @Test
+    public void testDecodeListArrayExplicitlyList0Only255() throws Throwable {
+        try {
+            doDecodeListArrayList0OnlyTestImpl(true, 255);
+            fail("Expected exception");
+        } catch (DecodeException e) {
+            // expected
+        }
+    }
+
+    @Test
+    public void testDecodeListArrayExplicitlyList0Only255WhenAllowed() throws Throwable {
+        doDecodeListArrayList0OnlyWhenAllowedTestImpl(true, 255);
+    }
+
+    @Test
+    public void testDecodeListArrayExplicitlyList0Only255Array32() throws Throwable {
+        try {
+            doDecodeListArrayList0OnlyTestImpl(false, 255);
+            fail("Expected exception");
+        } catch (DecodeException e) {
+            // expected
+        }
+    }
+
+    @Test
+    public void testDecodeListArrayExplicitlyList0Only255Array32WhenAllowed() throws Throwable {
+        doDecodeListArrayList0OnlyWhenAllowedTestImpl(false, 255);
+    }
+
+    @Test
+    public void testDecodeListArrayExplicitlyList0Only384() throws Throwable {
+        try {
+            doDecodeListArrayList0OnlyTestImpl(false, 384);
+            fail("Expected exception");
+        } catch (DecodeException e) {
+            // expected
+        }
+    }
+
+    @Test
+    public void testDecodeListArrayExplicitlyList0Only384WhenAllowed() throws Throwable {
+        doDecodeListArrayList0OnlyWhenAllowedTestImpl(false, 384);
+    }
+
+    private void doDecodeListArrayList0OnlyWhenAllowedTestImpl(final boolean array8, final int count) throws Exception {
+        decoder.setZeroWidthArrayElementLimit(count);
+        Object decoded = doDecodeListArrayList0OnlyTestImpl(array8, count);
+
+        assertNotNull(decoded);
+        assertTrue(decoded.getClass().isArray());
+        assertEquals(List.class, decoded.getClass().getComponentType());
+
+        List<?>[] expected = new List<?>[count];
+        for (int i = 0; i < count; i++) {
+            expected[i] = Collections.emptyList();
+        }
+
+        assertArrayEquals(expected, (List<?>[]) decoded);
+    }
+
+    private Object doDecodeListArrayList0OnlyTestImpl(final boolean array8, final int count) throws Exception {
+        if (array8) {
+            assertTrue("Cant encode array8 of list0 with count > 255", count < 256);
+        }
+
+        int encodingWidth = array8 ? 1 : 4;
+        int arrayPayloadSize = encodingWidth + 1; // variable width for element count + byte type descriptor
+        int expectedEncodedArraySize = 1 + encodingWidth + arrayPayloadSize; // array type code + variable width for
+                                                                             // array size + other encoded payload
+        // Write the array encoding code, array size, and element count
+        if (array8) {
+            buffer.put((byte) 0xE0); // 'array8' type descriptor code
+            buffer.put((byte) arrayPayloadSize);
+            buffer.put((byte) count);
+        } else {
+            buffer.put((byte) 0xF0); // 'array32' type descriptor code
+            buffer.putInt(arrayPayloadSize);
+            buffer.putInt(count);
+        }
+
+        buffer.put((byte) 0x45); // 'list0' type descriptor code
+
+        assertEquals(expectedEncodedArraySize, buffer.position());
+
+        buffer.flip();
+
+        return decoder.readObject();
+    }
+
+    @Test
+    public void testDecodeNullArray0() throws Throwable {
+        Object decoded = doDecodeNullArrayTestImpl(true, 0);
+        assertNotNull(decoded);
+        assertTrue(decoded.getClass().isArray());
+        assertEquals(Void.class, decoded.getClass().getComponentType());
+        assertArrayEquals(new Void[0], (Void[]) decoded);
+    }
+
+    @Test
+    public void testDecodeNullArray0Array32() throws Throwable {
+        Object decoded = doDecodeNullArrayTestImpl(false, 0);
+        assertNotNull(decoded);
+        assertTrue(decoded.getClass().isArray());
+        assertEquals(Void.class, decoded.getClass().getComponentType());
+        assertArrayEquals(new Void[0], (Void[]) decoded);
+    }
+
+    @Test
+    public void testDecodeNullArray255() throws Throwable {
+        try {
+            doDecodeNullArrayTestImpl(true, 255);
+            fail("Expected exception");
+        } catch (DecodeException e) {
+            // expected
+        }
+    }
+
+    @Test
+    public void testDecodeNullArray255WhenAllowed() throws Throwable {
+        doDecodeNullArrayWhenAllowedTestImpl(true, 255);
+    }
+
+    @Test
+    public void testDecodeNullArray255Array32() throws Throwable {
+        try {
+            doDecodeNullArrayTestImpl(false, 255);
+            fail("Expected exception");
+        } catch (DecodeException e) {
+            // expected
+        }
+    }
+
+    @Test
+    public void testDecodeNullArray255Array32WhenAllowed() throws Throwable {
+        doDecodeNullArrayWhenAllowedTestImpl(false, 255);
+    }
+
+    @Test
+    public void testDecodeNullArray256() throws Throwable {
+        try {
+            doDecodeNullArrayTestImpl(false, 256);
+            fail("Expected exception");
+        } catch (DecodeException e) {
+            // expected
+        }
+    }
+
+    @Test
+    public void testDecodeNullArray256WhenAllowed() throws Throwable {
+        doDecodeNullArrayWhenAllowedTestImpl(false, 256);
+    }
+
+    @Test
+    public void testDecodeNullArray384() throws Throwable {
+        try {
+            doDecodeNullArrayTestImpl(false, 384);
+            fail("Expected exception");
+        } catch (DecodeException e) {
+            // expected
+        }
+    }
+
+    @Test
+    public void testDecodeNullArray384WhenAllowed() throws Throwable {
+        doDecodeNullArrayWhenAllowedTestImpl(false, 384);
+    }
+
+    private void doDecodeNullArrayWhenAllowedTestImpl(boolean array8, final int count) throws Throwable {
+        decoder.setZeroWidthArrayElementLimit(count);
+        Object decoded = doDecodeNullArrayTestImpl(array8, count);
+        assertNotNull(decoded);
+        assertTrue(decoded.getClass().isArray());
+        assertEquals(Void.class, decoded.getClass().getComponentType());
+
+        final Void[] expected = new Void[count];
+
+        assertArrayEquals(expected, (Void[]) decoded);
+    }
+
+    private Object doDecodeNullArrayTestImpl(final boolean array8, final int count) throws Exception {
+        if (array8) {
+            assertTrue("Cant encode null array8 with count > 255", count < 256);
+        }
+
+        int encodingWidth = array8 ? 1 : 4;
+        int arrayPayloadSize = encodingWidth + 1; // variable width for element count + byte type descriptor
+        int expectedEncodedArraySize = 1 + encodingWidth + arrayPayloadSize; // array type code + variable width for
+                                                                             // array size + other encoded payload
+        // Write the array encoding code, array size, and element count
+        if (array8) {
+            buffer.put((byte) 0xE0); // 'array8' type descriptor code
+            buffer.put((byte) arrayPayloadSize);
+            buffer.put((byte) count);
+        } else {
+            buffer.put((byte) 0xF0); // 'array32' type descriptor code
+            buffer.putInt(arrayPayloadSize);
+            buffer.putInt(count);
+        }
+
+        buffer.put((byte) 0x40); // 'null' type descriptor code
+
+        assertEquals(expectedEncodedArraySize, buffer.position());
+
+        buffer.flip();
+
+        return decoder.readObject();
+    }
+
+    @Test
+    public void testEncodeNullArray0() throws Throwable {
+        doEncodeNullArrayTestImpl(0);
+    }
+
+    @Test
+    public void testEncodeNullArray1() throws Throwable {
+        try {
+            doEncodeNullArrayTestImpl(1);
+            fail("Expected exception");
+        } catch (IllegalArgumentException iae) {
+            // expected
+        }
+    }
+
+    @Test
+    public void testEncodeNullArray256() throws Throwable {
+        try {
+            doEncodeNullArrayTestImpl(256);
+            fail("Expected exception");
+        } catch (IllegalArgumentException iae) {
+            // expected
+        }
+    }
+
+    private void doEncodeNullArrayTestImpl(final int count) throws Exception {
+        Void[] source = new Void[count];
+
+        int encodingWidth = count < 256 ? 1 : 4;
+        int arrayPayloadSize = encodingWidth + 1; // variable width for element count + byte type descriptor
+        int expectedEncodedArraySize = 1 + encodingWidth + arrayPayloadSize; // array type code + variable width for
+                                                                             // array size + other encoded payload
+        final byte[] expectedEncoding = new byte[expectedEncodedArraySize];
+        ByteBuffer expectedEncodingWrapper = ByteBuffer.wrap(expectedEncoding);
+
+        // Write the array encoding code, array size, and element count
+        if(encodingWidth == 1) {
+            expectedEncodingWrapper.put((byte) 0xE0); // 'array8' type descriptor code
+            expectedEncodingWrapper.put((byte) arrayPayloadSize);
+            expectedEncodingWrapper.put((byte) count);
+        } else {
+            expectedEncodingWrapper.put((byte) 0xF0); // 'array32' type descriptor code
+            expectedEncodingWrapper.putInt(arrayPayloadSize);
+            expectedEncodingWrapper.putInt(count);
+        }
+
+        expectedEncodingWrapper.put((byte) 0x40); // 'null' type descriptor code
+
+        assertEquals(expectedEncodedArraySize, expectedEncodingWrapper.position());
+        expectedEncodingWrapper.flip();
+
+
+        // Now produce actual encoding of the array for later comparison
+        assertEquals("Unexpected buffer position", 0, buffer.position());
+        encoder.writeArray(source);
+
+        byte[] actualEncoding = new byte[buffer.position()];
+        buffer.flip();
+        buffer.get(actualEncoding);
+        assertFalse("Should have drained the encoder buffer contents", buffer.hasRemaining());
+
+        // Now verify expected encoding against the actual encoding of the array
+        assertEquals("Unexpected encoded payload length", expectedEncoding.length, actualEncoding.length);
+        assertArrayEquals("Unexpected actual array encoding", expectedEncoding, actualEncoding);
+    }
+
+    @Test
+    public void testArrayOfArraysOfMixedTypes() throws IOException {
+        final int size = 10;
+
+        Object[][] source = new Object[2][size];
+        for (int i = 0; i < size; ++i) {
+            source[0][i] = Short.valueOf((short) i);
+            source[1][i] = Integer.valueOf(i);
+        }
+
+        encoder.writeArray(source);
+
+        buffer.flip();
+
+        Object result = decoder.readObject(buffer);
+
+        assertNotNull(result);
+        assertTrue(result.getClass().isArray());
+
+        Object[] resultArray = (Object[]) result;
+
+        assertNotNull(resultArray);
+        assertEquals(2, resultArray.length);
+
+        assertTrue(resultArray[0].getClass().isArray());
+        assertEquals(short.class, resultArray[0].getClass().getComponentType());
+        assertTrue(resultArray[1].getClass().isArray());
+        assertEquals(int.class, resultArray[1].getClass().getComponentType());
+    }
+
+    @Test
+    public void testDecodeArrayOfDescribedExplicitTrue() throws IOException {
+        final byte describedPrimitiveConstructorCode = (byte) EncodingCodes.BOOLEAN_TRUE;
+        final Boolean described = Boolean.TRUE;
+        final byte arrayType = EncodingCodes.ARRAY32;
+
+        Object[] decoded = doDecodeArrayOfDescribedZeroWidthTestImpl(arrayType, describedPrimitiveConstructorCode);
+
+        assertArrayOfDescribedTypeValues(decoded, described);
+    }
+
+    @Test
+    public void testDecodeArrayOfDescribedExplicitTrueArray8() throws IOException {
+        final byte describedPrimitiveConstructorCode = (byte) EncodingCodes.BOOLEAN_TRUE;
+        final Boolean described = Boolean.TRUE;
+        final byte arrayType = EncodingCodes.ARRAY8;
+
+        Object[] decoded = doDecodeArrayOfDescribedZeroWidthTestImpl(arrayType, describedPrimitiveConstructorCode);
+
+        assertArrayOfDescribedTypeValues(decoded, described);
+    }
+
+    @Test
+    public void testDecodeArrayOfDescribedExplicitFalse() throws IOException {
+        final byte describedPrimitiveConstructorCode = (byte) EncodingCodes.BOOLEAN_FALSE;
+        final Boolean described = Boolean.FALSE;
+        final byte arrayType = EncodingCodes.ARRAY32;
+
+        Object[] decoded = doDecodeArrayOfDescribedZeroWidthTestImpl(arrayType, describedPrimitiveConstructorCode);
+
+        assertArrayOfDescribedTypeValues(decoded, described);
+    }
+
+    @Test
+    public void testDecodeArrayOfDescribedULong0() throws IOException {
+        final byte describedPrimitiveConstructorCode = (byte) EncodingCodes.ULONG0;
+        final UnsignedLong described = UnsignedLong.ZERO;
+        final byte arrayType = EncodingCodes.ARRAY32;
+
+        Object[] decoded = doDecodeArrayOfDescribedZeroWidthTestImpl(arrayType, describedPrimitiveConstructorCode);
+
+        assertArrayOfDescribedTypeValues(decoded, described);
+    }
+
+    @Test
+    public void testDecodeArrayOfDescribedUInt0() throws IOException {
+        final byte describedPrimitiveConstructorCode = (byte) EncodingCodes.UINT0;
+        final UnsignedInteger described = UnsignedInteger.ZERO;
+        final byte arrayType = EncodingCodes.ARRAY32;
+
+        Object[] decoded = doDecodeArrayOfDescribedZeroWidthTestImpl(arrayType, describedPrimitiveConstructorCode);
+
+        assertArrayOfDescribedTypeValues(decoded, described);
+    }
+
+    @Test
+    public void testDecodeArrayOfDescribedNull() throws IOException {
+        final byte describedPrimitiveConstructorCode = (byte) EncodingCodes.NULL;
+        final Object described = null;
+        final byte arrayType = EncodingCodes.ARRAY32;
+
+        Object[] decoded = doDecodeArrayOfDescribedZeroWidthTestImpl(arrayType, describedPrimitiveConstructorCode);
+
+        assertArrayOfDescribedTypeValues(decoded, described);
+    }
+
+    @Test
+    public void testDecodeArrayOfDescribedList0() throws IOException {
+        final byte describedPrimitiveConstructorCode = (byte) EncodingCodes.LIST0;
+        final List<Integer> described = new ArrayList<Integer>();
+        final byte arrayType = EncodingCodes.ARRAY32;
+
+        Object[] decoded = doDecodeArrayOfDescribedZeroWidthTestImpl(arrayType, describedPrimitiveConstructorCode);
+
+        assertArrayOfDescribedTypeValues(decoded, described);
+    }
+
+    @Test
+    public void testDecodeArrayOfDescribedNested() throws IOException {
+        final byte describedPrimitiveConstructorCode = (byte) EncodingCodes.DESCRIBED_TYPE_INDICATOR;
+
+        try {
+            doDecodeArrayOfDescribedZeroWidthTestImpl(EncodingCodes.ARRAY32, describedPrimitiveConstructorCode);
+            fail("Expected exception");
+        } catch(DecodeException de) {
+            assertEquals("DescribedType value encoding cannot be another DescribedType, must be a primitive type", de.getMessage());
+        }
+
+        buffer.clear();
+
+        try {
+            doDecodeArrayOfDescribedZeroWidthTestImpl(EncodingCodes.ARRAY8, describedPrimitiveConstructorCode);
+            fail("Expected exception");
+        } catch(DecodeException de) {
+            assertEquals("DescribedType value encoding cannot be another DescribedType, must be a primitive type", de.getMessage());
+        }
+    }
+
+    private Object[] doDecodeArrayOfDescribedZeroWidthTestImpl(final byte arrayTypeCode, final byte describedPrimitiveConstructorCode) {
+        if (!(describedPrimitiveConstructorCode == EncodingCodes.BOOLEAN_TRUE
+                || describedPrimitiveConstructorCode == EncodingCodes.BOOLEAN_FALSE
+                || describedPrimitiveConstructorCode == EncodingCodes.UINT0
+                || describedPrimitiveConstructorCode == EncodingCodes.ULONG0
+                || describedPrimitiveConstructorCode == EncodingCodes.LIST0
+                || describedPrimitiveConstructorCode == EncodingCodes.NULL
+                || describedPrimitiveConstructorCode == EncodingCodes.DESCRIBED_TYPE_INDICATOR)) {
+            throw new IllegalArgumentException("Expected a 0-width element encoding or described type indicator, got: "
+                    + EncodingCodes.toString(describedPrimitiveConstructorCode));
+        }
+
+        if (arrayTypeCode == EncodingCodes.ARRAY32) {
+            buffer.put(EncodingCodes.ARRAY32);
+            buffer.putInt(4 + 1 + 1 + 1 + 10 + 1);  // Size
+            buffer.putInt(2);  // Count
+        } else {
+            buffer.put(EncodingCodes.ARRAY8);
+            buffer.put((byte) (1 + 1 + 1 + 1 + 10 + 1));  // Size
+            buffer.put((byte) 2);  // Count
+        }
+        buffer.put((byte) 0); // Described Type Indicator
+        buffer.put(EncodingCodes.SYM8); // sym8 type code for symbolic descriptor
+        buffer.put((byte) 10); //symbol length
+        buffer.put("DESCRIPTOR".getBytes(StandardCharsets.US_ASCII));
+        buffer.put(describedPrimitiveConstructorCode); // ulong constructor type-code for described primitive
+        // We dont populate any value bytes as we are expecting 0 length element constructors (or described type indicator to throw)
+
+        byte[] expectedEncoding = new byte[buffer.position()];
+
+        buffer.flip();
+
+        buffer.get(expectedEncoding);
+        assertEquals("Should have drained the encoder buffer contents", 0, buffer.remaining());
+
+        buffer.flip();
+
+        // First verify that it throws with the element limit default
+        try {
+            decoder.readObject();
+            fail("Expected exception");
+        } catch (DecodeException de) {
+            // Expected
+        }
+
+        buffer.rewind();
+        assertEquals("Should have expected buffer content remaining", expectedEncoding.length, buffer.remaining());
+
+        // Now try again with permissive element limit
+        decoder.setZeroWidthArrayElementLimit(2);
+
+        Object o = decoder.readObject();
+        assertNotNull(o);
+        assertTrue(o.getClass().isArray());
+
+        Object[] decoded = (Object[]) o;
+        assertEquals(2, decoded.length);
+        return decoded;
+    }
+
+    private void assertArrayOfDescribedTypeValues(Object[] decoded, final Object described) {
+        assertTrue(decoded.length > 0);
+
+        for (int i = 0; i < decoded.length; i++) {
+            assertTrue(decoded[i] instanceof DescribedType);
+            DescribedType dt = (DescribedType) decoded[i];
+            assertEquals(Symbol.valueOf("DESCRIPTOR"), dt.getDescriptor());
+
+            assertEquals(described, dt.getDescribed());
+        }
+    }
+
+    @Test
+    public void testDecodeIntegerArray() throws Exception {
+        Object o = doDecodeIntegerArrayWithSpecificSizeCountTestImpl(false, 26, 6);
+        assertDecodedIntegerArray(o);
+
+        o = doDecodeIntegerArrayWithSpecificSizeCountTestImpl(true, 29, 6);
+        assertDecodedIntegerArray(o);
+    }
+
+    private void assertDecodedIntegerArray(Object o) {
+        assertNotNull(o);
+        assertTrue(o.getClass().isArray());
+        assertEquals(int.class, o.getClass().getComponentType());
+
+        int[] array = (int[]) o;
+
+        assertEquals(6, array.length);
+
+        assertEquals(6, array[0]);
+        assertEquals(5, array[1]);
+        assertEquals(4, array[2]);
+        assertEquals(3, array[3]);
+        assertEquals(2, array[4]);
+        assertEquals(1, array[5]);
+    }
+
+    @Test
+    public void testDecodeThrowsWithSizeBeyondAvailable() throws Exception {
+        try {
+            doDecodeIntegerArrayWithSpecificSizeCountTestImpl(false, 26 + 1, 6);
+            fail("Expected exception to be thrown");
+        } catch (DecodeException de) {
+            assertEquals("Array size 27 cant be more than remaining bytes 26", de.getMessage());
+        }
+
+        try {
+            doDecodeIntegerArrayWithSpecificSizeCountTestImpl(true, 29 + 1, 6);
+            fail("Expected exception to be thrown");
+        } catch (DecodeException de) {
+            assertEquals("Array size 30 cant be more than remaining bytes 29", de.getMessage());
+        }
+    }
+
+    @Test
+    public void testDecodeThrowsWithNegativeSize() throws Exception {
+        try {
+            doDecodeIntegerArrayWithSpecificSizeCountTestImpl(true, UnsignedInteger.MAX_VALUE.intValue(), 6);
+            fail("Expected exception to be thrown");
+        } catch (DecodeException de) {
+            assertEquals("Array size -1 cant be negative", de.getMessage());
+        }
+    }
+
+    @Test
+    public void testDecodeThrowsWithNegativeCount() throws Exception {
+        try {
+            doDecodeIntegerArrayWithSpecificSizeCountTestImpl(true, 29, UnsignedInteger.MAX_VALUE.intValue());
+            fail("Expected exception to be thrown");
+        } catch (DecodeException de) {
+            assertEquals("Array element count -1 cant be negative", de.getMessage());
+        }
+    }
+
+    @Test
+    public void testDecodeThrowsWithCountLargerThanSize() throws Exception {
+        try {
+            // Deliberately tries not to decode the full Array, so the 'remaining' checks dont foul
+            doDecodeIntegerArrayWithSpecificSizeCountTestImpl(false, 2, 6);
+            fail("Expected exception to be thrown");
+        } catch (DecodeException de) {
+            assertEquals("Array element count 6 is specified to be greater than the size of array (2)", de.getMessage());
+        }
+
+        try {
+            // Deliberately tries not to decode the full Array, so the 'remaining' checks dont foul
+            doDecodeIntegerArrayWithSpecificSizeCountTestImpl(true, 5, 6);
+            fail("Expected exception to be thrown");
+        } catch (DecodeException de) {
+            assertEquals("Array element count 6 is specified to be greater than the size of array (5)", de.getMessage());
+        }
+    }
+
+    private Object doDecodeIntegerArrayWithSpecificSizeCountTestImpl(final boolean array32, int size, int count) {
+        ByteBuffer tmpBuf = ByteBuffer.allocate(1024);
+        if (array32) {
+            tmpBuf.put((byte) 0xF0); // Array32 primitive type constructor
+            tmpBuf.putInt(size); // size
+            tmpBuf.putInt(count); // count
+        } else {
+            tmpBuf.put((byte) 0xE0); // Array8 primitive type constructor
+            tmpBuf.put((byte) size); // size
+            tmpBuf.put((byte) count); // count
+        }
+        tmpBuf.put((byte) 0x71); // int constructor
+        tmpBuf.putInt(6);
+        tmpBuf.putInt(5);
+        tmpBuf.putInt(4);
+        tmpBuf.putInt(3);
+        tmpBuf.putInt(2);
+        tmpBuf.putInt(1);
+
+        tmpBuf.flip();
+
+        int oldLimit = tmpBuf.limit();
+
+        decoder.setByteBuffer(tmpBuf);
+
+        final Object decoded;
+        try
+        {
+            decoded = decoder.readObject();
+        }
+        finally
+        {
+            assertEquals(oldLimit, tmpBuf.limit());
+        }
+        assertEquals(oldLimit, tmpBuf.position());
+
+        return decoded;
+    }
+
+    @Test
+    public void testDecodeArrayOverflowThrows() throws Exception {
+        try {
+            // Deliberately pass size for part Array, but count for full Array...will try to decode count, overflow
+            doDecodeIntegerArrayWithSpecificSizeCountTestImpl(false, 22 - 4, 5);
+            fail("Expected exception to be thrown");
+        } catch (BufferUnderflowException bue) {
+            // Expected
+        }
+
+        try {
+            // Deliberately pass size for part Array, but count for full Array...will try to decode count, overflow
+            doDecodeIntegerArrayWithSpecificSizeCountTestImpl(true, 25 - 4, 5);
+            fail("Expected exception to be thrown");
+        } catch (BufferUnderflowException bue) {
+            // Expected
+        }
+    }
+
+    @Test
+    public void testDecodeArrayUnderflowThrows() throws Exception {
+        try {
+            // Deliberately pass size for full Array, but count for part Array...will try to decode count, underflow
+            doDecodeIntegerArrayWithSpecificSizeCountTestImpl(false, 22, 5 - 1);
+            fail("Expected exception to be thrown");
+        } catch (DecodeException de) {
+            assertEquals("Array incorrectly encoded, left 4 bytes from size indicated 22", de.getMessage());
+        }
+
+        try {
+            // Deliberately pass size for full Array, but count for part Array...will try to decode count, underflow
+            doDecodeIntegerArrayWithSpecificSizeCountTestImpl(true, 25, 5 - 1);
+            fail("Expected exception to be thrown");
+        } catch (DecodeException de) {
+            assertEquals("Array incorrectly encoded, left 4 bytes from size indicated 25", de.getMessage());
+        }
+    }
+
+    @Test
+    public void testDecodeArrayFailsIfDepthLimitExceeded() throws Exception {
+        final int depth = 2;
+        decoder.setMaxDecodeDepth(1);
+
+        final int width = 2;
+        final int size = 10;
+        Object[][] source = new Object[width][size];
+        for (int i = 0; i < size; ++i) {
+            source[0][i] = Short.valueOf((short) i);
+            source[1][i] = Integer.valueOf(i);
+        }
+
+        encoder.writeObject(source);
+        buffer.flip();
+        buffer.mark();
+
+        try {
+            decoder.readObject();
+            fail("Expected exception to be thrown");
+        } catch (DecodeException de) {
+            assertEquals("The nesting of types in the object being decoded exceeded the configured limit: 1", de.getMessage());
+        }
+
+        decoder.setMaxDecodeDepth(depth);
+        buffer.reset();
+
+        try {
+            final Object result = decoder.readObject();
+
+            assertNotNull(result);
+            assertTrue(result.getClass().isArray());
+            assertEquals(Object.class, result.getClass().getComponentType());
+
+            Object[] decoded = (Object[]) result;
+            assertEquals(width, decoded.length);
+
+            assertTrue(decoded[0].getClass().isArray());
+            assertEquals(short.class, decoded[0].getClass().getComponentType());
+
+            assertTrue(decoded[1].getClass().isArray());
+            assertEquals(int.class, decoded[1].getClass().getComponentType());
+        } catch (Exception e) {
+            fail("Should be able to read the array after depth configuration change");
+        }
+    }
+
+    @Test
+    public void testDecodeArrayOfListsOfListsFailsIfDepthLimitExceeded() throws Exception {
+        decoder.setMaxDecodeDepth(1);
+
+        List<List<?>> elementList = new ArrayList<>();
+        elementList.add(Collections.emptyList());
+        elementList.add(Collections.emptyList());
+        elementList.add(Collections.emptyList());
+
+        Object[] source = new Object[2];
+        for (int i = 0; i < source.length; ++i) {
+            source[i] = elementList;
+        }
+
+        encoder.writeArray(source);
+        buffer.flip();
+        buffer.mark();
+
+        try {
+            decoder.readObject();
+            fail("Expected exception to be thrown");
+        } catch (DecodeException de) {
+            assertEquals("The nesting of types in the object being decoded exceeded the configured limit: 1", de.getMessage());
+        }
+
+        buffer.reset();
+        decoder.setMaxDecodeDepth(2);
+
+        try {
+            decoder.readObject();
+            fail("Expected exception to be thrown");
+        } catch (DecodeException de) {
+            assertEquals("The nesting of types in the object being decoded exceeded the configured limit: 2", de.getMessage());
+        }
+
+        buffer.reset();
+        decoder.setMaxDecodeDepth(3);
+
+        try {
+            final Object result = decoder.readObject();
+
+            assertNotNull(result);
+            assertTrue(result.getClass().isArray());
+        } catch (Exception e) {
+            fail("Should be able to read the array after depth configuration change");
+        }
+    }
+
+    @Test
+    public void testDecodeArrayOfListsOfMapsFailsIfDepthLimitExceeded() throws Exception {
+        decoder.setMaxDecodeDepth(1);
+
+        List<Map<?, ?>> elementList = new ArrayList<>();
+        elementList.add(Collections.emptyMap());
+        elementList.add(Collections.emptyMap());
+        elementList.add(Collections.emptyMap());
+
+        Object[] source = new Object[2];
+        for (int i = 0; i < source.length; ++i) {
+            source[i] = elementList;
+        }
+
+        encoder.writeArray(source);
+        buffer.flip();
+        buffer.mark();
+
+        try {
+            decoder.readObject();
+            fail("Expected exception to be thrown");
+        } catch (DecodeException de) {
+            assertEquals("The nesting of types in the object being decoded exceeded the configured limit: 1", de.getMessage());
+        }
+
+        buffer.reset();
+        decoder.setMaxDecodeDepth(2);
+
+        try {
+            decoder.readObject();
+            fail("Expected exception to be thrown");
+        } catch (DecodeException de) {
+            assertEquals("The nesting of types in the object being decoded exceeded the configured limit: 2", de.getMessage());
+        }
+
+        buffer.reset();
+        decoder.setMaxDecodeDepth(3);
+
+        try {
+            final Object result = decoder.readObject();
+
+            assertNotNull(result);
+            assertTrue(result.getClass().isArray());
+        } catch (Exception e) {
+            fail("Should be able to read the array after depth configuration change");
+        }
+    }
+
+    @Test
+    public void testDecodingOfDeeplyNestedArrayOfArray32() throws Exception {
+        doDecodingOfDeeplyNestedArrayOfArrayTestImpl(true, 10, true);
+        doDecodingOfDeeplyNestedArrayOfArrayTestImpl(true, 10, false);
+    }
+
+    @Test
+    public void testDecodingOfDeeplyNestedArrayOfArray8() throws Exception {
+        doDecodingOfDeeplyNestedArrayOfArrayTestImpl(false, 10, true);
+        doDecodingOfDeeplyNestedArrayOfArrayTestImpl(false, 10, false);
+    }
+
+    private void doDecodingOfDeeplyNestedArrayOfArrayTestImpl(final boolean array32, final int depth, boolean readObject) {
+        ByteBuffer tmpBuf = buildNestedArraysBuffer(depth, array32);
+        decoder.setByteBuffer(tmpBuf);
+
+        decoder.setMaxDecodeDepth(depth - 1);
+
+        try {
+            if (readObject) {
+                decoder.readObject();
+            } else {
+                decoder.readArray();
+            }
+            fail("Expected exception");
+        } catch (DecodeException de) {
+            assertEquals("The nesting of types in the object being decoded exceeded the configured limit: " + (depth - 1), de.getMessage());
+            //expected
+        }
+
+        decoder.setMaxDecodeDepth(depth);
+        tmpBuf.clear();
+
+        final Object result;
+        if (readObject) {
+            result = decoder.readObject();
+        } else {
+            result = decoder.readArray();
+        }
+
+        assertTrue(result instanceof Object[]);
+    }
+
+    private ByteBuffer buildNestedArraysBuffer(final int levels, boolean array32)
+    {
+        final int encodingWith = array32 ? 4 : 1;
+        final int totalEncodingSize = ((1 + (encodingWith * 2)) * levels) + 1; // Number of bytes for size + count + 1 byte for array type + 1 for element constructor, times levels
+                                                                               // + 1 for final element type constructor (final array is empty)
+
+        ByteBuffer tmpBuf = ByteBuffer.allocate(totalEncodingSize);
+
+        int currentArraySize = totalEncodingSize - encodingWith - 1; // Size does not include its own type code and size itself
+
+        if(!array32) {
+            assertTrue("Cant encode array8 of this depth within 255 byte array size limit", currentArraySize < 256);
+        }
+
+        final byte arrayTypeCode = array32 ? EncodingCodes.ARRAY32 : EncodingCodes.ARRAY8;
+        final int individualArrayBytes = 1 + (encodingWith * 2); // type code, then size, then count
+
+        for (int i = 1; i <= levels; i++)
+        {
+            // Type code
+            tmpBuf.put(arrayTypeCode);
+
+            // Size
+            if(array32) {
+                tmpBuf.putInt(currentArraySize);
+            } else {
+                tmpBuf.put((byte) currentArraySize);
+            }
+
+            if(i < levels) {
+                // Count (1 for all but last, the nested array value coming next)
+                if(array32) {
+                    tmpBuf.putInt(1);
+                } else {
+                    tmpBuf.put((byte) 1);
+                }
+
+                // Value will be written on next iteration
+
+                // Update encoded array size for next iteration
+                currentArraySize -= individualArrayBytes;
+            } else {
+                // Count (0 for the final empty array)
+                if(array32) {
+                    tmpBuf.putInt(0);
+                } else {
+                    tmpBuf.put((byte) 0);
+                }
+            }
+        }
+
+        assertEquals(encodingWith + 1, currentArraySize); // count of prior array + the final list0 type constructor below
+
+        tmpBuf.put(EncodingCodes.LIST0);
+
+        // Check we filled the buffer
+        assertEquals(0, tmpBuf.remaining());
+
+        tmpBuf.flip();
+
+        return tmpBuf;
+    }
 }
\ No newline at end of file
diff --git a/proton-j/src/test/java/org/apache/qpid/proton/codec/BinaryTypeCodecTest.java b/proton-j/src/test/java/org/apache/qpid/proton/codec/BinaryTypeCodecTest.java
index c84b93d3..6ea902a1 100644
--- a/proton-j/src/test/java/org/apache/qpid/proton/codec/BinaryTypeCodecTest.java
+++ b/proton-j/src/test/java/org/apache/qpid/proton/codec/BinaryTypeCodecTest.java
@@ -16,9 +16,15 @@
  */
 package org.apache.qpid.proton.codec;
 
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
 import java.io.IOException;
+import java.nio.ByteBuffer;
 
 import org.apache.qpid.proton.amqp.Binary;
+import org.apache.qpid.proton.amqp.UnsignedInteger;
 import org.junit.Test;
 import org.mockito.Mockito;
 
@@ -57,4 +63,75 @@ public class BinaryTypeCodecTest extends CodecTestSupport {
         // Check that the BinaryType tries to reserve space, actual encoding size not computed here.
         Mockito.verify(spy).ensureRemaining(Mockito.anyInt());
     }
+
+    @Test
+    public void testDecodeBinary() throws Exception {
+        final Binary expected = new Binary("54321".getBytes());
+
+        Object o = doDecodeBinaryWithSpecificSizeTestImpl(false, 5);
+        assertEquals(expected, o);
+
+        o = doDecodeBinaryWithSpecificSizeTestImpl(true, 5);
+        assertEquals(expected, o);
+    }
+
+    @Test
+    public void testDecodeThrowsWithSizeBeyondAvailable() throws Exception {
+        try {
+            doDecodeBinaryWithSpecificSizeTestImpl(false, 5 + 1);
+            fail("Expected exception to be thrown");
+        } catch (DecodeException de) {
+            assertEquals("binary length 6 cannot be more than remaining bytes 5", de.getMessage());
+        }
+
+        try {
+            doDecodeBinaryWithSpecificSizeTestImpl(true, 5 + 1);
+            fail("Expected exception to be thrown");
+        } catch (DecodeException de) {
+            assertEquals("binary length 6 cannot be more than remaining bytes 5", de.getMessage());
+        }
+    }
+
+    @Test
+    public void testDecodeThrowsWithNegativeSize() throws Exception {
+        try {
+            doDecodeBinaryWithSpecificSizeTestImpl(true, UnsignedInteger.MAX_VALUE.intValue());
+            fail("Expected exception to be thrown");
+        } catch (DecodeException de) {
+            assertEquals("binary length -1 cannot be negative", de.getMessage());
+        }
+    }
+
+    private Object doDecodeBinaryWithSpecificSizeTestImpl(final boolean sym32, int size) {
+        ByteBuffer tmpBuf = ByteBuffer.allocate(1024);
+        if (sym32) {
+            tmpBuf.put((byte) (byte) 0xB0); // vbin32 primitive type constructor
+            tmpBuf.putInt(size); // size
+        } else {
+            assertTrue(size < 256);
+            tmpBuf.put((byte) (byte) 0xA0); // vbin8 primitive type constructor
+            tmpBuf.put((byte) size); // size
+        }
+        tmpBuf.put((byte) 0x35); // "5"
+        tmpBuf.put((byte) 0x34); // "4"
+        tmpBuf.put((byte) 0x33); // "3"
+        tmpBuf.put((byte) 0x32); // "2"
+        tmpBuf.put((byte) 0x31); // "1"
+
+
+        tmpBuf.flip();
+        int oldLimit = tmpBuf.limit();
+
+        decoder.setByteBuffer(tmpBuf);
+
+        final Object decoded;
+        try {
+            decoded = decoder.readObject();
+        } finally {
+            assertEquals(oldLimit, tmpBuf.limit());
+        }
+        assertEquals(oldLimit, tmpBuf.position());
+
+        return decoded;
+    }
 }
diff --git a/proton-j/src/test/java/org/apache/qpid/proton/codec/CodecTestSupport.java b/proton-j/src/test/java/org/apache/qpid/proton/codec/CodecTestSupport.java
index bb33697a..f85cd984 100644
--- a/proton-j/src/test/java/org/apache/qpid/proton/codec/CodecTestSupport.java
+++ b/proton-j/src/test/java/org/apache/qpid/proton/codec/CodecTestSupport.java
@@ -27,13 +27,15 @@ public class CodecTestSupport {
 
     public static final int DEFAULT_MAX_BUFFER = 256 * 1024;
 
-    final DecoderImpl decoder = new DecoderImpl();
-    final EncoderImpl encoder = new EncoderImpl(decoder);
+    protected DecoderImpl decoder;
+    protected EncoderImpl encoder;
 
-    ByteBuffer buffer;
+    protected ByteBuffer buffer;
 
     @Before
     public void setUp() {
+        decoder = new DecoderImpl();
+        encoder = new EncoderImpl(decoder);
         AMQPDefinedTypes.registerAllTypes(decoder, encoder);
 
         buffer = ByteBuffer.allocate(getMaxBufferSize());
diff --git a/proton-j/src/test/java/org/apache/qpid/proton/codec/DecoderImplTest.java b/proton-j/src/test/java/org/apache/qpid/proton/codec/DecoderImplTest.java
new file mode 100644
index 00000000..2f0558f5
--- /dev/null
+++ b/proton-j/src/test/java/org/apache/qpid/proton/codec/DecoderImplTest.java
@@ -0,0 +1,221 @@
+/*
+ *
+ * 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
... 9377 lines suppressed ...


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@qpid.apache.org
For additional commands, e-mail: commits-help@qpid.apache.org