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

(qpid-proton-dotnet) 01/03: Various codec correctness and performance improvements

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

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

commit 5a0c8bd756243acea8bf81d0c01b7a0819ef6ac5
Author: Timothy Bish <ta...@gmail.com>
AuthorDate: Fri Jul 31 09:40:42 2026 -0400

    Various codec correctness and performance improvements
---
 .gitignore                                         |    1 +
 src/Proton.Client/Client/DecodeOptions.cs          |   80 +
 src/Proton.Client/Client/IDelivery.cs              |   24 +
 src/Proton.Client/Client/IStreamDelivery.cs        |   22 +
 .../Client/Implementation/ClientConnection.cs      |    3 +-
 .../Implementation/ClientConversionSupport.cs      |   72 +
 .../Client/Implementation/ClientDelivery.cs        |   16 +
 .../Client/Implementation/ClientMessageSupport.cs  |   80 +-
 .../Client/Implementation/ClientStreamDelivery.cs  |    7 +-
 .../Implementation/ClientStreamReceiverMessage.cs  |   12 +-
 src/Proton.Client/Client/StreamDecodeOptions.cs    |  117 +
 .../Decoders/AbstractDescribedListTypeDecoder.cs   |  232 ++
 .../Decoders/AbstractDescribedMapTypeDecoder.cs    |  248 ++
 .../Codec/Decoders/AbstractDescribedTypeDecoder.cs |    8 +-
 .../Codec/Decoders/AbstractPrimitiveTypeDecoder.cs |   32 +-
 .../Codec/Decoders/IPrimitiveArrayTypeDecoder.cs   |   34 +
 src/Proton/Codec/Decoders/IPrimitiveTypeDecoder.cs |    4 +
 .../Decoders/Messaging/AcceptedTypeDecoder.cs      |   66 +-
 .../Decoders/Messaging/AmqpSequenceTypeDecoder.cs  |   16 +-
 .../Decoders/Messaging/AmqpValueTypeDecoder.cs     |   50 +-
 .../Messaging/ApplicationPropertiesTypeDecoder.cs  |  136 +-
 .../Codec/Decoders/Messaging/DataTypeDecoder.cs    |   22 +-
 .../Decoders/Messaging/DeleteOnCloseTypeDecoder.cs |   66 +-
 .../DeleteOnNoLinksOrMessagesTypeDecoder.cs        |   66 +-
 .../Messaging/DeleteOnNoLinksTypeDecoder.cs        |   66 +-
 .../Messaging/DeleteOnNoMessagesTypeDecoder.cs     |   66 +-
 .../Messaging/DeliveryAnnotationsTypeDecoder.cs    |  136 +-
 .../Codec/Decoders/Messaging/FooterTypeDecoder.cs  |  136 +-
 .../Codec/Decoders/Messaging/HeaderTypeDecoder.cs  |   94 +-
 .../Messaging/MessageAnnotationsTypeDecoder.cs     |  136 +-
 .../Decoders/Messaging/ModifiedTypeDecoder.cs      |   94 +-
 .../Decoders/Messaging/PropertiesTypeDecoder.cs    |   94 +-
 .../Decoders/Messaging/ReceivedTypeDecoder.cs      |   84 +-
 .../Decoders/Messaging/RejectedTypeDecoder.cs      |  112 +-
 .../Decoders/Messaging/ReleasedTypeDecoder.cs      |   66 +-
 .../Codec/Decoders/Messaging/SourceTypeDecoder.cs  |   94 +-
 .../Codec/Decoders/Messaging/TargetTypeDecoder.cs  |   94 +-
 .../Primitives/AbstractArrayTypeDecoder.cs         |  154 +-
 .../Decoders/Primitives/AbstractBinaryDecoder.cs   |   39 +-
 .../Decoders/Primitives/AbstractListTypeDecoder.cs |  134 +-
 .../Decoders/Primitives/AbstractMapTypeDecoder.cs  |  163 +-
 .../Primitives/AbstractStringTypeDecoder.cs        |   38 +-
 .../Primitives/AbstractSymbolTypeDecoder.cs        |   52 +-
 .../Decoders/Primitives/BooleanFalseTypeDecoder.cs |   22 +
 .../Decoders/Primitives/BooleanTrueTypeDecoder.cs  |   22 +
 .../Codec/Decoders/Primitives/List0TypeDecoder.cs  |   82 +-
 .../Codec/Decoders/Primitives/NullTypeDecoder.cs   |   36 +
 ...llTypeDecoder.cs => SaslSymbol32TypeDecoder.cs} |   23 +-
 ...ullTypeDecoder.cs => SaslSymbol8TypeDecoder.cs} |   23 +-
 .../Primitives/UnsignedInteger0TypeDecoder.cs      |   24 +-
 .../Primitives/UnsignedLong0TypeDecoder.cs         |   24 +-
 src/Proton/Codec/Decoders/ProtonDecoder.cs         |  186 +-
 src/Proton/Codec/Decoders/ProtonDecoderFactory.cs  |    4 +-
 src/Proton/Codec/Decoders/ProtonDecoderState.cs    |   47 +-
 src/Proton/Codec/Decoders/ProtonStreamDecoder.cs   |  138 +-
 .../Codec/Decoders/ProtonStreamDecoderFactory.cs   |    4 +-
 .../Codec/Decoders/ProtonStreamDecoderState.cs     |   59 +-
 src/Proton/Codec/Decoders/ProtonStreamReadUtils.cs |    5 +
 .../Decoders/Security/SaslChallengeTypeDecoder.cs  |   92 +-
 .../Codec/Decoders/Security/SaslInitTypeDecoder.cs |   96 +-
 .../Decoders/Security/SaslMechanismsTypeDecoder.cs |   92 +-
 .../Decoders/Security/SaslOutcomeTypeDecoder.cs    |  120 +-
 .../Decoders/Security/SaslResponseTypeDecoder.cs   |   92 +-
 .../Transactions/CoordinatorTypeDecoder.cs         |   94 +-
 .../Decoders/Transactions/DeclareTypeDecoder.cs    |   94 +-
 .../Decoders/Transactions/DeclaredTypeDecoder.cs   |  100 +-
 .../Decoders/Transactions/DischargeTypeDecoder.cs  |  116 +-
 .../Transactions/TransactionalStateTypeDecoder.cs  |  116 +-
 .../Codec/Decoders/Transport/AttachTypeDecoder.cs  |   90 +-
 .../Codec/Decoders/Transport/BeginTypeDecoder.cs   |   90 +-
 .../Codec/Decoders/Transport/CloseTypeDecoder.cs   |   90 +-
 .../Codec/Decoders/Transport/DetachTypeDecoder.cs  |   90 +-
 .../Decoders/Transport/DispositionTypeDecoder.cs   |   90 +-
 .../Codec/Decoders/Transport/EndTypeDecoder.cs     |  108 +-
 .../Transport/ErrorConditionTypeDecoder.cs         |   90 +-
 .../Codec/Decoders/Transport/FlowTypeDecoder.cs    |   90 +-
 .../Codec/Decoders/Transport/OpenTypeDecoder.cs    |   90 +-
 .../Decoders/Transport/TransferTypeDecoder.cs      |   90 +-
 .../Codec/Decoders/UnknownDescribedTypeDecoder.cs  |   87 +-
 .../Encoders/AbstractDescribedMapTypeEncoder.cs    |   41 +-
 .../Messaging/ApplicationPropertiesTypeEncoder.cs  |   10 +-
 .../Messaging/DeliveryAnnotationsTypeEncoder.cs    |   10 +-
 .../Codec/Encoders/Messaging/FooterTypeEncoder.cs  |   10 +-
 .../Messaging/MessageAnnotationsTypeEncoder.cs     |   10 +-
 src/Proton/Codec/Encoders/ProtonEncoder.cs         |   26 +-
 src/Proton/Codec/Encoders/ProtonEncoderFactory.cs  |    4 +-
 src/Proton/Codec/IDecoderState.cs                  |   46 +
 src/Proton/Codec/IStreamDecoderState.cs            |  106 +
 .../Exceptions/ProtocolViolationException.cs       |    3 +-
 src/Proton/Engine/IEngineConfiguration.cs          |   14 +
 .../Engine/Implementation/ProtonConstants.cs       |    6 +
 .../Implementation/ProtonEngineConfiguration.cs    |    2 +
 .../Implementation/ProtonFrameDecodingHandler.cs   |   10 +-
 .../Implementation/ProtonIncomingDelivery.cs       |   29 +-
 src/Proton/Engine/Implementation/ProtonReceiver.cs |   33 +-
 .../Implementation/ProtonSessionIncomingWindow.cs  |   52 +-
 .../Implementation/ProtonSessionOutgoingWindow.cs  |   29 +-
 .../Implementation/ProtonTransactionManager.cs     |    2 +-
 .../Sasl/ProtonDefaultSaslClientAuthenticator.cs   |    3 +-
 .../Sasl/ProtonDefaultSaslServerAuthenticator .cs  |    2 +-
 .../Engine/Sasl/Client/AnonymousMechanism.cs       |    2 +-
 src/Proton/Engine/Sasl/Client/CramMD5Mechanism.cs  |    2 +-
 src/Proton/Engine/Sasl/Client/ExternalMechanism.cs |    2 +-
 src/Proton/Engine/Sasl/Client/IMechanism.cs        |    2 +-
 src/Proton/Engine/Sasl/Client/PlainMechanism.cs    |    2 +-
 .../Engine/Sasl/Client/SaslMechanismSelector.cs    |    2 +-
 .../Engine/Sasl/Client/ScramSHA1Mechanism.cs       |    2 +-
 .../Engine/Sasl/Client/ScramSHA256Mechanism.cs     |    2 +-
 .../Engine/Sasl/Client/ScramSHA512Mechanism.cs     |    2 +-
 src/Proton/Engine/Sasl/Client/XOauth2Mechanism.cs  |    2 +-
 src/Proton/Types/Security/SaslChallenge.cs         |    2 +-
 src/Proton/Types/Security/SaslInit.cs              |    2 +-
 src/Proton/Types/Security/SaslMechanisms.cs        |    2 +-
 src/Proton/Types/Security/SaslOutcome.cs           |    2 +-
 src/Proton/Types/Security/SaslResponse.cs          |    2 +-
 src/Proton/Types/Symbol.cs                         |  239 +-
 src/Proton/Types/UnknownDescribedType.cs           |    2 +-
 src/Proton/Utilities/StringUtils.cs                |   48 +
 src/Proton/Utilities/UnsettledDictionary.cs        | 1722 +++++++++++++
 .../Client/Implementation/ClientBaseTestFixture.cs |   56 +-
 .../Client/Implementation/ClientReceiverTest.cs    |  220 ++
 .../Implementation/ClientStreamReceiverTest.cs     |  276 +++
 .../Implementation/ClilentTransactionsTest.cs      |    3 +-
 .../Codec/Decoders/ProtonDecoderTest.cs            |  213 +-
 .../Codec/Decoders/ProtonStreamDecoderTest.cs      |  219 +-
 .../Codec/Messaging/AcceptedTypeCodecTest.cs       |  171 ++
 .../Codec/Messaging/AmqpSequenceTypeCodecTest.cs   |   79 +
 .../Codec/Messaging/AmqpValueTypeCodecTest.cs      |  295 +++
 .../ApplicationPropertiesTypeCodecTest.cs          |  674 ++++-
 .../Codec/Messaging/DataTypeCodecTest.cs           |   25 +
 .../Codec/Messaging/DeleteOnCloseTypeCodecTest.cs  |  170 ++
 .../DeleteOnNoLinksOrMessagesCodecTest.cs          |  170 ++
 .../Messaging/DeleteOnNoLinksTypeCodecTest.cs      |  169 ++
 .../Messaging/DeleteOnNoMessagesTypeCodecTest.cs   |  170 ++
 .../Messaging/DeliveryAnnotationsTypeCodecTest.cs  |  256 +-
 .../Codec/Messaging/FooterTypeCodecTest.cs         |  271 +-
 .../Codec/Messaging/HeaderTypeCodecTest.cs         |  233 ++
 .../Messaging/MessageAnnotationsTypeCodecTest.cs   |  268 +-
 .../Codec/Messaging/ModifiedTypeCodecTest.cs       |    8 +-
 .../Codec/Messaging/RejectedTypeCodecTest.cs       |    4 +-
 .../Codec/Primitives/ArrayTypeCodecTest.cs         |  320 +++
 .../Codec/Primitives/BinaryTypeCodecTest.cs        |   74 +
 .../Codec/Primitives/BooleanTypeCodecTest.cs       |  441 +++-
 .../Codec/Primitives/ByteTypeCodecTest.cs          |  189 ++
 .../Codec/Primitives/CharacterTypeCodecTests.cs    |  103 +
 .../Codec/Primitives/DoubleTypeCodecTest.cs        |   39 +
 .../Codec/Primitives/FloatTypeCodecTest.cs         |   39 +
 .../Codec/Primitives/IntegerTypeCodecTest.cs       |   81 +-
 .../Codec/Primitives/ListTypeCodecTest.cs          |  466 +++-
 .../Codec/Primitives/LongTypeCodecTest.cs          |   81 +-
 .../Codec/Primitives/MapTypeCodecTest.cs           |  323 ++-
 .../Codec/Primitives/NullTypeCodecTest.cs          |  229 +-
 .../Codec/Primitives/ShortTypeCodecTest.cs         |   39 +
 .../Codec/Primitives/StringTypeCodecTest.cs        |  109 +
 .../Codec/Primitives/SymbolTypeCodecTest.cs        |  109 +
 .../Primitives/UnsignedIntegerTypeCodecTest.cs     |  186 ++
 .../Codec/Primitives/UnsignedLongTypeCodecTest.cs  |  196 +-
 .../Codec/Security/SaslChallengeTypeCodecTest.cs   |  112 +
 .../Codec/Security/SaslInitTypeCodecTest.cs        |   30 +-
 .../Codec/Security/SaslMechanismsTypeCodecTest.cs  |  190 +-
 .../Codec/Security/SaslOutcomeTypeCodecTest.cs     |  112 +
 .../Codec/Security/SaslResponseTypeCodecTest.cs    |  112 +
 .../Codec/UnknownDescribedTypeCodecTest.cs         |  478 ++++
 .../Engine/Implementation/ProtonDecodeErrorTest.cs |   30 +-
 .../Engine/Implementation/ProtonReceiverTest.cs    |  205 +-
 .../Engine/Implementation/ProtonSessionTest.cs     |   85 +
 .../Implementation/Sasl/ProtonSaslHandlerTest.cs   |   28 +-
 .../Implementation/Sasl/ProtonSaslServerTest.cs    |    8 +-
 .../Engine/Sasl/Client/AbstractMechanismTest.cs    |    2 +-
 .../Engine/Sasl/Client/SaslMechanismsTest.cs       |    2 +-
 test/Proton.Tests/Types/Security/SaslInitTest.cs   |    3 +-
 .../Types/Security/SaslMechanismsTest.cs           |    4 +-
 test/Proton.Tests/Types/SymbolTest.cs              |  166 ++
 .../Utilities/UnsettledDictionaryTest.cs           | 2592 ++++++++++++++++++++
 174 files changed, 15507 insertions(+), 4097 deletions(-)

diff --git a/.gitignore b/.gitignore
index 20e91b6..14cec8b 100644
--- a/.gitignore
+++ b/.gitignore
@@ -53,6 +53,7 @@ bld/
 .vscode/
 # Uncomment if you have tasks that create the project's static files in wwwroot
 #wwwroot/
+*.lscache
 
 # Visual Studio 2017 auto generated files
 Generated\ Files/
diff --git a/src/Proton.Client/Client/DecodeOptions.cs b/src/Proton.Client/Client/DecodeOptions.cs
new file mode 100644
index 0000000..185d8ae
--- /dev/null
+++ b/src/Proton.Client/Client/DecodeOptions.cs
@@ -0,0 +1,80 @@
+/*
+ * 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.
+ */
+
+using System;
+using System.Collections.Generic;
+using Apache.Qpid.Proton.Codec.Decoders;
+
+namespace Apache.Qpid.Proton.Client
+{
+   public class DecodeOptions : ICloneable
+   {
+      /// <summary>
+      /// Creates a default Decode options instance.
+      /// </summary>
+      public DecodeOptions() : base()
+      {
+      }
+
+      /// <summary>
+      /// Create a new Decode options instance whose settings are copied from the instance provided.
+      /// </summary>
+      /// <param name="other">The decode options instance to copy</param>
+      public DecodeOptions(DecodeOptions other) : base()
+      {
+         other?.CopyInto(this);
+      }
+
+      /// <summary>
+      /// Clone this options instance, changes to the cloned options are not reflected
+      /// in this options instance.
+      /// </summary>
+      /// <returns>A deep copy of this options instance.</returns>
+      public object Clone()
+      {
+         return CopyInto(new DecodeOptions());
+      }
+
+      internal DecodeOptions CopyInto(DecodeOptions other)
+      {
+         other.MaxZeroWidthArrayElements = MaxZeroWidthArrayElements;
+         other.DepthLimit = DepthLimit;
+
+         return other;
+      }
+
+      /// <summary>
+      /// The configured maximum number of elements that can be decoded from an array
+      /// encoded with the zero width AMQP types (Null, UInt0. ULong0, List0, Boolean_False
+      /// and Boolean_True). These are uncommon encodings and can lead to small encodings
+      /// with large memory costs at decode which makes them discouraged for normal use.
+      /// It is recommended that for implementations that implement this limit configuration
+      /// the default be zero meaning zero width array encodings are disabled and will always
+      /// throw a NotImplementedException.
+      /// </summary>
+      public uint MaxZeroWidthArrayElements { get; set; } = ProtonDecoderState.DefaultMaxZeroWidthArrayElements;
+
+      /// <summary>
+      /// Access the configured maximum depth that nested types such as Lists, Maps and Arrays
+      /// can have before a DecodeException is thrown to allow the decoder to error
+      /// in cases where the depth of encoding exceeds what the environment is thought to be
+      /// able to support.
+      /// </summary>
+      public uint DepthLimit { get; set; } = ProtonDecoderState.DefaultMaxDecodeDepth;
+
+   }
+}
\ No newline at end of file
diff --git a/src/Proton.Client/Client/IDelivery.cs b/src/Proton.Client/Client/IDelivery.cs
index c0d8d11..b272954 100644
--- a/src/Proton.Client/Client/IDelivery.cs
+++ b/src/Proton.Client/Client/IDelivery.cs
@@ -53,6 +53,30 @@ namespace Apache.Qpid.Proton.Client
       /// <returns>the decoded message from the delivery payload</returns>
       IMessage<object> Message();
 
+      /// <summary>
+      /// Decodes the payload of the delivery and returns a new message.
+      ///
+      /// Calling this message claims the payload of the delivery for the returned Message
+      /// and excludes use of the RawInputStream method of the delivery object. Calling the
+      /// RawInputStream method after calling this method throws ClientIllegalStateException.
+      ///
+      /// The default implementation simply calls the normal message API without any options
+      /// to preserve backwards compatibility.
+      /// </summary>
+      /// <remarks>
+      /// If the incoming message carried any delivery annotations they can be accessed
+      /// via the Annotations method.  Re-sending the returned message will not also
+      /// send the incoming delivery annotations, the sender must include them in the
+      /// sender's send call if they are to be forwarded onto the next recipient.
+      /// </remarks>
+      /// <typeparam name="T">Body type of the message</typeparam>
+      /// <param name="options">The decode options to use when decoding the message</param>
+      /// <returns>the decoded message from the delivery payload</returns>
+      IMessage<object> Message(DecodeOptions options)
+      {
+         return Message();
+      }
+
       /// <summary>
       /// Create and return an read-only Stream that reads the raw payload bytes of the
       /// given delivery. Calling this method claims the payload of the delivery for the
diff --git a/src/Proton.Client/Client/IStreamDelivery.cs b/src/Proton.Client/Client/IStreamDelivery.cs
index 7261de3..3bb5309 100644
--- a/src/Proton.Client/Client/IStreamDelivery.cs
+++ b/src/Proton.Client/Client/IStreamDelivery.cs
@@ -54,6 +54,28 @@ namespace Apache.Qpid.Proton.Client
       /// <returns>the decoded message from the delivery payload</returns>
       IStreamReceiverMessage Message();
 
+      /// <summary>
+      /// Returns a stream receiver message type that will perform a decode of message
+      /// payload as portions of the streamed message arrive. The message API is inherently
+      /// a blocking API as the decoder will need to wait in some cases to decode a full
+      /// section the incoming message when it is requested.
+      ///
+      /// The default implementation of this method simply calls the original bare message
+      /// to preserve backwards compatibility.
+      /// </summary>
+      /// <remarks>
+      /// If the incoming message carried any delivery annotations they can be accessed
+      /// via the Annotations method.  Re-sending the returned message will not also
+      /// send the incoming delivery annotations, the sender must include them in the
+      /// sender's send call if they are to be forwarded onto the next recipient.
+      /// </remarks>
+      /// <typeparam name="T">Body type of the message</typeparam>
+      /// <returns>the decoded message from the delivery payload</returns>
+      IStreamReceiverMessage Message(StreamDecodeOptions options)
+      {
+         return Message();
+      }
+
       /// <summary>
       /// Decodes the delivery payload and returns a dictionary containing a copy of any
       /// associated delivery annotations that were transmitted with the message payload.
diff --git a/src/Proton.Client/Client/Implementation/ClientConnection.cs b/src/Proton.Client/Client/Implementation/ClientConnection.cs
index 751286e..a62727f 100644
--- a/src/Proton.Client/Client/Implementation/ClientConnection.cs
+++ b/src/Proton.Client/Client/Implementation/ClientConnection.cs
@@ -1002,8 +1002,7 @@ namespace Apache.Qpid.Proton.Client.Implementation
 
          if (options.SaslOptions.SaslEnabled)
          {
-            SaslMechanismSelector mechSelector = new(
-               ClientConversionSupport.ToSymbolSet(options.SaslOptions.AllowedMechanisms));
+            SaslMechanismSelector mechSelector = new(options.SaslOptions.AllowedMechanisms);
 
             engine.SaslDriver.Client().Authenticator =
                new SaslAuthenticator(mechSelector, new ClientSaslCredentialsProvider(this));
diff --git a/src/Proton.Client/Client/Implementation/ClientConversionSupport.cs b/src/Proton.Client/Client/Implementation/ClientConversionSupport.cs
index 31b7c21..f047825 100644
--- a/src/Proton.Client/Client/Implementation/ClientConversionSupport.cs
+++ b/src/Proton.Client/Client/Implementation/ClientConversionSupport.cs
@@ -44,6 +44,33 @@ namespace Apache.Qpid.Proton.Client.Implementation
          return result;
       }
 
+      /// <summary>
+      /// Convert the given string array into an array of Symbol instances
+      /// </summary>
+      /// <param name="stringArray"></param>
+      /// <returns></returns>
+      public static Symbol[] ToSaslSymbolArray(string[] stringArray)
+      {
+         Symbol[] result = null;
+
+         if (stringArray != null)
+         {
+            result = new Symbol[stringArray.Length];
+            for (int i = 0; i < stringArray.Length; ++i)
+            {
+               result[i] = Symbol.SaslLookup(stringArray[i]);
+            }
+         }
+
+         return result;
+      }
+
+      /// <summary>
+      /// Convert the given string array into an array of Symbol instances using the
+      /// SASL Symbol cache as the source of symbol values.
+      /// </summary>
+      /// <param name="stringArray"></param>
+      /// <returns></returns>
       public static Symbol[] ToSymbolArray(IEnumerable<DeliveryStateType> stateTypeArray)
       {
          Symbol[] result = null;
@@ -96,6 +123,26 @@ namespace Apache.Qpid.Proton.Client.Implementation
          return result;
       }
 
+      public static Dictionary<Symbol, object> ToSaslSymbolKeyedMap<V>(IEnumerable<KeyValuePair<string, V>> stringsMap)
+      {
+         Dictionary<Symbol, object> result;
+
+         if (stringsMap != null)
+         {
+            result = new Dictionary<Symbol, object>();
+            foreach (KeyValuePair<string, V> entry in stringsMap)
+            {
+               result.Add(Symbol.SaslLookup(entry.Key), entry.Value);
+            }
+         }
+         else
+         {
+            result = null;
+         }
+
+         return result;
+      }
+
       public static Dictionary<string, object> ToStringKeyedMap<V>(IEnumerable<KeyValuePair<Symbol, V>> symbolMap)
       {
          Dictionary<string, object> result;
@@ -141,6 +188,31 @@ namespace Apache.Qpid.Proton.Client.Implementation
          return result;
       }
 
+      /// <summary>
+      /// Converts an enumeration of string values into a set of Symbol values.
+      /// </summary>
+      /// <param name="strings">an enumeration of string values</param>
+      /// <returns>a set of Symbol value that match the input strings</returns>
+      public static ISet<Symbol> ToSaslSymbolSet(in IEnumerable<string> strings)
+      {
+         ISet<Symbol> result;
+
+         if (strings != null)
+         {
+            result = new HashSet<Symbol>();
+            foreach (string value in strings)
+            {
+               result.Add(Symbol.SaslLookup(value));
+            }
+         }
+         else
+         {
+            result = null;
+         }
+
+         return result;
+      }
+
       /// <summary>
       /// Converts an enumeration of Symbol values into a set of string values.
       /// </summary>
diff --git a/src/Proton.Client/Client/Implementation/ClientDelivery.cs b/src/Proton.Client/Client/Implementation/ClientDelivery.cs
index 45a4bb4..b16efeb 100644
--- a/src/Proton.Client/Client/Implementation/ClientDelivery.cs
+++ b/src/Proton.Client/Client/Implementation/ClientDelivery.cs
@@ -104,6 +104,22 @@ namespace Apache.Qpid.Proton.Client.Implementation
          return message;
       }
 
+      public IMessage<object> Message(DecodeOptions options)
+      {
+         if (rawInputStream != null)
+         {
+            throw new ClientIllegalStateException("Cannot access Delivery Annotations API after requesting an InputStream");
+         }
+
+         IMessage<object> message = cachedMessage;
+         if (message == null && payload.IsReadable)
+         {
+            message = cachedMessage = ClientMessageSupport.DecodeMessage(payload, SetDeliveryAnnotations, options);
+         }
+
+         return message;
+      }
+
       public IDelivery Disposition(IDeliveryState state, bool settled)
       {
          return receiver.DispositionAsync(this, state?.AsProtonType(), settled).ConfigureAwait(false).GetAwaiter().GetResult();
diff --git a/src/Proton.Client/Client/Implementation/ClientMessageSupport.cs b/src/Proton.Client/Client/Implementation/ClientMessageSupport.cs
index 9c35139..c3e0423 100644
--- a/src/Proton.Client/Client/Implementation/ClientMessageSupport.cs
+++ b/src/Proton.Client/Client/Implementation/ClientMessageSupport.cs
@@ -119,35 +119,42 @@ namespace Apache.Qpid.Proton.Client.Implementation
          ApplicationProperties applicationProperties = message.ApplicationProperties;
          Footer footer = message.Footer;
 
-         if (header != null)
+         try
          {
-            encoder.WriteObject(buffer, encoderState, header);
-         }
-         if (deliveryAnnotations != null)
-         {
-            encoder.WriteObject(buffer, encoderState, new DeliveryAnnotations(ClientConversionSupport.ToSymbolKeyedMap(deliveryAnnotations)));
-         }
-         if (messageAnnotations != null)
-         {
-            encoder.WriteObject(buffer, encoderState, messageAnnotations);
-         }
-         if (properties != null)
-         {
-            encoder.WriteObject(buffer, encoderState, properties);
-         }
-         if (applicationProperties != null)
-         {
-            encoder.WriteObject(buffer, encoderState, applicationProperties);
-         }
+            if (header != null)
+            {
+               encoder.WriteObject(buffer, encoderState, header);
+            }
+            if (deliveryAnnotations != null)
+            {
+               encoder.WriteObject(buffer, encoderState, new DeliveryAnnotations(ClientConversionSupport.ToSymbolKeyedMap(deliveryAnnotations)));
+            }
+            if (messageAnnotations != null)
+            {
+               encoder.WriteObject(buffer, encoderState, messageAnnotations);
+            }
+            if (properties != null)
+            {
+               encoder.WriteObject(buffer, encoderState, properties);
+            }
+            if (applicationProperties != null)
+            {
+               encoder.WriteObject(buffer, encoderState, applicationProperties);
+            }
 
-         message.ForEachBodySection(section => encoder.WriteObject(buffer, encoderState, section));
+            message.ForEachBodySection(section => encoder.WriteObject(buffer, encoderState, section));
 
-         if (footer != null)
+            if (footer != null)
+            {
+               encoder.WriteObject(buffer, encoderState, footer);
+            }
+
+            return buffer;
+         }
+         finally
          {
-            encoder.WriteObject(buffer, encoderState, footer);
+            encoderState.Reset();
          }
-
-         return buffer;
       }
 
       #endregion
@@ -159,15 +166,34 @@ namespace Apache.Qpid.Proton.Client.Implementation
          return DecodeMessage(DEFAULT_DECODER, DEFAULT_DECODER.NewDecoderState(), buffer, daConsumer);
       }
 
+      public static ClientMessage<object> DecodeMessage(IProtonBuffer buffer, Action<DeliveryAnnotations> daConsumer, DecodeOptions options)
+      {
+         IDecoderState state = DEFAULT_DECODER.NewDecoderState();
+
+         state.DepthLimit = options.DepthLimit;
+         state.MaxZeroWidthArrayElements = options.MaxZeroWidthArrayElements;
+
+         return DecodeMessage(DEFAULT_DECODER, state, buffer, daConsumer);
+      }
+
       public static ClientMessage<object> DecodeMessage(IDecoder decoder, IProtonBuffer buffer, Action<DeliveryAnnotations> daConsumer)
       {
          return DecodeMessage(decoder, decoder.NewDecoderState(), buffer, daConsumer);
       }
 
+      public static ClientMessage<object> DecodeMessage(IDecoder decoder, IProtonBuffer buffer, Action<DeliveryAnnotations> daConsumer, DecodeOptions options)
+      {
+         IDecoderState state = decoder.NewDecoderState();
+
+         state.DepthLimit = options.DepthLimit;
+         state.MaxZeroWidthArrayElements = options.MaxZeroWidthArrayElements;
+
+         return DecodeMessage(decoder, state, buffer, daConsumer);
+      }
+
       public static ClientMessage<object> DecodeMessage(IDecoder decoder, IDecoderState decoderState,
                                                         IProtonBuffer buffer, Action<DeliveryAnnotations> daConsumer)
       {
-
          ClientMessage<object> message = new();
 
          ISection section;
@@ -182,6 +208,10 @@ namespace Apache.Qpid.Proton.Client.Implementation
             {
                throw ClientExceptionSupport.CreateNonFatalOrPassthrough(e);
             }
+            finally
+            {
+               decoderState.Reset();
+            }
 
             switch (section.Type)
             {
diff --git a/src/Proton.Client/Client/Implementation/ClientStreamDelivery.cs b/src/Proton.Client/Client/Implementation/ClientStreamDelivery.cs
index 2b4895b..cc51e4d 100644
--- a/src/Proton.Client/Client/Implementation/ClientStreamDelivery.cs
+++ b/src/Proton.Client/Client/Implementation/ClientStreamDelivery.cs
@@ -112,6 +112,11 @@ namespace Apache.Qpid.Proton.Client.Implementation
       }
 
       public IStreamReceiverMessage Message()
+      {
+         return Message(new StreamDecodeOptions());
+      }
+
+      public IStreamReceiverMessage Message(StreamDecodeOptions options)
       {
          if (rawInputStream != null && message == null)
          {
@@ -120,7 +125,7 @@ namespace Apache.Qpid.Proton.Client.Implementation
 
          if (message == null)
          {
-            message = new ClientStreamReceiverMessage(receiver, this, rawInputStream = new RawDeliveryInputStream(this));
+            message = new ClientStreamReceiverMessage(receiver, this, options, rawInputStream = new RawDeliveryInputStream(this));
          }
 
          return message;
diff --git a/src/Proton.Client/Client/Implementation/ClientStreamReceiverMessage.cs b/src/Proton.Client/Client/Implementation/ClientStreamReceiverMessage.cs
index bffddec..19562fc 100644
--- a/src/Proton.Client/Client/Implementation/ClientStreamReceiverMessage.cs
+++ b/src/Proton.Client/Client/Implementation/ClientStreamReceiverMessage.cs
@@ -51,7 +51,7 @@ namespace Apache.Qpid.Proton.Client.Implementation
       private StreamState currentState = StreamState.IDLE;
       private MessageBodyInputStream bodyStream;
 
-      internal ClientStreamReceiverMessage(ClientStreamReceiver receiver, ClientStreamDelivery delivery, Stream deliveryStream)
+      internal ClientStreamReceiverMessage(ClientStreamReceiver receiver, ClientStreamDelivery delivery, StreamDecodeOptions decodeOptions, Stream deliveryStream)
       {
          this.receiver = receiver;
          this.delivery = delivery;
@@ -60,6 +60,16 @@ namespace Apache.Qpid.Proton.Client.Implementation
 
          this.protonDecoder = ProtonStreamDecoderFactory.Create();
          this.decoderState = protonDecoder.NewDecoderState();
+
+         // Configure the decoder state here which will then apply to all decode operations
+         decoderState.DepthLimit = decodeOptions.DepthLimit;
+         decoderState.MaxZeroWidthArrayElements = decodeOptions.MaxZeroWidthArrayElements;
+         decoderState.MaxStringSize = decodeOptions.MaxStringSize;
+         decoderState.MaxArraySize = decodeOptions.MaxArraySize;
+         decoderState.MaxListSize = decodeOptions.MaxListSize;
+         decoderState.MaxMapSize = decodeOptions.MaxMapSize;
+         decoderState.MaxBinarySize = decodeOptions.MaxBinarySize;
+         decoderState.MaxSymbolSize = decodeOptions.MaxSymbolSize;
       }
 
       public IStreamDelivery Delivery => delivery;
diff --git a/src/Proton.Client/Client/StreamDecodeOptions.cs b/src/Proton.Client/Client/StreamDecodeOptions.cs
new file mode 100644
index 0000000..87cff97
--- /dev/null
+++ b/src/Proton.Client/Client/StreamDecodeOptions.cs
@@ -0,0 +1,117 @@
+/*
+ * 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.
+ */
+
+using System;
+using System.Collections.Generic;
+using Apache.Qpid.Proton.Codec;
+using Apache.Qpid.Proton.Codec.Decoders;
+
+namespace Apache.Qpid.Proton.Client
+{
+   public class StreamDecodeOptions : ICloneable
+   {
+      /// <summary>
+      /// A default used to limit reads from a stream for types that encode a count or
+      /// size of encoded elements.
+      /// </summary>
+      protected static readonly uint DefaultMaxAllocationLimit = int.MaxValue - 8;
+
+      /// <summary>
+      /// Creates a default Decode options instance.
+      /// </summary>
+      public StreamDecodeOptions() : base()
+      {
+      }
+
+      /// <summary>
+      /// Create a new Decode options instance whose settings are copied from the instance provided.
+      /// </summary>
+      /// <param name="other">The decode options instance to copy</param>
+      public StreamDecodeOptions(StreamDecodeOptions other) : base()
+      {
+         other?.CopyInto(this);
+      }
+
+      /// <summary>
+      /// Clone this options instance, changes to the cloned options are not reflected
+      /// in this options instance.
+      /// </summary>
+      /// <returns>A deep copy of this options instance.</returns>
+      public object Clone()
+      {
+         return CopyInto(new StreamDecodeOptions());
+      }
+
+      internal StreamDecodeOptions CopyInto(StreamDecodeOptions other)
+      {
+         other.MaxZeroWidthArrayElements = MaxZeroWidthArrayElements;
+         other.DepthLimit = DepthLimit;
+
+         return other;
+      }
+
+      /// <summary>
+      /// The configured maximum number of elements that can be decoded from an array
+      /// encoded with the zero width AMQP types (Null, UInt0. ULong0, List0, Boolean_False
+      /// and Boolean_True). These are uncommon encodings and can lead to small encodings
+      /// with large memory costs at decode which makes them discouraged for normal use.
+      /// It is recommended that for implementations that implement this limit configuration
+      /// the default be zero meaning zero width array encodings are disabled and will always
+      /// throw a NotImplementedException.
+      /// </summary>
+      public uint MaxZeroWidthArrayElements { get; set; } = ProtonStreamDecoderState.DefaultMaxZeroWidthArrayElements;
+
+      /// <summary>
+      /// Access the configured maximum depth that nested types such as Lists, Maps and Arrays
+      /// can have before a DecodeException is thrown to allow the decoder to error
+      /// in cases where the depth of encoding exceeds what the environment is thought to be
+      /// able to support.
+      /// </summary>
+      public uint DepthLimit { get; set; } = ProtonStreamDecoderState.DefaultMaxDecodeDepth;
+
+      /// <summary>
+      /// The configured maximum size of an encoded string that can be decoded before an error is thrown.
+      /// </summary>
+      public uint MaxStringSize { get; set; } = DefaultMaxAllocationLimit;
+
+      /// <summary>
+      /// The configured maximum size of an encoded array that can be decoded before an error is thrown.
+      /// </summary>
+      public uint MaxArraySize { get; set; } = DefaultMaxAllocationLimit;
+
+      /// <summary>
+      /// The configured maximum size of an encoded Binary that can be decoded before an error is thrown.
+      /// </summary>
+      public uint MaxBinarySize { get; set; } = DefaultMaxAllocationLimit;
+
+      /// <summary>
+      /// The configured maximum size of an encoded Symbol that can be decoded before an error is thrown.
+      /// </summary>
+      public uint MaxSymbolSize { get; set; } = DefaultMaxAllocationLimit;
+
+      /// <summary>
+      /// The configured maximum encoded list size that can be decoded before an error is thrown.
+      /// </summary>
+      public uint MaxListSize { get; set; } = DefaultMaxAllocationLimit;
+
+      /// <summary>
+      /// The configured maximum encoded map size that can be decoded before an error is thrown.
+      /// </summary>
+      public uint MaxMapSize { get; set; } = DefaultMaxAllocationLimit;
+
+   }
+}
\ No newline at end of file
diff --git a/src/Proton/Codec/Decoders/AbstractDescribedListTypeDecoder.cs b/src/Proton/Codec/Decoders/AbstractDescribedListTypeDecoder.cs
new file mode 100644
index 0000000..0666fc8
--- /dev/null
+++ b/src/Proton/Codec/Decoders/AbstractDescribedListTypeDecoder.cs
@@ -0,0 +1,232 @@
+/*
+ * 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.
+ */
+
+using System;
+using System.IO;
+using Apache.Qpid.Proton.Buffer;
+using Apache.Qpid.Proton.Codec.Decoders.Primitives;
+using Apache.Qpid.Proton.Types;
+
+namespace Apache.Qpid.Proton.Codec.Decoders
+{
+   public abstract class AbstractDescribedListTypeDecoder : AbstractDescribedTypeDecoder
+   {
+      public sealed override object ReadValue(IProtonBuffer buffer, IDecoderState state)
+      {
+         state.IncreaseDepth();
+
+         try
+         {
+            ITypeDecoder decoder = state.Decoder.ReadNextTypeDecoder(buffer, state);
+
+            return ReadSingle(buffer, state, CheckIsExpectedTypeAndCast<IListTypeDecoder>(decoder));
+         }
+         finally
+         {
+            state.DecreaseDepth();
+         }
+      }
+
+      public sealed override object ReadValue(Stream stream, IStreamDecoderState state)
+      {
+         state.IncreaseDepth();
+
+         try
+         {
+            IStreamTypeDecoder decoder = state.Decoder.ReadNextTypeDecoder(stream, state);
+
+            return ReadSingle(stream, state, CheckIsExpectedTypeAndCast<IListTypeDecoder>(decoder));
+         }
+         finally
+         {
+            state.DecreaseDepth();
+         }
+      }
+
+      public sealed override void SkipValue(IProtonBuffer buffer, IDecoderState state)
+      {
+         CheckIsExpectedType<IListTypeDecoder>(state.Decoder.ReadNextTypeDecoder(buffer, state)).SkipValue(buffer, state);
+      }
+
+      public sealed override void SkipValue(Stream stream, IStreamDecoderState state)
+      {
+         CheckIsExpectedType<IListTypeDecoder>(state.Decoder.ReadNextTypeDecoder(stream, state)).SkipValue(stream, state);
+      }
+
+      public sealed override Array ReadArrayElements(IProtonBuffer buffer, IDecoderState state, int count)
+      {
+         ITypeDecoder decoder = state.Decoder.ReadNextTypeDecoder(buffer, state);
+         IListTypeDecoder listDecoder = CheckIsExpectedTypeAndCast<IListTypeDecoder>(decoder);
+
+         if (listDecoder.IsZeroWidth)
+         {
+            if (count > state.MaxZeroWidthArrayElements)
+            {
+               throw new DecodeException(
+                  "Array element count " + count + " is specified to be greater than limit for zero sized " +
+                  "encoded array types (" + state.MaxZeroWidthArrayElements + ")");
+            }
+         }
+         else if (count > buffer.ReadableBytes)
+         {
+            throw new DecodeException(
+                "Array encoded element count " + count + " is specified to be greater than the " +
+                "amount of data available " + buffer.ReadableBytes);
+         }
+
+         Array result = Array.CreateInstance(DecodesType, count);
+         for (int i = 0; i < count; ++i)
+         {
+            result.SetValue(ReadSingle(buffer, state, listDecoder), i);
+         }
+
+         return result;
+      }
+
+      public sealed override Array ReadArrayElements(Stream stream, IStreamDecoderState state, int count)
+      {
+         IStreamTypeDecoder decoder = state.Decoder.ReadNextTypeDecoder(stream, state);
+         IListTypeDecoder listDecoder = CheckIsExpectedTypeAndCast<IListTypeDecoder>(decoder);
+
+         if (listDecoder.IsZeroWidth && count > state.MaxZeroWidthArrayElements)
+         {
+            throw new DecodeException(
+                "Array element count " + count + " is specified to be greater than limit for zero sized " +
+                "encoded array types (" + state.MaxZeroWidthArrayElements + ")");
+         }
+         else if (count > state.MaxArraySize)
+         {
+            throw new DecodeException(
+                "Array encoded element count " + count + " is specified to be greater than the " +
+                "configured max array size " + state.MaxArraySize);
+         }
+
+         Array result = Array.CreateInstance(DecodesType, count);
+         for (int i = 0; i < count; ++i)
+         {
+            result.SetValue(ReadSingle(stream, state, listDecoder), i);
+         }
+
+         return result;
+      }
+
+      /// <summary>
+      /// Gets the minimum number of elements this described list is allowed to carry.
+      /// </summary>
+      protected abstract int MinListElements { get; }
+
+      /// <summary>
+      /// Gets the maximum number of elements this described list is allowed to carry.
+      /// </summary>
+      protected abstract int MaxListElements { get; }
+
+      protected virtual object ReadSingle(IProtonBuffer buffer, IDecoderState state, IListTypeDecoder listDecoder)
+      {
+         int size = listDecoder.ReadSize(buffer, state);
+         long expectedEndPos = buffer.ReadOffset + size;
+
+         if (size > buffer.ReadableBytes || size < 0)
+         {
+            throw new DecodeException(String.Format(
+               "List encoded size is specified to be greater than the amount " +
+               "of data available s:({0}) r:({1})", (uint) size, buffer.ReadableBytes));
+         }
+
+         int count = listDecoder.ReadCount(buffer, state);
+
+         if (count > size || count < 0)
+         {
+            throw new DecodeException(String.Format(
+               "List encoded count is specified to be greater than the reported encoded size " +
+               "s:({0}) c:({1})", size, (uint) count));
+         }
+
+         if (count < MinListElements)
+         {
+            throw new DecodeException(String.Format(
+                "Not enough list elements indicated in the encoded count, expected {0} but got {1}",
+                MinListElements, count));
+         }
+
+         if (count > MaxListElements)
+         {
+            throw new DecodeException(String.Format(
+                "To many elements indicated in the encoded count, maximum {0} but got {1}",
+                MaxListElements, count));
+         }
+
+         object type = ReadType(count, buffer, state.Decoder, state);
+
+         if (buffer.ReadOffset != expectedEndPos)
+         {
+            throw new DecodeException("List decoding did not read the expected amount of bytes: " + size);
+         }
+
+         return type;
+      }
+
+      protected virtual object ReadSingle(Stream stream, IStreamDecoderState state, IListTypeDecoder listDecoder)
+      {
+         int size = listDecoder.ReadSize(stream, state);
+
+         if (size > state.MaxListSize || size < 0)
+         {
+            throw new DecodeException(String.Format(
+                "List encoded size is specified to be greater than the configured maximum " +
+                "List size allowed s:({0}) c:({1})", (uint) size, state.MaxListSize));
+         }
+
+         int count = listDecoder.ReadCount(stream, state);
+
+         if (count > size || count < 0)
+         {
+            throw new DecodeException(String.Format(
+                "List encoded count is specified to be greater than the reported encoded size " +
+                "s:(%d) c:(%d)", size, (uint) count));
+         }
+
+         if (count < MinListElements)
+         {
+            throw new DecodeException(String.Format(
+                "Not enough list elements indicated in the encoded count, expected {0} but got {1}",
+                MinListElements, count));
+         }
+
+         if (count > MaxListElements)
+         {
+            throw new DecodeException(String.Format(
+                "To many elements indicated in the encoded count, maximum {0} but got {1}",
+                MaxListElements, count));
+         }
+
+         return ReadType(count, stream, state.Decoder, state);
+      }
+
+      /// <summary>
+      /// Reads the actual type from the byte stream with the given number of encoded list elements populated
+      /// </summary>
+      /// <returns>The decoded described type</returns>
+      protected abstract object ReadType(int count, IProtonBuffer buffer, IDecoder decoder, IDecoderState state);
+
+      /// <summary>
+      /// Reads the actual type from the byte stream with the given number of encoded list elements populated
+      /// </summary>
+      /// <returns>The decoded described type</returns>
+      protected abstract object ReadType(int count, Stream stream, IStreamDecoder decoder, IStreamDecoderState state);
+
+   }
+}
diff --git a/src/Proton/Codec/Decoders/AbstractDescribedMapTypeDecoder.cs b/src/Proton/Codec/Decoders/AbstractDescribedMapTypeDecoder.cs
new file mode 100644
index 0000000..0a58a72
--- /dev/null
+++ b/src/Proton/Codec/Decoders/AbstractDescribedMapTypeDecoder.cs
@@ -0,0 +1,248 @@
+/*
+ * 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.
+ */
+
+using System;
+using System.Collections.Generic;
+using System.IO;
+using Apache.Qpid.Proton.Buffer;
+using Apache.Qpid.Proton.Codec.Decoders.Primitives;
+using Apache.Qpid.Proton.Types;
+
+namespace Apache.Qpid.Proton.Codec.Decoders
+{
+   public abstract class AbstractDescribedMapTypeDecoder<K> : AbstractDescribedTypeDecoder
+   {
+      private static readonly int MAX_MAP_PREALLOCATION = 256;
+
+      public sealed override object ReadValue(IProtonBuffer buffer, IDecoderState state)
+      {
+         state.IncreaseDepth();
+
+         try
+         {
+            IMapTypeDecoder mapTypeDecoder =
+               CheckIsExpectedTypeAndCast<IMapTypeDecoder>(state.Decoder.ReadNextTypeDecoder(buffer, state));
+
+            return CreateDescribed(ReadMap(buffer, state, mapTypeDecoder));
+         }
+         finally
+         {
+            state.DecreaseDepth();
+         }
+      }
+
+      public sealed override object ReadValue(Stream stream, IStreamDecoderState state)
+      {
+         state.IncreaseDepth();
+
+         try
+         {
+            IMapTypeDecoder mapTypeDecoder =
+               CheckIsExpectedTypeAndCast<IMapTypeDecoder>(state.Decoder.ReadNextTypeDecoder(stream, state));
+
+            return CreateDescribed(ReadMap(stream, state, mapTypeDecoder));
+         }
+         finally
+         {
+            state.DecreaseDepth();
+         }
+      }
+
+      public sealed override void SkipValue(IProtonBuffer buffer, IDecoderState state)
+      {
+         CheckIsExpectedType<IMapTypeDecoder>(state.Decoder.ReadNextTypeDecoder(buffer, state)).SkipValue(buffer, state);
+      }
+
+      public sealed override void SkipValue(Stream stream, IStreamDecoderState state)
+      {
+         CheckIsExpectedType<IMapTypeDecoder>(state.Decoder.ReadNextTypeDecoder(stream, state)).SkipValue(stream, state);
+      }
+
+      public sealed override Array ReadArrayElements(IProtonBuffer buffer, IDecoderState state, int count)
+      {
+         ITypeDecoder decoder = state.Decoder.ReadNextTypeDecoder(buffer, state);
+
+         ValidateArrayConstraints(count, buffer, state, decoder);
+
+         Array result = Array.CreateInstance(DecodesType, count);
+         for (int i = 0; i < count; ++i)
+         {
+            result.SetValue(CreateDescribed(ReadMap(buffer, state, CheckIsExpectedTypeAndCast<IMapTypeDecoder>(decoder))), i);
+         }
+
+         return result;
+      }
+
+      public sealed override Array ReadArrayElements(Stream stream, IStreamDecoderState state, int count)
+      {
+         IStreamTypeDecoder decoder = state.Decoder.ReadNextTypeDecoder(stream, state);
+
+         ValidateArrayConstraints(count, stream, state, decoder);
+
+         Array result = Array.CreateInstance(DecodesType, count);
+         for (int i = 0; i < count; ++i)
+         {
+            result.SetValue(CreateDescribed(ReadMap(stream, state, CheckIsExpectedTypeAndCast<IMapTypeDecoder>(decoder))), i);
+         }
+
+         return result;
+      }
+
+      protected abstract object CreateDescribed(IDictionary<K, object> map);
+
+      protected abstract K ReadKey(IProtonBuffer buffer, IDecoder decoder, IDecoderState state);
+
+      protected abstract K ReadKey(Stream stream, IStreamDecoder decoder, IStreamDecoderState state);
+
+      private IDictionary<K, Object> ReadMap(IProtonBuffer buffer, IDecoderState state, IMapTypeDecoder mapDecoder)
+      {
+         int size = mapDecoder.ReadSize(buffer, state);
+         long expectedEndPos = buffer.ReadOffset + size;
+         int count = ValidateAndGetCount(size, buffer, state, mapDecoder);
+         IDecoder decoder = state.Decoder;
+         int entries = count / 2;
+
+         // Count include both key and value so we must include that in the loop
+         IDictionary<K, Object> map = new Dictionary<K, object>(Math.Min(MAX_MAP_PREALLOCATION, entries));
+
+         for (int i = 0; i < entries; i++)
+         {
+            map.TryAdd(ReadKey(buffer, decoder, state), decoder.ReadObject(buffer, state));
+         }
+
+         if (buffer.ReadOffset != expectedEndPos)
+         {
+            throw new DecodeException("Map decoding did not read the expected amount of bytes: " + size);
+         }
+
+         return map;
+      }
+
+      private IDictionary<K, Object> ReadMap(Stream stream, IStreamDecoderState state, IMapTypeDecoder mapDecoder)
+      {
+         int size = mapDecoder.ReadSize(stream, state);
+         int count = ValidateAndGetCount(size, stream, state, mapDecoder);
+         IStreamDecoder decoder = state.Decoder;
+         int entries = count / 2;
+
+         // Count include both key and value so we must include that in the loop
+         IDictionary<K, Object> map = new Dictionary<K, object>(Math.Min(MAX_MAP_PREALLOCATION, entries));
+
+         for (int i = 0; i < entries; i++)
+         {
+            map.TryAdd(ReadKey(stream, decoder, state), decoder.ReadObject(stream, state));
+         }
+
+         return map;
+      }
+
+      protected static int ValidateAndGetCount(int size, IProtonBuffer buffer, IDecoderState state, IMapTypeDecoder decoder)
+      {
+         if (size > buffer.ReadableBytes || size < 0)
+         {
+            throw new DecodeException(String.Format(
+               "Map encoded size is specified to be greater than the amount " +
+               "of data available s:({0}) r:({1})", (uint) size, buffer.ReadableBytes));
+         }
+
+         int count = decoder.ReadCount(buffer, state);
+
+         if (count > size || count < 0)
+         {
+            throw new DecodeException(String.Format(
+               "Map encoded count is specified to be greater than the reported encoded size " +
+               "s:({0}) c:({1})", size, (uint) count));
+         }
+
+         if (count % 2 != 0)
+         {
+            throw new DecodeException(String.Format(
+               "Map encoded number of elements {0} is not an even number.", count));
+         }
+
+         return count;
+      }
+
+      protected static int ValidateAndGetCount(int size, Stream stream, IStreamDecoderState state, IMapTypeDecoder decoder)
+      {
+         if (size > state.MaxMapSize || size < 0)
+         {
+            throw new DecodeException(String.Format(
+                "Map encoded size is specified to be greater than the configured maximum " +
+                "map size allowed s:({0}) c:({1})", (uint) size, state.MaxMapSize));
+         }
+
+         int count = decoder.ReadCount(stream, state);
+
+         if (count > size || count < 0)
+         {
+            throw new DecodeException(String.Format(
+                "Map encoded count is specified to be greater than the reported encoded size " +
+                "s:({0}) c:({1})", size, (uint) count));
+         }
+
+         if (count % 2 != 0)
+         {
+            throw new DecodeException(String.Format(
+                "Map encoded number of elements {0} is not an even number.", count));
+         }
+
+         return count;
+      }
+
+      protected static void ValidateArrayConstraints(int count, IProtonBuffer buffer, IDecoderState state, ITypeDecoder decoder)
+      {
+         if (decoder.GetType() == typeof(void))
+         {
+            if (count > state.MaxZeroWidthArrayElements || count < 0)
+            {
+               throw new DecodeException(String.Format(
+                  "Map encoded array count {0} is specified to be greater than limit for zero sized encoded array types (%d)",
+                  (uint) count, state.MaxZeroWidthArrayElements));
+            }
+         }
+         else
+         {
+            if (count > buffer.ReadableBytes || count < 0)
+            {
+               throw new DecodeException(String.Format(
+                  "Map encoded array count {0} is specified to be greater than the amount " +
+                  "of data available ({1})", (uint) count, buffer.ReadableBytes));
+            }
+         }
+      }
+
+      protected static void ValidateArrayConstraints(int count, Stream stream, IStreamDecoderState state, IStreamTypeDecoder decoder)
+      {
+         if (decoder.GetType() == typeof(void))
+         {
+            if (count > state.MaxZeroWidthArrayElements || count < 0)
+            {
+               throw new DecodeException(String.Format(
+                  "Map encoded array count {0} is specified to be greater than limit for zero sized encoded array types (%d)",
+                  (uint) count, state.MaxZeroWidthArrayElements));
+            }
+         }
+         else if (count > state.MaxArraySize || count < 0)
+         {
+            throw new DecodeException(String.Format(
+                  "Array encoded length {0} is specified to be greater than the amount " +
+                  "of the configured max array length ({1})", (uint) count, state.MaxStringSize));
+         }
+      }
+   }
+}
diff --git a/src/Proton/Codec/Decoders/AbstractDescribedTypeDecoder.cs b/src/Proton/Codec/Decoders/AbstractDescribedTypeDecoder.cs
index 903db6b..67b2d4e 100644
--- a/src/Proton/Codec/Decoders/AbstractDescribedTypeDecoder.cs
+++ b/src/Proton/Codec/Decoders/AbstractDescribedTypeDecoder.cs
@@ -75,22 +75,26 @@ namespace Apache.Qpid.Proton.Codec.Decoders
          return (T)actual;
       }
 
-      protected static void CheckIsExpectedType<T>(ITypeDecoder actual)
+      protected static ITypeDecoder CheckIsExpectedType<T>(ITypeDecoder actual)
       {
          if (!typeof(T).IsAssignableFrom(actual.GetType()))
          {
             throw new DecodeException(
                 "Expected " + typeof(T) + "encoding but got decoder for type: " + actual.GetType().Name);
          }
+
+         return actual;
       }
 
-      protected static void CheckIsExpectedType<T>(IStreamTypeDecoder actual)
+      protected static IStreamTypeDecoder CheckIsExpectedType<T>(IStreamTypeDecoder actual)
       {
          if (!typeof(T).IsAssignableFrom(actual.GetType()))
          {
             throw new DecodeException(
                 "Expected " + typeof(T) + "encoding but got decoder for type: " + actual.GetType().Name);
          }
+
+         return actual;
       }
    }
 }
diff --git a/src/Proton/Codec/Decoders/AbstractPrimitiveTypeDecoder.cs b/src/Proton/Codec/Decoders/AbstractPrimitiveTypeDecoder.cs
index 69204f5..92679ac 100644
--- a/src/Proton/Codec/Decoders/AbstractPrimitiveTypeDecoder.cs
+++ b/src/Proton/Codec/Decoders/AbstractPrimitiveTypeDecoder.cs
@@ -23,10 +23,14 @@ namespace Apache.Qpid.Proton.Codec.Decoders
 {
    public abstract class AbstractPrimitiveTypeDecoder : IPrimitiveTypeDecoder
    {
-      public bool IsArrayType => false;
+      public virtual bool IsArrayType => false;
 
-      public Array ReadArrayElements(IProtonBuffer buffer, IDecoderState state, int count)
+      public virtual bool IsZeroWidth => false;
+
+      public virtual Array ReadArrayElements(IProtonBuffer buffer, IDecoderState state, int count)
       {
+         ValidateArrayPreconditions(buffer, state, count);
+
          Array array = Array.CreateInstance(DecodesType, count);
          for (int i = 0; i < count; ++i)
          {
@@ -36,8 +40,10 @@ namespace Apache.Qpid.Proton.Codec.Decoders
          return array;
       }
 
-      public Array ReadArrayElements(Stream stream, IStreamDecoderState state, int count)
+      public virtual Array ReadArrayElements(Stream stream, IStreamDecoderState state, int count)
       {
+         ValidateArrayPreconditions(stream, state, count);
+
          Array array = Array.CreateInstance(DecodesType, count);
          for (int i = 0; i < count; ++i)
          {
@@ -47,6 +53,26 @@ namespace Apache.Qpid.Proton.Codec.Decoders
          return array;
       }
 
+      protected virtual void ValidateArrayPreconditions(IProtonBuffer buffer, IDecoderState state, int count)
+      {
+         if (count > buffer.ReadableBytes || count < 0)
+         {
+            throw new DecodeException(string.Format(
+               "Array count indicated {0} is greater than the amount of data available to decode ({1})",
+               (uint) count, buffer.ReadableBytes));
+         }
+      }
+
+      protected virtual void ValidateArrayPreconditions(Stream stream, IStreamDecoderState state, int count)
+      {
+         if (count > state.MaxArraySize || count < 0)
+         {
+            throw new DecodeException(string.Format(
+               "Array count indicated {0} is greater than the amount of the configured max array size ({1})",
+               (uint) count, state.MaxArraySize));
+         }
+      }
+
       #region Interface methods handed off to the subclass
 
       public abstract EncodingCodes EncodingCode { get; }
diff --git a/src/Proton/Codec/Decoders/IPrimitiveArrayTypeDecoder.cs b/src/Proton/Codec/Decoders/IPrimitiveArrayTypeDecoder.cs
index f6efaae..65d768b 100644
--- a/src/Proton/Codec/Decoders/IPrimitiveArrayTypeDecoder.cs
+++ b/src/Proton/Codec/Decoders/IPrimitiveArrayTypeDecoder.cs
@@ -15,6 +15,10 @@
  * limitations under the License.
  */
 
+using System;
+using System.IO;
+using Apache.Qpid.Proton.Buffer;
+
 namespace Apache.Qpid.Proton.Codec
 {
    /// <summary>
@@ -22,5 +26,35 @@ namespace Apache.Qpid.Proton.Codec
    /// </summary>
    public interface IPrimitiveArrayTypeDecoder : IPrimitiveTypeDecoder
    {
+      /// <summary>
+      /// Reads the given array from the bytes in the buffer but only if the type encoding
+      /// of the given array matches the given Type filter. This allows the caller to
+      /// effectively limit if the given array is decoded at all if the type encoding is
+      /// not a match and also can act to limit an array to only a single level as the type
+      /// encoding of nested arrays will not be the value type until the bottom of the array
+      /// is reached. To allow all types and any array nesting the caller should pass the
+      /// object Type.
+      /// </summary>
+      /// <param name="buffer"></param>
+      /// <param name="state"></param>
+      /// <param name="ofType"></param>
+      /// <returns>An array containing elements of the given type</returns>
+      object ReadValue(IProtonBuffer buffer, IDecoderState state, Type ofType);
+
+      /// <summary>
+      /// Reads the given array from the bytes in the buffer but only if the type encoding
+      /// of the given array matches the given Type filter. This allows the caller to
+      /// effectively limit if the given array is decoded at all if the type encoding is
+      /// not a match and also can act to limit an array to only a single level as the type
+      /// encoding of nested arrays will not be the value type until the bottom of the array
+      /// is reached. To allow all types and any array nesting the caller should pass the
+      /// object Type.
+      /// </summary>
+      /// <param name="stream"></param>
+      /// <param name="state"></param>
+      /// <param name="ofType"></param>
+      /// <returns>An array containing elements of the given type</returns>
+      object ReadValue(Stream stream, IStreamDecoderState state, Type ofType);
+
    }
 }
\ No newline at end of file
diff --git a/src/Proton/Codec/Decoders/IPrimitiveTypeDecoder.cs b/src/Proton/Codec/Decoders/IPrimitiveTypeDecoder.cs
index 125c252..95f3283 100644
--- a/src/Proton/Codec/Decoders/IPrimitiveTypeDecoder.cs
+++ b/src/Proton/Codec/Decoders/IPrimitiveTypeDecoder.cs
@@ -27,5 +27,9 @@ namespace Apache.Qpid.Proton.Codec
       /// </summary>
       EncodingCodes EncodingCode { get; }
 
+      /// <summary>
+      /// Returns true if this decoder return types that encode as zero width elements.
+      /// </summary>
+      bool IsZeroWidth { get; }
    }
 }
\ No newline at end of file
diff --git a/src/Proton/Codec/Decoders/Messaging/AcceptedTypeDecoder.cs b/src/Proton/Codec/Decoders/Messaging/AcceptedTypeDecoder.cs
index d7609cf..b8dd432 100644
--- a/src/Proton/Codec/Decoders/Messaging/AcceptedTypeDecoder.cs
+++ b/src/Proton/Codec/Decoders/Messaging/AcceptedTypeDecoder.cs
@@ -24,7 +24,7 @@ using Apache.Qpid.Proton.Types.Messaging;
 
 namespace Apache.Qpid.Proton.Codec.Decoders.Messaging
 {
-   public sealed class AcceptedTypeDecoder : AbstractDescribedTypeDecoder
+   public sealed class AcceptedTypeDecoder : AbstractDescribedListTypeDecoder
    {
       public override Symbol DescriptorSymbol => Accepted.DescriptorSymbol;
 
@@ -32,78 +32,32 @@ namespace Apache.Qpid.Proton.Codec.Decoders.Messaging
 
       public override Type DecodesType => typeof(Accepted);
 
-      public override object ReadValue(IProtonBuffer buffer, IDecoderState state)
-      {
-         ITypeDecoder decoder = state.Decoder.ReadNextTypeDecoder(buffer, state);
+      protected override int MinListElements => 0;
 
-         CheckIsExpectedType<IListTypeDecoder>(decoder);
+      protected override int MaxListElements => 0;
 
+      protected sealed override Accepted ReadSingle(IProtonBuffer buffer, IDecoderState state, IListTypeDecoder decoder)
+      {
          decoder.SkipValue(buffer, state);
 
          return Accepted.Instance;
       }
 
-      public override Array ReadArrayElements(IProtonBuffer buffer, IDecoderState state, int count)
-      {
-         ITypeDecoder decoder = state.Decoder.ReadNextTypeDecoder(buffer, state);
-
-         CheckIsExpectedType<IListTypeDecoder>(decoder);
-
-         Accepted[] result = new Accepted[count];
-
-         for (int i = 0; i < count; ++i)
-         {
-            decoder.SkipValue(buffer, state);
-            result[i] = Accepted.Instance;
-         }
-
-         return result;
-      }
-
-      public override void SkipValue(IProtonBuffer buffer, IDecoderState state)
-      {
-         ITypeDecoder decoder = state.Decoder.ReadNextTypeDecoder(buffer, state);
-
-         CheckIsExpectedType<IListTypeDecoder>(decoder);
-
-         decoder.SkipValue(buffer, state);
-      }
-
-      public override object ReadValue(Stream stream, IStreamDecoderState state)
+      protected sealed override Accepted ReadSingle(Stream stream, IStreamDecoderState state, IListTypeDecoder decoder)
       {
-         IStreamTypeDecoder decoder = state.Decoder.ReadNextTypeDecoder(stream, state);
-
-         CheckIsExpectedType<IListTypeDecoder>(decoder);
-
          decoder.SkipValue(stream, state);
 
          return Accepted.Instance;
       }
 
-      public override Array ReadArrayElements(Stream stream, IStreamDecoderState state, int count)
+      protected override Accepted ReadType(int count, IProtonBuffer buffer, IDecoder decoder, IDecoderState state)
       {
-         IStreamTypeDecoder decoder = state.Decoder.ReadNextTypeDecoder(stream, state);
-
-         CheckIsExpectedType<IListTypeDecoder>(decoder);
-
-         Accepted[] result = new Accepted[count];
-
-         for (int i = 0; i < count; ++i)
-         {
-            decoder.SkipValue(stream, state);
-            result[i] = Accepted.Instance;
-         }
-
-         return result;
+         throw new NotImplementedException("Should not be called for this AMQP type");
       }
 
-      public override void SkipValue(Stream stream, IStreamDecoderState state)
+      protected override Accepted ReadType(int count, Stream stream, IStreamDecoder decoder, IStreamDecoderState state)
       {
-         IStreamTypeDecoder decoder = state.Decoder.ReadNextTypeDecoder(stream, state);
-
-         CheckIsExpectedType<IListTypeDecoder>(decoder);
-
-         decoder.SkipValue(stream, state);
+         throw new NotImplementedException("Should not be called for this AMQP type");
       }
    }
 }
\ No newline at end of file
diff --git a/src/Proton/Codec/Decoders/Messaging/AmqpSequenceTypeDecoder.cs b/src/Proton/Codec/Decoders/Messaging/AmqpSequenceTypeDecoder.cs
index 3af4dca..36e54b2 100644
--- a/src/Proton/Codec/Decoders/Messaging/AmqpSequenceTypeDecoder.cs
+++ b/src/Proton/Codec/Decoders/Messaging/AmqpSequenceTypeDecoder.cs
@@ -33,7 +33,7 @@ namespace Apache.Qpid.Proton.Codec.Decoders.Messaging
 
       public override Type DecodesType => typeof(AmqpSequence);
 
-      public override object ReadValue(IProtonBuffer buffer, IDecoderState state)
+      public override AmqpSequence ReadValue(IProtonBuffer buffer, IDecoderState state)
       {
          ITypeDecoder decoder = state.Decoder.ReadNextTypeDecoder(buffer, state);
          IListTypeDecoder valueDecoder = CheckIsExpectedTypeAndCast<IListTypeDecoder>(decoder);
@@ -59,14 +59,10 @@ namespace Apache.Qpid.Proton.Codec.Decoders.Messaging
 
       public override void SkipValue(IProtonBuffer buffer, IDecoderState state)
       {
-         ITypeDecoder decoder = state.Decoder.ReadNextTypeDecoder(buffer, state);
-
-         CheckIsExpectedType<IListTypeDecoder>(decoder);
-
-         decoder.SkipValue(buffer, state);
+         CheckIsExpectedType<IListTypeDecoder>(state.Decoder.ReadNextTypeDecoder(buffer, state)).SkipValue(buffer, state);
       }
 
-      public override object ReadValue(Stream stream, IStreamDecoderState state)
+      public override AmqpSequence ReadValue(Stream stream, IStreamDecoderState state)
       {
          IStreamTypeDecoder decoder = state.Decoder.ReadNextTypeDecoder(stream, state);
          IListTypeDecoder valueDecoder = CheckIsExpectedTypeAndCast<IListTypeDecoder>(decoder);
@@ -92,11 +88,7 @@ namespace Apache.Qpid.Proton.Codec.Decoders.Messaging
 
       public override void SkipValue(Stream stream, IStreamDecoderState state)
       {
-         IStreamTypeDecoder decoder = state.Decoder.ReadNextTypeDecoder(stream, state);
-
-         CheckIsExpectedType<IListTypeDecoder>(decoder);
-
-         decoder.SkipValue(stream, state);
+         CheckIsExpectedType<IListTypeDecoder>(state.Decoder.ReadNextTypeDecoder(stream, state)).SkipValue(stream, state);
       }
    }
 }
\ No newline at end of file
diff --git a/src/Proton/Codec/Decoders/Messaging/AmqpValueTypeDecoder.cs b/src/Proton/Codec/Decoders/Messaging/AmqpValueTypeDecoder.cs
index afd0555..49e05d6 100644
--- a/src/Proton/Codec/Decoders/Messaging/AmqpValueTypeDecoder.cs
+++ b/src/Proton/Codec/Decoders/Messaging/AmqpValueTypeDecoder.cs
@@ -31,10 +31,18 @@ namespace Apache.Qpid.Proton.Codec.Decoders.Messaging
 
       public override Type DecodesType => typeof(AmqpValue);
 
-      public override object ReadValue(IProtonBuffer buffer, IDecoderState state)
+      public override AmqpValue ReadValue(IProtonBuffer buffer, IDecoderState state)
       {
-         ITypeDecoder decoder = state.Decoder.ReadNextTypeDecoder(buffer, state);
-         return new AmqpValue(decoder.ReadValue(buffer, state));
+         state.IncreaseDepth();
+
+         try
+         {
+            return new AmqpValue(state.Decoder.ReadNextTypeDecoder(buffer, state).ReadValue(buffer, state));
+         }
+         finally
+         {
+            state.DecreaseDepth();
+         }
       }
 
       public override Array ReadArrayElements(IProtonBuffer buffer, IDecoderState state, int count)
@@ -53,13 +61,30 @@ namespace Apache.Qpid.Proton.Codec.Decoders.Messaging
 
       public override void SkipValue(IProtonBuffer buffer, IDecoderState state)
       {
-         state.Decoder.ReadNextTypeDecoder(buffer, state).SkipValue(buffer, state);
+         state.IncreaseDepth();
+
+         try
+         {
+            state.Decoder.ReadNextTypeDecoder(buffer, state).SkipValue(buffer, state);
+         }
+         finally
+         {
+            state.DecreaseDepth();
+         }
       }
 
-      public override object ReadValue(Stream stream, IStreamDecoderState state)
+      public override AmqpValue ReadValue(Stream stream, IStreamDecoderState state)
       {
-         IStreamTypeDecoder decoder = state.Decoder.ReadNextTypeDecoder(stream, state);
-         return new AmqpValue(decoder.ReadValue(stream, state));
+         state.IncreaseDepth();
+
+         try
+         {
+            return new AmqpValue(state.Decoder.ReadNextTypeDecoder(stream, state).ReadValue(stream, state));
+         }
+         finally
+         {
+            state.DecreaseDepth();
+         }
       }
 
       public override Array ReadArrayElements(Stream stream, IStreamDecoderState state, int count)
@@ -78,7 +103,16 @@ namespace Apache.Qpid.Proton.Codec.Decoders.Messaging
 
       public override void SkipValue(Stream stream, IStreamDecoderState state)
       {
-         state.Decoder.ReadNextTypeDecoder(stream, state).SkipValue(stream, state);
+         state.IncreaseDepth();
+
+         try
+         {
+            state.Decoder.ReadNextTypeDecoder(stream, state).SkipValue(stream, state);
+         }
+         finally
+         {
+            state.DecreaseDepth();
+         }
       }
    }
 }
\ No newline at end of file
diff --git a/src/Proton/Codec/Decoders/Messaging/ApplicationPropertiesTypeDecoder.cs b/src/Proton/Codec/Decoders/Messaging/ApplicationPropertiesTypeDecoder.cs
index aecd636..544b773 100644
--- a/src/Proton/Codec/Decoders/Messaging/ApplicationPropertiesTypeDecoder.cs
+++ b/src/Proton/Codec/Decoders/Messaging/ApplicationPropertiesTypeDecoder.cs
@@ -25,7 +25,7 @@ using Apache.Qpid.Proton.Types.Messaging;
 
 namespace Apache.Qpid.Proton.Codec.Decoders.Messaging
 {
-   public sealed class ApplicationPropertiesTypeDecoder : AbstractDescribedTypeDecoder
+   public sealed class ApplicationPropertiesTypeDecoder : AbstractDescribedMapTypeDecoder<string>
    {
       public override Symbol DescriptorSymbol => ApplicationProperties.DescriptorSymbol;
 
@@ -33,141 +33,19 @@ namespace Apache.Qpid.Proton.Codec.Decoders.Messaging
 
       public override Type DecodesType => typeof(ApplicationProperties);
 
-      public override object ReadValue(IProtonBuffer buffer, IDecoderState state)
+      protected override ApplicationProperties CreateDescribed(IDictionary<string, object> map)
       {
-         ITypeDecoder decoder = state.Decoder.ReadNextTypeDecoder(buffer, state);
-
-         if (decoder is NullTypeDecoder)
-         {
-            return new ApplicationProperties();
-         }
-
-         return new ApplicationProperties(ReadMap(buffer, state, CheckIsExpectedTypeAndCast<IMapTypeDecoder>(decoder)));
-      }
-
-      public override Array ReadArrayElements(IProtonBuffer buffer, IDecoderState state, int count)
-      {
-         ITypeDecoder decoder = state.Decoder.ReadNextTypeDecoder(buffer, state);
-         ApplicationProperties[] result = new ApplicationProperties[count];
-
-         if (decoder is NullTypeDecoder)
-         {
-            for (int i = 0; i < count; ++i)
-            {
-               result[i] = new ApplicationProperties();
-            }
-            return result;
-         }
-
-         for (int i = 0; i < count; ++i)
-         {
-            result[i] = new ApplicationProperties(
-               ReadMap(buffer, state, CheckIsExpectedTypeAndCast<IMapTypeDecoder>(decoder)));
-         }
-
-         return result;
-      }
-
-      public override void SkipValue(IProtonBuffer buffer, IDecoderState state)
-      {
-         ITypeDecoder decoder = state.Decoder.ReadNextTypeDecoder(buffer, state);
-         if (decoder is not NullTypeDecoder)
-         {
-            CheckIsExpectedType<IMapTypeDecoder>(decoder);
-            decoder.SkipValue(buffer, state);
-         }
-      }
-
-      private static IDictionary<string, object> ReadMap(IProtonBuffer buffer, IDecoderState state, IMapTypeDecoder mapDecoder)
-      {
-         int size = mapDecoder.ReadSize(buffer, state);
-         int count = mapDecoder.ReadCount(buffer, state);
-
-         if (count > buffer.ReadableBytes)
-         {
-            throw new DecodeException(string.Format(
-               "Map encoded size {0} is specified to be greater than the amount " +
-               "of data available ({1})", size, buffer.ReadableBytes));
-         }
-
-         IDecoder decoder = state.Decoder;
-
-         // Count include both key and value so we must include that in the loop
-         IDictionary<string, object> map = new Dictionary<string, object>(count);
-         for (int i = 0; i < count / 2; i++)
-         {
-            string key = decoder.ReadString(buffer, state);
-            object value = decoder.ReadObject(buffer, state);
-
-            map.Add(key, value);
-         }
-
-         return map;
+         return new ApplicationProperties(map);
       }
 
-      public override object ReadValue(Stream stream, IStreamDecoderState state)
+      protected override string ReadKey(IProtonBuffer buffer, IDecoder decoder, IDecoderState state)
       {
-         IStreamTypeDecoder decoder = state.Decoder.ReadNextTypeDecoder(stream, state);
-
-         if (decoder is NullTypeDecoder)
-         {
-            return new ApplicationProperties();
-         }
-
-         return new ApplicationProperties(ReadMap(stream, state, CheckIsExpectedTypeAndCast<IMapTypeDecoder>(decoder)));
+         return decoder.ReadString(buffer, state);
       }
 
-      public override Array ReadArrayElements(Stream stream, IStreamDecoderState state, int count)
+      protected override string ReadKey(Stream stream, IStreamDecoder decoder, IStreamDecoderState state)
       {
-         IStreamTypeDecoder decoder = state.Decoder.ReadNextTypeDecoder(stream, state);
-         ApplicationProperties[] result = new ApplicationProperties[count];
-
-         if (decoder is NullTypeDecoder)
-         {
-            for (int i = 0; i < count; ++i)
-            {
-               result[i] = new ApplicationProperties();
-            }
-            return result;
-         }
-
-         for (int i = 0; i < count; ++i)
-         {
-            result[i] = new ApplicationProperties(
-               ReadMap(stream, state, CheckIsExpectedTypeAndCast<IMapTypeDecoder>(decoder)));
-         }
-
-         return result;
-      }
-
-      public override void SkipValue(Stream stream, IStreamDecoderState state)
-      {
-         IStreamTypeDecoder decoder = state.Decoder.ReadNextTypeDecoder(stream, state);
-         if (decoder is not NullTypeDecoder)
-         {
-            CheckIsExpectedType<IMapTypeDecoder>(decoder);
-            decoder.SkipValue(stream, state);
-         }
-      }
-
-      private static IDictionary<string, object> ReadMap(Stream stream, IStreamDecoderState state, IMapTypeDecoder mapDecoder)
-      {
-         _ = mapDecoder.ReadSize(stream, state);
-         int count = mapDecoder.ReadCount(stream, state);
-
-         IStreamDecoder decoder = state.Decoder;
-
-         // Count include both key and value so we must include that in the loop
-         IDictionary<string, object> map = new Dictionary<string, object>(count);
-         for (int i = 0; i < count / 2; i++)
-         {
-            string key = decoder.ReadString(stream, state);
-            object value = decoder.ReadObject(stream, state);
-
-            map.Add(key, value);
-         }
-
-         return map;
+         return decoder.ReadString(stream, state);
       }
    }
 }
\ No newline at end of file
diff --git a/src/Proton/Codec/Decoders/Messaging/DataTypeDecoder.cs b/src/Proton/Codec/Decoders/Messaging/DataTypeDecoder.cs
index a46d491..812382e 100644
--- a/src/Proton/Codec/Decoders/Messaging/DataTypeDecoder.cs
+++ b/src/Proton/Codec/Decoders/Messaging/DataTypeDecoder.cs
@@ -34,7 +34,7 @@ namespace Apache.Qpid.Proton.Codec.Decoders.Messaging
 
       public override Type DecodesType => typeof(Data);
 
-      public override object ReadValue(IProtonBuffer buffer, IDecoderState state)
+      public override Data ReadValue(IProtonBuffer buffer, IDecoderState state)
       {
          EncodingCodes encodingCode = (EncodingCodes)buffer.ReadByte();
          int size;
@@ -88,14 +88,10 @@ namespace Apache.Qpid.Proton.Codec.Decoders.Messaging
 
       public override void SkipValue(IProtonBuffer buffer, IDecoderState state)
       {
-         ITypeDecoder decoder = state.Decoder.ReadNextTypeDecoder(buffer, state);
-
-         CheckIsExpectedType<IBinaryTypeDecoder>(decoder);
-
-         decoder.SkipValue(buffer, state);
+         CheckIsExpectedType<IBinaryTypeDecoder>(state.Decoder.ReadNextTypeDecoder(buffer, state)).SkipValue(buffer, state);
       }
 
-      public override object ReadValue(Stream stream, IStreamDecoderState state)
+      public override Data ReadValue(Stream stream, IStreamDecoderState state)
       {
          EncodingCodes encodingCode = (EncodingCodes)ProtonStreamReadUtils.ReadByte(stream);
          int size;
@@ -114,6 +110,12 @@ namespace Apache.Qpid.Proton.Codec.Decoders.Messaging
                throw new DecodeException("Expected Binary type but found encoding: " + encodingCode);
          }
 
+         if (size > state.MaxBinarySize)
+         {
+            throw new DecodeException("Binary data size " + size + " is specified to be greater than the " +
+                                      "configured max binary size (" + state.MaxBinarySize + ")");
+         }
+
          return new Data(ProtonByteBufferAllocator.Instance.Wrap(ProtonStreamReadUtils.ReadBytes(stream, size)));
       }
 
@@ -134,11 +136,7 @@ namespace Apache.Qpid.Proton.Codec.Decoders.Messaging
 
       public override void SkipValue(Stream stream, IStreamDecoderState state)
       {
-         IStreamTypeDecoder decoder = state.Decoder.ReadNextTypeDecoder(stream, state);
-
-         CheckIsExpectedType<IBinaryTypeDecoder>(decoder);
-
-         decoder.SkipValue(stream, state);
+         CheckIsExpectedType<IBinaryTypeDecoder>(state.Decoder.ReadNextTypeDecoder(stream, state)).SkipValue(stream, state);
       }
    }
 }
\ No newline at end of file
diff --git a/src/Proton/Codec/Decoders/Messaging/DeleteOnCloseTypeDecoder.cs b/src/Proton/Codec/Decoders/Messaging/DeleteOnCloseTypeDecoder.cs
index 4a3a6a3..daeaf2d 100644
--- a/src/Proton/Codec/Decoders/Messaging/DeleteOnCloseTypeDecoder.cs
+++ b/src/Proton/Codec/Decoders/Messaging/DeleteOnCloseTypeDecoder.cs
@@ -24,7 +24,7 @@ using Apache.Qpid.Proton.Types.Messaging;
 
 namespace Apache.Qpid.Proton.Codec.Decoders.Messaging
 {
-   public sealed class DeleteOnCloseTypeDecoder : AbstractDescribedTypeDecoder
+   public sealed class DeleteOnCloseTypeDecoder : AbstractDescribedListTypeDecoder
    {
       public override Symbol DescriptorSymbol => DeleteOnClose.DescriptorSymbol;
 
@@ -32,78 +32,32 @@ namespace Apache.Qpid.Proton.Codec.Decoders.Messaging
 
       public override Type DecodesType => typeof(DeleteOnClose);
 
-      public override object ReadValue(IProtonBuffer buffer, IDecoderState state)
-      {
-         ITypeDecoder decoder = state.Decoder.ReadNextTypeDecoder(buffer, state);
+      protected override int MinListElements => 0;
 
-         CheckIsExpectedType<IListTypeDecoder>(decoder);
+      protected override int MaxListElements => 0;
 
+      protected sealed override DeleteOnClose ReadSingle(IProtonBuffer buffer, IDecoderState state, IListTypeDecoder decoder)
+      {
          decoder.SkipValue(buffer, state);
 
          return DeleteOnClose.Instance;
       }
 
-      public override Array ReadArrayElements(IProtonBuffer buffer, IDecoderState state, int count)
-      {
-         ITypeDecoder decoder = state.Decoder.ReadNextTypeDecoder(buffer, state);
-
-         CheckIsExpectedType<IListTypeDecoder>(decoder);
-
-         DeleteOnClose[] result = new DeleteOnClose[count];
-
-         for (int i = 0; i < count; ++i)
-         {
-            decoder.SkipValue(buffer, state);
-            result[i] = DeleteOnClose.Instance;
-         }
-
-         return result;
-      }
-
-      public override void SkipValue(IProtonBuffer buffer, IDecoderState state)
-      {
-         ITypeDecoder decoder = state.Decoder.ReadNextTypeDecoder(buffer, state);
-
-         CheckIsExpectedType<IListTypeDecoder>(decoder);
-
-         decoder.SkipValue(buffer, state);
-      }
-
-      public override object ReadValue(Stream stream, IStreamDecoderState state)
+      protected sealed override DeleteOnClose ReadSingle(Stream stream, IStreamDecoderState state, IListTypeDecoder decoder)
       {
-         IStreamTypeDecoder decoder = state.Decoder.ReadNextTypeDecoder(stream, state);
-
-         CheckIsExpectedType<IListTypeDecoder>(decoder);
-
          decoder.SkipValue(stream, state);
 
          return DeleteOnClose.Instance;
       }
 
-      public override Array ReadArrayElements(Stream stream, IStreamDecoderState state, int count)
+      protected override object ReadType(int count, IProtonBuffer buffer, IDecoder decoder, IDecoderState state)
       {
-         IStreamTypeDecoder decoder = state.Decoder.ReadNextTypeDecoder(stream, state);
-
-         CheckIsExpectedType<IListTypeDecoder>(decoder);
-
-         DeleteOnClose[] result = new DeleteOnClose[count];
-
-         for (int i = 0; i < count; ++i)
-         {
-            decoder.SkipValue(stream, state);
-            result[i] = DeleteOnClose.Instance;
-         }
-
-         return result;
+         throw new NotImplementedException("Should not be called for this AMQP type");
       }
 
-      public override void SkipValue(Stream stream, IStreamDecoderState state)
+      protected override object ReadType(int count, Stream stream, IStreamDecoder decoder, IStreamDecoderState state)
       {
-         IStreamTypeDecoder decoder = state.Decoder.ReadNextTypeDecoder(stream, state);
-
-         CheckIsExpectedType<IListTypeDecoder>(decoder);
-
-         decoder.SkipValue(stream, state);
+         throw new NotImplementedException("Should not be called for this AMQP type");
       }
    }
 }
\ No newline at end of file
diff --git a/src/Proton/Codec/Decoders/Messaging/DeleteOnNoLinksOrMessagesTypeDecoder.cs b/src/Proton/Codec/Decoders/Messaging/DeleteOnNoLinksOrMessagesTypeDecoder.cs
index 16a7df6..492e278 100644
--- a/src/Proton/Codec/Decoders/Messaging/DeleteOnNoLinksOrMessagesTypeDecoder.cs
+++ b/src/Proton/Codec/Decoders/Messaging/DeleteOnNoLinksOrMessagesTypeDecoder.cs
@@ -24,7 +24,7 @@ using Apache.Qpid.Proton.Types.Messaging;
 
 namespace Apache.Qpid.Proton.Codec.Decoders.Messaging
 {
-   public sealed class DeleteOnNoLinksOrMessagesTypeDecoder : AbstractDescribedTypeDecoder
+   public sealed class DeleteOnNoLinksOrMessagesTypeDecoder : AbstractDescribedListTypeDecoder
    {
       public override Symbol DescriptorSymbol => DeleteOnNoLinksOrMessages.DescriptorSymbol;
 
@@ -32,78 +32,32 @@ namespace Apache.Qpid.Proton.Codec.Decoders.Messaging
 
       public override Type DecodesType => typeof(DeleteOnNoLinksOrMessages);
 
-      public override object ReadValue(IProtonBuffer buffer, IDecoderState state)
-      {
-         ITypeDecoder decoder = state.Decoder.ReadNextTypeDecoder(buffer, state);
+      protected override int MinListElements => 0;
 
-         CheckIsExpectedType<IListTypeDecoder>(decoder);
+      protected override int MaxListElements => 0;
 
+      protected sealed override object ReadSingle(IProtonBuffer buffer, IDecoderState state, IListTypeDecoder decoder)
+      {
          decoder.SkipValue(buffer, state);
 
          return DeleteOnNoLinksOrMessages.Instance;
       }
 
-      public override Array ReadArrayElements(IProtonBuffer buffer, IDecoderState state, int count)
-      {
-         ITypeDecoder decoder = state.Decoder.ReadNextTypeDecoder(buffer, state);
-
-         CheckIsExpectedType<IListTypeDecoder>(decoder);
-
-         DeleteOnNoLinksOrMessages[] result = new DeleteOnNoLinksOrMessages[count];
-
-         for (int i = 0; i < count; ++i)
-         {
-            decoder.SkipValue(buffer, state);
-            result[i] = DeleteOnNoLinksOrMessages.Instance;
-         }
-
-         return result;
-      }
-
-      public override void SkipValue(IProtonBuffer buffer, IDecoderState state)
-      {
-         ITypeDecoder decoder = state.Decoder.ReadNextTypeDecoder(buffer, state);
-
-         CheckIsExpectedType<IListTypeDecoder>(decoder);
-
-         decoder.SkipValue(buffer, state);
-      }
-
-      public override object ReadValue(Stream stream, IStreamDecoderState state)
+      protected sealed override object ReadSingle(Stream stream, IStreamDecoderState state, IListTypeDecoder decoder)
       {
-         IStreamTypeDecoder decoder = state.Decoder.ReadNextTypeDecoder(stream, state);
-
-         CheckIsExpectedType<IListTypeDecoder>(decoder);
-
          decoder.SkipValue(stream, state);
 
          return DeleteOnNoLinksOrMessages.Instance;
       }
 
-      public override Array ReadArrayElements(Stream stream, IStreamDecoderState state, int count)
+      protected override object ReadType(int count, IProtonBuffer buffer, IDecoder decoder, IDecoderState state)
       {
-         IStreamTypeDecoder decoder = state.Decoder.ReadNextTypeDecoder(stream, state);
-
-         CheckIsExpectedType<IListTypeDecoder>(decoder);
-
-         DeleteOnNoLinksOrMessages[] result = new DeleteOnNoLinksOrMessages[count];
-
-         for (int i = 0; i < count; ++i)
-         {
-            decoder.SkipValue(stream, state);
-            result[i] = DeleteOnNoLinksOrMessages.Instance;
-         }
-
-         return result;
+         throw new NotImplementedException("Should not be called for this AMQP type");
       }
 
-      public override void SkipValue(Stream stream, IStreamDecoderState state)
+      protected override object ReadType(int count, Stream stream, IStreamDecoder decoder, IStreamDecoderState state)
       {
-         IStreamTypeDecoder decoder = state.Decoder.ReadNextTypeDecoder(stream, state);
-
-         CheckIsExpectedType<IListTypeDecoder>(decoder);
-
-         decoder.SkipValue(stream, state);
+         throw new NotImplementedException("Should not be called for this AMQP type");
       }
    }
 }
\ No newline at end of file
diff --git a/src/Proton/Codec/Decoders/Messaging/DeleteOnNoLinksTypeDecoder.cs b/src/Proton/Codec/Decoders/Messaging/DeleteOnNoLinksTypeDecoder.cs
index e53ea01..28d2180 100644
--- a/src/Proton/Codec/Decoders/Messaging/DeleteOnNoLinksTypeDecoder.cs
+++ b/src/Proton/Codec/Decoders/Messaging/DeleteOnNoLinksTypeDecoder.cs
@@ -24,7 +24,7 @@ using Apache.Qpid.Proton.Types.Messaging;
 
 namespace Apache.Qpid.Proton.Codec.Decoders.Messaging
 {
-   public sealed class DeleteOnNoLinksTypeDecoder : AbstractDescribedTypeDecoder
+   public sealed class DeleteOnNoLinksTypeDecoder : AbstractDescribedListTypeDecoder
    {
       public override Symbol DescriptorSymbol => DeleteOnNoLinks.DescriptorSymbol;
 
@@ -32,78 +32,32 @@ namespace Apache.Qpid.Proton.Codec.Decoders.Messaging
 
       public override Type DecodesType => typeof(DeleteOnNoLinks);
 
-      public override object ReadValue(IProtonBuffer buffer, IDecoderState state)
-      {
-         ITypeDecoder decoder = state.Decoder.ReadNextTypeDecoder(buffer, state);
+      protected override int MinListElements => 0;
 
-         CheckIsExpectedType<IListTypeDecoder>(decoder);
+      protected override int MaxListElements => 0;
 
+      protected sealed override object ReadSingle(IProtonBuffer buffer, IDecoderState state, IListTypeDecoder decoder)
+      {
          decoder.SkipValue(buffer, state);
 
          return DeleteOnNoLinks.Instance;
       }
 
-      public override Array ReadArrayElements(IProtonBuffer buffer, IDecoderState state, int count)
-      {
-         ITypeDecoder decoder = state.Decoder.ReadNextTypeDecoder(buffer, state);
-
-         CheckIsExpectedType<IListTypeDecoder>(decoder);
-
-         DeleteOnNoLinks[] result = new DeleteOnNoLinks[count];
-
-         for (int i = 0; i < count; ++i)
-         {
-            decoder.SkipValue(buffer, state);
-            result[i] = DeleteOnNoLinks.Instance;
-         }
-
-         return result;
-      }
-
-      public override void SkipValue(IProtonBuffer buffer, IDecoderState state)
-      {
-         ITypeDecoder decoder = state.Decoder.ReadNextTypeDecoder(buffer, state);
-
-         CheckIsExpectedType<IListTypeDecoder>(decoder);
-
-         decoder.SkipValue(buffer, state);
-      }
-
-      public override object ReadValue(Stream stream, IStreamDecoderState state)
+      protected sealed override object ReadSingle(Stream stream, IStreamDecoderState state, IListTypeDecoder decoder)
       {
-         IStreamTypeDecoder decoder = state.Decoder.ReadNextTypeDecoder(stream, state);
-
-         CheckIsExpectedType<IListTypeDecoder>(decoder);
-
          decoder.SkipValue(stream, state);
 
          return DeleteOnNoLinks.Instance;
       }
 
-      public override Array ReadArrayElements(Stream stream, IStreamDecoderState state, int count)
+      protected override object ReadType(int count, IProtonBuffer buffer, IDecoder decoder, IDecoderState state)
       {
-         IStreamTypeDecoder decoder = state.Decoder.ReadNextTypeDecoder(stream, state);
-
-         CheckIsExpectedType<IListTypeDecoder>(decoder);
-
-         DeleteOnNoLinks[] result = new DeleteOnNoLinks[count];
-
-         for (int i = 0; i < count; ++i)
-         {
-            decoder.SkipValue(stream, state);
-            result[i] = DeleteOnNoLinks.Instance;
-         }
-
-         return result;
+         throw new NotImplementedException("Should not be called for this AMQP type");
       }
 
-      public override void SkipValue(Stream stream, IStreamDecoderState state)
+      protected override object ReadType(int count, Stream stream, IStreamDecoder decoder, IStreamDecoderState state)
       {
-         IStreamTypeDecoder decoder = state.Decoder.ReadNextTypeDecoder(stream, state);
-
-         CheckIsExpectedType<IListTypeDecoder>(decoder);
-
-         decoder.SkipValue(stream, state);
+         throw new NotImplementedException("Should not be called for this AMQP type");
       }
    }
 }
\ No newline at end of file
diff --git a/src/Proton/Codec/Decoders/Messaging/DeleteOnNoMessagesTypeDecoder.cs b/src/Proton/Codec/Decoders/Messaging/DeleteOnNoMessagesTypeDecoder.cs
index 2b56d15..8c8fd90 100644
--- a/src/Proton/Codec/Decoders/Messaging/DeleteOnNoMessagesTypeDecoder.cs
+++ b/src/Proton/Codec/Decoders/Messaging/DeleteOnNoMessagesTypeDecoder.cs
@@ -24,7 +24,7 @@ using Apache.Qpid.Proton.Types.Messaging;
 
 namespace Apache.Qpid.Proton.Codec.Decoders.Messaging
 {
-   public sealed class DeleteOnNoMessagesTypeDecoder : AbstractDescribedTypeDecoder
+   public sealed class DeleteOnNoMessagesTypeDecoder : AbstractDescribedListTypeDecoder
    {
       public override Symbol DescriptorSymbol => DeleteOnNoMessages.DescriptorSymbol;
 
@@ -32,78 +32,32 @@ namespace Apache.Qpid.Proton.Codec.Decoders.Messaging
 
       public override Type DecodesType => typeof(DeleteOnNoMessages);
 
-      public override object ReadValue(IProtonBuffer buffer, IDecoderState state)
-      {
-         ITypeDecoder decoder = state.Decoder.ReadNextTypeDecoder(buffer, state);
+      protected override int MinListElements => 0;
 
-         CheckIsExpectedType<IListTypeDecoder>(decoder);
+      protected override int MaxListElements => 0;
 
+      protected sealed override DeleteOnNoMessages ReadSingle(IProtonBuffer buffer, IDecoderState state, IListTypeDecoder decoder)
+      {
          decoder.SkipValue(buffer, state);
 
          return DeleteOnNoMessages.Instance;
       }
 
-      public override Array ReadArrayElements(IProtonBuffer buffer, IDecoderState state, int count)
-      {
-         ITypeDecoder decoder = state.Decoder.ReadNextTypeDecoder(buffer, state);
-
-         CheckIsExpectedType<IListTypeDecoder>(decoder);
-
-         DeleteOnNoMessages[] result = new DeleteOnNoMessages[count];
-
-         for (int i = 0; i < count; ++i)
-         {
-            decoder.SkipValue(buffer, state);
-            result[i] = DeleteOnNoMessages.Instance;
-         }
-
-         return result;
-      }
-
-      public override void SkipValue(IProtonBuffer buffer, IDecoderState state)
-      {
-         ITypeDecoder decoder = state.Decoder.ReadNextTypeDecoder(buffer, state);
-
-         CheckIsExpectedType<IListTypeDecoder>(decoder);
-
-         decoder.SkipValue(buffer, state);
-      }
-
-      public override object ReadValue(Stream stream, IStreamDecoderState state)
+      protected sealed override DeleteOnNoMessages ReadSingle(Stream stream, IStreamDecoderState state, IListTypeDecoder decoder)
       {
-         IStreamTypeDecoder decoder = state.Decoder.ReadNextTypeDecoder(stream, state);
-
-         CheckIsExpectedType<IListTypeDecoder>(decoder);
-
          decoder.SkipValue(stream, state);
 
          return DeleteOnNoMessages.Instance;
       }
 
-      public override Array ReadArrayElements(Stream stream, IStreamDecoderState state, int count)
+      protected override DeleteOnNoMessages ReadType(int count, IProtonBuffer buffer, IDecoder decoder, IDecoderState state)
       {
-         IStreamTypeDecoder decoder = state.Decoder.ReadNextTypeDecoder(stream, state);
-
-         CheckIsExpectedType<IListTypeDecoder>(decoder);
-
-         DeleteOnNoMessages[] result = new DeleteOnNoMessages[count];
-
-         for (int i = 0; i < count; ++i)
-         {
-            decoder.SkipValue(stream, state);
-            result[i] = DeleteOnNoMessages.Instance;
-         }
-
-         return result;
+         throw new NotImplementedException("Should not be called for this AMQP type");
       }
 
-      public override void SkipValue(Stream stream, IStreamDecoderState state)
+      protected override DeleteOnNoMessages ReadType(int count, Stream stream, IStreamDecoder decoder, IStreamDecoderState state)
       {
-         IStreamTypeDecoder decoder = state.Decoder.ReadNextTypeDecoder(stream, state);
-
-         CheckIsExpectedType<IListTypeDecoder>(decoder);
-
-         decoder.SkipValue(stream, state);
+         throw new NotImplementedException("Should not be called for this AMQP type");
       }
    }
 }
\ No newline at end of file
diff --git a/src/Proton/Codec/Decoders/Messaging/DeliveryAnnotationsTypeDecoder.cs b/src/Proton/Codec/Decoders/Messaging/DeliveryAnnotationsTypeDecoder.cs
index e24c59b..c490df3 100644
--- a/src/Proton/Codec/Decoders/Messaging/DeliveryAnnotationsTypeDecoder.cs
+++ b/src/Proton/Codec/Decoders/Messaging/DeliveryAnnotationsTypeDecoder.cs
@@ -25,7 +25,7 @@ using Apache.Qpid.Proton.Types.Messaging;
 
 namespace Apache.Qpid.Proton.Codec.Decoders.Messaging
 {
-   public sealed class DeliveryAnnotationsTypeDecoder : AbstractDescribedTypeDecoder
+   public sealed class DeliveryAnnotationsTypeDecoder : AbstractDescribedMapTypeDecoder<Symbol>
    {
       public override Symbol DescriptorSymbol => DeliveryAnnotations.DescriptorSymbol;
 
@@ -33,141 +33,19 @@ namespace Apache.Qpid.Proton.Codec.Decoders.Messaging
 
       public override Type DecodesType => typeof(DeliveryAnnotations);
 
-      public override object ReadValue(IProtonBuffer buffer, IDecoderState state)
+      protected override DeliveryAnnotations CreateDescribed(IDictionary<Symbol, object> map)
       {
-         ITypeDecoder decoder = state.Decoder.ReadNextTypeDecoder(buffer, state);
-
-         if (decoder is NullTypeDecoder)
-         {
-            return new DeliveryAnnotations();
-         }
-
-         return new DeliveryAnnotations(ReadMap(buffer, state, CheckIsExpectedTypeAndCast<IMapTypeDecoder>(decoder)));
-      }
-
-      public override Array ReadArrayElements(IProtonBuffer buffer, IDecoderState state, int count)
-      {
-         ITypeDecoder decoder = state.Decoder.ReadNextTypeDecoder(buffer, state);
-         DeliveryAnnotations[] result = new DeliveryAnnotations[count];
-
-         if (decoder is NullTypeDecoder)
-         {
-            for (int i = 0; i < count; ++i)
-            {
-               result[i] = new DeliveryAnnotations();
-            }
-            return result;
-         }
-
-         for (int i = 0; i < count; ++i)
-         {
-            result[i] = new DeliveryAnnotations(
-               ReadMap(buffer, state, CheckIsExpectedTypeAndCast<IMapTypeDecoder>(decoder)));
-         }
-
-         return result;
-      }
-
-      public override void SkipValue(IProtonBuffer buffer, IDecoderState state)
-      {
-         ITypeDecoder decoder = state.Decoder.ReadNextTypeDecoder(buffer, state);
-         if (decoder is not NullTypeDecoder)
-         {
-            CheckIsExpectedType<IMapTypeDecoder>(decoder);
-            decoder.SkipValue(buffer, state);
-         }
-      }
-
-      private static IDictionary<Symbol, object> ReadMap(IProtonBuffer buffer, IDecoderState state, IMapTypeDecoder mapDecoder)
-      {
-         int size = mapDecoder.ReadSize(buffer, state);
-         int count = mapDecoder.ReadCount(buffer, state);
-
-         if (count > buffer.ReadableBytes)
-         {
-            throw new DecodeException(string.Format(
-                    "Map encoded size {0} is specified to be greater than the amount " +
-                    "of data available ({1})", size, buffer.ReadableBytes));
-         }
-
-         IDecoder decoder = state.Decoder;
-
-         // Count include both key and value so we must include that in the loop
-         IDictionary<Symbol, object> map = new Dictionary<Symbol, object>(count);
-         for (int i = 0; i < count / 2; i++)
-         {
-            Symbol key = decoder.ReadSymbol(buffer, state);
-            object value = decoder.ReadObject(buffer, state);
-
-            map.Add(key, value);
-         }
-
-         return map;
+         return new DeliveryAnnotations(map);
       }
 
-      public override object ReadValue(Stream stream, IStreamDecoderState state)
+      protected override Symbol ReadKey(IProtonBuffer buffer, IDecoder decoder, IDecoderState state)
       {
-         IStreamTypeDecoder decoder = state.Decoder.ReadNextTypeDecoder(stream, state);
-
-         if (decoder is NullTypeDecoder)
-         {
-            return new DeliveryAnnotations();
-         }
-
-         return new DeliveryAnnotations(ReadMap(stream, state, CheckIsExpectedTypeAndCast<IMapTypeDecoder>(decoder)));
+         return decoder.ReadSymbol(buffer, state);
       }
 
-      public override Array ReadArrayElements(Stream stream, IStreamDecoderState state, int count)
+      protected override Symbol ReadKey(Stream stream, IStreamDecoder decoder, IStreamDecoderState state)
       {
-         IStreamTypeDecoder decoder = state.Decoder.ReadNextTypeDecoder(stream, state);
-         DeliveryAnnotations[] result = new DeliveryAnnotations[count];
-
-         if (decoder is NullTypeDecoder)
-         {
-            for (int i = 0; i < count; ++i)
-            {
-               result[i] = new DeliveryAnnotations();
-            }
-            return result;
-         }
-
-         for (int i = 0; i < count; ++i)
-         {
-            result[i] = new DeliveryAnnotations(
-               ReadMap(stream, state, CheckIsExpectedTypeAndCast<IMapTypeDecoder>(decoder)));
-         }
-
-         return result;
-      }
-
-      public override void SkipValue(Stream stream, IStreamDecoderState state)
-      {
-         IStreamTypeDecoder decoder = state.Decoder.ReadNextTypeDecoder(stream, state);
-         if (decoder is not NullTypeDecoder)
-         {
-            CheckIsExpectedType<IMapTypeDecoder>(decoder);
-            decoder.SkipValue(stream, state);
-         }
-      }
-
-      private static IDictionary<Symbol, object> ReadMap(Stream stream, IStreamDecoderState state, IMapTypeDecoder mapDecoder)
-      {
-         _ = mapDecoder.ReadSize(stream, state);
-         int count = mapDecoder.ReadCount(stream, state);
-
-         IStreamDecoder decoder = state.Decoder;
-
-         // Count include both key and value so we must include that in the loop
-         IDictionary<Symbol, object> map = new Dictionary<Symbol, object>(count);
-         for (int i = 0; i < count / 2; i++)
-         {
-            Symbol key = decoder.ReadSymbol(stream, state);
-            object value = decoder.ReadObject(stream, state);
-
-            map.Add(key, value);
-         }
-
-         return map;
+         return decoder.ReadSymbol(stream, state);
       }
    }
 }
\ No newline at end of file
diff --git a/src/Proton/Codec/Decoders/Messaging/FooterTypeDecoder.cs b/src/Proton/Codec/Decoders/Messaging/FooterTypeDecoder.cs
index 05203c3..434e039 100644
--- a/src/Proton/Codec/Decoders/Messaging/FooterTypeDecoder.cs
+++ b/src/Proton/Codec/Decoders/Messaging/FooterTypeDecoder.cs
@@ -25,7 +25,7 @@ using Apache.Qpid.Proton.Types.Messaging;
 
 namespace Apache.Qpid.Proton.Codec.Decoders.Messaging
 {
-   public sealed class FooterTypeDecoder : AbstractDescribedTypeDecoder
+   public sealed class FooterTypeDecoder : AbstractDescribedMapTypeDecoder<Symbol>
    {
       public override Symbol DescriptorSymbol => Footer.DescriptorSymbol;
 
@@ -33,141 +33,19 @@ namespace Apache.Qpid.Proton.Codec.Decoders.Messaging
 
       public override Type DecodesType => typeof(Footer);
 
-      public override object ReadValue(IProtonBuffer buffer, IDecoderState state)
+      protected override Footer CreateDescribed(IDictionary<Symbol, object> map)
       {
-         ITypeDecoder decoder = state.Decoder.ReadNextTypeDecoder(buffer, state);
-
-         if (decoder is NullTypeDecoder)
-         {
-            return new Footer();
-         }
-
-         return new Footer(ReadMap(buffer, state, CheckIsExpectedTypeAndCast<IMapTypeDecoder>(decoder)));
-      }
-
-      public override Array ReadArrayElements(IProtonBuffer buffer, IDecoderState state, int count)
-      {
-         ITypeDecoder decoder = state.Decoder.ReadNextTypeDecoder(buffer, state);
-         Footer[] result = new Footer[count];
-
-         if (decoder is NullTypeDecoder)
-         {
-            for (int i = 0; i < count; ++i)
-            {
-               result[i] = new Footer();
-            }
-            return result;
-         }
-
-         for (int i = 0; i < count; ++i)
-         {
-            result[i] = new Footer(
-               ReadMap(buffer, state, CheckIsExpectedTypeAndCast<IMapTypeDecoder>(decoder)));
-         }
-
-         return result;
-      }
-
-      public override void SkipValue(IProtonBuffer buffer, IDecoderState state)
-      {
-         ITypeDecoder decoder = state.Decoder.ReadNextTypeDecoder(buffer, state);
-         if (decoder is not NullTypeDecoder)
-         {
-            CheckIsExpectedType<IMapTypeDecoder>(decoder);
-            decoder.SkipValue(buffer, state);
-         }
-      }
-
-      private static IDictionary<Symbol, object> ReadMap(IProtonBuffer buffer, IDecoderState state, IMapTypeDecoder mapDecoder)
-      {
-         int size = mapDecoder.ReadSize(buffer, state);
-         int count = mapDecoder.ReadCount(buffer, state);
-
-         if (count > buffer.ReadableBytes)
-         {
-            throw new DecodeException(string.Format(
-                    "Map encoded size {0} is specified to be greater than the amount " +
-                    "of data available ({1})", size, buffer.ReadableBytes));
-         }
-
-         IDecoder decoder = state.Decoder;
-
-         // Count include both key and value so we must include that in the loop
-         IDictionary<Symbol, object> map = new Dictionary<Symbol, object>(count);
-         for (int i = 0; i < count / 2; i++)
-         {
-            Symbol key = decoder.ReadSymbol(buffer, state);
-            object value = decoder.ReadObject(buffer, state);
-
-            map.Add(key, value);
-         }
-
-         return map;
+         return new Footer(map);
       }
 
-      public override object ReadValue(Stream stream, IStreamDecoderState state)
+      protected override Symbol ReadKey(IProtonBuffer buffer, IDecoder decoder, IDecoderState state)
       {
-         IStreamTypeDecoder decoder = state.Decoder.ReadNextTypeDecoder(stream, state);
-
-         if (decoder is NullTypeDecoder)
-         {
-            return new Footer();
-         }
-
-         return new Footer(ReadMap(stream, state, CheckIsExpectedTypeAndCast<IMapTypeDecoder>(decoder)));
+         return decoder.ReadSymbol(buffer, state);
       }
 
-      public override Array ReadArrayElements(Stream stream, IStreamDecoderState state, int count)
+      protected override Symbol ReadKey(Stream stream, IStreamDecoder decoder, IStreamDecoderState state)
       {
-         IStreamTypeDecoder decoder = state.Decoder.ReadNextTypeDecoder(stream, state);
-         Footer[] result = new Footer[count];
-
-         if (decoder is NullTypeDecoder)
-         {
-            for (int i = 0; i < count; ++i)
-            {
-               result[i] = new Footer();
-            }
-            return result;
-         }
-
-         for (int i = 0; i < count; ++i)
-         {
-            result[i] = new Footer(
-               ReadMap(stream, state, CheckIsExpectedTypeAndCast<IMapTypeDecoder>(decoder)));
-         }
-
-         return result;
-      }
-
-      public override void SkipValue(Stream stream, IStreamDecoderState state)
-      {
-         IStreamTypeDecoder decoder = state.Decoder.ReadNextTypeDecoder(stream, state);
-         if (decoder is not NullTypeDecoder)
-         {
-            CheckIsExpectedType<IMapTypeDecoder>(decoder);
-            decoder.SkipValue(stream, state);
-         }
-      }
-
-      private static IDictionary<Symbol, object> ReadMap(Stream stream, IStreamDecoderState state, IMapTypeDecoder mapDecoder)
-      {
-         _ = mapDecoder.ReadSize(stream, state);
-         int count = mapDecoder.ReadCount(stream, state);
-
-         IStreamDecoder decoder = state.Decoder;
-
-         // Count include both key and value so we must include that in the loop
-         IDictionary<Symbol, object> map = new Dictionary<Symbol, object>(count);
-         for (int i = 0; i < count / 2; i++)
-         {
-            Symbol key = decoder.ReadSymbol(stream, state);
-            object value = decoder.ReadObject(stream, state);
-
-            map.Add(key, value);
-         }
-
-         return map;
+         return decoder.ReadSymbol(stream, state);
       }
    }
 }
\ No newline at end of file
diff --git a/src/Proton/Codec/Decoders/Messaging/HeaderTypeDecoder.cs b/src/Proton/Codec/Decoders/Messaging/HeaderTypeDecoder.cs
index 74e5b67..2a39614 100644
--- a/src/Proton/Codec/Decoders/Messaging/HeaderTypeDecoder.cs
+++ b/src/Proton/Codec/Decoders/Messaging/HeaderTypeDecoder.cs
@@ -24,7 +24,7 @@ using Apache.Qpid.Proton.Types.Messaging;
 
 namespace Apache.Qpid.Proton.Codec.Decoders.Messaging
 {
-   public sealed class HeaderTypeDecoder : AbstractDescribedTypeDecoder
+   public sealed class HeaderTypeDecoder : AbstractDescribedListTypeDecoder
    {
       private static readonly int MinHeaderListEntries = 0;
       private static readonly int MaxHeaderListEntries = 5;
@@ -35,54 +35,14 @@ namespace Apache.Qpid.Proton.Codec.Decoders.Messaging
 
       public override Type DecodesType => typeof(Header);
 
-      public override object ReadValue(IProtonBuffer buffer, IDecoderState state)
-      {
-         ITypeDecoder decoder = state.Decoder.ReadNextTypeDecoder(buffer, state);
-
-         return ReadHeader(buffer, state, CheckIsExpectedTypeAndCast<IListTypeDecoder>(decoder));
-      }
-
-      public override Array ReadArrayElements(IProtonBuffer buffer, IDecoderState state, int count)
-      {
-         ITypeDecoder decoder = state.Decoder.ReadNextTypeDecoder(buffer, state);
-         IListTypeDecoder listDecoder = CheckIsExpectedTypeAndCast<IListTypeDecoder>(decoder);
-
-         Header[] result = new Header[count];
-         for (int i = 0; i < count; ++i)
-         {
-            result[i] = ReadHeader(buffer, state, listDecoder);
-         }
+      protected override int MinListElements => MinHeaderListEntries;
 
-         return result;
-      }
+      protected override int MaxListElements => MaxHeaderListEntries;
 
-      public override void SkipValue(IProtonBuffer buffer, IDecoderState state)
-      {
-         ITypeDecoder decoder = state.Decoder.ReadNextTypeDecoder(buffer, state);
-
-         CheckIsExpectedType<IListTypeDecoder>(decoder);
-
-         decoder.SkipValue(buffer, state);
-      }
-
-      private static Header ReadHeader(IProtonBuffer buffer, IDecoderState state, IListTypeDecoder listDecoder)
+      protected override Header ReadType(int count, IProtonBuffer buffer, IDecoder decoder, IDecoderState state)
       {
          Header result = new();
 
-         _ = listDecoder.ReadSize(buffer, state);
-         int count = listDecoder.ReadCount(buffer, state);
-
-         // Don't decode anything if things already look wrong.
-         if (count < MinHeaderListEntries)
-         {
-            throw new DecodeException("Not enough entries in Header list encoding: " + count);
-         }
-
-         if (count > MaxHeaderListEntries)
-         {
-            throw new DecodeException("To many entries in Header list encoding: " + count);
-         }
-
          for (int index = 0; index < count; ++index)
          {
             // Peek ahead and see if there is a null in the next slot, if so we don't call
@@ -118,54 +78,10 @@ namespace Apache.Qpid.Proton.Codec.Decoders.Messaging
          return result;
       }
 
-      public override object ReadValue(Stream stream, IStreamDecoderState state)
-      {
-         IStreamTypeDecoder decoder = state.Decoder.ReadNextTypeDecoder(stream, state);
-
-         return ReadHeader(stream, state, CheckIsExpectedTypeAndCast<IListTypeDecoder>(decoder));
-      }
-
-      public override Array ReadArrayElements(Stream stream, IStreamDecoderState state, int count)
-      {
-         IStreamTypeDecoder decoder = state.Decoder.ReadNextTypeDecoder(stream, state);
-         IListTypeDecoder listDecoder = CheckIsExpectedTypeAndCast<IListTypeDecoder>(decoder);
-
-         Header[] result = new Header[count];
-         for (int i = 0; i < count; ++i)
-         {
-            result[i] = ReadHeader(stream, state, listDecoder);
-         }
-
-         return result;
-      }
-
-      public override void SkipValue(Stream stream, IStreamDecoderState state)
-      {
-         IStreamTypeDecoder decoder = state.Decoder.ReadNextTypeDecoder(stream, state);
-
-         CheckIsExpectedType<IListTypeDecoder>(decoder);
-
-         decoder.SkipValue(stream, state);
-      }
-
-      private static Header ReadHeader(Stream stream, IStreamDecoderState state, IListTypeDecoder listDecoder)
+      protected override Header ReadType(int count, Stream stream, IStreamDecoder decoder, IStreamDecoderState state)
       {
          Header result = new();
 
-         _ = listDecoder.ReadSize(stream, state);
-         int count = listDecoder.ReadCount(stream, state);
-
-         // Don't decode anything if things already look wrong.
-         if (count < MinHeaderListEntries)
-         {
-            throw new DecodeException("Not enough entries in Header list encoding: " + count);
-         }
-
-         if (count > MaxHeaderListEntries)
-         {
-            throw new DecodeException("To many entries in Header list encoding: " + count);
-         }
-
          for (int index = 0; index < count; ++index)
          {
             // Peek ahead and see if there is a null in the next slot, if so we don't call
diff --git a/src/Proton/Codec/Decoders/Messaging/MessageAnnotationsTypeDecoder.cs b/src/Proton/Codec/Decoders/Messaging/MessageAnnotationsTypeDecoder.cs
index 29066ab..0a0d075 100644
--- a/src/Proton/Codec/Decoders/Messaging/MessageAnnotationsTypeDecoder.cs
+++ b/src/Proton/Codec/Decoders/Messaging/MessageAnnotationsTypeDecoder.cs
@@ -25,7 +25,7 @@ using Apache.Qpid.Proton.Types.Messaging;
 
 namespace Apache.Qpid.Proton.Codec.Decoders.Messaging
 {
-   public sealed class MessageAnnotationsTypeDecoder : AbstractDescribedTypeDecoder
+   public sealed class MessageAnnotationsTypeDecoder : AbstractDescribedMapTypeDecoder<Symbol>
    {
       public override Symbol DescriptorSymbol => MessageAnnotations.DescriptorSymbol;
 
@@ -33,141 +33,19 @@ namespace Apache.Qpid.Proton.Codec.Decoders.Messaging
 
       public override Type DecodesType => typeof(MessageAnnotations);
 
-      public override object ReadValue(IProtonBuffer buffer, IDecoderState state)
+      protected override MessageAnnotations CreateDescribed(IDictionary<Symbol, object> map)
       {
-         ITypeDecoder decoder = state.Decoder.ReadNextTypeDecoder(buffer, state);
-
-         if (decoder is NullTypeDecoder)
-         {
-            return new MessageAnnotations();
-         }
-
-         return new MessageAnnotations(ReadMap(buffer, state, CheckIsExpectedTypeAndCast<IMapTypeDecoder>(decoder)));
-      }
-
-      public override Array ReadArrayElements(IProtonBuffer buffer, IDecoderState state, int count)
-      {
-         ITypeDecoder decoder = state.Decoder.ReadNextTypeDecoder(buffer, state);
-         MessageAnnotations[] result = new MessageAnnotations[count];
-
-         if (decoder is NullTypeDecoder)
-         {
-            for (int i = 0; i < count; ++i)
-            {
-               result[i] = new MessageAnnotations();
-            }
-            return result;
-         }
-
-         for (int i = 0; i < count; ++i)
-         {
-            result[i] = new MessageAnnotations(
-               ReadMap(buffer, state, CheckIsExpectedTypeAndCast<IMapTypeDecoder>(decoder)));
-         }
-
-         return result;
-      }
-
-      public override void SkipValue(IProtonBuffer buffer, IDecoderState state)
-      {
-         ITypeDecoder decoder = state.Decoder.ReadNextTypeDecoder(buffer, state);
-         if (decoder is not NullTypeDecoder)
-         {
-            CheckIsExpectedType<IMapTypeDecoder>(decoder);
-            decoder.SkipValue(buffer, state);
-         }
-      }
-
-      private static IDictionary<Symbol, object> ReadMap(IProtonBuffer buffer, IDecoderState state, IMapTypeDecoder mapDecoder)
-      {
-         int size = mapDecoder.ReadSize(buffer, state);
-         int count = mapDecoder.ReadCount(buffer, state);
-
-         if (count > buffer.ReadableBytes)
-         {
-            throw new DecodeException(string.Format(
-                    "Map encoded size {0} is specified to be greater than the amount " +
-                    "of data available ({1})", size, buffer.ReadableBytes));
-         }
-
-         IDecoder decoder = state.Decoder;
-
-         // Count include both key and value so we must include that in the loop
-         IDictionary<Symbol, object> map = new Dictionary<Symbol, object>(count);
-         for (int i = 0; i < count / 2; i++)
-         {
-            Symbol key = decoder.ReadSymbol(buffer, state);
-            object value = decoder.ReadObject(buffer, state);
-
-            map.Add(key, value);
-         }
-
-         return map;
+         return new MessageAnnotations(map);
       }
 
-      public override object ReadValue(Stream stream, IStreamDecoderState state)
+      protected override Symbol ReadKey(IProtonBuffer buffer, IDecoder decoder, IDecoderState state)
       {
-         IStreamTypeDecoder decoder = state.Decoder.ReadNextTypeDecoder(stream, state);
-
-         if (decoder is NullTypeDecoder)
-         {
-            return new MessageAnnotations();
-         }
-
-         return new MessageAnnotations(ReadMap(stream, state, CheckIsExpectedTypeAndCast<IMapTypeDecoder>(decoder)));
+         return decoder.ReadSymbol(buffer, state);
       }
 
-      public override Array ReadArrayElements(Stream stream, IStreamDecoderState state, int count)
+      protected override Symbol ReadKey(Stream stream, IStreamDecoder decoder, IStreamDecoderState state)
       {
-         IStreamTypeDecoder decoder = state.Decoder.ReadNextTypeDecoder(stream, state);
-         MessageAnnotations[] result = new MessageAnnotations[count];
-
-         if (decoder is NullTypeDecoder)
-         {
-            for (int i = 0; i < count; ++i)
-            {
-               result[i] = new MessageAnnotations();
-            }
-            return result;
-         }
-
-         for (int i = 0; i < count; ++i)
-         {
-            result[i] = new MessageAnnotations(
-               ReadMap(stream, state, CheckIsExpectedTypeAndCast<IMapTypeDecoder>(decoder)));
-         }
-
-         return result;
-      }
-
-      public override void SkipValue(Stream stream, IStreamDecoderState state)
-      {
-         IStreamTypeDecoder decoder = state.Decoder.ReadNextTypeDecoder(stream, state);
-         if (decoder is not NullTypeDecoder)
-         {
-            CheckIsExpectedType<IMapTypeDecoder>(decoder);
-            decoder.SkipValue(stream, state);
-         }
-      }
-
-      private static IDictionary<Symbol, object> ReadMap(Stream stream, IStreamDecoderState state, IMapTypeDecoder mapDecoder)
-      {
-         _ = mapDecoder.ReadSize(stream, state);
-         int count = mapDecoder.ReadCount(stream, state);
-
-         IStreamDecoder decoder = state.Decoder;
-
-         // Count include both key and value so we must include that in the loop
-         IDictionary<Symbol, object> map = new Dictionary<Symbol, object>(count);
-         for (int i = 0; i < count / 2; i++)
-         {
-            Symbol key = decoder.ReadSymbol(stream, state);
-            object value = decoder.ReadObject(stream, state);
-
-            map.Add(key, value);
-         }
-
-         return map;
+         return decoder.ReadSymbol(stream, state);
       }
    }
 }
\ No newline at end of file
diff --git a/src/Proton/Codec/Decoders/Messaging/ModifiedTypeDecoder.cs b/src/Proton/Codec/Decoders/Messaging/ModifiedTypeDecoder.cs
index 313077e..842a21a 100644
--- a/src/Proton/Codec/Decoders/Messaging/ModifiedTypeDecoder.cs
+++ b/src/Proton/Codec/Decoders/Messaging/ModifiedTypeDecoder.cs
@@ -24,7 +24,7 @@ using Apache.Qpid.Proton.Types.Messaging;
 
 namespace Apache.Qpid.Proton.Codec.Decoders.Messaging
 {
-   public sealed class ModifiedTypeDecoder : AbstractDescribedTypeDecoder
+   public sealed class ModifiedTypeDecoder : AbstractDescribedListTypeDecoder
    {
       private static readonly int MinModifiedListEntries = 0;
       private static readonly int MaxModifiedListEntries = 3;
@@ -35,54 +35,14 @@ namespace Apache.Qpid.Proton.Codec.Decoders.Messaging
 
       public override Type DecodesType => typeof(Modified);
 
-      public override object ReadValue(IProtonBuffer buffer, IDecoderState state)
-      {
-         ITypeDecoder decoder = state.Decoder.ReadNextTypeDecoder(buffer, state);
-
-         return ReadModified(buffer, state, CheckIsExpectedTypeAndCast<IListTypeDecoder>(decoder));
-      }
-
-      public override Array ReadArrayElements(IProtonBuffer buffer, IDecoderState state, int count)
-      {
-         ITypeDecoder decoder = state.Decoder.ReadNextTypeDecoder(buffer, state);
-         IListTypeDecoder listDecoder = CheckIsExpectedTypeAndCast<IListTypeDecoder>(decoder);
-
-         Modified[] result = new Modified[count];
-         for (int i = 0; i < count; ++i)
-         {
-            result[i] = ReadModified(buffer, state, listDecoder);
-         }
+      protected override int MinListElements => MinModifiedListEntries;
 
-         return result;
-      }
+      protected override int MaxListElements => MaxModifiedListEntries;
 
-      public override void SkipValue(IProtonBuffer buffer, IDecoderState state)
-      {
-         ITypeDecoder decoder = state.Decoder.ReadNextTypeDecoder(buffer, state);
-
-         CheckIsExpectedType<IListTypeDecoder>(decoder);
-
-         decoder.SkipValue(buffer, state);
-      }
-
-      private static Modified ReadModified(IProtonBuffer buffer, IDecoderState state, IListTypeDecoder listDecoder)
+      protected override Modified ReadType(int count, IProtonBuffer buffer, IDecoder decoder, IDecoderState state)
       {
          Modified result = new();
 
-         _ = listDecoder.ReadSize(buffer, state);
-         int count = listDecoder.ReadCount(buffer, state);
-
-         // Don't decode anything if things already look wrong.
-         if (count < MinModifiedListEntries)
-         {
-            throw new DecodeException("Not enough entries in Modified list encoding: " + count);
-         }
-
-         if (count > MaxModifiedListEntries)
-         {
-            throw new DecodeException("To many entries in Modified list encoding: " + count);
-         }
-
          for (int index = 0; index < count; ++index)
          {
             // Peek ahead and see if there is a null in the next slot, if so we don't call
@@ -112,54 +72,10 @@ namespace Apache.Qpid.Proton.Codec.Decoders.Messaging
          return result;
       }
 
-      public override object ReadValue(Stream stream, IStreamDecoderState state)
-      {
-         IStreamTypeDecoder decoder = state.Decoder.ReadNextTypeDecoder(stream, state);
-
-         return ReadModified(stream, state, CheckIsExpectedTypeAndCast<IListTypeDecoder>(decoder));
-      }
-
-      public override Array ReadArrayElements(Stream stream, IStreamDecoderState state, int count)
-      {
-         IStreamTypeDecoder decoder = state.Decoder.ReadNextTypeDecoder(stream, state);
-         IListTypeDecoder listDecoder = CheckIsExpectedTypeAndCast<IListTypeDecoder>(decoder);
-
-         Modified[] result = new Modified[count];
-         for (int i = 0; i < count; ++i)
-         {
-            result[i] = ReadModified(stream, state, listDecoder);
-         }
-
-         return result;
-      }
-
-      public override void SkipValue(Stream stream, IStreamDecoderState state)
-      {
-         IStreamTypeDecoder decoder = state.Decoder.ReadNextTypeDecoder(stream, state);
-
-         CheckIsExpectedType<IListTypeDecoder>(decoder);
-
-         decoder.SkipValue(stream, state);
-      }
-
-      private static Modified ReadModified(Stream stream, IStreamDecoderState state, IListTypeDecoder listDecoder)
+      protected override Modified ReadType(int count, Stream stream, IStreamDecoder decoder, IStreamDecoderState state)
       {
          Modified result = new();
 
-         _ = listDecoder.ReadSize(stream, state);
-         int count = listDecoder.ReadCount(stream, state);
-
-         // Don't decode anything if things already look wrong.
-         if (count < MinModifiedListEntries)
-         {
-            throw new DecodeException("Not enough entries in Modified list encoding: " + count);
-         }
-
-         if (count > MaxModifiedListEntries)
-         {
-            throw new DecodeException("To many entries in Modified list encoding: " + count);
-         }
-
          for (int index = 0; index < count; ++index)
          {
             // Peek ahead and see if there is a null in the next slot, if so we don't call
diff --git a/src/Proton/Codec/Decoders/Messaging/PropertiesTypeDecoder.cs b/src/Proton/Codec/Decoders/Messaging/PropertiesTypeDecoder.cs
index eb4409f..fe3f830 100644
--- a/src/Proton/Codec/Decoders/Messaging/PropertiesTypeDecoder.cs
+++ b/src/Proton/Codec/Decoders/Messaging/PropertiesTypeDecoder.cs
@@ -24,7 +24,7 @@ using Apache.Qpid.Proton.Types.Messaging;
 
 namespace Apache.Qpid.Proton.Codec.Decoders.Messaging
 {
-   public sealed class PropertiesTypeDecoder : AbstractDescribedTypeDecoder
+   public sealed class PropertiesTypeDecoder : AbstractDescribedListTypeDecoder
    {
       private static readonly int MinPropertiesListEntries = 0;
       private static readonly int MaxPropertiesListEntries = 13;
@@ -35,54 +35,14 @@ namespace Apache.Qpid.Proton.Codec.Decoders.Messaging
 
       public override Type DecodesType => typeof(Properties);
 
-      public override object ReadValue(IProtonBuffer buffer, IDecoderState state)
-      {
-         ITypeDecoder decoder = state.Decoder.ReadNextTypeDecoder(buffer, state);
-
-         return ReadProperties(buffer, state, CheckIsExpectedTypeAndCast<IListTypeDecoder>(decoder));
-      }
-
-      public override Array ReadArrayElements(IProtonBuffer buffer, IDecoderState state, int count)
-      {
-         ITypeDecoder decoder = state.Decoder.ReadNextTypeDecoder(buffer, state);
-         IListTypeDecoder listDecoder = CheckIsExpectedTypeAndCast<IListTypeDecoder>(decoder);
-
-         Properties[] result = new Properties[count];
-         for (int i = 0; i < count; ++i)
-         {
-            result[i] = ReadProperties(buffer, state, listDecoder);
-         }
+      protected override int MinListElements => MinPropertiesListEntries;
 
-         return result;
-      }
+      protected override int MaxListElements => MaxPropertiesListEntries;
 
-      public override void SkipValue(IProtonBuffer buffer, IDecoderState state)
-      {
-         ITypeDecoder decoder = state.Decoder.ReadNextTypeDecoder(buffer, state);
-
-         CheckIsExpectedType<IListTypeDecoder>(decoder);
-
-         decoder.SkipValue(buffer, state);
-      }
-
-      private static Properties ReadProperties(IProtonBuffer buffer, IDecoderState state, IListTypeDecoder listDecoder)
+      protected override Properties ReadType(int count, IProtonBuffer buffer, IDecoder decoder, IDecoderState state)
       {
          Properties result = new();
 
-         _ = listDecoder.ReadSize(buffer, state);
-         int count = listDecoder.ReadCount(buffer, state);
-
-         // Don't decode anything if things already look wrong.
-         if (count < MinPropertiesListEntries)
-         {
-            throw new DecodeException("Not enough entries in Properties list encoding: " + count);
-         }
-
-         if (count > MaxPropertiesListEntries)
-         {
-            throw new DecodeException("To many entries in Properties list encoding: " + count);
-         }
-
          for (int index = 0; index < count; ++index)
          {
             // Peek ahead and see if there is a null in the next slot, if so we don't call
@@ -142,54 +102,10 @@ namespace Apache.Qpid.Proton.Codec.Decoders.Messaging
          return result;
       }
 
-      public override object ReadValue(Stream stream, IStreamDecoderState state)
-      {
-         IStreamTypeDecoder decoder = state.Decoder.ReadNextTypeDecoder(stream, state);
-
-         return ReadProperties(stream, state, CheckIsExpectedTypeAndCast<IListTypeDecoder>(decoder));
-      }
-
-      public override Array ReadArrayElements(Stream stream, IStreamDecoderState state, int count)
-      {
-         IStreamTypeDecoder decoder = state.Decoder.ReadNextTypeDecoder(stream, state);
-         IListTypeDecoder listDecoder = CheckIsExpectedTypeAndCast<IListTypeDecoder>(decoder);
-
-         Properties[] result = new Properties[count];
-         for (int i = 0; i < count; ++i)
-         {
-            result[i] = ReadProperties(stream, state, listDecoder);
-         }
-
-         return result;
-      }
-
-      public override void SkipValue(Stream stream, IStreamDecoderState state)
-      {
-         IStreamTypeDecoder decoder = state.Decoder.ReadNextTypeDecoder(stream, state);
-
-         CheckIsExpectedType<IListTypeDecoder>(decoder);
-
-         decoder.SkipValue(stream, state);
-      }
-
-      private static Properties ReadProperties(Stream stream, IStreamDecoderState state, IListTypeDecoder listDecoder)
+      protected override Properties ReadType(int count, Stream stream, IStreamDecoder decoder, IStreamDecoderState state)
       {
          Properties result = new();
 
-         _ = listDecoder.ReadSize(stream, state);
-         int count = listDecoder.ReadCount(stream, state);
-
-         // Don't decode anything if things already look wrong.
-         if (count < MinPropertiesListEntries)
-         {
-            throw new DecodeException("Not enough entries in Properties list encoding: " + count);
-         }
-
-         if (count > MaxPropertiesListEntries)
-         {
-            throw new DecodeException("To many entries in Properties list encoding: " + count);
-         }
-
          for (int index = 0; index < count; ++index)
          {
             // Peek ahead and see if there is a null in the next slot, if so we don't call
diff --git a/src/Proton/Codec/Decoders/Messaging/ReceivedTypeDecoder.cs b/src/Proton/Codec/Decoders/Messaging/ReceivedTypeDecoder.cs
index 5289887..3f0d578 100644
--- a/src/Proton/Codec/Decoders/Messaging/ReceivedTypeDecoder.cs
+++ b/src/Proton/Codec/Decoders/Messaging/ReceivedTypeDecoder.cs
@@ -24,7 +24,7 @@ using Apache.Qpid.Proton.Types.Messaging;
 
 namespace Apache.Qpid.Proton.Codec.Decoders.Messaging
 {
-   public sealed class ReceivedTypeDecoder : AbstractDescribedTypeDecoder
+   public sealed class ReceivedTypeDecoder : AbstractDescribedListTypeDecoder
    {
       private static readonly int RequiredReceivedListEntries = 2;
 
@@ -34,49 +34,14 @@ namespace Apache.Qpid.Proton.Codec.Decoders.Messaging
 
       public override Type DecodesType => typeof(Received);
 
-      public override object ReadValue(IProtonBuffer buffer, IDecoderState state)
-      {
-         ITypeDecoder decoder = state.Decoder.ReadNextTypeDecoder(buffer, state);
-
-         return ReadReceived(buffer, state, CheckIsExpectedTypeAndCast<IListTypeDecoder>(decoder));
-      }
-
-      public override Array ReadArrayElements(IProtonBuffer buffer, IDecoderState state, int count)
-      {
-         ITypeDecoder decoder = state.Decoder.ReadNextTypeDecoder(buffer, state);
-         IListTypeDecoder listDecoder = CheckIsExpectedTypeAndCast<IListTypeDecoder>(decoder);
-
-         Received[] result = new Received[count];
-         for (int i = 0; i < count; ++i)
-         {
-            result[i] = ReadReceived(buffer, state, listDecoder);
-         }
-
-         return result;
-      }
-
-      public override void SkipValue(IProtonBuffer buffer, IDecoderState state)
-      {
-         ITypeDecoder decoder = state.Decoder.ReadNextTypeDecoder(buffer, state);
+      protected override int MinListElements => RequiredReceivedListEntries;
 
-         CheckIsExpectedType<IListTypeDecoder>(decoder);
-
-         decoder.SkipValue(buffer, state);
-      }
+      protected override int MaxListElements => RequiredReceivedListEntries;
 
-      private static Received ReadReceived(IProtonBuffer buffer, IDecoderState state, IListTypeDecoder listDecoder)
+      protected override Received ReadType(int count, IProtonBuffer buffer, IDecoder decoder, IDecoderState state)
       {
          Received result = new();
 
-         _ = listDecoder.ReadSize(buffer, state);
-         int count = listDecoder.ReadCount(buffer, state);
-
-         // Don't decode anything if things already look wrong.
-         if (count != RequiredReceivedListEntries)
-         {
-            throw new DecodeException("Invalid number of entries in Received list encoding: " + count);
-         }
-
          for (int index = 0; index < count; ++index)
          {
             // Peek ahead and see if there is a null in the next slot, if so we don't call
@@ -103,49 +68,10 @@ namespace Apache.Qpid.Proton.Codec.Decoders.Messaging
          return result;
       }
 
-      public override object ReadValue(Stream stream, IStreamDecoderState state)
-      {
-         IStreamTypeDecoder decoder = state.Decoder.ReadNextTypeDecoder(stream, state);
-
-         return ReadReceived(stream, state, CheckIsExpectedTypeAndCast<IListTypeDecoder>(decoder));
-      }
-
-      public override Array ReadArrayElements(Stream stream, IStreamDecoderState state, int count)
-      {
-         IStreamTypeDecoder decoder = state.Decoder.ReadNextTypeDecoder(stream, state);
-         IListTypeDecoder listDecoder = CheckIsExpectedTypeAndCast<IListTypeDecoder>(decoder);
-
-         Received[] result = new Received[count];
-         for (int i = 0; i < count; ++i)
-         {
-            result[i] = ReadReceived(stream, state, listDecoder);
-         }
-
-         return result;
-      }
-
-      public override void SkipValue(Stream stream, IStreamDecoderState state)
-      {
-         IStreamTypeDecoder decoder = state.Decoder.ReadNextTypeDecoder(stream, state);
-
-         CheckIsExpectedType<IListTypeDecoder>(decoder);
-
-         decoder.SkipValue(stream, state);
-      }
-
-      private static Received ReadReceived(Stream stream, IStreamDecoderState state, IListTypeDecoder listDecoder)
+      protected override Received ReadType(int count, Stream stream, IStreamDecoder decoder, IStreamDecoderState state)
       {
          Received result = new();
 
-         _ = listDecoder.ReadSize(stream, state);
-         int count = listDecoder.ReadCount(stream, state);
-
-         // Don't decode anything if things already look wrong.
-         if (count != RequiredReceivedListEntries)
-         {
-            throw new DecodeException("Invalid number of entries in Received list encoding: " + count);
-         }
-
          for (int index = 0; index < count; ++index)
          {
             // Peek ahead and see if there is a null in the next slot, if so we don't call
diff --git a/src/Proton/Codec/Decoders/Messaging/RejectedTypeDecoder.cs b/src/Proton/Codec/Decoders/Messaging/RejectedTypeDecoder.cs
index 3106475..3e37539 100644
--- a/src/Proton/Codec/Decoders/Messaging/RejectedTypeDecoder.cs
+++ b/src/Proton/Codec/Decoders/Messaging/RejectedTypeDecoder.cs
@@ -25,7 +25,7 @@ using Apache.Qpid.Proton.Types.Transport;
 
 namespace Apache.Qpid.Proton.Codec.Decoders.Messaging
 {
-   public sealed class RejectedTypeDecoder : AbstractDescribedTypeDecoder
+   public sealed class RejectedTypeDecoder : AbstractDescribedListTypeDecoder
    {
       private static readonly int MinRejectedListEntries = 0;
       private static readonly int MaxRejectedListEntries = 1;
@@ -36,123 +36,29 @@ namespace Apache.Qpid.Proton.Codec.Decoders.Messaging
 
       public override Type DecodesType => typeof(Rejected);
 
-      public override object ReadValue(IProtonBuffer buffer, IDecoderState state)
-      {
-         ITypeDecoder decoder = state.Decoder.ReadNextTypeDecoder(buffer, state);
-
-         return ReadRejected(buffer, state, CheckIsExpectedTypeAndCast<IListTypeDecoder>(decoder));
-      }
-
-      public override Array ReadArrayElements(IProtonBuffer buffer, IDecoderState state, int count)
-      {
-         ITypeDecoder decoder = state.Decoder.ReadNextTypeDecoder(buffer, state);
-         IListTypeDecoder listDecoder = CheckIsExpectedTypeAndCast<IListTypeDecoder>(decoder);
-
-         Rejected[] result = new Rejected[count];
-         for (int i = 0; i < count; ++i)
-         {
-            result[i] = ReadRejected(buffer, state, listDecoder);
-         }
+      protected override int MinListElements => MinRejectedListEntries;
 
-         return result;
-      }
+      protected override int MaxListElements => MaxRejectedListEntries;
 
-      public override void SkipValue(IProtonBuffer buffer, IDecoderState state)
-      {
-         ITypeDecoder decoder = state.Decoder.ReadNextTypeDecoder(buffer, state);
-
-         CheckIsExpectedType<IListTypeDecoder>(decoder);
-
-         decoder.SkipValue(buffer, state);
-      }
-
-      private static Rejected ReadRejected(IProtonBuffer buffer, IDecoderState state, IListTypeDecoder listDecoder)
+      protected override Rejected ReadType(int count, IProtonBuffer buffer, IDecoder decoder, IDecoderState state)
       {
          Rejected result = new Rejected();
 
-         _ = listDecoder.ReadSize(buffer, state);
-         int count = listDecoder.ReadCount(buffer, state);
-
-         // Don't decode anything if things already look wrong.
-         if (count < MinRejectedListEntries)
-         {
-            throw new DecodeException("Not enough entries in Rejected list encoding: " + count);
-         }
-
-         if (count > MaxRejectedListEntries)
-         {
-            throw new DecodeException("To many entries in Rejected list encoding: " + count);
-         }
-
-         for (int index = 0; index < count; ++index)
-         {
-            switch (index)
-            {
-               case 0:
-                  result.Error = state.Decoder.ReadObject<ErrorCondition>(buffer, state);
-                  break;
-            }
-         }
-
-         return result;
-      }
-
-      public override object ReadValue(Stream stream, IStreamDecoderState state)
-      {
-         IStreamTypeDecoder decoder = state.Decoder.ReadNextTypeDecoder(stream, state);
-
-         return ReadRejected(stream, state, CheckIsExpectedTypeAndCast<IListTypeDecoder>(decoder));
-      }
-
-      public override Array ReadArrayElements(Stream stream, IStreamDecoderState state, int count)
-      {
-         IStreamTypeDecoder decoder = state.Decoder.ReadNextTypeDecoder(stream, state);
-         IListTypeDecoder listDecoder = CheckIsExpectedTypeAndCast<IListTypeDecoder>(decoder);
-
-         Rejected[] result = new Rejected[count];
-         for (int i = 0; i < count; ++i)
+         if (count == 1)
          {
-            result[i] = ReadRejected(stream, state, listDecoder);
+            result.Error = state.Decoder.ReadObject<ErrorCondition>(buffer, state);
          }
 
          return result;
       }
 
-      public override void SkipValue(Stream stream, IStreamDecoderState state)
-      {
-         IStreamTypeDecoder decoder = state.Decoder.ReadNextTypeDecoder(stream, state);
-
-         CheckIsExpectedType<IListTypeDecoder>(decoder);
-
-         decoder.SkipValue(stream, state);
-      }
-
-      private static Rejected ReadRejected(Stream stream, IStreamDecoderState state, IListTypeDecoder listDecoder)
+      protected override Rejected ReadType(int count, Stream stream, IStreamDecoder decoder, IStreamDecoderState state)
       {
          Rejected result = new Rejected();
 
-         _ = listDecoder.ReadSize(stream, state);
-         int count = listDecoder.ReadCount(stream, state);
-
-         // Don't decode anything if things already look wrong.
-         if (count < MinRejectedListEntries)
-         {
-            throw new DecodeException("Not enough entries in Rejected list encoding: " + count);
-         }
-
-         if (count > MaxRejectedListEntries)
-         {
-            throw new DecodeException("To many entries in Rejected list encoding: " + count);
-         }
-
-         for (int index = 0; index < count; ++index)
+         if (count == 1)
          {
-            switch (index)
-            {
-               case 0:
-                  result.Error = state.Decoder.ReadObject<ErrorCondition>(stream, state);
-                  break;
-            }
+            result.Error = state.Decoder.ReadObject<ErrorCondition>(stream, state);
          }
 
          return result;
diff --git a/src/Proton/Codec/Decoders/Messaging/ReleasedTypeDecoder.cs b/src/Proton/Codec/Decoders/Messaging/ReleasedTypeDecoder.cs
index 1ebcceb..885b0a3 100644
--- a/src/Proton/Codec/Decoders/Messaging/ReleasedTypeDecoder.cs
+++ b/src/Proton/Codec/Decoders/Messaging/ReleasedTypeDecoder.cs
@@ -24,7 +24,7 @@ using Apache.Qpid.Proton.Types.Messaging;
 
 namespace Apache.Qpid.Proton.Codec.Decoders.Messaging
 {
-   public sealed class ReleasedTypeDecoder : AbstractDescribedTypeDecoder
+   public sealed class ReleasedTypeDecoder : AbstractDescribedListTypeDecoder
    {
       public override Symbol DescriptorSymbol => Released.DescriptorSymbol;
 
@@ -32,78 +32,32 @@ namespace Apache.Qpid.Proton.Codec.Decoders.Messaging
 
       public override Type DecodesType => typeof(Released);
 
-      public override object ReadValue(IProtonBuffer buffer, IDecoderState state)
-      {
-         ITypeDecoder decoder = state.Decoder.ReadNextTypeDecoder(buffer, state);
+      protected override int MinListElements => 0;
 
-         CheckIsExpectedType<IListTypeDecoder>(decoder);
+      protected override int MaxListElements => 0;
 
+      protected sealed override Released ReadSingle(IProtonBuffer buffer, IDecoderState state, IListTypeDecoder decoder)
+      {
          decoder.SkipValue(buffer, state);
 
          return Released.Instance;
       }
 
-      public override Array ReadArrayElements(IProtonBuffer buffer, IDecoderState state, int count)
-      {
-         ITypeDecoder decoder = state.Decoder.ReadNextTypeDecoder(buffer, state);
-
-         CheckIsExpectedType<IListTypeDecoder>(decoder);
-
-         Released[] result = new Released[count];
-
-         for (int i = 0; i < count; ++i)
-         {
-            decoder.SkipValue(buffer, state);
-            result[i] = Released.Instance;
-         }
-
-         return result;
-      }
-
-      public override void SkipValue(IProtonBuffer buffer, IDecoderState state)
-      {
-         ITypeDecoder decoder = state.Decoder.ReadNextTypeDecoder(buffer, state);
-
-         CheckIsExpectedType<IListTypeDecoder>(decoder);
-
-         decoder.SkipValue(buffer, state);
-      }
-
-      public override object ReadValue(Stream stream, IStreamDecoderState state)
+      protected sealed override Released ReadSingle(Stream stream, IStreamDecoderState state, IListTypeDecoder decoder)
       {
-         IStreamTypeDecoder decoder = state.Decoder.ReadNextTypeDecoder(stream, state);
-
-         CheckIsExpectedType<IListTypeDecoder>(decoder);
-
          decoder.SkipValue(stream, state);
 
          return Released.Instance;
       }
 
-      public override Array ReadArrayElements(Stream stream, IStreamDecoderState state, int count)
+      protected override object ReadType(int count, IProtonBuffer buffer, IDecoder decoder, IDecoderState state)
       {
-         IStreamTypeDecoder decoder = state.Decoder.ReadNextTypeDecoder(stream, state);
-
-         CheckIsExpectedType<IListTypeDecoder>(decoder);
-
-         Released[] result = new Released[count];
-
-         for (int i = 0; i < count; ++i)
-         {
-            decoder.SkipValue(stream, state);
-            result[i] = Released.Instance;
-         }
-
-         return result;
+         throw new NotImplementedException("Should not be called for this AMQP type");
       }
 
-      public override void SkipValue(Stream stream, IStreamDecoderState state)
+      protected override object ReadType(int count, Stream stream, IStreamDecoder decoder, IStreamDecoderState state)
       {
-         IStreamTypeDecoder decoder = state.Decoder.ReadNextTypeDecoder(stream, state);
-
-         CheckIsExpectedType<IListTypeDecoder>(decoder);
-
-         decoder.SkipValue(stream, state);
+         throw new NotImplementedException("Should not be called for this AMQP type");
       }
    }
 }
\ No newline at end of file
diff --git a/src/Proton/Codec/Decoders/Messaging/SourceTypeDecoder.cs b/src/Proton/Codec/Decoders/Messaging/SourceTypeDecoder.cs
index 3a0586e..3ab9ff0 100644
--- a/src/Proton/Codec/Decoders/Messaging/SourceTypeDecoder.cs
+++ b/src/Proton/Codec/Decoders/Messaging/SourceTypeDecoder.cs
@@ -24,7 +24,7 @@ using Apache.Qpid.Proton.Types.Messaging;
 
 namespace Apache.Qpid.Proton.Codec.Decoders.Messaging
 {
-   public sealed class SourceTypeDecoder : AbstractDescribedTypeDecoder
+   public sealed class SourceTypeDecoder : AbstractDescribedListTypeDecoder
    {
       private static readonly int MinSourceListEntries = 0;
       private static readonly int MaxSourceListEntries = 11;
@@ -35,54 +35,14 @@ namespace Apache.Qpid.Proton.Codec.Decoders.Messaging
 
       public override Type DecodesType => typeof(Source);
 
-      public override object ReadValue(IProtonBuffer buffer, IDecoderState state)
-      {
-         ITypeDecoder decoder = state.Decoder.ReadNextTypeDecoder(buffer, state);
-
-         return ReadSource(buffer, state, CheckIsExpectedTypeAndCast<IListTypeDecoder>(decoder));
-      }
-
-      public override Array ReadArrayElements(IProtonBuffer buffer, IDecoderState state, int count)
-      {
-         ITypeDecoder decoder = state.Decoder.ReadNextTypeDecoder(buffer, state);
-         IListTypeDecoder listDecoder = CheckIsExpectedTypeAndCast<IListTypeDecoder>(decoder);
-
-         Source[] result = new Source[count];
-         for (int i = 0; i < count; ++i)
-         {
-            result[i] = ReadSource(buffer, state, listDecoder);
-         }
+      protected override int MinListElements => MinSourceListEntries;
 
-         return result;
-      }
+      protected override int MaxListElements => MaxSourceListEntries;
 
-      public override void SkipValue(IProtonBuffer buffer, IDecoderState state)
-      {
-         ITypeDecoder decoder = state.Decoder.ReadNextTypeDecoder(buffer, state);
-
-         CheckIsExpectedType<IListTypeDecoder>(decoder);
-
-         decoder.SkipValue(buffer, state);
-      }
-
-      private static Source ReadSource(IProtonBuffer buffer, IDecoderState state, IListTypeDecoder listDecoder)
+      protected override Source ReadType(int count, IProtonBuffer buffer, IDecoder decoder, IDecoderState state)
       {
          Source result = new();
 
-         _ = listDecoder.ReadSize(buffer, state);
-         int count = listDecoder.ReadCount(buffer, state);
-
-         // Don't decode anything if things already look wrong.
-         if (count < MinSourceListEntries)
-         {
-            throw new DecodeException("Not enough entries in Source list encoding: " + count);
-         }
-
-         if (count > MaxSourceListEntries)
-         {
-            throw new DecodeException("To many entries in Source list encoding: " + count);
-         }
-
          for (int index = 0; index < count; ++index)
          {
             switch (index)
@@ -128,54 +88,10 @@ namespace Apache.Qpid.Proton.Codec.Decoders.Messaging
          return result;
       }
 
-      public override object ReadValue(Stream stream, IStreamDecoderState state)
-      {
-         IStreamTypeDecoder decoder = state.Decoder.ReadNextTypeDecoder(stream, state);
-
-         return ReadSource(stream, state, CheckIsExpectedTypeAndCast<IListTypeDecoder>(decoder));
-      }
-
-      public override Array ReadArrayElements(Stream stream, IStreamDecoderState state, int count)
-      {
-         IStreamTypeDecoder decoder = state.Decoder.ReadNextTypeDecoder(stream, state);
-         IListTypeDecoder listDecoder = CheckIsExpectedTypeAndCast<IListTypeDecoder>(decoder);
-
-         Source[] result = new Source[count];
-         for (int i = 0; i < count; ++i)
-         {
-            result[i] = ReadSource(stream, state, listDecoder);
-         }
-
-         return result;
-      }
-
-      public override void SkipValue(Stream stream, IStreamDecoderState state)
-      {
-         IStreamTypeDecoder decoder = state.Decoder.ReadNextTypeDecoder(stream, state);
-
-         CheckIsExpectedType<IListTypeDecoder>(decoder);
-
-         decoder.SkipValue(stream, state);
-      }
-
-      private static Source ReadSource(Stream stream, IStreamDecoderState state, IListTypeDecoder listDecoder)
+      protected override Source ReadType(int count, Stream stream, IStreamDecoder decoder, IStreamDecoderState state)
       {
          Source result = new();
 
-         _ = listDecoder.ReadSize(stream, state);
-         int count = listDecoder.ReadCount(stream, state);
-
-         // Don't decode anything if things already look wrong.
-         if (count < MinSourceListEntries)
-         {
-            throw new DecodeException("Not enough entries in Source list encoding: " + count);
-         }
-
-         if (count > MaxSourceListEntries)
-         {
-            throw new DecodeException("To many entries in Source list encoding: " + count);
-         }
-
          for (int index = 0; index < count; ++index)
          {
             switch (index)
diff --git a/src/Proton/Codec/Decoders/Messaging/TargetTypeDecoder.cs b/src/Proton/Codec/Decoders/Messaging/TargetTypeDecoder.cs
index 1898ba5..80ba729 100644
--- a/src/Proton/Codec/Decoders/Messaging/TargetTypeDecoder.cs
+++ b/src/Proton/Codec/Decoders/Messaging/TargetTypeDecoder.cs
@@ -24,7 +24,7 @@ using Apache.Qpid.Proton.Types.Messaging;
 
 namespace Apache.Qpid.Proton.Codec.Decoders.Messaging
 {
-   public sealed class TargetTypeDecoder : AbstractDescribedTypeDecoder
+   public sealed class TargetTypeDecoder : AbstractDescribedListTypeDecoder
    {
       private static readonly int MinTargetListEntries = 0;
       private static readonly int MaxTargetListEntries = 7;
@@ -35,54 +35,14 @@ namespace Apache.Qpid.Proton.Codec.Decoders.Messaging
 
       public override Type DecodesType => typeof(Target);
 
-      public override object ReadValue(IProtonBuffer buffer, IDecoderState state)
-      {
-         ITypeDecoder decoder = state.Decoder.ReadNextTypeDecoder(buffer, state);
-
-         return ReadTarget(buffer, state, CheckIsExpectedTypeAndCast<IListTypeDecoder>(decoder));
-      }
-
-      public override Array ReadArrayElements(IProtonBuffer buffer, IDecoderState state, int count)
-      {
-         ITypeDecoder decoder = state.Decoder.ReadNextTypeDecoder(buffer, state);
-         IListTypeDecoder listDecoder = CheckIsExpectedTypeAndCast<IListTypeDecoder>(decoder);
-
-         Target[] result = new Target[count];
-         for (int i = 0; i < count; ++i)
-         {
-            result[i] = ReadTarget(buffer, state, listDecoder);
-         }
+      protected override int MinListElements => MinTargetListEntries;
 
-         return result;
-      }
+      protected override int MaxListElements => MaxTargetListEntries;
 
-      public override void SkipValue(IProtonBuffer buffer, IDecoderState state)
-      {
-         ITypeDecoder decoder = state.Decoder.ReadNextTypeDecoder(buffer, state);
-
-         CheckIsExpectedType<IListTypeDecoder>(decoder);
-
-         decoder.SkipValue(buffer, state);
-      }
-
-      private static Target ReadTarget(IProtonBuffer buffer, IDecoderState state, IListTypeDecoder listDecoder)
+      protected override Target ReadType(int count, IProtonBuffer buffer, IDecoder decoder, IDecoderState state)
       {
          Target result = new();
 
-         _ = listDecoder.ReadSize(buffer, state);
-         int count = listDecoder.ReadCount(buffer, state);
-
-         // Don't decode anything if things already look wrong.
-         if (count < MinTargetListEntries)
-         {
-            throw new DecodeException("Not enough entries in Target list encoding: " + count);
-         }
-
-         if (count > MaxTargetListEntries)
-         {
-            throw new DecodeException("To many entries in Target list encoding: " + count);
-         }
-
          for (int index = 0; index < count; ++index)
          {
             switch (index)
@@ -116,54 +76,10 @@ namespace Apache.Qpid.Proton.Codec.Decoders.Messaging
          return result;
       }
 
-      public override object ReadValue(Stream stream, IStreamDecoderState state)
-      {
-         IStreamTypeDecoder decoder = state.Decoder.ReadNextTypeDecoder(stream, state);
-
-         return ReadTarget(stream, state, CheckIsExpectedTypeAndCast<IListTypeDecoder>(decoder));
-      }
-
-      public override Array ReadArrayElements(Stream stream, IStreamDecoderState state, int count)
-      {
-         IStreamTypeDecoder decoder = state.Decoder.ReadNextTypeDecoder(stream, state);
-         IListTypeDecoder listDecoder = CheckIsExpectedTypeAndCast<IListTypeDecoder>(decoder);
-
-         Target[] result = new Target[count];
-         for (int i = 0; i < count; ++i)
-         {
-            result[i] = ReadTarget(stream, state, listDecoder);
-         }
-
-         return result;
-      }
-
-      public override void SkipValue(Stream stream, IStreamDecoderState state)
-      {
-         IStreamTypeDecoder decoder = state.Decoder.ReadNextTypeDecoder(stream, state);
-
-         CheckIsExpectedType<IListTypeDecoder>(decoder);
-
-         decoder.SkipValue(stream, state);
-      }
-
-      private static Target ReadTarget(Stream stream, IStreamDecoderState state, IListTypeDecoder listDecoder)
+      protected override Target ReadType(int count, Stream stream, IStreamDecoder decoder, IStreamDecoderState state)
       {
          Target result = new();
 
-         _ = listDecoder.ReadSize(stream, state);
-         int count = listDecoder.ReadCount(stream, state);
-
-         // Don't decode anything if things already look wrong.
-         if (count < MinTargetListEntries)
-         {
-            throw new DecodeException("Not enough entries in Target list encoding: " + count);
-         }
-
-         if (count > MaxTargetListEntries)
-         {
-            throw new DecodeException("To many entries in Target list encoding: " + count);
-         }
-
          for (int index = 0; index < count; ++index)
          {
             switch (index)
diff --git a/src/Proton/Codec/Decoders/Primitives/AbstractArrayTypeDecoder.cs b/src/Proton/Codec/Decoders/Primitives/AbstractArrayTypeDecoder.cs
index 5ddacfc..d1ee35e 100644
--- a/src/Proton/Codec/Decoders/Primitives/AbstractArrayTypeDecoder.cs
+++ b/src/Proton/Codec/Decoders/Primitives/AbstractArrayTypeDecoder.cs
@@ -25,76 +25,160 @@ namespace Apache.Qpid.Proton.Codec.Decoders.Primitives
    {
       public override Type DecodesType => typeof(Array);
 
+      public override bool IsArrayType => true;
+
       public override object ReadValue(IProtonBuffer buffer, IDecoderState state)
       {
-         int size = ReadSize(buffer, state);
-         int count = ReadCount(buffer, state);
+         return ReadValue(buffer, state, typeof(object));
+      }
 
-         if (EncodingCode == EncodingCodes.Array32)
-         {
-            size -= 8; // 4 bytes each for size and count;
-         }
-         else
-         {
-            size -= 2; // 1 byte each for size and count;
-         }
+      public object ReadValue(IProtonBuffer buffer, IDecoderState state, Type ofType)
+      {
+         state.IncreaseDepth();
 
-         if (size > buffer.ReadableBytes)
+         try
          {
-            throw new DecodeException(string.Format(
-                "Array size indicated {0} is greater than the amount of data available to decode ({1})",
-                size, buffer.ReadableBytes));
-         }
+            int size = ReadSize(buffer, state);
+
+            if (size > buffer.ReadableBytes || size < 0)
+            {
+               throw new DecodeException(string.Format(
+                  "Array size indicated {0} is greater than the amount of data available to decode ({1})",
+                  (uint) size, buffer.ReadableBytes));
+            }
 
-         ITypeDecoder decoder = state.Decoder.ReadNextTypeDecoder(buffer, state);
+            long startOffset = buffer.ReadOffset;
+            int count = ReadCount(buffer, state);
 
-         if (decoder is IPrimitiveArrayTypeDecoder arrayDecoder)
-         {
-            object[] array = new object[count];
-            for (int i = 0; i < count; i++)
+            ITypeDecoder decoder = state.Decoder.ReadNextTypeDecoder(buffer, state);
+
+            if (!decoder.DecodesType.IsAssignableTo(ofType))
+            {
+               throw new DecodeException(String.Format(
+                  "Unexpected type {0}. Expected a type assignable to {1}", decoder.DecodesType,ofType.Name));
+            }
+
+            Array array;
+
+            if (decoder is IPrimitiveArrayTypeDecoder arrayDecoder)
+            {
+               if (count > buffer.ReadableBytes || count < 0)
+               {
+                  throw new DecodeException(string.Format(
+                     "Array count indicated {0} is greater than the amount of data available to decode ({1})",
+                     (uint) count, buffer.ReadableBytes));
+               }
+
+               array = Array.CreateInstance(decoder.DecodesType, count);
+
+               for (int i = 0; i < count; i++)
+               {
+                  array.SetValue(arrayDecoder.ReadValue(buffer, state), i);
+               }
+            }
+            else
+            {
+               array = decoder.ReadArrayElements(buffer, state, count);
+            }
+
+            if (buffer.ReadOffset - startOffset != size)
             {
-               array[i] = arrayDecoder.ReadValue(buffer, state);
+               throw new DecodeException(
+                  "Encoded size indicates the array encoding should have been " + size +
+                  " bytes but the actual bytes read was " + (buffer.ReadOffset - startOffset));
             }
 
             return array;
          }
-         else
+         finally
          {
-            return decoder.ReadArrayElements(buffer, state, count);
+            state.DecreaseDepth();
          }
       }
 
       public override object ReadValue(Stream stream, IStreamDecoderState state)
       {
-         _ = ReadSize(stream, state);
-         int count = ReadCount(stream, state);
+         return ReadValue(stream, state, typeof(object));
+      }
 
-         IStreamTypeDecoder decoder = state.Decoder.ReadNextTypeDecoder(stream, state);
+      public object ReadValue(Stream stream, IStreamDecoderState state, Type ofType)
+      {
+         state.IncreaseDepth();
 
-         if (decoder is IPrimitiveArrayTypeDecoder arrayDecoder)
+         try
          {
-            object[] array = new object[count];
-            for (int i = 0; i < count; i++)
+            int size = ReadSize(stream, state);
+
+            if (size > state.MaxArraySize || size < 0)
             {
-               array[i] = arrayDecoder.ReadValue(stream, state);
+               throw new DecodeException(string.Format(
+                  "Array size indicated {0} is greater than the maximum encoded length to decode ({1})",
+                  (uint) size, state.MaxArraySize));
             }
 
-            return array;
+            int count = ReadCount(stream, state);
+
+            IStreamTypeDecoder decoder = state.Decoder.ReadNextTypeDecoder(stream, state);
+
+            if (!decoder.DecodesType.IsAssignableTo(ofType))
+            {
+               throw new DecodeException(String.Format(
+                  "Unexpected type {0}. Expected a type assignable to {1}", decoder.DecodesType,ofType.Name));
+            }
+
+            if (decoder is IPrimitiveArrayTypeDecoder arrayDecoder)
+            {
+               if (count > size || count < 0)
+               {
+                  throw new DecodeException(string.Format(
+                     "Array length indicated {0} is greater than the encoded array size ({1})", (uint) count, size));
+               }
+
+               object[] array = new object[count];
+               for (int i = 0; i < count; i++)
+               {
+                  array[i] = arrayDecoder.ReadValue(stream, state);
+               }
+
+               return array;
+            }
+            else
+            {
+               return decoder.ReadArrayElements(stream, state, count);
+            }
          }
-         else
+         finally
          {
-            return decoder.ReadArrayElements(stream, state, count);
+            state.DecreaseDepth();
          }
       }
 
       public override void SkipValue(IProtonBuffer buffer, IDecoderState state)
       {
-         buffer.SkipBytes(ReadSize(buffer, state));
+         int size = ReadSize(buffer, state);
+
+         if (size > buffer.ReadableBytes || size < 0)
+         {
+            throw new DecodeException(string.Format(
+               "Array size indicated {0} is greater than the amount of data available to decode ({1})",
+               (uint) size, buffer.ReadableBytes));
+         }
+
+         buffer.SkipBytes(size);
       }
 
       public override void SkipValue(Stream stream, IStreamDecoderState state)
       {
-         ProtonStreamReadUtils.SkipBytes(stream, ReadSize(stream, state));
+         int size = ReadSize(stream, state);
+
+         if (size > state.MaxArraySize || size < 0)
+         {
+            throw new DecodeException(string.Format(
+               "Array size indicated {0} is greater than the maximum encoded length to decode ({1})",
+               (uint) size, state.MaxArraySize));
+         }
+
+         ProtonStreamReadUtils.SkipBytes(stream, size);
       }
 
       protected abstract int ReadSize(IProtonBuffer buffer, IDecoderState state);
diff --git a/src/Proton/Codec/Decoders/Primitives/AbstractBinaryDecoder.cs b/src/Proton/Codec/Decoders/Primitives/AbstractBinaryDecoder.cs
index 6a1ef27..a38cfe3 100644
--- a/src/Proton/Codec/Decoders/Primitives/AbstractBinaryDecoder.cs
+++ b/src/Proton/Codec/Decoders/Primitives/AbstractBinaryDecoder.cs
@@ -29,11 +29,11 @@ namespace Apache.Qpid.Proton.Codec.Decoders.Primitives
       {
          int length = ReadSize(buffer, state);
 
-         if (length > buffer.ReadableBytes)
+         if (length > buffer.ReadableBytes || length < 0)
          {
             throw new DecodeException(
-                string.Format("Binary data size {0} is specified to be greater than the amount " +
-                              "of data available ({1})", length, buffer.ReadableBytes));
+               string.Format("Binary data size {0} is specified to be greater than the amount " +
+                             "of data available ({1})", (uint) length, buffer.ReadableBytes));
          }
 
          IProtonBuffer payload = buffer.Copy(buffer.ReadOffset, length);
@@ -45,10 +45,19 @@ namespace Apache.Qpid.Proton.Codec.Decoders.Primitives
 
       public override object ReadValue(Stream stream, IStreamDecoderState state)
       {
+         int length = ReadSize(stream, state);
+
+         if (length > state.MaxBinarySize || length < 0)
+         {
+            throw new DecodeException(String.Format(
+                  "Binary encoded length is specified to be greater than the maximum allowed length " +
+                  "l:(%d) m:(%d)", (uint) length, state.MaxBinarySize));
+         }
+
          try
          {
             return ProtonByteBufferAllocator.Instance.Wrap(
-               ProtonStreamReadUtils.ReadBytes(stream, ReadSize(stream, state)));
+               ProtonStreamReadUtils.ReadBytes(stream, length));
          }
          catch (IOException ex)
          {
@@ -60,11 +69,11 @@ namespace Apache.Qpid.Proton.Codec.Decoders.Primitives
       {
          int length = ReadSize(buffer, state);
 
-         if (length > buffer.ReadableBytes)
+         if (length > buffer.ReadableBytes || length < 0)
          {
             throw new DecodeException(
                 string.Format("Binary data size {0} is specified to be greater than the amount " +
-                              "of data available ({1})", length, buffer.ReadableBytes));
+                              "of data available ({1})", (uint) length, buffer.ReadableBytes));
          }
 
          buffer.SkipBytes(length);
@@ -72,18 +81,16 @@ namespace Apache.Qpid.Proton.Codec.Decoders.Primitives
 
       public override void SkipValue(Stream stream, IStreamDecoderState state)
       {
-         try
-         {
-            ProtonStreamReadUtils.SkipBytes(stream, ReadSize(stream, state));
-         }
-         catch (ArgumentOutOfRangeException ex)
-         {
-            throw new DecodeException("Error while skipping Binary payload bytes", ex);
-         }
-         catch (IOException ex)
+         int length = ReadSize(stream, state);
+
+         if (length > state.MaxBinarySize || length < 0)
          {
-            throw new DecodeException("Error while skipping Binary payload bytes", ex);
+            throw new DecodeException(String.Format(
+                  "Binary encoded length is specified to be greater than the maximum allowed length " +
+                  "l:(%d) m:(%d)", length, state.MaxBinarySize));
          }
+
+         ProtonStreamReadUtils.SkipBytes(stream, (uint) length);
       }
 
       #region BinaryTypeDecoder abstract methods
diff --git a/src/Proton/Codec/Decoders/Primitives/AbstractListTypeDecoder.cs b/src/Proton/Codec/Decoders/Primitives/AbstractListTypeDecoder.cs
index a48d08b..f149a9e 100644
--- a/src/Proton/Codec/Decoders/Primitives/AbstractListTypeDecoder.cs
+++ b/src/Proton/Codec/Decoders/Primitives/AbstractListTypeDecoder.cs
@@ -29,50 +29,91 @@ namespace Apache.Qpid.Proton.Codec.Decoders.Primitives
    /// </summary>
    public abstract class AbstractListTypeDecoder : AbstractPrimitiveTypeDecoder, IListTypeDecoder
    {
+      private static readonly int MAX_LIST_PREALLOCATION = 256;
+
       public override Type DecodesType => typeof(IList);
 
       public IList<T> ReadList<T>(IProtonBuffer buffer, IDecoderState state)
       {
-         int size = ReadSize(buffer, state);
+         state.IncreaseDepth();
 
-         // Ensure we do not allocate an array of size greater then the available data, otherwise there is a risk for an OOM error
-         if (size > buffer.ReadableBytes)
+         try
          {
-            throw new DecodeException(string.Format(
-                    "List element size {0} is specified to be greater than the amount " +
-                    "of data available ({1})", size, buffer.ReadableBytes));
+            int size = ReadSize(buffer, state);
+            long expectedEndPos = buffer.ReadOffset + size;
+
+            // Ensure we do not allocate an array of size greater then the available data, otherwise there is a risk for an OOM error
+            if (size > buffer.ReadableBytes || size < 0)
+            {
+               throw new DecodeException(string.Format(
+                     "List element size {0} is specified to be greater than the amount " +
+                     "of data available ({1})", (uint) size, buffer.ReadableBytes));
+            }
+
+            int count = ReadCount(buffer, state);
+
+            if (count > size || count < 0)
+            {
+               throw new DecodeException(String.Format(
+                     "List encoded element count is specified to be greater than the encoded size " +
+                     "s:(%d) c:(%d)", size, count));
+            }
+
+            IList<T> list = new List<T>(Math.Min(MAX_LIST_PREALLOCATION, count));
+            for (int i = 0; i < count; i++)
+            {
+               list.Add(state.Decoder.ReadObject<T>(buffer, state));
+            }
+
+            if (buffer.ReadOffset != expectedEndPos)
+            {
+               throw new DecodeException("List decoding did not read the expected amount of bytes: " + size);
+            }
+
+            return list;
          }
-
-         int count = ReadCount(buffer, state);
-
-         if (count > buffer.ReadableBytes)
+         finally
          {
-            throw new DecodeException(string.Format(
-                    "List encoded element count {0} is specified to be greater than the amount " +
-                    "of data available ({1})", count, buffer.ReadableBytes));
+            state.DecreaseDepth();
          }
-
-         IList<T> list = new List<T>(count);
-         for (int i = 0; i < count; i++)
-         {
-            list.Add(state.Decoder.ReadObject<T>(buffer, state));
-         }
-
-         return list;
       }
 
       public IList<T> ReadList<T>(Stream stream, IStreamDecoderState state)
       {
-         ReadSize(stream, state);
-         int count = ReadCount(stream, state);
+         state.IncreaseDepth();
 
-         IList<T> list = new List<T>(count);
-         for (int i = 0; i < count; i++)
+         try
          {
-            list.Add(state.Decoder.ReadObject<T>(stream, state));
+            int size = ReadSize(stream, state);
+
+            if (size > state.MaxListSize || size < 0)
+            {
+               throw new DecodeException(String.Format(
+                     "List encoding size is specified to be greater than the maximum allowed size " +
+                     "c:(%d) m:(%d)", (uint) size, state.MaxListSize));
+            }
+
+            int count = ReadCount(stream, state);
+
+            if (count > size || count < 0)
+            {
+               throw new DecodeException(String.Format(
+                     "List encoded element count is specified to be greater than the encoded size " +
+                     "s:(%d) c:(%d)", size, count));
+            }
+
+            IList<T> list = new List<T>(Math.Min(MAX_LIST_PREALLOCATION, count));
+            for (int i = 0; i < count; i++)
+            {
+               list.Add(state.Decoder.ReadObject<T>(stream, state));
+            }
+
+            return list;
+         }
+         finally
+         {
+            state.DecreaseDepth();
          }
-
-         return list;
       }
 
       public override object ReadValue(IProtonBuffer buffer, IDecoderState state)
@@ -87,18 +128,47 @@ namespace Apache.Qpid.Proton.Codec.Decoders.Primitives
 
       public override void SkipValue(IProtonBuffer buffer, IDecoderState state)
       {
-         buffer.SkipBytes(ReadSize(buffer, state));
+         int size = ReadSize(buffer, state);
+
+         if (size > buffer.ReadableBytes || size < 0)
+         {
+            throw new DecodeException(string.Format(
+                    "List element size {0} is specified to be greater than the amount " +
+                    "of data available ({1})", (uint) size, buffer.ReadableBytes));
+         }
+
+         state.IncreaseDepth();
+
+         try
+         {
+            buffer.SkipBytes(size);
+         }
+         finally
+         {
+            state.DecreaseDepth();
+         }
       }
 
       public override void SkipValue(Stream stream, IStreamDecoderState state)
       {
+         int size = ReadSize(stream, state);
+
+         if (size > state.MaxListSize || size < 0)
+         {
+            throw new DecodeException(String.Format(
+                  "List encoding size is specified to be greater than the maximum allowed size " +
+                  "c:(%d) m:(%d)", (uint) size, state.MaxListSize));
+         }
+
+         state.IncreaseDepth();
+
          try
          {
-            ProtonStreamReadUtils.SkipBytes(stream, ReadSize(stream, state));
+            ProtonStreamReadUtils.SkipBytes(stream, size);
          }
-         catch (IOException ex)
+         finally
          {
-            throw new DecodeException("Error while reading List payload bytes", ex);
+            state.DecreaseDepth();
          }
       }
 
diff --git a/src/Proton/Codec/Decoders/Primitives/AbstractMapTypeDecoder.cs b/src/Proton/Codec/Decoders/Primitives/AbstractMapTypeDecoder.cs
index e2d1136..3358039 100644
--- a/src/Proton/Codec/Decoders/Primitives/AbstractMapTypeDecoder.cs
+++ b/src/Proton/Codec/Decoders/Primitives/AbstractMapTypeDecoder.cs
@@ -29,63 +29,110 @@ namespace Apache.Qpid.Proton.Codec.Decoders.Primitives
    /// </summary>
    public abstract class AbstractMapTypeDecoder : AbstractPrimitiveTypeDecoder, IMapTypeDecoder
    {
+      private static readonly int MAX_MAP_PREALLOCATION = 256;
+
       public override Type DecodesType => typeof(IDictionary);
 
       public IDictionary<K, V> ReadMap<K, V>(IProtonBuffer buffer, IDecoderState state)
       {
-         int size = ReadSize(buffer, state);
-
-         // Ensure we do not allocate an array of size greater then the available data, otherwise there is a risk for an OOM error
-         if (size > buffer.ReadableBytes)
-         {
-            throw new DecodeException(string.Format(
-                    "Map element size {0} is specified to be greater than the amount " +
-                    "of data available ({1})", size, buffer.ReadableBytes));
-         }
+         state.IncreaseDepth();
 
-         int count = ReadCount(buffer, state);
-
-         if (count % 2 != 0)
+         try
          {
-            throw new DecodeException(string.Format(
-                "Map encoded number of elements {0} is not an even number.", count));
+            int size = ReadSize(buffer, state);
+            long expectedEndPos = buffer.ReadOffset + size;
+
+            // Ensure we do not allocate an array of size greater then the available data, otherwise there is a risk for an OOM error
+            if (size > buffer.ReadableBytes || size < 0)
+            {
+               throw new DecodeException(string.Format(
+                     "Map element size {0} is specified to be greater than the amount " +
+                     "of data available ({1})", (uint) size, buffer.ReadableBytes));
+            }
+
+            int count = ReadCount(buffer, state);
+
+            if (count > size || count < 0)
+            {
+               throw new DecodeException(string.Format(
+                     "Map element count {0} is specified to be greater than the amount " +
+                     "of data available ({1})", (uint) count, size));
+            }
+
+            if (count % 2 != 0)
+            {
+               throw new DecodeException(string.Format(
+                  "Map encoded number of elements {0} is not an even number.", count));
+            }
+
+            int elements = count / 2;
+            IDictionary<K, V> map = new Dictionary<K, V>(Math.Min(MAX_MAP_PREALLOCATION, elements));
+            for (int i = 0; i < elements; i++)
+            {
+               K key = state.Decoder.ReadObject<K>(buffer, state);
+               V value = state.Decoder.ReadObject<V>(buffer, state);
+
+               map.Add(key, value);
+            }
+
+            if (buffer.ReadOffset != expectedEndPos)
+            {
+               throw new DecodeException("Map decoding did not read the expected amount of bytes: " + size);
+            }
+
+            return map;
          }
-
-         // Count include both key and value so we must include that in the loop
-         IDictionary<K, V> map = new Dictionary<K, V>(count);
-         for (int i = 0; i < count / 2; i++)
+         finally
          {
-            K key = state.Decoder.ReadObject<K>(buffer, state);
-            V value = state.Decoder.ReadObject<V>(buffer, state);
-
-            map.Add(key, value);
+            state.DecreaseDepth();
          }
-
-         return map;
       }
 
       public IDictionary<K, V> ReadMap<K, V>(Stream stream, IStreamDecoderState state)
       {
-         ReadSize(stream, state);
-         int count = ReadCount(stream, state);
+         state.IncreaseDepth();
 
-         if (count % 2 != 0)
+         try
          {
-            throw new DecodeException(string.Format(
-                "Map encoded number of elements {0} is not an even number.", count));
+            int size = ReadSize(stream, state);
+
+            if (size > state.MaxMapSize || size < 0)
+            {
+               throw new DecodeException(string.Format(
+                     "Map encoding size {0} is specified to be greater than the maximum " +
+                     "allowed size value ({1})", (uint) size, state.MaxMapSize));
+            }
+
+            int count = ReadCount(stream, state);
+
+            if (count > size || count < 0)
+            {
+               throw new DecodeException(string.Format(
+                     "Map element count {0} is specified to be greater than the amount " +
+                     "of data available ({1})", (uint) count, size));
+            }
+
+            if (count % 2 != 0)
+            {
+               throw new DecodeException(string.Format(
+                  "Map encoded number of elements {0} is not an even number.", count));
+            }
+
+            int elements = count / 2;
+            IDictionary<K, V> map = new Dictionary<K, V>(Math.Min(MAX_MAP_PREALLOCATION, elements));
+            for (int i = 0; i < elements; i++)
+            {
+               K key = state.Decoder.ReadObject<K>(stream, state);
+               V value = state.Decoder.ReadObject<V>(stream, state);
+               map.Add(key, value);
+            }
+
+            return map;
          }
-
-         // Count include both key and value so we must include that in the loop
-         IDictionary<K, V> map = new Dictionary<K, V>(count);
-         for (int i = 0; i < count / 2; i++)
+         finally
          {
-            K key = state.Decoder.ReadObject<K>(stream, state);
-            V value = state.Decoder.ReadObject<V>(stream, state);
-
-            map.Add(key, value);
+            state.DecreaseDepth();
          }
-
-         return map;
       }
 
       public override object ReadValue(IProtonBuffer buffer, IDecoderState state)
@@ -100,18 +147,48 @@ namespace Apache.Qpid.Proton.Codec.Decoders.Primitives
 
       public override void SkipValue(IProtonBuffer buffer, IDecoderState state)
       {
-         buffer.SkipBytes(ReadSize(buffer, state));
+         int size = ReadSize(buffer, state);
+
+         // Ensure we do not allocate an array of size greater then the available data, otherwise there is a risk for an OOM error
+         if (size > buffer.ReadableBytes || size < 0)
+         {
+            throw new DecodeException(string.Format(
+                    "Map element size {0} is specified to be greater than the amount " +
+                    "of data available ({1})", (uint) size, buffer.ReadableBytes));
+         }
+
+         state.IncreaseDepth();
+
+         try
+         {
+            buffer.SkipBytes(size);
+         }
+         finally
+         {
+            state.DecreaseDepth();
+         }
       }
 
       public override void SkipValue(Stream stream, IStreamDecoderState state)
       {
+         int size = ReadSize(stream, state);
+
+         if (size > state.MaxMapSize || size < 0)
+         {
+            throw new DecodeException(string.Format(
+                  "Map encoding size {0} is specified to be greater than the maximum " +
+                  "allowed size value ({1})", (uint) size, state.MaxMapSize));
+         }
+
+         state.IncreaseDepth();
+
          try
          {
-            ProtonStreamReadUtils.SkipBytes(stream, ReadSize(stream, state));
+            ProtonStreamReadUtils.SkipBytes(stream, size);
          }
-         catch (IOException ex)
+         finally
          {
-            throw new DecodeException("Error while reading List payload bytes", ex);
+            state.DecreaseDepth();
          }
       }
 
diff --git a/src/Proton/Codec/Decoders/Primitives/AbstractStringTypeDecoder.cs b/src/Proton/Codec/Decoders/Primitives/AbstractStringTypeDecoder.cs
index 579c539..16ab578 100644
--- a/src/Proton/Codec/Decoders/Primitives/AbstractStringTypeDecoder.cs
+++ b/src/Proton/Codec/Decoders/Primitives/AbstractStringTypeDecoder.cs
@@ -30,11 +30,11 @@ namespace Apache.Qpid.Proton.Codec.Decoders.Primitives
       {
          int length = ReadSize(buffer, state);
 
-         if (length > buffer.ReadableBytes)
+         if (length > buffer.ReadableBytes || length < 0)
          {
             throw new DecodeException(string.Format(
-                    "String encoded size {0} is specified to be greater than the amount " +
-                    "of data available ({1})", length, buffer.ReadableBytes));
+                  "String encoded size {0} is specified to be greater than the amount " +
+                  "of data available ({1})", (uint) length, buffer.ReadableBytes));
          }
 
          if (length != 0)
@@ -51,6 +51,13 @@ namespace Apache.Qpid.Proton.Codec.Decoders.Primitives
       {
          int length = ReadSize(stream, state);
 
+         if (length > state.MaxStringSize || length < 0)
+         {
+            throw new DecodeException(string.Format(
+                  "String encoded size {0} is specified to be greater than the amount " +
+                  "allowed by configuration ({1})", (uint) length, state.MaxStringSize));
+         }
+
          if (length != 0)
          {
             return state.DecodeUtf8(stream, length);
@@ -63,19 +70,30 @@ namespace Apache.Qpid.Proton.Codec.Decoders.Primitives
 
       public override void SkipValue(IProtonBuffer buffer, IDecoderState state)
       {
-         buffer.SkipBytes(ReadSize(buffer, state));
+         int length = ReadSize(buffer, state);
+
+         if (length > buffer.ReadableBytes || length < 0)
+         {
+            throw new DecodeException(string.Format(
+                  "String encoded size {0} is specified to be greater than the amount " +
+                  "of data available ({1})", (uint) length, buffer.ReadableBytes));
+         }
+
+         buffer.SkipBytes(length);
       }
 
       public override void SkipValue(Stream stream, IStreamDecoderState state)
       {
-         try
-         {
-            ProtonStreamReadUtils.SkipBytes(stream, ReadSize(stream, state));
-         }
-         catch (IOException ex)
+         int length = ReadSize(stream, state);
+
+         if (length > state.MaxStringSize || length < 0)
          {
-            throw new DecodeException("Error while reading String payload bytes", ex);
+            throw new DecodeException(string.Format(
+                  "String encoded size {0} is specified to be greater than the amount " +
+                  "allowed by configuration ({1})", (uint) length, state.MaxStringSize));
          }
+
+         ProtonStreamReadUtils.SkipBytes(stream, length);
       }
 
       protected abstract int ReadSize(IProtonBuffer buffer, IDecoderState state);
diff --git a/src/Proton/Codec/Decoders/Primitives/AbstractSymbolTypeDecoder.cs b/src/Proton/Codec/Decoders/Primitives/AbstractSymbolTypeDecoder.cs
index 4fa7bf2..f4af667 100644
--- a/src/Proton/Codec/Decoders/Primitives/AbstractSymbolTypeDecoder.cs
+++ b/src/Proton/Codec/Decoders/Primitives/AbstractSymbolTypeDecoder.cs
@@ -39,18 +39,18 @@ namespace Apache.Qpid.Proton.Codec.Decoders.Primitives
             return Symbol.Lookup("");
          }
 
-         if (length > buffer.ReadableBytes)
+         if (length > buffer.ReadableBytes || length < 0)
          {
             throw new DecodeException(string.Format(
                     "Symbol encoded size {0} is specified to be greater than the amount " +
-                    "of data available {1}", length, buffer.ReadableBytes));
+                    "of data available {1}", (uint) length, buffer.ReadableBytes));
          }
 
          IProtonBuffer symbolBuffer = buffer.Copy(buffer.ReadOffset, length);
 
          buffer.SkipBytes(length);
 
-         return Symbol.Lookup(symbolBuffer);
+         return SymbolLookup(symbolBuffer, true);
       }
 
       public override Symbol ReadValue(Stream stream, IStreamDecoderState state)
@@ -62,6 +62,13 @@ namespace Apache.Qpid.Proton.Codec.Decoders.Primitives
             return Symbol.Lookup("");
          }
 
+         if (length > state.MaxSymbolSize || length < 0)
+         {
+            throw new DecodeException(String.Format(
+                  "Binary encoded length is specified to be greater than the maximum allowed length " +
+                  "l:(%d) m:(%d)", (uint) length, state.MaxSymbolSize));
+         }
+
          byte[] symbolBytes;
 
          try
@@ -73,29 +80,52 @@ namespace Apache.Qpid.Proton.Codec.Decoders.Primitives
             throw new DecodeException("Error while reading Symbol payload bytes", ex);
          }
 
-         return Symbol.Lookup(ProtonByteBufferAllocator.Instance.Wrap(symbolBytes));
+         return SymbolLookup(ProtonByteBufferAllocator.Instance.Wrap(symbolBytes), false);
       }
 
       public override void SkipValue(IProtonBuffer buffer, IDecoderState state)
       {
-         buffer.SkipBytes(ReadSize(buffer, state));
+         int length = ReadSize(buffer, state);
+
+         if (length > buffer.ReadableBytes || length < 0)
+         {
+            throw new DecodeException(string.Format(
+                  "Symbol encoded size {0} is specified to be greater than the amount " +
+                  "of data available {1}", (uint) length, buffer.ReadableBytes));
+         }
+
+         buffer.SkipBytes(length);
       }
 
       public override void SkipValue(Stream stream, IStreamDecoderState state)
       {
-         try
-         {
-            ProtonStreamReadUtils.SkipBytes(stream, ReadSize(stream, state));
-         }
-         catch (IOException ex)
+         int length = ReadSize(stream, state);
+
+         if (length > state.MaxSymbolSize || length < 0)
          {
-            throw new DecodeException("Error while reading String payload bytes", ex);
+            throw new DecodeException(String.Format(
+                  "Binary encoded length is specified to be greater than the maximum allowed length " +
+                  "l:(%d) m:(%d)", (uint) length, state.MaxSymbolSize));
          }
+
+         ProtonStreamReadUtils.SkipBytes(stream, length);
       }
 
       protected abstract int ReadSize(IProtonBuffer buffer, IDecoderState state);
 
       protected abstract int ReadSize(Stream stream, IStreamDecoderState state);
 
+      /// <summary>
+      /// Gets a singleton Symbol instance that matches the given IProtonBuffer byte view
+      /// of the Symbol. A subclass can override this to produce the Symbol singleton from
+      /// a source other than the default which is the general symbol cache.
+      /// </summary>
+      /// <param name="buffer"></param>
+      /// <param name="copyOnCreate"></param>
+      /// <returns>A Symbol object that is backed by the value in the buffer</returns>
+      protected virtual Symbol SymbolLookup(IProtonBuffer buffer, bool copyOnCreate)
+      {
+         return Symbol.Lookup(buffer, copyOnCreate);
+      }
    }
 }
\ No newline at end of file
diff --git a/src/Proton/Codec/Decoders/Primitives/BooleanFalseTypeDecoder.cs b/src/Proton/Codec/Decoders/Primitives/BooleanFalseTypeDecoder.cs
index aee6d1a..df69626 100644
--- a/src/Proton/Codec/Decoders/Primitives/BooleanFalseTypeDecoder.cs
+++ b/src/Proton/Codec/Decoders/Primitives/BooleanFalseTypeDecoder.cs
@@ -27,6 +27,8 @@ namespace Apache.Qpid.Proton.Codec.Decoders.Primitives
 
       public override Type DecodesType => typeof(bool);
 
+      public override bool IsZeroWidth => true;
+
       public override object ReadValue(IProtonBuffer buffer, IDecoderState state) => false;
 
       public override object ReadValue(Stream stream, IStreamDecoderState state) => false;
@@ -38,5 +40,25 @@ namespace Apache.Qpid.Proton.Codec.Decoders.Primitives
       public override void SkipValue(Stream stream, IStreamDecoderState state)
       {
       }
+
+      protected override void ValidateArrayPreconditions(IProtonBuffer buffer, IDecoderState state, int count)
+      {
+         if (count > state.MaxZeroWidthArrayElements || count < 0)
+         {
+            throw new DecodeException(string.Format(
+               "Array size indicated {0} is greater than the amount of elements allowed for zero sized primitives ({1})",
+               (uint) count, state.MaxZeroWidthArrayElements));
+         }
+      }
+
+      protected override void ValidateArrayPreconditions(Stream stream, IStreamDecoderState state, int count)
+      {
+         if (count > state.MaxZeroWidthArrayElements || count < 0)
+         {
+            throw new DecodeException(string.Format(
+               "Array size indicated {0} is greater than the amount of elements allowed for zero sized primitives ({1})",
+               (uint) count, state.MaxZeroWidthArrayElements));
+         }
+      }
    }
 }
\ No newline at end of file
diff --git a/src/Proton/Codec/Decoders/Primitives/BooleanTrueTypeDecoder.cs b/src/Proton/Codec/Decoders/Primitives/BooleanTrueTypeDecoder.cs
index d56c57b..9489d89 100644
--- a/src/Proton/Codec/Decoders/Primitives/BooleanTrueTypeDecoder.cs
+++ b/src/Proton/Codec/Decoders/Primitives/BooleanTrueTypeDecoder.cs
@@ -27,6 +27,8 @@ namespace Apache.Qpid.Proton.Codec.Decoders.Primitives
 
       public override Type DecodesType => typeof(bool);
 
+      public override bool IsZeroWidth => true;
+
       public override object ReadValue(IProtonBuffer buffer, IDecoderState state) => true;
 
       public override object ReadValue(Stream stream, IStreamDecoderState state) => true;
@@ -38,5 +40,25 @@ namespace Apache.Qpid.Proton.Codec.Decoders.Primitives
       public override void SkipValue(Stream stream, IStreamDecoderState state)
       {
       }
+
+      protected override void ValidateArrayPreconditions(IProtonBuffer buffer, IDecoderState state, int count)
+      {
+         if (count > state.MaxZeroWidthArrayElements || count < 0)
+         {
+            throw new DecodeException(string.Format(
+               "Array size indicated {0} is greater than the amount of elements allowed for zero sized primitives ({1})",
+               (uint) count, state.MaxZeroWidthArrayElements));
+         }
+      }
+
+      protected override void ValidateArrayPreconditions(Stream stream, IStreamDecoderState state, int count)
+      {
+         if (count > state.MaxZeroWidthArrayElements || count < 0)
+         {
+            throw new DecodeException(string.Format(
+               "Array size indicated {0} is greater than the amount of elements allowed for zero sized primitives ({1})",
+               (uint) count, state.MaxZeroWidthArrayElements));
+         }
+      }
    }
 }
\ No newline at end of file
diff --git a/src/Proton/Codec/Decoders/Primitives/List0TypeDecoder.cs b/src/Proton/Codec/Decoders/Primitives/List0TypeDecoder.cs
index edfd17f..6f9eb5f 100644
--- a/src/Proton/Codec/Decoders/Primitives/List0TypeDecoder.cs
+++ b/src/Proton/Codec/Decoders/Primitives/List0TypeDecoder.cs
@@ -29,6 +29,8 @@ namespace Apache.Qpid.Proton.Codec.Decoders.Primitives
 
       public override Type DecodesType => typeof(IList);
 
+      public override bool IsZeroWidth => true;
+
       public int ReadCount(IProtonBuffer buffer, IDecoderState state)
       {
          return 0;
@@ -41,12 +43,30 @@ namespace Apache.Qpid.Proton.Codec.Decoders.Primitives
 
       public IList<T> ReadList<T>(IProtonBuffer buffer, IDecoderState state)
       {
-         return (IList<T>) Array.Empty<T>();
+         state.IncreaseDepth();
+
+         try
+         {
+            return (IList<T>)Array.Empty<T>();
+         }
+         finally
+         {
+            state.DecreaseDepth();
+         }
       }
 
       public IList<T> ReadList<T>(Stream stream, IStreamDecoderState state)
       {
-         return (IList<T>) Array.Empty<T>();
+         state.IncreaseDepth();
+
+         try
+         {
+            return (IList<T>)Array.Empty<T>();
+         }
+         finally
+         {
+            state.DecreaseDepth();
+         }
       }
 
       public int ReadSize(IProtonBuffer buffer, IDecoderState state)
@@ -61,20 +81,74 @@ namespace Apache.Qpid.Proton.Codec.Decoders.Primitives
 
       public override object ReadValue(IProtonBuffer buffer, IDecoderState state)
       {
-         return (IList) Array.Empty<object>();
+         state.IncreaseDepth();
+
+         try
+         {
+            return (IList) Array.Empty<object>();
+         }
+         finally
+         {
+            state.DecreaseDepth();
+         }
       }
 
       public override object ReadValue(Stream stream, IStreamDecoderState state)
       {
-         return (IList) Array.Empty<object>();
+         state.IncreaseDepth();
+
+         try
+         {
+            return (IList) Array.Empty<object>();
+         }
+         finally
+         {
+            state.DecreaseDepth();
+         }
       }
 
       public override void SkipValue(IProtonBuffer buffer, IDecoderState state)
       {
+         try
+         {
+            state.IncreaseDepth();
+         }
+         finally
+         {
+            state.DecreaseDepth();
+         }
       }
 
       public override void SkipValue(Stream stream, IStreamDecoderState state)
       {
+         try
+         {
+            state.IncreaseDepth();
+         }
+         finally
+         {
+            state.DecreaseDepth();
+         }
+      }
+
+      protected override void ValidateArrayPreconditions(IProtonBuffer buffer, IDecoderState state, int count)
+      {
+         if (count > state.MaxZeroWidthArrayElements || count < 0)
+         {
+            throw new DecodeException(string.Format(
+               "Array size indicated {0} is greater than the amount of elements allowed for zero sized primitives ({1})",
+               (uint) count, state.MaxZeroWidthArrayElements));
+         }
+      }
+
+      protected override void ValidateArrayPreconditions(Stream stream, IStreamDecoderState state, int count)
+      {
+         if (count > state.MaxZeroWidthArrayElements || count < 0)
+         {
+            throw new DecodeException(string.Format(
+               "Array size indicated {0} is greater than the amount of elements allowed for zero sized primitives ({1})",
+               (uint) count, state.MaxZeroWidthArrayElements));
+         }
       }
    }
 }
\ No newline at end of file
diff --git a/src/Proton/Codec/Decoders/Primitives/NullTypeDecoder.cs b/src/Proton/Codec/Decoders/Primitives/NullTypeDecoder.cs
index dca1ea2..c99837c 100644
--- a/src/Proton/Codec/Decoders/Primitives/NullTypeDecoder.cs
+++ b/src/Proton/Codec/Decoders/Primitives/NullTypeDecoder.cs
@@ -27,6 +27,8 @@ namespace Apache.Qpid.Proton.Codec.Decoders.Primitives
 
       public override Type DecodesType => typeof(void);
 
+      public override bool IsZeroWidth => true;
+
       public override object ReadValue(IProtonBuffer buffer, IDecoderState state)
       {
          return null;
@@ -44,5 +46,39 @@ namespace Apache.Qpid.Proton.Codec.Decoders.Primitives
       public override void SkipValue(Stream stream, IStreamDecoderState state)
       {
       }
+
+      public override Array ReadArrayElements(IProtonBuffer buffer, IDecoderState state, int count)
+      {
+         ValidateArrayPreconditions(buffer, state, count);
+
+         return new object[count];
+      }
+
+      public override Array ReadArrayElements(Stream stream, IStreamDecoderState state, int count)
+      {
+         ValidateArrayPreconditions(stream, state, count);
+
+         return new object[count];
+      }
+
+      protected override void ValidateArrayPreconditions(IProtonBuffer buffer, IDecoderState state, int count)
+      {
+         if (count > state.MaxZeroWidthArrayElements || count < 0)
+         {
+            throw new DecodeException(string.Format(
+               "Array size indicated {0} is greater than the amount of elements allowed for zero sized primitives ({1})",
+               (uint) count, state.MaxZeroWidthArrayElements));
+         }
+      }
+
+      protected override void ValidateArrayPreconditions(Stream stream, IStreamDecoderState state, int count)
+      {
+         if (count > state.MaxZeroWidthArrayElements || count < 0)
+         {
+            throw new DecodeException(string.Format(
+               "Array size indicated {0} is greater than the amount of elements allowed for zero sized primitives ({1})",
+               (uint) count, state.MaxZeroWidthArrayElements));
+         }
+      }
    }
 }
\ No newline at end of file
diff --git a/src/Proton/Codec/Decoders/Primitives/NullTypeDecoder.cs b/src/Proton/Codec/Decoders/Primitives/SaslSymbol32TypeDecoder.cs
similarity index 68%
copy from src/Proton/Codec/Decoders/Primitives/NullTypeDecoder.cs
copy to src/Proton/Codec/Decoders/Primitives/SaslSymbol32TypeDecoder.cs
index dca1ea2..9f5ffc6 100644
--- a/src/Proton/Codec/Decoders/Primitives/NullTypeDecoder.cs
+++ b/src/Proton/Codec/Decoders/Primitives/SaslSymbol32TypeDecoder.cs
@@ -15,34 +15,29 @@
  * limitations under the License.
  */
 
-using System;
 using System.IO;
 using Apache.Qpid.Proton.Buffer;
+using Apache.Qpid.Proton.Types;
 
 namespace Apache.Qpid.Proton.Codec.Decoders.Primitives
 {
-   public sealed class NullTypeDecoder : AbstractPrimitiveTypeDecoder
+   public sealed class SaslSymbol32TypeDecoder : AbstractSymbolTypeDecoder
    {
-      public override EncodingCodes EncodingCode => EncodingCodes.Null;
+      public override EncodingCodes EncodingCode => EncodingCodes.Sym32;
 
-      public override Type DecodesType => typeof(void);
-
-      public override object ReadValue(IProtonBuffer buffer, IDecoderState state)
-      {
-         return null;
-      }
-
-      public override object ReadValue(Stream stream, IStreamDecoderState state)
+      protected override int ReadSize(IProtonBuffer buffer, IDecoderState state)
       {
-         return null;
+         return buffer.ReadInt();
       }
 
-      public override void SkipValue(IProtonBuffer buffer, IDecoderState state)
+      protected override int ReadSize(Stream stream, IStreamDecoderState state)
       {
+         return ProtonStreamReadUtils.ReadInt(stream);
       }
 
-      public override void SkipValue(Stream stream, IStreamDecoderState state)
+      protected override Symbol SymbolLookup(IProtonBuffer buffer, bool copyOnCreate)
       {
+         return Symbol.SaslLookup(buffer, copyOnCreate);
       }
    }
 }
\ No newline at end of file
diff --git a/src/Proton/Codec/Decoders/Primitives/NullTypeDecoder.cs b/src/Proton/Codec/Decoders/Primitives/SaslSymbol8TypeDecoder.cs
similarity index 68%
copy from src/Proton/Codec/Decoders/Primitives/NullTypeDecoder.cs
copy to src/Proton/Codec/Decoders/Primitives/SaslSymbol8TypeDecoder.cs
index dca1ea2..bd85378 100644
--- a/src/Proton/Codec/Decoders/Primitives/NullTypeDecoder.cs
+++ b/src/Proton/Codec/Decoders/Primitives/SaslSymbol8TypeDecoder.cs
@@ -15,34 +15,29 @@
  * limitations under the License.
  */
 
-using System;
 using System.IO;
 using Apache.Qpid.Proton.Buffer;
+using Apache.Qpid.Proton.Types;
 
 namespace Apache.Qpid.Proton.Codec.Decoders.Primitives
 {
-   public sealed class NullTypeDecoder : AbstractPrimitiveTypeDecoder
+   public sealed class SaslSymbol8TypeDecoder : AbstractSymbolTypeDecoder
    {
-      public override EncodingCodes EncodingCode => EncodingCodes.Null;
+      public override EncodingCodes EncodingCode => EncodingCodes.Sym8;
 
-      public override Type DecodesType => typeof(void);
-
-      public override object ReadValue(IProtonBuffer buffer, IDecoderState state)
-      {
-         return null;
-      }
-
-      public override object ReadValue(Stream stream, IStreamDecoderState state)
+      protected override int ReadSize(IProtonBuffer buffer, IDecoderState state)
       {
-         return null;
+         return buffer.ReadUnsignedByte();
       }
 
-      public override void SkipValue(IProtonBuffer buffer, IDecoderState state)
+      protected override int ReadSize(Stream stream, IStreamDecoderState state)
       {
+         return ProtonStreamReadUtils.ReadUnsignedByte(stream);
       }
 
-      public override void SkipValue(Stream stream, IStreamDecoderState state)
+      protected override Symbol SymbolLookup(IProtonBuffer buffer, bool copyOnCreate)
       {
+         return Symbol.SaslLookup(buffer, copyOnCreate);
       }
    }
 }
\ No newline at end of file
diff --git a/src/Proton/Codec/Decoders/Primitives/UnsignedInteger0TypeDecoder.cs b/src/Proton/Codec/Decoders/Primitives/UnsignedInteger0TypeDecoder.cs
index 336f53c..0721ba3 100644
--- a/src/Proton/Codec/Decoders/Primitives/UnsignedInteger0TypeDecoder.cs
+++ b/src/Proton/Codec/Decoders/Primitives/UnsignedInteger0TypeDecoder.cs
@@ -27,9 +27,11 @@ namespace Apache.Qpid.Proton.Codec.Decoders.Primitives
 
       public override Type DecodesType => typeof(uint);
 
+      public override bool IsZeroWidth => true;
+
       public override object ReadValue(IProtonBuffer buffer, IDecoderState state)
       {
-         return (uint) 0;
+         return (uint)0;
       }
 
       public override object ReadValue(Stream stream, IStreamDecoderState state)
@@ -44,5 +46,25 @@ namespace Apache.Qpid.Proton.Codec.Decoders.Primitives
       public override void SkipValue(Stream stream, IStreamDecoderState state)
       {
       }
+
+      protected override void ValidateArrayPreconditions(IProtonBuffer buffer, IDecoderState state, int count)
+      {
+         if (count > state.MaxZeroWidthArrayElements || count < 0)
+         {
+            throw new DecodeException(string.Format(
+               "Array size indicated {0} is greater than the amount of elements allowed for zero sized primitives ({1})",
+               (uint) count, state.MaxZeroWidthArrayElements));
+         }
+      }
+
+      protected override void ValidateArrayPreconditions(Stream stream, IStreamDecoderState state, int count)
+      {
+         if (count > state.MaxZeroWidthArrayElements || count < 0)
+         {
+            throw new DecodeException(string.Format(
+               "Array size indicated {0} is greater than the amount of elements allowed for zero sized primitives ({1})",
+               (uint) count, state.MaxZeroWidthArrayElements));
+         }
+      }
    }
 }
\ No newline at end of file
diff --git a/src/Proton/Codec/Decoders/Primitives/UnsignedLong0TypeDecoder.cs b/src/Proton/Codec/Decoders/Primitives/UnsignedLong0TypeDecoder.cs
index 36e18db..e4e1b2c 100644
--- a/src/Proton/Codec/Decoders/Primitives/UnsignedLong0TypeDecoder.cs
+++ b/src/Proton/Codec/Decoders/Primitives/UnsignedLong0TypeDecoder.cs
@@ -27,9 +27,11 @@ namespace Apache.Qpid.Proton.Codec.Decoders.Primitives
 
       public override Type DecodesType => typeof(ulong);
 
+      public override bool IsZeroWidth => true;
+
       public override object ReadValue(IProtonBuffer buffer, IDecoderState state)
       {
-         return (ulong) 0;
+         return (ulong)0;
       }
 
       public override object ReadValue(Stream stream, IStreamDecoderState state)
@@ -44,5 +46,25 @@ namespace Apache.Qpid.Proton.Codec.Decoders.Primitives
       public override void SkipValue(Stream stream, IStreamDecoderState state)
       {
       }
+
+      protected override void ValidateArrayPreconditions(IProtonBuffer buffer, IDecoderState state, int count)
+      {
+         if (count > state.MaxZeroWidthArrayElements || count < 0)
+         {
+            throw new DecodeException(string.Format(
+               "Array size indicated {0} is greater than the amount of elements allowed for zero sized primitives ({1})",
+               (uint) count, state.MaxZeroWidthArrayElements));
+         }
+      }
+
+      protected override void ValidateArrayPreconditions(Stream stream, IStreamDecoderState state, int count)
+      {
+         if (count > state.MaxZeroWidthArrayElements || count < 0)
+         {
+            throw new DecodeException(string.Format(
+               "Array size indicated {0} is greater than the amount of elements allowed for zero sized primitives ({1})",
+               (uint) count, state.MaxZeroWidthArrayElements));
+         }
+      }
    }
 }
\ No newline at end of file
diff --git a/src/Proton/Codec/Decoders/ProtonDecoder.cs b/src/Proton/Codec/Decoders/ProtonDecoder.cs
index fc09b30..8c5baf4 100644
--- a/src/Proton/Codec/Decoders/ProtonDecoder.cs
+++ b/src/Proton/Codec/Decoders/ProtonDecoder.cs
@@ -25,6 +25,18 @@ namespace Apache.Qpid.Proton.Codec.Decoders
 {
    public sealed class ProtonDecoder : IDecoder
    {
+      /// <summary>
+      /// The number of unknown described type decoders that are cached for performance
+      /// but limited for memory protection.
+      /// </summary>
+      public static readonly int UnknownDescribedTypeCacheLimit = 16;
+
+      /// <summary>
+      /// If the descriptor for an unknown described type is a Symbol then it must have a
+      /// length shorter than this value to be cached for future lookup.
+      /// </summary>
+      public static readonly int UnknownDescribedTypeDescriptorSizeLimit = 64;
+
       /// <summary>
       /// The decoders for primitives are fixed and cannot be altered by users who want
       /// to register custom decoders. The decoders created here are stateless and can be
@@ -32,6 +44,12 @@ namespace Apache.Qpid.Proton.Codec.Decoders
       /// </summary>
       private static readonly IPrimitiveTypeDecoder[] primitiveDecoders = new IPrimitiveTypeDecoder[256];
 
+      public enum DecoderMode
+      {
+         Sasl,
+         Default
+      }
+
       static ProtonDecoder()
       {
          primitiveDecoders[(int)EncodingCodes.Boolean] = new BooleanTypeDecoder();
@@ -77,6 +95,8 @@ namespace Apache.Qpid.Proton.Codec.Decoders
          // Initialize the locally used primitive type decoders for the main API
          symbol8Decoder = (Symbol8TypeDecoder)primitiveDecoders[(int)EncodingCodes.Sym8];
          symbol32Decoder = (Symbol32TypeDecoder)primitiveDecoders[(int)EncodingCodes.Sym32];
+         saslSymbol8Decoder = new SaslSymbol8TypeDecoder();
+         saslSymbol32Decoder = new SaslSymbol32TypeDecoder();
          binary8Decoder = (Binary8TypeDecoder)primitiveDecoders[(int)EncodingCodes.VBin8];
          binary32Decoder = (Binary32TypeDecoder)primitiveDecoders[(int)EncodingCodes.VBin32];
          list8Decoder = (List8TypeDecoder)primitiveDecoders[(int)EncodingCodes.List8];
@@ -94,14 +114,28 @@ namespace Apache.Qpid.Proton.Codec.Decoders
       private readonly IDictionary<object, IDescribedTypeDecoder> describedTypeDecoders =
          new Dictionary<object, IDescribedTypeDecoder>();
 
+      /// <summary>
+      /// Registry of decoders for described types which are not registered which can be updated with a
+      /// limited number of cached decoders to speed up processing
+      /// </summary>
+      private readonly IDictionary<object, IDescribedTypeDecoder> unknownDescribedTypeDecoders =
+         new Dictionary<object, IDescribedTypeDecoder>();
+
       /// <summary>
       /// Quick access to decoders that handle AMQP types like Transfer, Properties etc.
       /// </summary>
       private readonly IDescribedTypeDecoder[] amqpTypeDecoders = new IDescribedTypeDecoder[256];
 
+      /// <summary>
+      /// User supplied callback to decode descriptors that are reserved types.
+      /// </summary>
+      private readonly Func<IProtonBuffer, IDecoderState, object> reservedDescriptorDecoder;
+
       // Internal Decoders used to prevent user to access Proton specific decoding methods
       private static readonly Symbol8TypeDecoder symbol8Decoder;
       private static readonly Symbol32TypeDecoder symbol32Decoder;
+      private static readonly SaslSymbol8TypeDecoder saslSymbol8Decoder;
+      private static readonly SaslSymbol32TypeDecoder saslSymbol32Decoder;
       private static readonly Binary8TypeDecoder binary8Decoder;
       private static readonly Binary32TypeDecoder binary32Decoder;
       private static readonly List8TypeDecoder list8Decoder;
@@ -113,6 +147,44 @@ namespace Apache.Qpid.Proton.Codec.Decoders
 
       private ProtonDecoderState cachedDecoderState;
 
+      private readonly DecoderMode decoderMode;
+      private readonly IPrimitiveTypeDecoder[] localPrimitiveDecoders;
+      private readonly AbstractSymbolTypeDecoder localSymbol8Decoder;
+      private readonly AbstractSymbolTypeDecoder localSymbol32Decoder;
+
+      public ProtonDecoder() : this(DecoderMode.Default)
+      {
+      }
+
+      public ProtonDecoder(DecoderMode mode) : this(mode, null)
+      {
+      }
+
+      public ProtonDecoder(Func<IProtonBuffer, IDecoderState, object> descriptorDecoder) : this(DecoderMode.Default, descriptorDecoder)
+      {
+      }
+
+      public ProtonDecoder(DecoderMode mode, Func<IProtonBuffer, IDecoderState, object> descriptorDecoder)
+      {
+         decoderMode = mode;
+         reservedDescriptorDecoder = descriptorDecoder;
+
+         if (decoderMode == DecoderMode.Sasl)
+         {
+            localSymbol8Decoder = saslSymbol8Decoder;
+            localSymbol32Decoder = saslSymbol32Decoder;
+            localPrimitiveDecoders = (IPrimitiveTypeDecoder[])primitiveDecoders.Clone();
+            localPrimitiveDecoders[(int)EncodingCodes.Sym8] = saslSymbol8Decoder;
+            localPrimitiveDecoders[(int)EncodingCodes.Sym32] = saslSymbol32Decoder;
+         }
+         else
+         {
+            localSymbol8Decoder = symbol8Decoder;
+            localSymbol32Decoder = symbol32Decoder;
+            localPrimitiveDecoders = primitiveDecoders;
+         }
+      }
+
       public IDecoderState NewDecoderState()
       {
          return new ProtonDecoderState(this);
@@ -492,8 +564,8 @@ namespace Apache.Qpid.Proton.Codec.Decoders
 
          return encodingCode switch
          {
-            EncodingCodes.Sym8 => symbol8Decoder.ReadValue(buffer, state),
-            EncodingCodes.Sym32 => symbol32Decoder.ReadValue(buffer, state),
+            EncodingCodes.Sym8 => localSymbol8Decoder.ReadValue(buffer, state),
+            EncodingCodes.Sym32 => localSymbol32Decoder.ReadValue(buffer, state),
             EncodingCodes.Null => null,
             _ => throw new DecodeException("Expected Symbol type but found encoding: " + encodingCode),
          };
@@ -569,9 +641,9 @@ namespace Apache.Qpid.Proton.Codec.Decoders
 
       public T ReadObject<T>(IProtonBuffer buffer, IDecoderState state)
       {
-         object result = ReadObject(buffer, state);
+         ITypeDecoder decoder = ReadNextTypeDecoder(buffer, state);
 
-         if (result == null)
+         if (decoder.DecodesType == typeof(void))
          {
             if (typeof(T).IsValueType)
             {
@@ -582,44 +654,48 @@ namespace Apache.Qpid.Proton.Codec.Decoders
                return default;
             }
          }
-         else if (result.GetType().IsAssignableTo(typeof(T)))
+         else if (decoder.IsArrayType)
+         {
+            return (T)((IPrimitiveArrayTypeDecoder)decoder).ReadValue(buffer, state, typeof(T));
+         }
+         else if (decoder.DecodesType.IsAssignableTo(typeof(T)))
          {
-            return (T)result;
+            return (T)decoder.ReadValue(buffer, state);
          }
          else
          {
-            throw SignalUnexpectedType(result, typeof(T));
+            throw SignalUnexpectedType(decoder.DecodesType, typeof(T));
          }
       }
 
       public T[] ReadMultiple<T>(IProtonBuffer buffer, IDecoderState state)
       {
-         object val = ReadObject(buffer, state);
+         ITypeDecoder decoder = ReadNextTypeDecoder(buffer, state);
 
-         if (val == null)
+         if (decoder.DecodesType == typeof(void))
          {
-            return null;
-         }
-         else if (val.GetType().IsArray)
-         {
-            if (typeof(T).IsAssignableFrom(val.GetType().GetElementType()))
+            if (typeof(T).IsValueType)
             {
-               return (T[])val;
+               throw SignalUnexpectedType(typeof(T));
             }
             else
             {
-               throw SignalUnexpectedType(val, typeof(T).MakeArrayType());
+               return default;
             }
          }
-         else if (typeof(T).IsAssignableFrom(val.GetType()))
+         else if (decoder.IsArrayType)
+         {
+            return (T[])((IPrimitiveArrayTypeDecoder)decoder).ReadValue(buffer, state, typeof(T));
+         }
+         else if (decoder.DecodesType.IsAssignableTo(typeof(T)))
          {
             T[] array = (T[])Array.CreateInstance(typeof(T), 1);
-            array[0] = (T)val;
+            array[0] = (T) decoder.ReadValue(buffer, state);
             return array;
          }
          else
          {
-            throw SignalUnexpectedType(val, typeof(T).MakeArrayType());
+            throw SignalUnexpectedType(decoder.DecodesType, typeof(T));
          }
       }
 
@@ -665,7 +741,7 @@ namespace Apache.Qpid.Proton.Codec.Decoders
          }
          else
          {
-            return primitiveDecoders[(byte)encodingCode];
+            return localPrimitiveDecoders[(byte)encodingCode];
          }
       }
 
@@ -680,13 +756,47 @@ namespace Apache.Qpid.Proton.Codec.Decoders
          }
          catch (Exception)
          {
-            buffer.ReadOffset = readPos;
-            descriptor = ReadObject(buffer, state);
+            try
+            {
+               buffer.ReadOffset = readPos;
+               descriptor = ReadSymbol(buffer, state);
+            }
+            catch (Exception)
+            {
+               buffer.ReadOffset = readPos;
+               if (decoderMode != DecoderMode.Sasl)
+               {
+                  if (reservedDescriptorDecoder != null)
+                  {
+                     state.IncreaseDepth();
+                     try
+                     {
+                        descriptor = reservedDescriptorDecoder.Invoke(buffer, state);
+                     }
+                     finally
+                     {
+                        state.DecreaseDepth();
+                     }
+                  }
+                  else
+                  {
+                     throw new DecodeException(String.Format(
+                        "Cannot decode a type that is using a reserved type descriptor: {}", PeekNextTypeDecoder(buffer, state).DecodesType));
+                  }
+               }
+               else
+               {
+                  throw new DecodeException("Cannot decode reserved descriptor type in SASL mode.");
+               }
+            }
          }
 
          if (!describedTypeDecoders.TryGetValue(descriptor, out IDescribedTypeDecoder typeDecoder))
          {
-            typeDecoder = HandleUnknownDescribedType(descriptor);
+            if (!unknownDescribedTypeDecoders.TryGetValue(descriptor, out typeDecoder))
+            {
+               typeDecoder = HandleUnknownDescribedType(descriptor);
+            }
          }
 
          return typeDecoder;
@@ -733,22 +843,42 @@ namespace Apache.Qpid.Proton.Codec.Decoders
          }
       }
 
-      private static InvalidCastException SignalUnexpectedType(in Type type)
+      private static DecodeException SignalUnexpectedType(in Type type)
       {
-         return new InvalidCastException(
+         return new DecodeException(
             "Unexpected null decoding, Expected " + type.Name + ".");
       }
 
-      private static InvalidCastException SignalUnexpectedType(in object val, in Type type)
+      private static DecodeException SignalUnexpectedType(in object val, in Type type)
       {
-         return new InvalidCastException(
+         return new DecodeException(
             "Unexpected type " + val.GetType().Name + ". Expected " + type.Name + ".");
       }
 
       private IDescribedTypeDecoder HandleUnknownDescribedType(in object descriptor)
       {
+         if (DecoderMode.Sasl == decoderMode)
+         {
+            throw new DecodeException("Cannot decode unknown described types from a SASL mode decoder");
+         }
+
+         bool canCache;
+
+         if (descriptor is Symbol symbol && symbol.Length > UnknownDescribedTypeDescriptorSizeLimit)
+         {
+            canCache = false;
+         }
+         else
+         {
+            canCache = descriptor is ulong;
+         }
+
          UnknownDescribedTypeDecoder typeDecoder = new(descriptor);
-         describedTypeDecoders.Add(descriptor, typeDecoder);
+
+         if (canCache && unknownDescribedTypeDecoders.Count < UnknownDescribedTypeCacheLimit)
+         {
+            unknownDescribedTypeDecoders.Add(descriptor, typeDecoder);
+         }
 
          return typeDecoder;
       }
diff --git a/src/Proton/Codec/Decoders/ProtonDecoderFactory.cs b/src/Proton/Codec/Decoders/ProtonDecoderFactory.cs
index 20440f4..3a548a3 100644
--- a/src/Proton/Codec/Decoders/ProtonDecoderFactory.cs
+++ b/src/Proton/Codec/Decoders/ProtonDecoderFactory.cs
@@ -29,7 +29,7 @@ namespace Apache.Qpid.Proton.Codec.Decoders
    {
       public static ProtonDecoder Create()
       {
-         ProtonDecoder decoder = new();
+         ProtonDecoder decoder = new(ProtonDecoder.DecoderMode.Default);
 
          AddMessagingTypeDecoders(decoder);
          AddTransactionTypeDecoders(decoder);
@@ -40,7 +40,7 @@ namespace Apache.Qpid.Proton.Codec.Decoders
 
       public static ProtonDecoder CreateSasl()
       {
-         ProtonDecoder decoder = new();
+         ProtonDecoder decoder = new(ProtonDecoder.DecoderMode.Sasl);
 
          AddSaslTypeDecoders(decoder);
 
diff --git a/src/Proton/Codec/Decoders/ProtonDecoderState.cs b/src/Proton/Codec/Decoders/ProtonDecoderState.cs
index b5cf861..a060e8b 100644
--- a/src/Proton/Codec/Decoders/ProtonDecoderState.cs
+++ b/src/Proton/Codec/Decoders/ProtonDecoderState.cs
@@ -22,8 +22,22 @@ namespace Apache.Qpid.Proton.Codec.Decoders
 {
    public class ProtonDecoderState : IDecoderState
    {
+      /// <summary>
+      /// The default number of elements a zero width array encoding is allowed to have
+      /// before an decoder exception is thrown.
+      /// </summary>
+      public static readonly uint DefaultMaxZeroWidthArrayElements = 0;
+
+      /// <summary>
+      /// The default maximum depth the decoder will allow before triggering an DecodeException when the
+      /// state depth value is increased during a decode process of complex types that can next objects.
+      /// </summary>
+      public static readonly uint DefaultMaxDecodeDepth = 32;
+
       private readonly ProtonDecoder decoder;
 
+      private uint decodeDepth;
+
       public ProtonDecoderState(ProtonDecoder decoder)
       {
          this.decoder = decoder;
@@ -38,7 +52,7 @@ namespace Apache.Qpid.Proton.Codec.Decoders
 
       public void Reset()
       {
-         // Nothing needed yet.
+         decodeDepth = 0;
       }
 
       public IDecoder Decoder
@@ -46,8 +60,39 @@ namespace Apache.Qpid.Proton.Codec.Decoders
          get { return this.decoder; }
       }
 
+      public uint MaxZeroWidthArrayElements { get; set; } = DefaultMaxZeroWidthArrayElements;
+
+      public uint DepthLimit { get; set; } = DefaultMaxDecodeDepth;
+
+      public void IncreaseDepth()
+      {
+         if (++decodeDepth > DepthLimit)
+         {
+            --decodeDepth; // Unwind decrement to ensure the depth returns to zero.
+            throw new DecodeException(
+               "The nesting of types in the object being decoded exceeded the configured limit: " + DepthLimit);
+         }
+      }
+
+      public void DecreaseDepth()
+      {
+         decodeDepth = decodeDepth > 0 ? decodeDepth - 1 : 0;
+      }
+
       public string DecodeUtf8(IProtonBuffer buffer, int length)
       {
+         if (length < 0)
+         {
+            throw new DecodeException("Specified UTF length:" + length + " cannot be negative.");
+         }
+
+         if (length > buffer.ReadableBytes)
+         {
+            throw new DecodeException(string.Format(
+                "String encoded size %d is specified to be greater than the amount " +
+                "of data available (%d)", length, buffer.ReadableBytes));
+         }
+
          if (Utf8Decoder == null)
          {
             return InternalDecode(buffer, length);
diff --git a/src/Proton/Codec/Decoders/ProtonStreamDecoder.cs b/src/Proton/Codec/Decoders/ProtonStreamDecoder.cs
index 8bb36cc..e3663bf 100644
--- a/src/Proton/Codec/Decoders/ProtonStreamDecoder.cs
+++ b/src/Proton/Codec/Decoders/ProtonStreamDecoder.cs
@@ -26,6 +26,18 @@ namespace Apache.Qpid.Proton.Codec.Decoders
 {
    public sealed class ProtonStreamDecoder : IStreamDecoder
    {
+      /// <summary>
+      /// The number of unknown described type decoders that are cached for performance
+      /// but limited for memory protection.
+      /// </summary>
+      public static readonly int UnknownDescribedTypeCacheLimit = 16;
+
+      /// <summary>
+      /// If the descriptor for an unknown described type is a Symbol then it must have a
+      /// length shorter than this value to be cached for future lookup.
+      /// </summary>
+      public static readonly int UnknownDescribedTypeDescriptorSizeLimit = 64;
+
       /// <summary>
       /// The decoders for primitives are fixed and cannot be altered by users who want
       /// to register custom decoders. The decoders created here are stateless and can be
@@ -33,6 +45,12 @@ namespace Apache.Qpid.Proton.Codec.Decoders
       /// </summary>
       private static readonly IPrimitiveTypeDecoder[] primitiveDecoders = new IPrimitiveTypeDecoder[256];
 
+      public enum DecoderMode
+      {
+         Sasl,
+         Default
+      }
+
       static ProtonStreamDecoder()
       {
          primitiveDecoders[(int)EncodingCodes.Boolean] = new BooleanTypeDecoder();
@@ -78,6 +96,8 @@ namespace Apache.Qpid.Proton.Codec.Decoders
          // Initialize the locally used primitive type decoders for the main API
          symbol8Decoder = (Symbol8TypeDecoder)primitiveDecoders[(int)EncodingCodes.Sym8];
          symbol32Decoder = (Symbol32TypeDecoder)primitiveDecoders[(int)EncodingCodes.Sym32];
+         saslSymbol8Decoder = new SaslSymbol8TypeDecoder();
+         saslSymbol32Decoder = new SaslSymbol32TypeDecoder();
          binary8Decoder = (Binary8TypeDecoder)primitiveDecoders[(int)EncodingCodes.VBin8];
          binary32Decoder = (Binary32TypeDecoder)primitiveDecoders[(int)EncodingCodes.VBin32];
          list8Decoder = (List8TypeDecoder)primitiveDecoders[(int)EncodingCodes.List8];
@@ -95,6 +115,13 @@ namespace Apache.Qpid.Proton.Codec.Decoders
       private readonly IDictionary<object, IStreamDescribedTypeDecoder> describedTypeDecoders =
          new Dictionary<object, IStreamDescribedTypeDecoder>();
 
+      /// <summary>
+      /// Registry of decoders for described types which are not registered which can be updated with a
+      /// limited number of cached decoders to speed up processing
+      /// </summary>
+      private readonly IDictionary<object, IStreamDescribedTypeDecoder> unknownDescribedTypeDecoders =
+         new Dictionary<object, IStreamDescribedTypeDecoder>();
+
       /// <summary>
       /// Quick access to decoders that handle AMQP types like Transfer, Properties etc.
       /// </summary>
@@ -103,6 +130,8 @@ namespace Apache.Qpid.Proton.Codec.Decoders
       // Internal Decoders used to prevent user to access Proton specific decoding methods
       private static readonly Symbol8TypeDecoder symbol8Decoder;
       private static readonly Symbol32TypeDecoder symbol32Decoder;
+      private static readonly SaslSymbol8TypeDecoder saslSymbol8Decoder;
+      private static readonly SaslSymbol32TypeDecoder saslSymbol32Decoder;
       private static readonly Binary8TypeDecoder binary8Decoder;
       private static readonly Binary32TypeDecoder binary32Decoder;
       private static readonly List8TypeDecoder list8Decoder;
@@ -112,6 +141,35 @@ namespace Apache.Qpid.Proton.Codec.Decoders
       private static readonly String8TypeDecoder string8Decoder;
       private static readonly String32TypeDecoder string32Decoder;
 
+      private readonly DecoderMode decoderMode;
+      private readonly IPrimitiveTypeDecoder[] localPrimitiveDecoders;
+      private readonly AbstractSymbolTypeDecoder localSymbol8Decoder;
+      private readonly AbstractSymbolTypeDecoder localSymbol32Decoder;
+
+      public ProtonStreamDecoder() : this(DecoderMode.Default)
+      {
+      }
+
+      public ProtonStreamDecoder(DecoderMode mode)
+      {
+         decoderMode = mode;
+
+         if (decoderMode == DecoderMode.Sasl)
+         {
+            localSymbol8Decoder = saslSymbol8Decoder;
+            localSymbol32Decoder = saslSymbol32Decoder;
+            localPrimitiveDecoders = (IPrimitiveTypeDecoder[])primitiveDecoders.Clone();
+            localPrimitiveDecoders[(int)EncodingCodes.Sym8] = saslSymbol8Decoder;
+            localPrimitiveDecoders[(int)EncodingCodes.Sym32] = saslSymbol32Decoder;
+         }
+         else
+         {
+            localSymbol8Decoder = symbol8Decoder;
+            localSymbol32Decoder = symbol32Decoder;
+            localPrimitiveDecoders = primitiveDecoders;
+         }
+      }
+
       private ProtonStreamDecoderState cachedDecoderState;
 
       public IStreamDecoderState NewDecoderState()
@@ -493,8 +551,8 @@ namespace Apache.Qpid.Proton.Codec.Decoders
 
          return encodingCode switch
          {
-            EncodingCodes.Sym8 => symbol8Decoder.ReadValue(stream, state),
-            EncodingCodes.Sym32 => symbol32Decoder.ReadValue(stream, state),
+            EncodingCodes.Sym8 => localSymbol8Decoder.ReadValue(stream, state),
+            EncodingCodes.Sym32 => localSymbol32Decoder.ReadValue(stream, state),
             EncodingCodes.Null => null,
             _ => throw new DecodeException("Expected Symbol type but found encoding: " + encodingCode),
          };
@@ -570,9 +628,9 @@ namespace Apache.Qpid.Proton.Codec.Decoders
 
       public T ReadObject<T>(Stream stream, IStreamDecoderState state)
       {
-         object result = ReadObject(stream, state);
+         IStreamTypeDecoder decoder = ReadNextTypeDecoder(stream, state);
 
-         if (result == null)
+         if (decoder.DecodesType == typeof(void))
          {
             if (typeof(T).IsValueType)
             {
@@ -583,44 +641,48 @@ namespace Apache.Qpid.Proton.Codec.Decoders
                return default;
             }
          }
-         else if (result.GetType().IsAssignableTo(typeof(T)))
+         else if (decoder.IsArrayType)
+         {
+            return (T)((IPrimitiveArrayTypeDecoder)decoder).ReadValue(stream, state, typeof(T));
+         }
+         else if (decoder.DecodesType.IsAssignableTo(typeof(T)))
          {
-            return (T)result;
+            return (T)decoder.ReadValue(stream, state);
          }
          else
          {
-            throw SignalUnexpectedType(result, typeof(T));
+            throw SignalUnexpectedType(decoder.DecodesType, typeof(T));
          }
       }
 
       public T[] ReadMultiple<T>(Stream stream, IStreamDecoderState state)
       {
-         object val = ReadObject(stream, state);
+         IStreamTypeDecoder decoder = ReadNextTypeDecoder(stream, state);
 
-         if (val == null)
+         if (decoder.DecodesType == typeof(void))
          {
-            return null;
-         }
-         else if (val.GetType().IsArray)
-         {
-            if (typeof(T).IsAssignableFrom(val.GetType().GetElementType()))
+            if (typeof(T).IsValueType)
             {
-               return (T[])val;
+               throw SignalUnexpectedType(typeof(T));
             }
             else
             {
-               throw SignalUnexpectedType(val, typeof(T).MakeArrayType());
+               return default;
             }
          }
-         else if (typeof(T).IsAssignableFrom(val.GetType()))
+         else if (decoder.IsArrayType)
+         {
+            return (T[])((IPrimitiveArrayTypeDecoder)decoder).ReadValue(stream, state, typeof(T));
+         }
+         else if (decoder.DecodesType.IsAssignableTo(typeof(T)))
          {
             T[] array = (T[])Array.CreateInstance(typeof(T), 1);
-            array[0] = (T)val;
+            array[0] = (T) decoder.ReadValue(stream, state);
             return array;
          }
          else
          {
-            throw SignalUnexpectedType(val, typeof(T).MakeArrayType());
+            throw SignalUnexpectedType(decoder.DecodesType, typeof(T));
          }
       }
 
@@ -673,7 +735,7 @@ namespace Apache.Qpid.Proton.Codec.Decoders
          }
          else
          {
-            return primitiveDecoders[(byte)encodingCode];
+            return localPrimitiveDecoders[(byte)encodingCode];
          }
       }
 
@@ -688,9 +750,13 @@ namespace Apache.Qpid.Proton.Codec.Decoders
             EncodingCodes.Sym32 => symbol32Decoder.ReadValue(stream, state),
             _ => throw new DecodeException("Expected Descriptor type but found encoding: " + encodingCode),
          };
+
          if (!describedTypeDecoders.TryGetValue(descriptor, out IStreamDescribedTypeDecoder streamTypeDecoder))
          {
-            streamTypeDecoder = HandleUnknownDescribedType(descriptor);
+            if (!unknownDescribedTypeDecoders.TryGetValue(descriptor, out streamTypeDecoder))
+            {
+                streamTypeDecoder = HandleUnknownDescribedType(descriptor);
+            }
          }
 
          return streamTypeDecoder;
@@ -739,22 +805,42 @@ namespace Apache.Qpid.Proton.Codec.Decoders
          return this;
       }
 
-      private static InvalidCastException SignalUnexpectedType(in Type type)
+      private static DecodeException SignalUnexpectedType(in Type type)
       {
-         return new InvalidCastException(
+         return new DecodeException(
             "Unexpected null decoding, Expected " + type.Name + ".");
       }
 
-      private static InvalidCastException SignalUnexpectedType(in object val, in Type type)
+      private static DecodeException SignalUnexpectedType(in object val, in Type type)
       {
-         return new InvalidCastException(
+         return new DecodeException(
             "Unexpected type " + val.GetType().Name + ". Expected " + type.Name + ".");
       }
 
       private IStreamDescribedTypeDecoder HandleUnknownDescribedType(in object descriptor)
       {
+         if (DecoderMode.Sasl == decoderMode)
+         {
+            throw new DecodeException("Cannot decode unknown described types from a SASL mode decoder");
+         }
+
+         bool canCache;
+
+         if (descriptor is Symbol symbol && symbol.Length > UnknownDescribedTypeDescriptorSizeLimit)
+         {
+            canCache = false;
+         }
+         else
+         {
+            canCache = descriptor is ulong;
+         }
+
          IStreamDescribedTypeDecoder typeDecoder = new UnknownDescribedTypeDecoder(descriptor);
-         describedTypeDecoders.Add(descriptor, (UnknownDescribedTypeDecoder)typeDecoder);
+
+         if (canCache && unknownDescribedTypeDecoders.Count < UnknownDescribedTypeCacheLimit)
+         {
+            unknownDescribedTypeDecoders.Add(descriptor, typeDecoder);
+         }
 
          return typeDecoder;
       }
diff --git a/src/Proton/Codec/Decoders/ProtonStreamDecoderFactory.cs b/src/Proton/Codec/Decoders/ProtonStreamDecoderFactory.cs
index 7014b5c..861d8b2 100644
--- a/src/Proton/Codec/Decoders/ProtonStreamDecoderFactory.cs
+++ b/src/Proton/Codec/Decoders/ProtonStreamDecoderFactory.cs
@@ -29,7 +29,7 @@ namespace Apache.Qpid.Proton.Codec.Decoders
    {
       public static ProtonStreamDecoder Create()
       {
-         ProtonStreamDecoder decoder = new();
+         ProtonStreamDecoder decoder = new(ProtonStreamDecoder.DecoderMode.Default);
 
          AddMessagingTypeDecoders(decoder);
          AddTransactionTypeDecoders(decoder);
@@ -40,7 +40,7 @@ namespace Apache.Qpid.Proton.Codec.Decoders
 
       public static ProtonStreamDecoder CreateSasl()
       {
-         ProtonStreamDecoder decoder = new();
+         ProtonStreamDecoder decoder = new(ProtonStreamDecoder.DecoderMode.Sasl);
 
          AddSaslTypeDecoders(decoder);
 
diff --git a/src/Proton/Codec/Decoders/ProtonStreamDecoderState.cs b/src/Proton/Codec/Decoders/ProtonStreamDecoderState.cs
index 7c266d0..e51675e 100644
--- a/src/Proton/Codec/Decoders/ProtonStreamDecoderState.cs
+++ b/src/Proton/Codec/Decoders/ProtonStreamDecoderState.cs
@@ -22,10 +22,24 @@ namespace Apache.Qpid.Proton.Codec.Decoders
 {
    public class ProtonStreamDecoderState : IStreamDecoderState
    {
+      /// <summary>
+      /// The default number of elements a zero width array encoding is allowed to have
+      /// before an decoder exception is thrown.
+      /// </summary>
+      public static readonly uint DefaultMaxZeroWidthArrayElements = 0;
+
+      /// <summary>
+      /// The default maximum depth the decoder will allow before triggering an DecodeException when the
+      /// state depth value is increased during a decode process of complex types that can next objects.
+      /// </summary>
+      public static readonly uint DefaultMaxDecodeDepth = 32;
+
       private readonly ProtonStreamDecoder decoder;
 
       public IStreamDecoder Decoder => decoder;
 
+      private uint decodeDepth;
+
       public ProtonStreamDecoderState(ProtonStreamDecoder parent) : base()
       {
          this.decoder = parent;
@@ -38,13 +52,56 @@ namespace Apache.Qpid.Proton.Codec.Decoders
       /// </summary>
       public IUtf8StreamDecoder Utf8Decoder { get; set; }
 
+      public uint MaxZeroWidthArrayElements { get; set; } = DefaultMaxZeroWidthArrayElements;
+
+      public uint MaxStringSize { get; set; } = IStreamDecoderState.DefaultMaxAllocationLimit;
+
+      public uint MaxArraySize { get; set; } = IStreamDecoderState.DefaultMaxAllocationLimit;
+
+      public uint MaxBinarySize { get; set; } = IStreamDecoderState.DefaultMaxAllocationLimit;
+
+      public uint MaxSymbolSize { get; set; } = IStreamDecoderState.DefaultMaxAllocationLimit;
+
+      public uint MaxListSize { get; set; } = IStreamDecoderState.DefaultMaxAllocationLimit;
+
+      public uint MaxMapSize { get; set; } = IStreamDecoderState.DefaultMaxAllocationLimit;
+
       public void Reset()
       {
-         throw new NotImplementedException();
+         decodeDepth = 0;
+      }
+
+      public uint DepthLimit { get; set; } = DefaultMaxDecodeDepth;
+
+      public void IncreaseDepth()
+      {
+         if (++decodeDepth > DepthLimit)
+         {
+            --decodeDepth; // Unwind decrement to ensure the depth returns to zero.
+            throw new DecodeException(
+               "The nesting of types in the object being decoded exceeded the configured limit: " + DepthLimit);
+         }
+      }
+
+      public void DecreaseDepth()
+      {
+         decodeDepth = decodeDepth > 0 ? decodeDepth - 1 : 0;
       }
 
       public string DecodeUtf8(Stream stream, int length)
       {
+         if (length < 0)
+         {
+            throw new DecodeException("Specified UTF length:" + length + " cannot be negative.");
+         }
+
+         if (length > MaxStringSize)
+         {
+            throw new DecodeException(string.Format(
+                "String encoded size %d is specified to be greater than the amount " +
+                "of data available (%d)", length, MaxStringSize));
+         }
+
          if (Utf8Decoder == null)
          {
             return InternalDecode(stream, length);
diff --git a/src/Proton/Codec/Decoders/ProtonStreamReadUtils.cs b/src/Proton/Codec/Decoders/ProtonStreamReadUtils.cs
index 3e04ef3..8b97324 100644
--- a/src/Proton/Codec/Decoders/ProtonStreamReadUtils.cs
+++ b/src/Proton/Codec/Decoders/ProtonStreamReadUtils.cs
@@ -287,6 +287,11 @@ namespace Apache.Qpid.Proton.Codec.Decoders
                throw new DecodeException("Stream was not able to skip the requested amount of bytes: " + amount);
             }
          }
+         catch (ArgumentOutOfRangeException ex)
+         {
+            throw new DecodeException(
+                string.Format("Error while attempting to skip {0} bytes in the given InputStream", amount), ex);
+         }
          catch (IOException ex)
          {
             throw new DecodeException(
diff --git a/src/Proton/Codec/Decoders/Security/SaslChallengeTypeDecoder.cs b/src/Proton/Codec/Decoders/Security/SaslChallengeTypeDecoder.cs
index 2895451..719f4ba 100644
--- a/src/Proton/Codec/Decoders/Security/SaslChallengeTypeDecoder.cs
+++ b/src/Proton/Codec/Decoders/Security/SaslChallengeTypeDecoder.cs
@@ -24,7 +24,7 @@ using Apache.Qpid.Proton.Types.Security;
 
 namespace Apache.Qpid.Proton.Codec.Decoders.Security
 {
-   public sealed class SaslChallengeTypeDecoder : AbstractDescribedTypeDecoder
+   public sealed class SaslChallengeTypeDecoder : AbstractDescribedListTypeDecoder
    {
       private static readonly int RequiredListEntries = 1;
 
@@ -34,102 +34,24 @@ namespace Apache.Qpid.Proton.Codec.Decoders.Security
 
       public override Type DecodesType => typeof(SaslChallenge);
 
-      public override object ReadValue(IProtonBuffer buffer, IDecoderState state)
-      {
-         ITypeDecoder decoder = state.Decoder.ReadNextTypeDecoder(buffer, state);
-
-         return ReadType(buffer, state, CheckIsExpectedTypeAndCast<IListTypeDecoder>(decoder));
-      }
-
-      public override Array ReadArrayElements(IProtonBuffer buffer, IDecoderState state, int count)
-      {
-         ITypeDecoder decoder = state.Decoder.ReadNextTypeDecoder(buffer, state);
-
-         CheckIsExpectedType<IListTypeDecoder>(decoder);
-
-         SaslChallenge[] result = new SaslChallenge[count];
-         for (int i = 0; i < count; ++i)
-         {
-            result[i] = ReadType(buffer, state, CheckIsExpectedTypeAndCast<IListTypeDecoder>(decoder));
-         }
-
-         return result;
-      }
-
-      public override void SkipValue(IProtonBuffer buffer, IDecoderState state)
-      {
-         ITypeDecoder decoder = state.Decoder.ReadNextTypeDecoder(buffer, state);
+      protected override int MinListElements => RequiredListEntries;
 
-         CheckIsExpectedType<IListTypeDecoder>(decoder);
+      protected override int MaxListElements => RequiredListEntries;
 
-         decoder.SkipValue(buffer, state);
-      }
-
-      private static SaslChallenge ReadType(IProtonBuffer buffer, IDecoderState state, IListTypeDecoder listDecoder)
+      protected override SaslChallenge ReadType(int count, IProtonBuffer buffer, IDecoder decoder, IDecoderState state)
       {
          SaslChallenge result = new();
 
-         _ = listDecoder.ReadSize(buffer, state);
-         int count = listDecoder.ReadCount(buffer, state);
-
-         if (count != RequiredListEntries)
-         {
-            throw new DecodeException("SASL Challenge must contain at least one entry: " + count);
-         }
-         else
-         {
-            result.Challenge = state.Decoder.ReadBinary(buffer, state);
-         }
-
-         return result;
-      }
-
-      public override object ReadValue(Stream stream, IStreamDecoderState state)
-      {
-         IStreamTypeDecoder decoder = state.Decoder.ReadNextTypeDecoder(stream, state);
-
-         return ReadType(stream, state, CheckIsExpectedTypeAndCast<IListTypeDecoder>(decoder));
-      }
-
-      public override Array ReadArrayElements(Stream stream, IStreamDecoderState state, int count)
-      {
-         IStreamTypeDecoder decoder = state.Decoder.ReadNextTypeDecoder(stream, state);
-
-         CheckIsExpectedType<IListTypeDecoder>(decoder);
-
-         SaslChallenge[] result = new SaslChallenge[count];
-         for (int i = 0; i < count; ++i)
-         {
-            result[i] = ReadType(stream, state, CheckIsExpectedTypeAndCast<IListTypeDecoder>(decoder));
-         }
+         result.Challenge = state.Decoder.ReadBinary(buffer, state);
 
          return result;
       }
 
-      public override void SkipValue(Stream stream, IStreamDecoderState state)
-      {
-         IStreamTypeDecoder decoder = state.Decoder.ReadNextTypeDecoder(stream, state);
-
-         CheckIsExpectedType<IListTypeDecoder>(decoder);
-
-         decoder.SkipValue(stream, state);
-      }
-
-      private static SaslChallenge ReadType(Stream stream, IStreamDecoderState state, IListTypeDecoder listDecoder)
+      protected override SaslChallenge ReadType(int count, Stream stream, IStreamDecoder decoder, IStreamDecoderState state)
       {
          SaslChallenge result = new();
 
-         _ = listDecoder.ReadSize(stream, state);
-         int count = listDecoder.ReadCount(stream, state);
-
-         if (count != RequiredListEntries)
-         {
-            throw new DecodeException("SASL Challenge must contain at least one entry: " + count);
-         }
-         else
-         {
-            result.Challenge = state.Decoder.ReadBinary(stream, state);
-         }
+         result.Challenge = state.Decoder.ReadBinary(stream, state);
 
          return result;
       }
diff --git a/src/Proton/Codec/Decoders/Security/SaslInitTypeDecoder.cs b/src/Proton/Codec/Decoders/Security/SaslInitTypeDecoder.cs
index f9004b5..42639d5 100644
--- a/src/Proton/Codec/Decoders/Security/SaslInitTypeDecoder.cs
+++ b/src/Proton/Codec/Decoders/Security/SaslInitTypeDecoder.cs
@@ -24,7 +24,7 @@ using Apache.Qpid.Proton.Types.Security;
 
 namespace Apache.Qpid.Proton.Codec.Decoders.Security
 {
-   public sealed class SaslInitTypeDecoder : AbstractDescribedTypeDecoder
+   public sealed class SaslInitTypeDecoder : AbstractDescribedListTypeDecoder
    {
       private static readonly int MinListEntries = 0;
       private static readonly int MaxListEntries = 3;
@@ -35,55 +35,14 @@ namespace Apache.Qpid.Proton.Codec.Decoders.Security
 
       public override Type DecodesType => typeof(SaslInit);
 
-      public override object ReadValue(IProtonBuffer buffer, IDecoderState state)
-      {
-         ITypeDecoder decoder = state.Decoder.ReadNextTypeDecoder(buffer, state);
-
-         return ReadType(buffer, state, CheckIsExpectedTypeAndCast<IListTypeDecoder>(decoder));
-      }
-
-      public override Array ReadArrayElements(IProtonBuffer buffer, IDecoderState state, int count)
-      {
-         ITypeDecoder decoder = state.Decoder.ReadNextTypeDecoder(buffer, state);
-
-         CheckIsExpectedType<IListTypeDecoder>(decoder);
-
-         SaslInit[] result = new SaslInit[count];
-         for (int i = 0; i < count; ++i)
-         {
-            result[i] = ReadType(buffer, state, CheckIsExpectedTypeAndCast<IListTypeDecoder>(decoder));
-         }
-
-         return result;
-      }
+      protected override int MinListElements => MinListEntries;
 
-      public override void SkipValue(IProtonBuffer buffer, IDecoderState state)
-      {
-         ITypeDecoder decoder = state.Decoder.ReadNextTypeDecoder(buffer, state);
-
-         CheckIsExpectedType<IListTypeDecoder>(decoder);
-
-         decoder.SkipValue(buffer, state);
-      }
+      protected override int MaxListElements => MaxListEntries;
 
-      private static SaslInit ReadType(IProtonBuffer buffer, IDecoderState state, IListTypeDecoder listDecoder)
+      protected override SaslInit ReadType(int count, IProtonBuffer buffer, IDecoder decoder, IDecoderState state)
       {
          SaslInit result = new();
 
-         _ = listDecoder.ReadSize(buffer, state);
-         int count = listDecoder.ReadCount(buffer, state);
-
-         // Don't decode anything if things already look wrong.
-         if (count < MinListEntries)
-         {
-            throw new DecodeException("Not enough entries in SaslInit list encoding: " + count);
-         }
-
-         if (count > MaxListEntries)
-         {
-            throw new DecodeException("To many entries in SaslInit list encoding: " + count);
-         }
-
          for (int index = 0; index < count; ++index)
          {
             switch (index)
@@ -103,55 +62,10 @@ namespace Apache.Qpid.Proton.Codec.Decoders.Security
          return result;
       }
 
-      public override object ReadValue(Stream stream, IStreamDecoderState state)
-      {
-         IStreamTypeDecoder decoder = state.Decoder.ReadNextTypeDecoder(stream, state);
-
-         return ReadType(stream, state, CheckIsExpectedTypeAndCast<IListTypeDecoder>(decoder));
-      }
-
-      public override Array ReadArrayElements(Stream stream, IStreamDecoderState state, int count)
-      {
-         IStreamTypeDecoder decoder = state.Decoder.ReadNextTypeDecoder(stream, state);
-
-         CheckIsExpectedType<IListTypeDecoder>(decoder);
-
-         SaslInit[] result = new SaslInit[count];
-         for (int i = 0; i < count; ++i)
-         {
-            result[i] = ReadType(stream, state, CheckIsExpectedTypeAndCast<IListTypeDecoder>(decoder));
-         }
-
-         return result;
-      }
-
-      public override void SkipValue(Stream stream, IStreamDecoderState state)
-      {
-         IStreamTypeDecoder decoder = state.Decoder.ReadNextTypeDecoder(stream, state);
-
-         CheckIsExpectedType<IListTypeDecoder>(decoder);
-
-         decoder.SkipValue(stream, state);
-      }
-
-      private static SaslInit ReadType(Stream stream, IStreamDecoderState state, IListTypeDecoder listDecoder)
+      protected override SaslInit ReadType(int count, Stream stream, IStreamDecoder decoder, IStreamDecoderState state)
       {
          SaslInit result = new();
 
-         _ = listDecoder.ReadSize(stream, state);
-         int count = listDecoder.ReadCount(stream, state);
-
-         // Don't decode anything if things already look wrong.
-         if (count < MinListEntries)
-         {
-            throw new DecodeException("Not enough entries in SaslInit list encoding: " + count);
-         }
-
-         if (count > MaxListEntries)
-         {
-            throw new DecodeException("To many entries in SaslInit list encoding: " + count);
-         }
-
          for (int index = 0; index < count; ++index)
          {
             switch (index)
diff --git a/src/Proton/Codec/Decoders/Security/SaslMechanismsTypeDecoder.cs b/src/Proton/Codec/Decoders/Security/SaslMechanismsTypeDecoder.cs
index acedaba..65e3319 100644
--- a/src/Proton/Codec/Decoders/Security/SaslMechanismsTypeDecoder.cs
+++ b/src/Proton/Codec/Decoders/Security/SaslMechanismsTypeDecoder.cs
@@ -24,7 +24,7 @@ using Apache.Qpid.Proton.Types.Security;
 
 namespace Apache.Qpid.Proton.Codec.Decoders.Security
 {
-   public sealed class SaslMechanismsTypeDecoder : AbstractDescribedTypeDecoder
+   public sealed class SaslMechanismsTypeDecoder : AbstractDescribedListTypeDecoder
    {
       private static readonly int RequiredListEntries = 1;
 
@@ -34,102 +34,24 @@ namespace Apache.Qpid.Proton.Codec.Decoders.Security
 
       public override Type DecodesType => typeof(SaslMechanisms);
 
-      public override object ReadValue(IProtonBuffer buffer, IDecoderState state)
-      {
-         ITypeDecoder decoder = state.Decoder.ReadNextTypeDecoder(buffer, state);
-
-         return ReadType(buffer, state, CheckIsExpectedTypeAndCast<IListTypeDecoder>(decoder));
-      }
-
-      public override Array ReadArrayElements(IProtonBuffer buffer, IDecoderState state, int count)
-      {
-         ITypeDecoder decoder = state.Decoder.ReadNextTypeDecoder(buffer, state);
-
-         CheckIsExpectedType<IListTypeDecoder>(decoder);
-
-         SaslMechanisms[] result = new SaslMechanisms[count];
-         for (int i = 0; i < count; ++i)
-         {
-            result[i] = ReadType(buffer, state, CheckIsExpectedTypeAndCast<IListTypeDecoder>(decoder));
-         }
-
-         return result;
-      }
-
-      public override void SkipValue(IProtonBuffer buffer, IDecoderState state)
-      {
-         ITypeDecoder decoder = state.Decoder.ReadNextTypeDecoder(buffer, state);
+      protected override int MinListElements => RequiredListEntries;
 
-         CheckIsExpectedType<IListTypeDecoder>(decoder);
+      protected override int MaxListElements => RequiredListEntries;
 
-         decoder.SkipValue(buffer, state);
-      }
-
-      private static SaslMechanisms ReadType(IProtonBuffer buffer, IDecoderState state, IListTypeDecoder listDecoder)
+      protected override SaslMechanisms ReadType(int count, IProtonBuffer buffer, IDecoder decoder, IDecoderState state)
       {
          SaslMechanisms result = new();
 
-         _ = listDecoder.ReadSize(buffer, state);
-         int count = listDecoder.ReadCount(buffer, state);
-
-         if (count != RequiredListEntries)
-         {
-            throw new DecodeException("SASL Mechanisms must contain at least one entry: " + count);
-         }
-         else
-         {
-            result.Mechanisms = state.Decoder.ReadMultiple<Symbol>(buffer, state);
-         }
-
-         return result;
-      }
-
-      public override object ReadValue(Stream stream, IStreamDecoderState state)
-      {
-         IStreamTypeDecoder decoder = state.Decoder.ReadNextTypeDecoder(stream, state);
-
-         return ReadType(stream, state, CheckIsExpectedTypeAndCast<IListTypeDecoder>(decoder));
-      }
-
-      public override Array ReadArrayElements(Stream stream, IStreamDecoderState state, int count)
-      {
-         IStreamTypeDecoder decoder = state.Decoder.ReadNextTypeDecoder(stream, state);
-
-         CheckIsExpectedType<IListTypeDecoder>(decoder);
-
-         SaslMechanisms[] result = new SaslMechanisms[count];
-         for (int i = 0; i < count; ++i)
-         {
-            result[i] = ReadType(stream, state, CheckIsExpectedTypeAndCast<IListTypeDecoder>(decoder));
-         }
+         result.Mechanisms = state.Decoder.ReadMultiple<Symbol>(buffer, state);
 
          return result;
       }
 
-      public override void SkipValue(Stream stream, IStreamDecoderState state)
-      {
-         IStreamTypeDecoder decoder = state.Decoder.ReadNextTypeDecoder(stream, state);
-
-         CheckIsExpectedType<IListTypeDecoder>(decoder);
-
-         decoder.SkipValue(stream, state);
-      }
-
-      private static SaslMechanisms ReadType(Stream stream, IStreamDecoderState state, IListTypeDecoder listDecoder)
+      protected override SaslMechanisms ReadType(int count, Stream stream, IStreamDecoder decoder, IStreamDecoderState state)
       {
          SaslMechanisms result = new();
 
-         _ = listDecoder.ReadSize(stream, state);
-         int count = listDecoder.ReadCount(stream, state);
-
-         if (count != RequiredListEntries)
-         {
-            throw new DecodeException("SASL Mechanisms must contain at least one entry: " + count);
-         }
-         else
-         {
-            result.Mechanisms = state.Decoder.ReadMultiple<Symbol>(stream, state);
-         }
+         result.Mechanisms = state.Decoder.ReadMultiple<Symbol>(stream, state);
 
          return result;
       }
diff --git a/src/Proton/Codec/Decoders/Security/SaslOutcomeTypeDecoder.cs b/src/Proton/Codec/Decoders/Security/SaslOutcomeTypeDecoder.cs
index e863cca..cacd25c 100644
--- a/src/Proton/Codec/Decoders/Security/SaslOutcomeTypeDecoder.cs
+++ b/src/Proton/Codec/Decoders/Security/SaslOutcomeTypeDecoder.cs
@@ -24,7 +24,7 @@ using Apache.Qpid.Proton.Types.Security;
 
 namespace Apache.Qpid.Proton.Codec.Decoders.Security
 {
-   public sealed class SaslOutcomeTypeDecoder : AbstractDescribedTypeDecoder
+   public sealed class SaslOutcomeTypeDecoder : AbstractDescribedListTypeDecoder
    {
       private static readonly int MinListEntries = 1;
       private static readonly int MaxListEntries = 2;
@@ -35,131 +35,33 @@ namespace Apache.Qpid.Proton.Codec.Decoders.Security
 
       public override Type DecodesType => typeof(SaslOutcome);
 
-      public override object ReadValue(IProtonBuffer buffer, IDecoderState state)
-      {
-         ITypeDecoder decoder = state.Decoder.ReadNextTypeDecoder(buffer, state);
-
-         return ReadType(buffer, state, CheckIsExpectedTypeAndCast<IListTypeDecoder>(decoder));
-      }
-
-      public override Array ReadArrayElements(IProtonBuffer buffer, IDecoderState state, int count)
-      {
-         ITypeDecoder decoder = state.Decoder.ReadNextTypeDecoder(buffer, state);
-
-         CheckIsExpectedType<IListTypeDecoder>(decoder);
+      protected override int MinListElements => MinListEntries;
 
-         SaslOutcome[] result = new SaslOutcome[count];
-         for (int i = 0; i < count; ++i)
-         {
-            result[i] = ReadType(buffer, state, CheckIsExpectedTypeAndCast<IListTypeDecoder>(decoder));
-         }
-
-         return result;
-      }
+      protected override int MaxListElements => MaxListEntries;
 
-      public override void SkipValue(IProtonBuffer buffer, IDecoderState state)
-      {
-         ITypeDecoder decoder = state.Decoder.ReadNextTypeDecoder(buffer, state);
-
-         CheckIsExpectedType<IListTypeDecoder>(decoder);
-
-         decoder.SkipValue(buffer, state);
-      }
-
-      private static SaslOutcome ReadType(IProtonBuffer buffer, IDecoderState state, IListTypeDecoder listDecoder)
+      protected override SaslOutcome ReadType(int count, IProtonBuffer buffer, IDecoder decoder, IDecoderState state)
       {
          SaslOutcome result = new();
 
-         _ = listDecoder.ReadSize(buffer, state);
-         int count = listDecoder.ReadCount(buffer, state);
-
-         // Don't decode anything if things already look wrong.
-         if (count < MinListEntries)
-         {
-            throw new DecodeException("Not enough entries in SaslOutcome list encoding: " + count);
-         }
-
-         if (count > MaxListEntries)
-         {
-            throw new DecodeException("To many entries in SaslOutcome list encoding: " + count);
-         }
-
-         for (int index = 0; index < count; ++index)
-         {
-            switch (index)
-            {
-               case 0:
-                  result.Code = (SaslCode)state.Decoder.ReadUnsignedByte(buffer, state);
-                  break;
-               case 1:
-                  result.AdditionalData = state.Decoder.ReadBinary(buffer, state);
-                  break;
-            }
-         }
-
-         return result;
-      }
-
-      public override object ReadValue(Stream stream, IStreamDecoderState state)
-      {
-         IStreamTypeDecoder decoder = state.Decoder.ReadNextTypeDecoder(stream, state);
-
-         return ReadType(stream, state, CheckIsExpectedTypeAndCast<IListTypeDecoder>(decoder));
-      }
-
-      public override Array ReadArrayElements(Stream stream, IStreamDecoderState state, int count)
-      {
-         IStreamTypeDecoder decoder = state.Decoder.ReadNextTypeDecoder(stream, state);
-
-         CheckIsExpectedType<IListTypeDecoder>(decoder);
+         result.Code = (SaslCode)state.Decoder.ReadUnsignedByte(buffer, state);
 
-         SaslOutcome[] result = new SaslOutcome[count];
-         for (int i = 0; i < count; ++i)
+         if (count == 2)
          {
-            result[i] = ReadType(stream, state, CheckIsExpectedTypeAndCast<IListTypeDecoder>(decoder));
+            result.AdditionalData = state.Decoder.ReadBinary(buffer, state);
          }
 
          return result;
       }
 
-      public override void SkipValue(Stream stream, IStreamDecoderState state)
-      {
-         IStreamTypeDecoder decoder = state.Decoder.ReadNextTypeDecoder(stream, state);
-
-         CheckIsExpectedType<IListTypeDecoder>(decoder);
-
-         decoder.SkipValue(stream, state);
-      }
-
-      private static SaslOutcome ReadType(Stream stream, IStreamDecoderState state, IListTypeDecoder listDecoder)
+      protected override SaslOutcome ReadType(int count, Stream stream, IStreamDecoder decoder, IStreamDecoderState state)
       {
          SaslOutcome result = new();
 
-         _ = listDecoder.ReadSize(stream, state);
-         int count = listDecoder.ReadCount(stream, state);
-
-         // Don't decode anything if things already look wrong.
-         if (count < MinListEntries)
-         {
-            throw new DecodeException("Not enough entries in SaslOutcome list encoding: " + count);
-         }
-
-         if (count > MaxListEntries)
-         {
-            throw new DecodeException("To many entries in SaslOutcome list encoding: " + count);
-         }
+         result.Code = (SaslCode)state.Decoder.ReadUnsignedByte(stream, state);
 
-         for (int index = 0; index < count; ++index)
+         if (count == 2)
          {
-            switch (index)
-            {
-               case 0:
-                  result.Code = (SaslCode)state.Decoder.ReadUnsignedByte(stream, state);
-                  break;
-               case 1:
-                  result.AdditionalData = state.Decoder.ReadBinary(stream, state);
-                  break;
-            }
+            result.AdditionalData = state.Decoder.ReadBinary(stream, state);
          }
 
          return result;
diff --git a/src/Proton/Codec/Decoders/Security/SaslResponseTypeDecoder.cs b/src/Proton/Codec/Decoders/Security/SaslResponseTypeDecoder.cs
index b6afef4..96b3fb0 100644
--- a/src/Proton/Codec/Decoders/Security/SaslResponseTypeDecoder.cs
+++ b/src/Proton/Codec/Decoders/Security/SaslResponseTypeDecoder.cs
@@ -24,7 +24,7 @@ using Apache.Qpid.Proton.Types.Security;
 
 namespace Apache.Qpid.Proton.Codec.Decoders.Security
 {
-   public sealed class SaslResponseTypeDecoder : AbstractDescribedTypeDecoder
+   public sealed class SaslResponseTypeDecoder : AbstractDescribedListTypeDecoder
    {
       private static readonly int RequiredListEntries = 1;
 
@@ -34,102 +34,24 @@ namespace Apache.Qpid.Proton.Codec.Decoders.Security
 
       public override Type DecodesType => typeof(SaslResponse);
 
-      public override object ReadValue(IProtonBuffer buffer, IDecoderState state)
-      {
-         ITypeDecoder decoder = state.Decoder.ReadNextTypeDecoder(buffer, state);
-
-         return ReadType(buffer, state, CheckIsExpectedTypeAndCast<IListTypeDecoder>(decoder));
-      }
-
-      public override Array ReadArrayElements(IProtonBuffer buffer, IDecoderState state, int count)
-      {
-         ITypeDecoder decoder = state.Decoder.ReadNextTypeDecoder(buffer, state);
-
-         CheckIsExpectedType<IListTypeDecoder>(decoder);
-
-         SaslResponse[] result = new SaslResponse[count];
-         for (int i = 0; i < count; ++i)
-         {
-            result[i] = ReadType(buffer, state, CheckIsExpectedTypeAndCast<IListTypeDecoder>(decoder));
-         }
-
-         return result;
-      }
-
-      public override void SkipValue(IProtonBuffer buffer, IDecoderState state)
-      {
-         ITypeDecoder decoder = state.Decoder.ReadNextTypeDecoder(buffer, state);
+      protected override int MinListElements => RequiredListEntries;
 
-         CheckIsExpectedType<IListTypeDecoder>(decoder);
+      protected override int MaxListElements => RequiredListEntries;
 
-         decoder.SkipValue(buffer, state);
-      }
-
-      private static SaslResponse ReadType(IProtonBuffer buffer, IDecoderState state, IListTypeDecoder listDecoder)
+      protected override SaslResponse ReadType(int count, IProtonBuffer buffer, IDecoder decoder, IDecoderState state)
       {
          SaslResponse result = new();
 
-         _ = listDecoder.ReadSize(buffer, state);
-         int count = listDecoder.ReadCount(buffer, state);
-
-         if (count != RequiredListEntries)
-         {
-            throw new DecodeException("SASL Response must contain at least one entry: " + count);
-         }
-         else
-         {
-            result.Response = state.Decoder.ReadBinary(buffer, state);
-         }
-
-         return result;
-      }
-
-      public override object ReadValue(Stream stream, IStreamDecoderState state)
-      {
-         IStreamTypeDecoder decoder = state.Decoder.ReadNextTypeDecoder(stream, state);
-
-         return ReadType(stream, state, CheckIsExpectedTypeAndCast<IListTypeDecoder>(decoder));
-      }
-
-      public override Array ReadArrayElements(Stream stream, IStreamDecoderState state, int count)
-      {
-         IStreamTypeDecoder decoder = state.Decoder.ReadNextTypeDecoder(stream, state);
-
-         CheckIsExpectedType<IListTypeDecoder>(decoder);
-
-         SaslResponse[] result = new SaslResponse[count];
-         for (int i = 0; i < count; ++i)
-         {
-            result[i] = ReadType(stream, state, CheckIsExpectedTypeAndCast<IListTypeDecoder>(decoder));
-         }
+         result.Response = state.Decoder.ReadBinary(buffer, state);
 
          return result;
       }
 
-      public override void SkipValue(Stream stream, IStreamDecoderState state)
-      {
-         IStreamTypeDecoder decoder = state.Decoder.ReadNextTypeDecoder(stream, state);
-
-         CheckIsExpectedType<IListTypeDecoder>(decoder);
-
-         decoder.SkipValue(stream, state);
-      }
-
-      private static SaslResponse ReadType(Stream stream, IStreamDecoderState state, IListTypeDecoder listDecoder)
+      protected override SaslResponse ReadType(int count, Stream stream, IStreamDecoder decoder, IStreamDecoderState state)
       {
          SaslResponse result = new();
 
-         _ = listDecoder.ReadSize(stream, state);
-         int count = listDecoder.ReadCount(stream, state);
-
-         if (count != RequiredListEntries)
-         {
-            throw new DecodeException("SASL Response must contain at least one entry: " + count);
-         }
-         else
-         {
-            result.Response = state.Decoder.ReadBinary(stream, state);
-         }
+         result.Response = state.Decoder.ReadBinary(stream, state);
 
          return result;
       }
diff --git a/src/Proton/Codec/Decoders/Transactions/CoordinatorTypeDecoder.cs b/src/Proton/Codec/Decoders/Transactions/CoordinatorTypeDecoder.cs
index d2ac1a4..9c4a48a 100644
--- a/src/Proton/Codec/Decoders/Transactions/CoordinatorTypeDecoder.cs
+++ b/src/Proton/Codec/Decoders/Transactions/CoordinatorTypeDecoder.cs
@@ -24,7 +24,7 @@ using Apache.Qpid.Proton.Types.Transactions;
 
 namespace Apache.Qpid.Proton.Codec.Decoders.Transactions
 {
-   public sealed class CoordinatorTypeDecoder : AbstractDescribedTypeDecoder
+   public sealed class CoordinatorTypeDecoder : AbstractDescribedListTypeDecoder
    {
       private static readonly int MinRequiredListEntries = 0;
       private static readonly int MaxRequiredListEntries = 1;
@@ -35,53 +35,15 @@ namespace Apache.Qpid.Proton.Codec.Decoders.Transactions
 
       public override Type DecodesType => typeof(Coordinator);
 
-      public override object ReadValue(IProtonBuffer buffer, IDecoderState state)
-      {
-         ITypeDecoder decoder = state.Decoder.ReadNextTypeDecoder(buffer, state);
-
-         return ReadType(buffer, state, CheckIsExpectedTypeAndCast<IListTypeDecoder>(decoder));
-      }
-
-      public override Array ReadArrayElements(IProtonBuffer buffer, IDecoderState state, int count)
-      {
-         ITypeDecoder decoder = state.Decoder.ReadNextTypeDecoder(buffer, state);
-
-         CheckIsExpectedType<IListTypeDecoder>(decoder);
-
-         Coordinator[] result = new Coordinator[count];
-         for (int i = 0; i < count; ++i)
-         {
-            result[i] = ReadType(buffer, state, CheckIsExpectedTypeAndCast<IListTypeDecoder>(decoder));
-         }
-
-         return result;
-      }
-
-      public override void SkipValue(IProtonBuffer buffer, IDecoderState state)
-      {
-         ITypeDecoder decoder = state.Decoder.ReadNextTypeDecoder(buffer, state);
+      protected override int MinListElements => MinRequiredListEntries;
 
-         CheckIsExpectedType<IListTypeDecoder>(decoder);
+      protected override int MaxListElements => MaxRequiredListEntries;
 
-         decoder.SkipValue(buffer, state);
-      }
-
-      private static Coordinator ReadType(IProtonBuffer buffer, IDecoderState state, IListTypeDecoder listDecoder)
+      protected override Coordinator ReadType(int count, IProtonBuffer buffer, IDecoder decoder, IDecoderState state)
       {
          Coordinator result = new();
 
-         _ = listDecoder.ReadSize(buffer, state);
-         int count = listDecoder.ReadCount(buffer, state);
-
-         if (count < MinRequiredListEntries)
-         {
-            throw new DecodeException("Not enough entries in Coordinator list encoding: " + count);
-         }
-         else if (count > MaxRequiredListEntries)
-         {
-            throw new DecodeException("To many entries in Coordinator list encoding: " + count);
-         }
-         else if (count == 1)
+         if (count == 1)
          {
             result.Capabilities = state.Decoder.ReadMultiple<Symbol>(buffer, state);
          }
@@ -89,53 +51,11 @@ namespace Apache.Qpid.Proton.Codec.Decoders.Transactions
          return result;
       }
 
-      public override object ReadValue(Stream stream, IStreamDecoderState state)
-      {
-         IStreamTypeDecoder decoder = state.Decoder.ReadNextTypeDecoder(stream, state);
-
-         return ReadType(stream, state, CheckIsExpectedTypeAndCast<IListTypeDecoder>(decoder));
-      }
-
-      public override Array ReadArrayElements(Stream stream, IStreamDecoderState state, int count)
-      {
-         IStreamTypeDecoder decoder = state.Decoder.ReadNextTypeDecoder(stream, state);
-
-         CheckIsExpectedType<IListTypeDecoder>(decoder);
-
-         Coordinator[] result = new Coordinator[count];
-         for (int i = 0; i < count; ++i)
-         {
-            result[i] = ReadType(stream, state, CheckIsExpectedTypeAndCast<IListTypeDecoder>(decoder));
-         }
-
-         return result;
-      }
-
-      public override void SkipValue(Stream stream, IStreamDecoderState state)
-      {
-         IStreamTypeDecoder decoder = state.Decoder.ReadNextTypeDecoder(stream, state);
-
-         CheckIsExpectedType<IListTypeDecoder>(decoder);
-
-         decoder.SkipValue(stream, state);
-      }
-
-      private static Coordinator ReadType(Stream stream, IStreamDecoderState state, IListTypeDecoder listDecoder)
+      protected override Coordinator ReadType(int count, Stream stream, IStreamDecoder decoder, IStreamDecoderState state)
       {
          Coordinator result = new();
 
-         _ = listDecoder.ReadSize(stream, state);
-         int count = listDecoder.ReadCount(stream, state);
-
-         if (count < MinRequiredListEntries)
-         {
-            throw new DecodeException("Not enough entries in Coordinator list encoding: " + count);
-         }
-         else if (count > MaxRequiredListEntries)
-         {
-            throw new DecodeException("To many entries in Coordinator list encoding: " + count);
-         }
-         else if (count == 1)
+         if (count == 1)
          {
             result.Capabilities = state.Decoder.ReadMultiple<Symbol>(stream, state);
          }
diff --git a/src/Proton/Codec/Decoders/Transactions/DeclareTypeDecoder.cs b/src/Proton/Codec/Decoders/Transactions/DeclareTypeDecoder.cs
index a425ca1..71fc341 100644
--- a/src/Proton/Codec/Decoders/Transactions/DeclareTypeDecoder.cs
+++ b/src/Proton/Codec/Decoders/Transactions/DeclareTypeDecoder.cs
@@ -24,7 +24,7 @@ using Apache.Qpid.Proton.Types.Transactions;
 
 namespace Apache.Qpid.Proton.Codec.Decoders.Transactions
 {
-   public sealed class DeclareTypeDecoder : AbstractDescribedTypeDecoder
+   public sealed class DeclareTypeDecoder : AbstractDescribedListTypeDecoder
    {
       private static readonly int MinRequiredListEntries = 0;
       private static readonly int MaxRequiredListEntries = 1;
@@ -35,53 +35,15 @@ namespace Apache.Qpid.Proton.Codec.Decoders.Transactions
 
       public override Type DecodesType => typeof(Declare);
 
-      public override object ReadValue(IProtonBuffer buffer, IDecoderState state)
-      {
-         ITypeDecoder decoder = state.Decoder.ReadNextTypeDecoder(buffer, state);
-
-         return ReadType(buffer, state, CheckIsExpectedTypeAndCast<IListTypeDecoder>(decoder));
-      }
-
-      public override Array ReadArrayElements(IProtonBuffer buffer, IDecoderState state, int count)
-      {
-         ITypeDecoder decoder = state.Decoder.ReadNextTypeDecoder(buffer, state);
-
-         CheckIsExpectedType<IListTypeDecoder>(decoder);
-
-         Declare[] result = new Declare[count];
-         for (int i = 0; i < count; ++i)
-         {
-            result[i] = ReadType(buffer, state, CheckIsExpectedTypeAndCast<IListTypeDecoder>(decoder));
-         }
-
-         return result;
-      }
-
-      public override void SkipValue(IProtonBuffer buffer, IDecoderState state)
-      {
-         ITypeDecoder decoder = state.Decoder.ReadNextTypeDecoder(buffer, state);
+      protected override int MinListElements => MinRequiredListEntries;
 
-         CheckIsExpectedType<IListTypeDecoder>(decoder);
+      protected override int MaxListElements => MaxRequiredListEntries;
 
-         decoder.SkipValue(buffer, state);
-      }
-
-      private static Declare ReadType(IProtonBuffer buffer, IDecoderState state, IListTypeDecoder listDecoder)
+      protected override Declare ReadType(int count, IProtonBuffer buffer, IDecoder decoder, IDecoderState state)
       {
          Declare result = new();
 
-         _ = listDecoder.ReadSize(buffer, state);
-         int count = listDecoder.ReadCount(buffer, state);
-
-         if (count < MinRequiredListEntries)
-         {
-            throw new DecodeException("Not enough entries in Declare list encoding: " + count);
-         }
-         else if (count > MaxRequiredListEntries)
-         {
-            throw new DecodeException("To many entries in Declare list encoding: " + count);
-         }
-         else if (count == 1)
+         if (count == 1)
          {
             result.GlobalTxnId = (IGlobalTxnId)state.Decoder.ReadObject(buffer, state);
          }
@@ -89,53 +51,11 @@ namespace Apache.Qpid.Proton.Codec.Decoders.Transactions
          return result;
       }
 
-      public override object ReadValue(Stream stream, IStreamDecoderState state)
-      {
-         IStreamTypeDecoder decoder = state.Decoder.ReadNextTypeDecoder(stream, state);
-
-         return ReadType(stream, state, CheckIsExpectedTypeAndCast<IListTypeDecoder>(decoder));
-      }
-
-      public override Array ReadArrayElements(Stream stream, IStreamDecoderState state, int count)
-      {
-         IStreamTypeDecoder decoder = state.Decoder.ReadNextTypeDecoder(stream, state);
-
-         CheckIsExpectedType<IListTypeDecoder>(decoder);
-
-         Declare[] result = new Declare[count];
-         for (int i = 0; i < count; ++i)
-         {
-            result[i] = ReadType(stream, state, CheckIsExpectedTypeAndCast<IListTypeDecoder>(decoder));
-         }
-
-         return result;
-      }
-
-      public override void SkipValue(Stream stream, IStreamDecoderState state)
-      {
-         IStreamTypeDecoder decoder = state.Decoder.ReadNextTypeDecoder(stream, state);
-
-         CheckIsExpectedType<IListTypeDecoder>(decoder);
-
-         decoder.SkipValue(stream, state);
-      }
-
-      private static Declare ReadType(Stream stream, IStreamDecoderState state, IListTypeDecoder listDecoder)
+      protected override Declare ReadType(int count, Stream stream, IStreamDecoder decoder, IStreamDecoderState state)
       {
          Declare result = new();
 
-         _ = listDecoder.ReadSize(stream, state);
-         int count = listDecoder.ReadCount(stream, state);
-
-         if (count < MinRequiredListEntries)
-         {
-            throw new DecodeException("Not enough entries in Declare list encoding: " + count);
-         }
-         else if (count > MaxRequiredListEntries)
-         {
-            throw new DecodeException("To many entries in Declare list encoding: " + count);
-         }
-         else if (count == 1)
+         if (count == 1)
          {
             result.GlobalTxnId = (IGlobalTxnId)state.Decoder.ReadObject(stream, state);
          }
diff --git a/src/Proton/Codec/Decoders/Transactions/DeclaredTypeDecoder.cs b/src/Proton/Codec/Decoders/Transactions/DeclaredTypeDecoder.cs
index fd2cfe5..9b6aabd 100644
--- a/src/Proton/Codec/Decoders/Transactions/DeclaredTypeDecoder.cs
+++ b/src/Proton/Codec/Decoders/Transactions/DeclaredTypeDecoder.cs
@@ -24,7 +24,7 @@ using Apache.Qpid.Proton.Types.Transactions;
 
 namespace Apache.Qpid.Proton.Codec.Decoders.Transactions
 {
-   public sealed class DeclaredTypeDecoder : AbstractDescribedTypeDecoder
+   public sealed class DeclaredTypeDecoder : AbstractDescribedListTypeDecoder
    {
       private static readonly int MinRequiredListEntries = 1;
       private static readonly int MaxRequiredListEntries = 1;
@@ -35,110 +35,24 @@ namespace Apache.Qpid.Proton.Codec.Decoders.Transactions
 
       public override Type DecodesType => typeof(Declared);
 
-      public override object ReadValue(IProtonBuffer buffer, IDecoderState state)
-      {
-         ITypeDecoder decoder = state.Decoder.ReadNextTypeDecoder(buffer, state);
-
-         return ReadType(buffer, state, CheckIsExpectedTypeAndCast<IListTypeDecoder>(decoder));
-      }
-
-      public override Array ReadArrayElements(IProtonBuffer buffer, IDecoderState state, int count)
-      {
-         ITypeDecoder decoder = state.Decoder.ReadNextTypeDecoder(buffer, state);
-
-         CheckIsExpectedType<IListTypeDecoder>(decoder);
-
-         Declared[] result = new Declared[count];
-         for (int i = 0; i < count; ++i)
-         {
-            result[i] = ReadType(buffer, state, CheckIsExpectedTypeAndCast<IListTypeDecoder>(decoder));
-         }
-
-         return result;
-      }
-
-      public override void SkipValue(IProtonBuffer buffer, IDecoderState state)
-      {
-         ITypeDecoder decoder = state.Decoder.ReadNextTypeDecoder(buffer, state);
+      protected override int MinListElements => MinRequiredListEntries;
 
-         CheckIsExpectedType<IListTypeDecoder>(decoder);
+      protected override int MaxListElements => MaxRequiredListEntries;
 
-         decoder.SkipValue(buffer, state);
-      }
-
-      private static Declared ReadType(IProtonBuffer buffer, IDecoderState state, IListTypeDecoder listDecoder)
+      protected override Declared ReadType(int count, IProtonBuffer buffer, IDecoder decoder, IDecoderState state)
       {
          Declared result = new();
 
-         _ = listDecoder.ReadSize(buffer, state);
-         int count = listDecoder.ReadCount(buffer, state);
-
-         if (count < MinRequiredListEntries)
-         {
-            throw new DecodeException("The txn-id field cannot be omitted");
-         }
-         else if (count > MaxRequiredListEntries)
-         {
-            throw new DecodeException("To many entries in Declared list encoding: " + count);
-         }
-         else if (count == 1)
-         {
-            result.TxnId = state.Decoder.ReadBinary(buffer, state);
-         }
-
-         return result;
-      }
-
-      public override object ReadValue(Stream stream, IStreamDecoderState state)
-      {
-         IStreamTypeDecoder decoder = state.Decoder.ReadNextTypeDecoder(stream, state);
-
-         return ReadType(stream, state, CheckIsExpectedTypeAndCast<IListTypeDecoder>(decoder));
-      }
-
-      public override Array ReadArrayElements(Stream stream, IStreamDecoderState state, int count)
-      {
-         IStreamTypeDecoder decoder = state.Decoder.ReadNextTypeDecoder(stream, state);
-
-         CheckIsExpectedType<IListTypeDecoder>(decoder);
-
-         Declared[] result = new Declared[count];
-         for (int i = 0; i < count; ++i)
-         {
-            result[i] = ReadType(stream, state, CheckIsExpectedTypeAndCast<IListTypeDecoder>(decoder));
-         }
+         result.TxnId = state.Decoder.ReadBinary(buffer, state);
 
          return result;
       }
 
-      public override void SkipValue(Stream stream, IStreamDecoderState state)
-      {
-         IStreamTypeDecoder decoder = state.Decoder.ReadNextTypeDecoder(stream, state);
-
-         CheckIsExpectedType<IListTypeDecoder>(decoder);
-
-         decoder.SkipValue(stream, state);
-      }
-
-      private static Declared ReadType(Stream stream, IStreamDecoderState state, IListTypeDecoder listDecoder)
+      protected override Declared ReadType(int count, Stream stream, IStreamDecoder decoder, IStreamDecoderState state)
       {
          Declared result = new();
 
-         _ = listDecoder.ReadSize(stream, state);
-         int count = listDecoder.ReadCount(stream, state);
-
-         if (count < MinRequiredListEntries)
-         {
-            throw new DecodeException("The txn-id field cannot be omitted");
-         }
-         else if (count > MaxRequiredListEntries)
-         {
-            throw new DecodeException("To many entries in Declared list encoding: " + count);
-         }
-         else if (count == 1)
-         {
-            result.TxnId = state.Decoder.ReadBinary(stream, state);
-         }
+         result.TxnId = state.Decoder.ReadBinary(stream, state);
 
          return result;
       }
diff --git a/src/Proton/Codec/Decoders/Transactions/DischargeTypeDecoder.cs b/src/Proton/Codec/Decoders/Transactions/DischargeTypeDecoder.cs
index 64b5b97..5df5df2 100644
--- a/src/Proton/Codec/Decoders/Transactions/DischargeTypeDecoder.cs
+++ b/src/Proton/Codec/Decoders/Transactions/DischargeTypeDecoder.cs
@@ -24,7 +24,7 @@ using Apache.Qpid.Proton.Types.Transactions;
 
 namespace Apache.Qpid.Proton.Codec.Decoders.Transactions
 {
-   public sealed class DischargeTypeDecoder : AbstractDescribedTypeDecoder
+   public sealed class DischargeTypeDecoder : AbstractDescribedListTypeDecoder
    {
       private static readonly int MinRequiredListEntries = 1;
       private static readonly int MaxRequiredListEntries = 2;
@@ -35,127 +35,33 @@ namespace Apache.Qpid.Proton.Codec.Decoders.Transactions
 
       public override Type DecodesType => typeof(Discharge);
 
-      public override object ReadValue(IProtonBuffer buffer, IDecoderState state)
-      {
-         ITypeDecoder decoder = state.Decoder.ReadNextTypeDecoder(buffer, state);
-
-         return ReadType(buffer, state, CheckIsExpectedTypeAndCast<IListTypeDecoder>(decoder));
-      }
-
-      public override Array ReadArrayElements(IProtonBuffer buffer, IDecoderState state, int count)
-      {
-         ITypeDecoder decoder = state.Decoder.ReadNextTypeDecoder(buffer, state);
-
-         CheckIsExpectedType<IListTypeDecoder>(decoder);
-
-         Discharge[] result = new Discharge[count];
-         for (int i = 0; i < count; ++i)
-         {
-            result[i] = ReadType(buffer, state, CheckIsExpectedTypeAndCast<IListTypeDecoder>(decoder));
-         }
-
-         return result;
-      }
-
-      public override void SkipValue(IProtonBuffer buffer, IDecoderState state)
-      {
-         ITypeDecoder decoder = state.Decoder.ReadNextTypeDecoder(buffer, state);
+      protected override int MinListElements => MinRequiredListEntries;
 
-         CheckIsExpectedType<IListTypeDecoder>(decoder);
+      protected override int MaxListElements => MaxRequiredListEntries;
 
-         decoder.SkipValue(buffer, state);
-      }
-
-      private static Discharge ReadType(IProtonBuffer buffer, IDecoderState state, IListTypeDecoder listDecoder)
+      protected override Discharge ReadType(int count, IProtonBuffer buffer, IDecoder decoder, IDecoderState state)
       {
          Discharge result = new();
 
-         _ = listDecoder.ReadSize(buffer, state);
-         int count = listDecoder.ReadCount(buffer, state);
-
-         if (count < MinRequiredListEntries)
-         {
-            throw new DecodeException("Not enough entries in Discharge list encoding: " + count);
-         }
-         else if (count > MaxRequiredListEntries)
-         {
-            throw new DecodeException("To many entries in Discharge list encoding: " + count);
-         }
-
-         for (int index = 0; index < count; ++index)
-         {
-            switch (index)
-            {
-               case 0:
-                  result.TxnId = state.Decoder.ReadBinary(buffer, state);
-                  break;
-               case 1:
-                  result.Fail = state.Decoder.ReadBoolean(buffer, state) ?? false;
-                  break;
-            }
-         }
-
-         return result;
-      }
-
-      public override object ReadValue(Stream stream, IStreamDecoderState state)
-      {
-         IStreamTypeDecoder decoder = state.Decoder.ReadNextTypeDecoder(stream, state);
-
-         return ReadType(stream, state, CheckIsExpectedTypeAndCast<IListTypeDecoder>(decoder));
-      }
-
-      public override Array ReadArrayElements(Stream stream, IStreamDecoderState state, int count)
-      {
-         IStreamTypeDecoder decoder = state.Decoder.ReadNextTypeDecoder(stream, state);
+         result.TxnId = state.Decoder.ReadBinary(buffer, state);
 
-         CheckIsExpectedType<IListTypeDecoder>(decoder);
-
-         Discharge[] result = new Discharge[count];
-         for (int i = 0; i < count; ++i)
+         if (count == 2)
          {
-            result[i] = ReadType(stream, state, CheckIsExpectedTypeAndCast<IListTypeDecoder>(decoder));
+            result.Fail = state.Decoder.ReadBoolean(buffer, state) ?? false;
          }
 
          return result;
       }
 
-      public override void SkipValue(Stream stream, IStreamDecoderState state)
-      {
-         IStreamTypeDecoder decoder = state.Decoder.ReadNextTypeDecoder(stream, state);
-
-         CheckIsExpectedType<IListTypeDecoder>(decoder);
-
-         decoder.SkipValue(stream, state);
-      }
-
-      private static Discharge ReadType(Stream stream, IStreamDecoderState state, IListTypeDecoder listDecoder)
+      protected override Discharge ReadType(int count, Stream stream, IStreamDecoder decoder, IStreamDecoderState state)
       {
          Discharge result = new();
 
-         _ = listDecoder.ReadSize(stream, state);
-         int count = listDecoder.ReadCount(stream, state);
-
-         if (count < MinRequiredListEntries)
-         {
-            throw new DecodeException("Not enough entries in Discharge list encoding: " + count);
-         }
-         else if (count > MaxRequiredListEntries)
-         {
-            throw new DecodeException("To many entries in Discharge list encoding: " + count);
-         }
+         result.TxnId = state.Decoder.ReadBinary(stream, state);
 
-         for (int index = 0; index < count; ++index)
+         if (count == 2)
          {
-            switch (index)
-            {
-               case 0:
-                  result.TxnId = state.Decoder.ReadBinary(stream, state);
-                  break;
-               case 1:
-                  result.Fail = state.Decoder.ReadBoolean(stream, state) ?? false;
-                  break;
-            }
+            result.Fail = state.Decoder.ReadBoolean(stream, state) ?? false;
          }
 
          return result;
diff --git a/src/Proton/Codec/Decoders/Transactions/TransactionalStateTypeDecoder.cs b/src/Proton/Codec/Decoders/Transactions/TransactionalStateTypeDecoder.cs
index 7ca6f42..bb2f61a 100644
--- a/src/Proton/Codec/Decoders/Transactions/TransactionalStateTypeDecoder.cs
+++ b/src/Proton/Codec/Decoders/Transactions/TransactionalStateTypeDecoder.cs
@@ -25,7 +25,7 @@ using Apache.Qpid.Proton.Types.Messaging;
 
 namespace Apache.Qpid.Proton.Codec.Decoders.Transactions
 {
-   public sealed class TransactionalStateTypeDecoder : AbstractDescribedTypeDecoder
+   public sealed class TransactionalStateTypeDecoder : AbstractDescribedListTypeDecoder
    {
       private static readonly int MinRequiredListEntries = 1;
       private static readonly int MaxRequiredListEntries = 2;
@@ -36,127 +36,33 @@ namespace Apache.Qpid.Proton.Codec.Decoders.Transactions
 
       public override Type DecodesType => typeof(TransactionalState);
 
-      public override object ReadValue(IProtonBuffer buffer, IDecoderState state)
-      {
-         ITypeDecoder decoder = state.Decoder.ReadNextTypeDecoder(buffer, state);
-
-         return ReadType(buffer, state, CheckIsExpectedTypeAndCast<IListTypeDecoder>(decoder));
-      }
-
-      public override Array ReadArrayElements(IProtonBuffer buffer, IDecoderState state, int count)
-      {
-         ITypeDecoder decoder = state.Decoder.ReadNextTypeDecoder(buffer, state);
-
-         CheckIsExpectedType<IListTypeDecoder>(decoder);
-
-         TransactionalState[] result = new TransactionalState[count];
-         for (int i = 0; i < count; ++i)
-         {
-            result[i] = ReadType(buffer, state, CheckIsExpectedTypeAndCast<IListTypeDecoder>(decoder));
-         }
-
-         return result;
-      }
-
-      public override void SkipValue(IProtonBuffer buffer, IDecoderState state)
-      {
-         ITypeDecoder decoder = state.Decoder.ReadNextTypeDecoder(buffer, state);
+      protected override int MinListElements => MinRequiredListEntries;
 
-         CheckIsExpectedType<IListTypeDecoder>(decoder);
+      protected override int MaxListElements => MaxRequiredListEntries;
 
-         decoder.SkipValue(buffer, state);
-      }
-
-      private static TransactionalState ReadType(IProtonBuffer buffer, IDecoderState state, IListTypeDecoder listDecoder)
+      protected override TransactionalState ReadType(int count, IProtonBuffer buffer, IDecoder decoder, IDecoderState state)
       {
          TransactionalState result = new();
 
-         _ = listDecoder.ReadSize(buffer, state);
-         int count = listDecoder.ReadCount(buffer, state);
-
-         if (count < MinRequiredListEntries)
-         {
-            throw new DecodeException("Not enough entries in TransactionalState list encoding: " + count);
-         }
-         else if (count > MaxRequiredListEntries)
-         {
-            throw new DecodeException("To many entries in TransactionalState list encoding: " + count);
-         }
-
-         for (int index = 0; index < count; ++index)
-         {
-            switch (index)
-            {
-               case 0:
-                  result.TxnId = state.Decoder.ReadBinary(buffer, state);
-                  break;
-               case 1:
-                  result.Outcome = state.Decoder.ReadObject<IOutcome>(buffer, state);
-                  break;
-            }
-         }
-
-         return result;
-      }
-
-      public override object ReadValue(Stream stream, IStreamDecoderState state)
-      {
-         IStreamTypeDecoder decoder = state.Decoder.ReadNextTypeDecoder(stream, state);
-
-         return ReadType(stream, state, CheckIsExpectedTypeAndCast<IListTypeDecoder>(decoder));
-      }
-
-      public override Array ReadArrayElements(Stream stream, IStreamDecoderState state, int count)
-      {
-         IStreamTypeDecoder decoder = state.Decoder.ReadNextTypeDecoder(stream, state);
+         result.TxnId = state.Decoder.ReadBinary(buffer, state);
 
-         CheckIsExpectedType<IListTypeDecoder>(decoder);
-
-         TransactionalState[] result = new TransactionalState[count];
-         for (int i = 0; i < count; ++i)
+         if (count == 2)
          {
-            result[i] = ReadType(stream, state, CheckIsExpectedTypeAndCast<IListTypeDecoder>(decoder));
+            result.Outcome = state.Decoder.ReadObject<IOutcome>(buffer, state);
          }
 
          return result;
       }
 
-      public override void SkipValue(Stream stream, IStreamDecoderState state)
-      {
-         IStreamTypeDecoder decoder = state.Decoder.ReadNextTypeDecoder(stream, state);
-
-         CheckIsExpectedType<IListTypeDecoder>(decoder);
-
-         decoder.SkipValue(stream, state);
-      }
-
-      private static TransactionalState ReadType(Stream stream, IStreamDecoderState state, IListTypeDecoder listDecoder)
+      protected override TransactionalState ReadType(int count, Stream stream, IStreamDecoder decoder, IStreamDecoderState state)
       {
          TransactionalState result = new();
 
-         _ = listDecoder.ReadSize(stream, state);
-         int count = listDecoder.ReadCount(stream, state);
-
-         if (count < MinRequiredListEntries)
-         {
-            throw new DecodeException("Not enough entries in TransactionalState list encoding: " + count);
-         }
-         else if (count > MaxRequiredListEntries)
-         {
-            throw new DecodeException("To many entries in TransactionalState list encoding: " + count);
-         }
+         result.TxnId = state.Decoder.ReadBinary(stream, state);
 
-         for (int index = 0; index < count; ++index)
+         if (count == 2)
          {
-            switch (index)
-            {
-               case 0:
-                  result.TxnId = state.Decoder.ReadBinary(stream, state);
-                  break;
-               case 1:
-                  result.Outcome = state.Decoder.ReadObject<IOutcome>(stream, state);
-                  break;
-            }
+            result.Outcome = state.Decoder.ReadObject<IOutcome>(stream, state);
          }
 
          return result;
diff --git a/src/Proton/Codec/Decoders/Transport/AttachTypeDecoder.cs b/src/Proton/Codec/Decoders/Transport/AttachTypeDecoder.cs
index 9258f5d..f4d52d7 100644
--- a/src/Proton/Codec/Decoders/Transport/AttachTypeDecoder.cs
+++ b/src/Proton/Codec/Decoders/Transport/AttachTypeDecoder.cs
@@ -25,7 +25,7 @@ using Apache.Qpid.Proton.Types.Messaging;
 
 namespace Apache.Qpid.Proton.Codec.Decoders.Transport
 {
-   public sealed class AttachTypeDecoder : AbstractDescribedTypeDecoder
+   public sealed class AttachTypeDecoder : AbstractDescribedListTypeDecoder
    {
       private static readonly int MinAttachListEntries = 3;
       private static readonly int MaxAttachListEntries = 14;
@@ -36,52 +36,14 @@ namespace Apache.Qpid.Proton.Codec.Decoders.Transport
 
       public override Type DecodesType => typeof(Attach);
 
-      public override object ReadValue(IProtonBuffer buffer, IDecoderState state)
-      {
-         ITypeDecoder decoder = state.Decoder.ReadNextTypeDecoder(buffer, state);
-
-         return ReadAttach(buffer, state, CheckIsExpectedTypeAndCast<IListTypeDecoder>(decoder));
-      }
-
-      public override Array ReadArrayElements(IProtonBuffer buffer, IDecoderState state, int count)
-      {
-         ITypeDecoder decoder = state.Decoder.ReadNextTypeDecoder(buffer, state);
-
-         Attach[] result = new Attach[count];
-         for (int i = 0; i < count; ++i)
-         {
-            result[i] = ReadAttach(buffer, state, CheckIsExpectedTypeAndCast<IListTypeDecoder>(decoder));
-         }
+      protected override int MinListElements => MinAttachListEntries;
 
-         return result;
-      }
+      protected override int MaxListElements => MaxAttachListEntries;
 
-      public override void SkipValue(IProtonBuffer buffer, IDecoderState state)
-      {
-         ITypeDecoder decoder = state.Decoder.ReadNextTypeDecoder(buffer, state);
-
-         CheckIsExpectedType<IListTypeDecoder>(decoder);
-
-         decoder.SkipValue(buffer, state);
-      }
-
-      private static Attach ReadAttach(IProtonBuffer buffer, IDecoderState state, IListTypeDecoder listDecoder)
+      protected override Attach ReadType(int count, IProtonBuffer buffer, IDecoder decoder, IDecoderState state)
       {
          Attach result = new();
 
-         _ = listDecoder.ReadSize(buffer, state);
-         int count = listDecoder.ReadCount(buffer, state);
-
-         if (count < MinAttachListEntries)
-         {
-            throw new DecodeException(ErrorForMissingRequiredFields(count));
-         }
-
-         if (count > MaxAttachListEntries)
-         {
-            throw new DecodeException("To many entries in Attach list encoding: " + count);
-         }
-
          for (int index = 0; index < count; ++index)
          {
             // Peek ahead and see if there is a null in the next slot, if so we don't call
@@ -153,52 +115,10 @@ namespace Apache.Qpid.Proton.Codec.Decoders.Transport
          return result;
       }
 
-      public override object ReadValue(Stream stream, IStreamDecoderState state)
-      {
-         IStreamTypeDecoder decoder = state.Decoder.ReadNextTypeDecoder(stream, state);
-
-         return ReadAttach(stream, state, CheckIsExpectedTypeAndCast<IListTypeDecoder>(decoder));
-      }
-
-      public override Array ReadArrayElements(Stream stream, IStreamDecoderState state, int count)
-      {
-         IStreamTypeDecoder decoder = state.Decoder.ReadNextTypeDecoder(stream, state);
-
-         Attach[] result = new Attach[count];
-         for (int i = 0; i < count; ++i)
-         {
-            result[i] = ReadAttach(stream, state, CheckIsExpectedTypeAndCast<IListTypeDecoder>(decoder));
-         }
-
-         return result;
-      }
-
-      public override void SkipValue(Stream stream, IStreamDecoderState state)
-      {
-         IStreamTypeDecoder decoder = state.Decoder.ReadNextTypeDecoder(stream, state);
-
-         CheckIsExpectedType<IListTypeDecoder>(decoder);
-
-         decoder.SkipValue(stream, state);
-      }
-
-      private static Attach ReadAttach(Stream stream, IStreamDecoderState state, IListTypeDecoder listDecoder)
+      protected override Attach ReadType(int count, Stream stream, IStreamDecoder decoder, IStreamDecoderState state)
       {
          Attach result = new();
 
-         _ = listDecoder.ReadSize(stream, state);
-         int count = listDecoder.ReadCount(stream, state);
-
-         if (count < MinAttachListEntries)
-         {
-            throw new DecodeException(ErrorForMissingRequiredFields(count));
-         }
-
-         if (count > MaxAttachListEntries)
-         {
-            throw new DecodeException("To many entries in Attach list encoding: " + count);
-         }
-
          for (int index = 0; index < count; ++index)
          {
             // Peek ahead and see if there is a null in the next slot, if so we don't call
diff --git a/src/Proton/Codec/Decoders/Transport/BeginTypeDecoder.cs b/src/Proton/Codec/Decoders/Transport/BeginTypeDecoder.cs
index e9e98e7..c19a642 100644
--- a/src/Proton/Codec/Decoders/Transport/BeginTypeDecoder.cs
+++ b/src/Proton/Codec/Decoders/Transport/BeginTypeDecoder.cs
@@ -24,7 +24,7 @@ using Apache.Qpid.Proton.Types.Transport;
 
 namespace Apache.Qpid.Proton.Codec.Decoders.Transport
 {
-   public sealed class BeginTypeDecoder : AbstractDescribedTypeDecoder
+   public sealed class BeginTypeDecoder : AbstractDescribedListTypeDecoder
    {
       private static readonly int MinBeginListEntries = 4;
       private static readonly int MaxBeginListEntries = 8;
@@ -35,52 +35,14 @@ namespace Apache.Qpid.Proton.Codec.Decoders.Transport
 
       public override Type DecodesType => typeof(Begin);
 
-      public override object ReadValue(IProtonBuffer buffer, IDecoderState state)
-      {
-         ITypeDecoder decoder = state.Decoder.ReadNextTypeDecoder(buffer, state);
-
-         return ReadBegin(buffer, state, CheckIsExpectedTypeAndCast<IListTypeDecoder>(decoder));
-      }
-
-      public override Array ReadArrayElements(IProtonBuffer buffer, IDecoderState state, int count)
-      {
-         ITypeDecoder decoder = state.Decoder.ReadNextTypeDecoder(buffer, state);
-
-         Begin[] result = new Begin[count];
-         for (int i = 0; i < count; ++i)
-         {
-            result[i] = ReadBegin(buffer, state, CheckIsExpectedTypeAndCast<IListTypeDecoder>(decoder));
-         }
+      protected override int MinListElements => MinBeginListEntries;
 
-         return result;
-      }
+      protected override int MaxListElements => MaxBeginListEntries;
 
-      public override void SkipValue(IProtonBuffer buffer, IDecoderState state)
-      {
-         ITypeDecoder decoder = state.Decoder.ReadNextTypeDecoder(buffer, state);
-
-         CheckIsExpectedType<IListTypeDecoder>(decoder);
-
-         decoder.SkipValue(buffer, state);
-      }
-
-      private static Begin ReadBegin(IProtonBuffer buffer, IDecoderState state, IListTypeDecoder listDecoder)
+      protected override Begin ReadType(int count, IProtonBuffer buffer, IDecoder decoder, IDecoderState state)
       {
          Begin result = new();
 
-         _ = listDecoder.ReadSize(buffer, state);
-         int count = listDecoder.ReadCount(buffer, state);
-
-         if (count < MinBeginListEntries)
-         {
-            throw new DecodeException(ErrorForMissingRequiredFields(count));
-         }
-
-         if (count > MaxBeginListEntries)
-         {
-            throw new DecodeException("To many entries in Begin list encoding: " + count);
-         }
-
          for (int index = 0; index < count; ++index)
          {
             // Peek ahead and see if there is a null in the next slot, if so we don't call
@@ -132,52 +94,10 @@ namespace Apache.Qpid.Proton.Codec.Decoders.Transport
          return result;
       }
 
-      public override object ReadValue(Stream stream, IStreamDecoderState state)
-      {
-         IStreamTypeDecoder decoder = state.Decoder.ReadNextTypeDecoder(stream, state);
-
-         return ReadBegin(stream, state, CheckIsExpectedTypeAndCast<IListTypeDecoder>(decoder));
-      }
-
-      public override Array ReadArrayElements(Stream stream, IStreamDecoderState state, int count)
-      {
-         IStreamTypeDecoder decoder = state.Decoder.ReadNextTypeDecoder(stream, state);
-
-         Begin[] result = new Begin[count];
-         for (int i = 0; i < count; ++i)
-         {
-            result[i] = ReadBegin(stream, state, CheckIsExpectedTypeAndCast<IListTypeDecoder>(decoder));
-         }
-
-         return result;
-      }
-
-      public override void SkipValue(Stream stream, IStreamDecoderState state)
-      {
-         IStreamTypeDecoder decoder = state.Decoder.ReadNextTypeDecoder(stream, state);
-
-         CheckIsExpectedType<IListTypeDecoder>(decoder);
-
-         decoder.SkipValue(stream, state);
-      }
-
-      private static Begin ReadBegin(Stream stream, IStreamDecoderState state, IListTypeDecoder listDecoder)
+      protected override Begin ReadType(int count, Stream stream, IStreamDecoder decoder, IStreamDecoderState state)
       {
          Begin result = new();
 
-         _ = listDecoder.ReadSize(stream, state);
-         int count = listDecoder.ReadCount(stream, state);
-
-         if (count < MinBeginListEntries)
-         {
-            throw new DecodeException(ErrorForMissingRequiredFields(count));
-         }
-
-         if (count > MaxBeginListEntries)
-         {
-            throw new DecodeException("To many entries in Begin list encoding: " + count);
-         }
-
          for (int index = 0; index < count; ++index)
          {
             // Peek ahead and see if there is a null in the next slot, if so we don't call
diff --git a/src/Proton/Codec/Decoders/Transport/CloseTypeDecoder.cs b/src/Proton/Codec/Decoders/Transport/CloseTypeDecoder.cs
index 672c957..23e9b23 100644
--- a/src/Proton/Codec/Decoders/Transport/CloseTypeDecoder.cs
+++ b/src/Proton/Codec/Decoders/Transport/CloseTypeDecoder.cs
@@ -24,7 +24,7 @@ using Apache.Qpid.Proton.Types.Transport;
 
 namespace Apache.Qpid.Proton.Codec.Decoders.Transport
 {
-   public sealed class CloseTypeDecoder : AbstractDescribedTypeDecoder
+   public sealed class CloseTypeDecoder : AbstractDescribedListTypeDecoder
    {
       private static readonly int MinCloseListEntries = 0;
       private static readonly int MaxCloseListEntries = 1;
@@ -35,51 +35,15 @@ namespace Apache.Qpid.Proton.Codec.Decoders.Transport
 
       public override Type DecodesType => typeof(Close);
 
-      public override object ReadValue(IProtonBuffer buffer, IDecoderState state)
-      {
-         ITypeDecoder decoder = state.Decoder.ReadNextTypeDecoder(buffer, state);
+      protected override int MinListElements => MinCloseListEntries;
 
-         return ReadClose(buffer, state, CheckIsExpectedTypeAndCast<IListTypeDecoder>(decoder));
-      }
+      protected override int MaxListElements => MaxCloseListEntries;
 
-      public override Array ReadArrayElements(IProtonBuffer buffer, IDecoderState state, int count)
-      {
-         ITypeDecoder decoder = state.Decoder.ReadNextTypeDecoder(buffer, state);
-
-         Close[] result = new Close[count];
-         for (int i = 0; i < count; ++i)
-         {
-            result[i] = ReadClose(buffer, state, CheckIsExpectedTypeAndCast<IListTypeDecoder>(decoder));
-         }
-
-         return result;
-      }
-
-      public override void SkipValue(IProtonBuffer buffer, IDecoderState state)
-      {
-         ITypeDecoder decoder = state.Decoder.ReadNextTypeDecoder(buffer, state);
-
-         CheckIsExpectedType<IListTypeDecoder>(decoder);
-
-         decoder.SkipValue(buffer, state);
-      }
-
-      private static Close ReadClose(IProtonBuffer buffer, IDecoderState state, IListTypeDecoder listDecoder)
+      protected override Close ReadType(int count, IProtonBuffer buffer, IDecoder decoder, IDecoderState state)
       {
          Close result = new();
 
-         _ = listDecoder.ReadSize(buffer, state);
-         int count = listDecoder.ReadCount(buffer, state);
-
-         if (count < MinCloseListEntries)
-         {
-            throw new DecodeException("Not enough entries in Close list encoding: " + count);
-         }
-         else if (count > MaxCloseListEntries)
-         {
-            throw new DecodeException("To many entries in Close list encoding: " + count);
-         }
-         else if (count == 1)
+          if (count == 1)
          {
             result.Error = state.Decoder.ReadObject<ErrorCondition>(buffer, state);
          }
@@ -87,51 +51,11 @@ namespace Apache.Qpid.Proton.Codec.Decoders.Transport
          return result;
       }
 
-      public override object ReadValue(Stream stream, IStreamDecoderState state)
-      {
-         IStreamTypeDecoder decoder = state.Decoder.ReadNextTypeDecoder(stream, state);
-
-         return ReadClose(stream, state, CheckIsExpectedTypeAndCast<IListTypeDecoder>(decoder));
-      }
-
-      public override Array ReadArrayElements(Stream stream, IStreamDecoderState state, int count)
-      {
-         IStreamTypeDecoder decoder = state.Decoder.ReadNextTypeDecoder(stream, state);
-
-         Close[] result = new Close[count];
-         for (int i = 0; i < count; ++i)
-         {
-            result[i] = ReadClose(stream, state, CheckIsExpectedTypeAndCast<IListTypeDecoder>(decoder));
-         }
-
-         return result;
-      }
-
-      public override void SkipValue(Stream stream, IStreamDecoderState state)
-      {
-         IStreamTypeDecoder decoder = state.Decoder.ReadNextTypeDecoder(stream, state);
-
-         CheckIsExpectedType<IListTypeDecoder>(decoder);
-
-         decoder.SkipValue(stream, state);
-      }
-
-      private static Close ReadClose(Stream stream, IStreamDecoderState state, IListTypeDecoder listDecoder)
+      protected override Close ReadType(int count, Stream stream, IStreamDecoder decoder, IStreamDecoderState state)
       {
          Close result = new();
 
-         _ = listDecoder.ReadSize(stream, state);
-         int count = listDecoder.ReadCount(stream, state);
-
-         if (count < MinCloseListEntries)
-         {
-            throw new DecodeException("Not enough entries in Close list encoding: " + count);
-         }
-         else if (count > MaxCloseListEntries)
-         {
-            throw new DecodeException("To many entries in Close list encoding: " + count);
-         }
-         else if (count == 1)
+         if (count == 1)
          {
             result.Error = state.Decoder.ReadObject<ErrorCondition>(stream, state);
          }
diff --git a/src/Proton/Codec/Decoders/Transport/DetachTypeDecoder.cs b/src/Proton/Codec/Decoders/Transport/DetachTypeDecoder.cs
index 86b9083..54e63bd 100644
--- a/src/Proton/Codec/Decoders/Transport/DetachTypeDecoder.cs
+++ b/src/Proton/Codec/Decoders/Transport/DetachTypeDecoder.cs
@@ -24,7 +24,7 @@ using Apache.Qpid.Proton.Types.Transport;
 
 namespace Apache.Qpid.Proton.Codec.Decoders.Transport
 {
-   public sealed class DetachTypeDecoder : AbstractDescribedTypeDecoder
+   public sealed class DetachTypeDecoder : AbstractDescribedListTypeDecoder
    {
       private static readonly int MinDetachListEntries = 1;
       private static readonly int MaxDetachListEntries = 3;
@@ -35,52 +35,14 @@ namespace Apache.Qpid.Proton.Codec.Decoders.Transport
 
       public override Type DecodesType => typeof(Detach);
 
-      public override object ReadValue(IProtonBuffer buffer, IDecoderState state)
-      {
-         ITypeDecoder decoder = state.Decoder.ReadNextTypeDecoder(buffer, state);
-
-         return ReadDetach(buffer, state, CheckIsExpectedTypeAndCast<IListTypeDecoder>(decoder));
-      }
-
-      public override Array ReadArrayElements(IProtonBuffer buffer, IDecoderState state, int count)
-      {
-         ITypeDecoder decoder = state.Decoder.ReadNextTypeDecoder(buffer, state);
-
-         Detach[] result = new Detach[count];
-         for (int i = 0; i < count; ++i)
-         {
-            result[i] = ReadDetach(buffer, state, CheckIsExpectedTypeAndCast<IListTypeDecoder>(decoder));
-         }
+      protected override int MinListElements => MinDetachListEntries;
 
-         return result;
-      }
+      protected override int MaxListElements => MaxDetachListEntries;
 
-      public override void SkipValue(IProtonBuffer buffer, IDecoderState state)
-      {
-         ITypeDecoder decoder = state.Decoder.ReadNextTypeDecoder(buffer, state);
-
-         CheckIsExpectedType<IListTypeDecoder>(decoder);
-
-         decoder.SkipValue(buffer, state);
-      }
-
-      private static Detach ReadDetach(IProtonBuffer buffer, IDecoderState state, IListTypeDecoder listDecoder)
+      protected override Detach ReadType(int count, IProtonBuffer buffer, IDecoder decoder, IDecoderState state)
       {
          Detach result = new();
 
-         _ = listDecoder.ReadSize(buffer, state);
-         int count = listDecoder.ReadCount(buffer, state);
-
-         if (count < MinDetachListEntries)
-         {
-            throw new DecodeException(ErrorForMissingRequiredFields(count));
-         }
-
-         if (count > MaxDetachListEntries)
-         {
-            throw new DecodeException("To many entries in Detach list encoding: " + count);
-         }
-
          for (int index = 0; index < count; ++index)
          {
             // Peek ahead and see if there is a null in the next slot, if so we don't call
@@ -116,52 +78,10 @@ namespace Apache.Qpid.Proton.Codec.Decoders.Transport
          return result;
       }
 
-      public override object ReadValue(Stream stream, IStreamDecoderState state)
-      {
-         IStreamTypeDecoder decoder = state.Decoder.ReadNextTypeDecoder(stream, state);
-
-         return ReadDetach(stream, state, CheckIsExpectedTypeAndCast<IListTypeDecoder>(decoder));
-      }
-
-      public override Array ReadArrayElements(Stream stream, IStreamDecoderState state, int count)
-      {
-         IStreamTypeDecoder decoder = state.Decoder.ReadNextTypeDecoder(stream, state);
-
-         Detach[] result = new Detach[count];
-         for (int i = 0; i < count; ++i)
-         {
-            result[i] = ReadDetach(stream, state, CheckIsExpectedTypeAndCast<IListTypeDecoder>(decoder));
-         }
-
-         return result;
-      }
-
-      public override void SkipValue(Stream stream, IStreamDecoderState state)
-      {
-         IStreamTypeDecoder decoder = state.Decoder.ReadNextTypeDecoder(stream, state);
-
-         CheckIsExpectedType<IListTypeDecoder>(decoder);
-
-         decoder.SkipValue(stream, state);
-      }
-
-      private static Detach ReadDetach(Stream stream, IStreamDecoderState state, IListTypeDecoder listDecoder)
+      protected override Detach ReadType(int count, Stream stream, IStreamDecoder decoder, IStreamDecoderState state)
       {
          Detach result = new();
 
-         _ = listDecoder.ReadSize(stream, state);
-         int count = listDecoder.ReadCount(stream, state);
-
-         if (count < MinDetachListEntries)
-         {
-            throw new DecodeException(ErrorForMissingRequiredFields(count));
-         }
-
-         if (count > MaxDetachListEntries)
-         {
-            throw new DecodeException("To many entries in Detach list encoding: " + count);
-         }
-
          for (int index = 0; index < count; ++index)
          {
             // Peek ahead and see if there is a null in the next slot, if so we don't call
diff --git a/src/Proton/Codec/Decoders/Transport/DispositionTypeDecoder.cs b/src/Proton/Codec/Decoders/Transport/DispositionTypeDecoder.cs
index bba9d9f..cb8b5b7 100644
--- a/src/Proton/Codec/Decoders/Transport/DispositionTypeDecoder.cs
+++ b/src/Proton/Codec/Decoders/Transport/DispositionTypeDecoder.cs
@@ -24,7 +24,7 @@ using Apache.Qpid.Proton.Types.Transport;
 
 namespace Apache.Qpid.Proton.Codec.Decoders.Transport
 {
-   public sealed class DispositionTypeDecoder : AbstractDescribedTypeDecoder
+   public sealed class DispositionTypeDecoder : AbstractDescribedListTypeDecoder
    {
       private static readonly int MinDispositionListEntries = 2;
       private static readonly int MaxDispositionListEntries = 6;
@@ -35,52 +35,14 @@ namespace Apache.Qpid.Proton.Codec.Decoders.Transport
 
       public override Type DecodesType => typeof(Disposition);
 
-      public override object ReadValue(IProtonBuffer buffer, IDecoderState state)
-      {
-         ITypeDecoder decoder = state.Decoder.ReadNextTypeDecoder(buffer, state);
-
-         return ReadDisposition(buffer, state, CheckIsExpectedTypeAndCast<IListTypeDecoder>(decoder));
-      }
-
-      public override Array ReadArrayElements(IProtonBuffer buffer, IDecoderState state, int count)
-      {
-         ITypeDecoder decoder = state.Decoder.ReadNextTypeDecoder(buffer, state);
-
-         Disposition[] result = new Disposition[count];
-         for (int i = 0; i < count; ++i)
-         {
-            result[i] = ReadDisposition(buffer, state, CheckIsExpectedTypeAndCast<IListTypeDecoder>(decoder));
-         }
+      protected override int MinListElements => MinDispositionListEntries;
 
-         return result;
-      }
+      protected override int MaxListElements => MaxDispositionListEntries;
 
-      public override void SkipValue(IProtonBuffer buffer, IDecoderState state)
-      {
-         ITypeDecoder decoder = state.Decoder.ReadNextTypeDecoder(buffer, state);
-
-         CheckIsExpectedType<IListTypeDecoder>(decoder);
-
-         decoder.SkipValue(buffer, state);
-      }
-
-      private static Disposition ReadDisposition(IProtonBuffer buffer, IDecoderState state, IListTypeDecoder listDecoder)
+      protected override Disposition ReadType(int count, IProtonBuffer buffer, IDecoder decoder, IDecoderState state)
       {
          Disposition result = new();
 
-         _ = listDecoder.ReadSize(buffer, state);
-         int count = listDecoder.ReadCount(buffer, state);
-
-         if (count < MinDispositionListEntries)
-         {
-            throw new DecodeException(ErrorForMissingRequiredFields(count));
-         }
-
-         if (count > MaxDispositionListEntries)
-         {
-            throw new DecodeException("To many entries in Disposition list encoding: " + count);
-         }
-
          for (int index = 0; index < count; ++index)
          {
             // Peek ahead and see if there is a null in the next slot, if so we don't call
@@ -125,52 +87,10 @@ namespace Apache.Qpid.Proton.Codec.Decoders.Transport
          return result;
       }
 
-      public override object ReadValue(Stream stream, IStreamDecoderState state)
-      {
-         IStreamTypeDecoder decoder = state.Decoder.ReadNextTypeDecoder(stream, state);
-
-         return ReadDisposition(stream, state, CheckIsExpectedTypeAndCast<IListTypeDecoder>(decoder));
-      }
-
-      public override Array ReadArrayElements(Stream stream, IStreamDecoderState state, int count)
-      {
-         IStreamTypeDecoder decoder = state.Decoder.ReadNextTypeDecoder(stream, state);
-
-         Disposition[] result = new Disposition[count];
-         for (int i = 0; i < count; ++i)
-         {
-            result[i] = ReadDisposition(stream, state, CheckIsExpectedTypeAndCast<IListTypeDecoder>(decoder));
-         }
-
-         return result;
-      }
-
-      public override void SkipValue(Stream stream, IStreamDecoderState state)
-      {
-         IStreamTypeDecoder decoder = state.Decoder.ReadNextTypeDecoder(stream, state);
-
-         CheckIsExpectedType<IListTypeDecoder>(decoder);
-
-         decoder.SkipValue(stream, state);
-      }
-
-      private static Disposition ReadDisposition(Stream stream, IStreamDecoderState state, IListTypeDecoder listDecoder)
+      protected override Disposition ReadType(int count, Stream stream, IStreamDecoder decoder, IStreamDecoderState state)
       {
          Disposition result = new();
 
-         _ = listDecoder.ReadSize(stream, state);
-         int count = listDecoder.ReadCount(stream, state);
-
-         if (count < MinDispositionListEntries)
-         {
-            throw new DecodeException(ErrorForMissingRequiredFields(count));
-         }
-
-         if (count > MaxDispositionListEntries)
-         {
-            throw new DecodeException("To many entries in Disposition list encoding: " + count);
-         }
-
          for (int index = 0; index < count; ++index)
          {
             // Peek ahead and see if there is a null in the next slot, if so we don't call
diff --git a/src/Proton/Codec/Decoders/Transport/EndTypeDecoder.cs b/src/Proton/Codec/Decoders/Transport/EndTypeDecoder.cs
index c68eb52..23704d7 100644
--- a/src/Proton/Codec/Decoders/Transport/EndTypeDecoder.cs
+++ b/src/Proton/Codec/Decoders/Transport/EndTypeDecoder.cs
@@ -24,7 +24,7 @@ using Apache.Qpid.Proton.Types.Transport;
 
 namespace Apache.Qpid.Proton.Codec.Decoders.Transport
 {
-   public sealed class EndTypeDecoder : AbstractDescribedTypeDecoder
+   public sealed class EndTypeDecoder : AbstractDescribedListTypeDecoder
    {
       private static readonly int MinEndListEntries = 0;
       private static readonly int MaxEndListEntries = 1;
@@ -35,119 +35,29 @@ namespace Apache.Qpid.Proton.Codec.Decoders.Transport
 
       public override Type DecodesType => typeof(End);
 
-      public override object ReadValue(IProtonBuffer buffer, IDecoderState state)
-      {
-         ITypeDecoder decoder = state.Decoder.ReadNextTypeDecoder(buffer, state);
-
-         return ReadEnd(buffer, state, CheckIsExpectedTypeAndCast<IListTypeDecoder>(decoder));
-      }
-
-      public override Array ReadArrayElements(IProtonBuffer buffer, IDecoderState state, int count)
-      {
-         ITypeDecoder decoder = state.Decoder.ReadNextTypeDecoder(buffer, state);
-
-         End[] result = new End[count];
-         for (int i = 0; i < count; ++i)
-         {
-            result[i] = ReadEnd(buffer, state, CheckIsExpectedTypeAndCast<IListTypeDecoder>(decoder));
-         }
+      protected override int MinListElements => MinEndListEntries;
 
-         return result;
-      }
+      protected override int MaxListElements => MaxEndListEntries;
 
-      public override void SkipValue(IProtonBuffer buffer, IDecoderState state)
-      {
-         ITypeDecoder decoder = state.Decoder.ReadNextTypeDecoder(buffer, state);
-
-         CheckIsExpectedType<IListTypeDecoder>(decoder);
-
-         decoder.SkipValue(buffer, state);
-      }
-
-      private static End ReadEnd(IProtonBuffer buffer, IDecoderState state, IListTypeDecoder listDecoder)
+      protected override End ReadType(int count, IProtonBuffer buffer, IDecoder decoder, IDecoderState state)
       {
          End result = new();
 
-         _ = listDecoder.ReadSize(buffer, state);
-         int count = listDecoder.ReadCount(buffer, state);
-
-         if (count < MinEndListEntries)
-         {
-            throw new DecodeException("Not enough entries in End list encoding: " + count);
-         }
-
-         if (count > MaxEndListEntries)
-         {
-            throw new DecodeException("To many entries in End list encoding: " + count);
-         }
-
-         for (int index = 0; index < count; ++index)
-         {
-            switch (index)
-            {
-               case 0:
-                  result.Error = state.Decoder.ReadObject<ErrorCondition>(buffer, state);
-                  break;
-            }
-         }
-
-         return result;
-      }
-
-      public override object ReadValue(Stream stream, IStreamDecoderState state)
-      {
-         IStreamTypeDecoder decoder = state.Decoder.ReadNextTypeDecoder(stream, state);
-
-         return ReadEnd(stream, state, CheckIsExpectedTypeAndCast<IListTypeDecoder>(decoder));
-      }
-
-      public override Array ReadArrayElements(Stream stream, IStreamDecoderState state, int count)
-      {
-         IStreamTypeDecoder decoder = state.Decoder.ReadNextTypeDecoder(stream, state);
-
-         End[] result = new End[count];
-         for (int i = 0; i < count; ++i)
+         if (count == 1)
          {
-            result[i] = ReadEnd(stream, state, CheckIsExpectedTypeAndCast<IListTypeDecoder>(decoder));
+            result.Error = state.Decoder.ReadObject<ErrorCondition>(buffer, state);
          }
 
          return result;
       }
 
-      public override void SkipValue(Stream stream, IStreamDecoderState state)
-      {
-         IStreamTypeDecoder decoder = state.Decoder.ReadNextTypeDecoder(stream, state);
-
-         CheckIsExpectedType<IListTypeDecoder>(decoder);
-
-         decoder.SkipValue(stream, state);
-      }
-
-      private static End ReadEnd(Stream stream, IStreamDecoderState state, IListTypeDecoder listDecoder)
+      protected override End ReadType(int count, Stream stream, IStreamDecoder decoder, IStreamDecoderState state)
       {
          End result = new();
 
-         _ = listDecoder.ReadSize(stream, state);
-         int count = listDecoder.ReadCount(stream, state);
-
-         if (count < MinEndListEntries)
-         {
-            throw new DecodeException("Not enough entries in End list encoding: " + count);
-         }
-
-         if (count > MaxEndListEntries)
-         {
-            throw new DecodeException("To many entries in End list encoding: " + count);
-         }
-
-         for (int index = 0; index < count; ++index)
+         if (count == 1)
          {
-            switch (index)
-            {
-               case 0:
-                  result.Error = state.Decoder.ReadObject<ErrorCondition>(stream, state);
-                  break;
-            }
+            result.Error = state.Decoder.ReadObject<ErrorCondition>(stream, state);
          }
 
          return result;
diff --git a/src/Proton/Codec/Decoders/Transport/ErrorConditionTypeDecoder.cs b/src/Proton/Codec/Decoders/Transport/ErrorConditionTypeDecoder.cs
index ee7a254..45729d7 100644
--- a/src/Proton/Codec/Decoders/Transport/ErrorConditionTypeDecoder.cs
+++ b/src/Proton/Codec/Decoders/Transport/ErrorConditionTypeDecoder.cs
@@ -25,7 +25,7 @@ using Apache.Qpid.Proton.Types.Transport;
 
 namespace Apache.Qpid.Proton.Codec.Decoders.Transport
 {
-   public sealed class ErrorConditionTypeDecoder : AbstractDescribedTypeDecoder
+   public sealed class ErrorConditionTypeDecoder : AbstractDescribedListTypeDecoder
    {
       private static readonly int MinErrorConditionListEntries = 1;
       private static readonly int MaxErrorConditionListEntries = 3;
@@ -36,50 +36,12 @@ namespace Apache.Qpid.Proton.Codec.Decoders.Transport
 
       public override Type DecodesType => typeof(ErrorCondition);
 
-      public override object ReadValue(IProtonBuffer buffer, IDecoderState state)
-      {
-         ITypeDecoder decoder = state.Decoder.ReadNextTypeDecoder(buffer, state);
-
-         return ReadErrorCondition(buffer, state, CheckIsExpectedTypeAndCast<IListTypeDecoder>(decoder));
-      }
-
-      public override Array ReadArrayElements(IProtonBuffer buffer, IDecoderState state, int count)
-      {
-         ITypeDecoder decoder = state.Decoder.ReadNextTypeDecoder(buffer, state);
-
-         ErrorCondition[] result = new ErrorCondition[count];
-         for (int i = 0; i < count; ++i)
-         {
-            result[i] = ReadErrorCondition(buffer, state, CheckIsExpectedTypeAndCast<IListTypeDecoder>(decoder));
-         }
-
-         return result;
-      }
-
-      public override void SkipValue(IProtonBuffer buffer, IDecoderState state)
-      {
-         ITypeDecoder decoder = state.Decoder.ReadNextTypeDecoder(buffer, state);
-
-         CheckIsExpectedType<IListTypeDecoder>(decoder);
+      protected override int MinListElements => MinErrorConditionListEntries;
 
-         decoder.SkipValue(buffer, state);
-      }
+      protected override int MaxListElements => MaxErrorConditionListEntries;
 
-      private static ErrorCondition ReadErrorCondition(IProtonBuffer buffer, IDecoderState state, IListTypeDecoder listDecoder)
+      protected override ErrorCondition ReadType(int count, IProtonBuffer buffer, IDecoder decoder, IDecoderState state)
       {
-         _ = listDecoder.ReadSize(buffer, state);
-         int count = listDecoder.ReadCount(buffer, state);
-
-         if (count < MinErrorConditionListEntries)
-         {
-            throw new DecodeException("Not enough entries in ErrorCondition list encoding: " + count);
-         }
-
-         if (count > MaxErrorConditionListEntries)
-         {
-            throw new DecodeException("To many entries in ErrorCondition list encoding: " + count);
-         }
-
          Symbol condition = null;
          string description = null;
          IDictionary<Symbol, object> info = null;
@@ -103,50 +65,8 @@ namespace Apache.Qpid.Proton.Codec.Decoders.Transport
          return new ErrorCondition(condition, description, info);
       }
 
-      public override object ReadValue(Stream stream, IStreamDecoderState state)
-      {
-         IStreamTypeDecoder decoder = state.Decoder.ReadNextTypeDecoder(stream, state);
-
-         return ReadErrorCondition(stream, state, CheckIsExpectedTypeAndCast<IListTypeDecoder>(decoder));
-      }
-
-      public override Array ReadArrayElements(Stream stream, IStreamDecoderState state, int count)
-      {
-         IStreamTypeDecoder decoder = state.Decoder.ReadNextTypeDecoder(stream, state);
-
-         ErrorCondition[] result = new ErrorCondition[count];
-         for (int i = 0; i < count; ++i)
-         {
-            result[i] = ReadErrorCondition(stream, state, CheckIsExpectedTypeAndCast<IListTypeDecoder>(decoder));
-         }
-
-         return result;
-      }
-
-      public override void SkipValue(Stream stream, IStreamDecoderState state)
-      {
-         IStreamTypeDecoder decoder = state.Decoder.ReadNextTypeDecoder(stream, state);
-
-         CheckIsExpectedType<IListTypeDecoder>(decoder);
-
-         decoder.SkipValue(stream, state);
-      }
-
-      private static ErrorCondition ReadErrorCondition(Stream stream, IStreamDecoderState state, IListTypeDecoder listDecoder)
+      protected override ErrorCondition ReadType(int count, Stream stream, IStreamDecoder decoder, IStreamDecoderState state)
       {
-         _ = listDecoder.ReadSize(stream, state);
-         int count = listDecoder.ReadCount(stream, state);
-
-         if (count < MinErrorConditionListEntries)
-         {
-            throw new DecodeException("Not enough entries in ErrorCondition list encoding: " + count);
-         }
-
-         if (count > MaxErrorConditionListEntries)
-         {
-            throw new DecodeException("To many entries in ErrorCondition list encoding: " + count);
-         }
-
          Symbol condition = null;
          string description = null;
          IDictionary<Symbol, object> info = null;
diff --git a/src/Proton/Codec/Decoders/Transport/FlowTypeDecoder.cs b/src/Proton/Codec/Decoders/Transport/FlowTypeDecoder.cs
index 830fd31..3ad3fbe 100644
--- a/src/Proton/Codec/Decoders/Transport/FlowTypeDecoder.cs
+++ b/src/Proton/Codec/Decoders/Transport/FlowTypeDecoder.cs
@@ -24,7 +24,7 @@ using Apache.Qpid.Proton.Types.Transport;
 
 namespace Apache.Qpid.Proton.Codec.Decoders.Transport
 {
-   public sealed class FlowTypeDecoder : AbstractDescribedTypeDecoder
+   public sealed class FlowTypeDecoder : AbstractDescribedListTypeDecoder
    {
       private static readonly int MinFlowListEntries = 4;
       private static readonly int MaxFlowListEntries = 11;
@@ -35,52 +35,14 @@ namespace Apache.Qpid.Proton.Codec.Decoders.Transport
 
       public override Type DecodesType => typeof(Flow);
 
-      public override object ReadValue(IProtonBuffer buffer, IDecoderState state)
-      {
-         ITypeDecoder decoder = state.Decoder.ReadNextTypeDecoder(buffer, state);
-
-         return ReadFlow(buffer, state, CheckIsExpectedTypeAndCast<IListTypeDecoder>(decoder));
-      }
-
-      public override Array ReadArrayElements(IProtonBuffer buffer, IDecoderState state, int count)
-      {
-         ITypeDecoder decoder = state.Decoder.ReadNextTypeDecoder(buffer, state);
-
-         Flow[] result = new Flow[count];
-         for (int i = 0; i < count; ++i)
-         {
-            result[i] = ReadFlow(buffer, state, CheckIsExpectedTypeAndCast<IListTypeDecoder>(decoder));
-         }
+      protected override int MinListElements => MinFlowListEntries;
 
-         return result;
-      }
+      protected override int MaxListElements => MaxFlowListEntries;
 
-      public override void SkipValue(IProtonBuffer buffer, IDecoderState state)
-      {
-         ITypeDecoder decoder = state.Decoder.ReadNextTypeDecoder(buffer, state);
-
-         CheckIsExpectedType<IListTypeDecoder>(decoder);
-
-         decoder.SkipValue(buffer, state);
-      }
-
-      private static Flow ReadFlow(IProtonBuffer buffer, IDecoderState state, IListTypeDecoder listDecoder)
+      protected override Flow ReadType(int count, IProtonBuffer buffer, IDecoder decoder, IDecoderState state)
       {
          Flow result = new();
 
-         _ = listDecoder.ReadSize(buffer, state);
-         int count = listDecoder.ReadCount(buffer, state);
-
-         if (count < MinFlowListEntries)
-         {
-            throw new DecodeException(ErrorForMissingRequiredFields(count));
-         }
-
-         if (count > MaxFlowListEntries)
-         {
-            throw new DecodeException("To many entries in Flow list encoding: " + count);
-         }
-
          for (int index = 0; index < count; ++index)
          {
             // Peek ahead and see if there is a null in the next slot, if so we don't call
@@ -141,52 +103,10 @@ namespace Apache.Qpid.Proton.Codec.Decoders.Transport
          return result;
       }
 
-      public override object ReadValue(Stream stream, IStreamDecoderState state)
-      {
-         IStreamTypeDecoder decoder = state.Decoder.ReadNextTypeDecoder(stream, state);
-
-         return ReadFlow(stream, state, CheckIsExpectedTypeAndCast<IListTypeDecoder>(decoder));
-      }
-
-      public override Array ReadArrayElements(Stream stream, IStreamDecoderState state, int count)
-      {
-         IStreamTypeDecoder decoder = state.Decoder.ReadNextTypeDecoder(stream, state);
-
-         Flow[] result = new Flow[count];
-         for (int i = 0; i < count; ++i)
-         {
-            result[i] = ReadFlow(stream, state, CheckIsExpectedTypeAndCast<IListTypeDecoder>(decoder));
-         }
-
-         return result;
-      }
-
-      public override void SkipValue(Stream stream, IStreamDecoderState state)
-      {
-         IStreamTypeDecoder decoder = state.Decoder.ReadNextTypeDecoder(stream, state);
-
-         CheckIsExpectedType<IListTypeDecoder>(decoder);
-
-         decoder.SkipValue(stream, state);
-      }
-
-      private static Flow ReadFlow(Stream stream, IStreamDecoderState state, IListTypeDecoder listDecoder)
+      protected override Flow ReadType(int count, Stream stream, IStreamDecoder decoder, IStreamDecoderState state)
       {
          Flow result = new();
 
-         _ = listDecoder.ReadSize(stream, state);
-         int count = listDecoder.ReadCount(stream, state);
-
-         if (count < MinFlowListEntries)
-         {
-            throw new DecodeException(ErrorForMissingRequiredFields(count));
-         }
-
-         if (count > MaxFlowListEntries)
-         {
-            throw new DecodeException("To many entries in Flow list encoding: " + count);
-         }
-
          for (int index = 0; index < count; ++index)
          {
             // Peek ahead and see if there is a null in the next slot, if so we don't call
diff --git a/src/Proton/Codec/Decoders/Transport/OpenTypeDecoder.cs b/src/Proton/Codec/Decoders/Transport/OpenTypeDecoder.cs
index 7734a62..ad4fbb6 100644
--- a/src/Proton/Codec/Decoders/Transport/OpenTypeDecoder.cs
+++ b/src/Proton/Codec/Decoders/Transport/OpenTypeDecoder.cs
@@ -24,7 +24,7 @@ using Apache.Qpid.Proton.Types.Transport;
 
 namespace Apache.Qpid.Proton.Codec.Decoders.Transport
 {
-   public sealed class OpenTypeDecoder : AbstractDescribedTypeDecoder
+   public sealed class OpenTypeDecoder : AbstractDescribedListTypeDecoder
    {
       private static readonly int MinOpenListEntries = 1;
       private static readonly int MaxOpenListEntries = 10;
@@ -35,52 +35,14 @@ namespace Apache.Qpid.Proton.Codec.Decoders.Transport
 
       public override Type DecodesType => typeof(Open);
 
-      public override object ReadValue(IProtonBuffer buffer, IDecoderState state)
-      {
-         ITypeDecoder decoder = state.Decoder.ReadNextTypeDecoder(buffer, state);
-
-         return ReadOpen(buffer, state, CheckIsExpectedTypeAndCast<IListTypeDecoder>(decoder));
-      }
-
-      public override Array ReadArrayElements(IProtonBuffer buffer, IDecoderState state, int count)
-      {
-         ITypeDecoder decoder = state.Decoder.ReadNextTypeDecoder(buffer, state);
-
-         Open[] result = new Open[count];
-         for (int i = 0; i < count; ++i)
-         {
-            result[i] = ReadOpen(buffer, state, CheckIsExpectedTypeAndCast<IListTypeDecoder>(decoder));
-         }
+      protected override int MinListElements => MinOpenListEntries;
 
-         return result;
-      }
+      protected override int MaxListElements => MaxOpenListEntries;
 
-      public override void SkipValue(IProtonBuffer buffer, IDecoderState state)
-      {
-         ITypeDecoder decoder = state.Decoder.ReadNextTypeDecoder(buffer, state);
-
-         CheckIsExpectedType<IListTypeDecoder>(decoder);
-
-         decoder.SkipValue(buffer, state);
-      }
-
-      private static Open ReadOpen(IProtonBuffer buffer, IDecoderState state, IListTypeDecoder listDecoder)
+      protected override Open ReadType(int count, IProtonBuffer buffer, IDecoder decoder, IDecoderState state)
       {
          Open result = new();
 
-         _ = listDecoder.ReadSize(buffer, state);
-         int count = listDecoder.ReadCount(buffer, state);
-
-         if (count < MinOpenListEntries)
-         {
-            throw new DecodeException("The container-id field cannot be omitted from the Open");
-         }
-
-         if (count > MaxOpenListEntries)
-         {
-            throw new DecodeException("To many entries in Open list encoding: " + count);
-         }
-
          for (int index = 0; index < count; ++index)
          {
             // Peek ahead and see if there is a null in the next slot, if so we don't call
@@ -136,52 +98,10 @@ namespace Apache.Qpid.Proton.Codec.Decoders.Transport
          return result;
       }
 
-      public override object ReadValue(Stream stream, IStreamDecoderState state)
-      {
-         IStreamTypeDecoder decoder = state.Decoder.ReadNextTypeDecoder(stream, state);
-
-         return ReadOpen(stream, state, CheckIsExpectedTypeAndCast<IListTypeDecoder>(decoder));
-      }
-
-      public override Array ReadArrayElements(Stream stream, IStreamDecoderState state, int count)
-      {
-         IStreamTypeDecoder decoder = state.Decoder.ReadNextTypeDecoder(stream, state);
-
-         Open[] result = new Open[count];
-         for (int i = 0; i < count; ++i)
-         {
-            result[i] = ReadOpen(stream, state, CheckIsExpectedTypeAndCast<IListTypeDecoder>(decoder));
-         }
-
-         return result;
-      }
-
-      public override void SkipValue(Stream stream, IStreamDecoderState state)
-      {
-         IStreamTypeDecoder decoder = state.Decoder.ReadNextTypeDecoder(stream, state);
-
-         CheckIsExpectedType<IListTypeDecoder>(decoder);
-
-         decoder.SkipValue(stream, state);
-      }
-
-      private static Open ReadOpen(Stream stream, IStreamDecoderState state, IListTypeDecoder listDecoder)
+      protected override Open ReadType(int count, Stream stream, IStreamDecoder decoder, IStreamDecoderState state)
       {
          Open result = new();
 
-         _ = listDecoder.ReadSize(stream, state);
-         int count = listDecoder.ReadCount(stream, state);
-
-         if (count < MinOpenListEntries)
-         {
-            throw new DecodeException("The container-id field cannot be omitted from the Open");
-         }
-
-         if (count > MaxOpenListEntries)
-         {
-            throw new DecodeException("To many entries in Open list encoding: " + count);
-         }
-
          for (int index = 0; index < count; ++index)
          {
             // Peek ahead and see if there is a null in the next slot, if so we don't call
diff --git a/src/Proton/Codec/Decoders/Transport/TransferTypeDecoder.cs b/src/Proton/Codec/Decoders/Transport/TransferTypeDecoder.cs
index 904d410..9cbada0 100644
--- a/src/Proton/Codec/Decoders/Transport/TransferTypeDecoder.cs
+++ b/src/Proton/Codec/Decoders/Transport/TransferTypeDecoder.cs
@@ -24,7 +24,7 @@ using Apache.Qpid.Proton.Types.Transport;
 
 namespace Apache.Qpid.Proton.Codec.Decoders.Transport
 {
-   public sealed class TransferTypeDecoder : AbstractDescribedTypeDecoder
+   public sealed class TransferTypeDecoder : AbstractDescribedListTypeDecoder
    {
       private static readonly int MinTransferListEntries = 1;
       private static readonly int MaxTransferListEntries = 11;
@@ -35,52 +35,14 @@ namespace Apache.Qpid.Proton.Codec.Decoders.Transport
 
       public override Type DecodesType => typeof(Transfer);
 
-      public override object ReadValue(IProtonBuffer buffer, IDecoderState state)
-      {
-         ITypeDecoder decoder = state.Decoder.ReadNextTypeDecoder(buffer, state);
-
-         return ReadTransfer(buffer, state, CheckIsExpectedTypeAndCast<IListTypeDecoder>(decoder));
-      }
-
-      public override Array ReadArrayElements(IProtonBuffer buffer, IDecoderState state, int count)
-      {
-         ITypeDecoder decoder = state.Decoder.ReadNextTypeDecoder(buffer, state);
-
-         Transfer[] result = new Transfer[count];
-         for (int i = 0; i < count; ++i)
-         {
-            result[i] = ReadTransfer(buffer, state, CheckIsExpectedTypeAndCast<IListTypeDecoder>(decoder));
-         }
+      protected override int MinListElements => MinTransferListEntries;
 
-         return result;
-      }
+      protected override int MaxListElements => MaxTransferListEntries;
 
-      public override void SkipValue(IProtonBuffer buffer, IDecoderState state)
-      {
-         ITypeDecoder decoder = state.Decoder.ReadNextTypeDecoder(buffer, state);
-
-         CheckIsExpectedType<IListTypeDecoder>(decoder);
-
-         decoder.SkipValue(buffer, state);
-      }
-
-      private static Transfer ReadTransfer(IProtonBuffer buffer, IDecoderState state, IListTypeDecoder listDecoder)
+      protected override Transfer ReadType(int count, IProtonBuffer buffer, IDecoder decoder, IDecoderState state)
       {
          Transfer result = new();
 
-         _ = listDecoder.ReadSize(buffer, state);
-         int count = listDecoder.ReadCount(buffer, state);
-
-         if (count < MinTransferListEntries)
-         {
-            throw new DecodeException("The handle field cannot be omitted from the Transfer");
-         }
-
-         if (count > MaxTransferListEntries)
-         {
-            throw new DecodeException("To many entries in Transfer list encoding: " + count);
-         }
-
          for (int index = 0; index < count; ++index)
          {
             // Peek ahead and see if there is a null in the next slot, if so we don't call
@@ -140,52 +102,10 @@ namespace Apache.Qpid.Proton.Codec.Decoders.Transport
          return result;
       }
 
-      public override object ReadValue(Stream stream, IStreamDecoderState state)
-      {
-         IStreamTypeDecoder decoder = state.Decoder.ReadNextTypeDecoder(stream, state);
-
-         return ReadTransfer(stream, state, CheckIsExpectedTypeAndCast<IListTypeDecoder>(decoder));
-      }
-
-      public override Array ReadArrayElements(Stream stream, IStreamDecoderState state, int count)
-      {
-         IStreamTypeDecoder decoder = state.Decoder.ReadNextTypeDecoder(stream, state);
-
-         Transfer[] result = new Transfer[count];
-         for (int i = 0; i < count; ++i)
-         {
-            result[i] = ReadTransfer(stream, state, CheckIsExpectedTypeAndCast<IListTypeDecoder>(decoder));
-         }
-
-         return result;
-      }
-
-      public override void SkipValue(Stream stream, IStreamDecoderState state)
-      {
-         IStreamTypeDecoder decoder = state.Decoder.ReadNextTypeDecoder(stream, state);
-
-         CheckIsExpectedType<IListTypeDecoder>(decoder);
-
-         decoder.SkipValue(stream, state);
-      }
-
-      private static Transfer ReadTransfer(Stream stream, IStreamDecoderState state, IListTypeDecoder listDecoder)
+      protected override Transfer ReadType(int count, Stream stream, IStreamDecoder decoder, IStreamDecoderState state)
       {
          Transfer result = new();
 
-         _ = listDecoder.ReadSize(stream, state);
-         int count = listDecoder.ReadCount(stream, state);
-
-         if (count < MinTransferListEntries)
-         {
-            throw new DecodeException("The handle field cannot be omitted from the Transfer");
-         }
-
-         if (count > MaxTransferListEntries)
-         {
-            throw new DecodeException("To many entries in Transfer list encoding: " + count);
-         }
-
          for (int index = 0; index < count; ++index)
          {
             // Peek ahead and see if there is a null in the next slot, if so we don't call
diff --git a/src/Proton/Codec/Decoders/UnknownDescribedTypeDecoder.cs b/src/Proton/Codec/Decoders/UnknownDescribedTypeDecoder.cs
index 3804a97..89bf5d5 100644
--- a/src/Proton/Codec/Decoders/UnknownDescribedTypeDecoder.cs
+++ b/src/Proton/Codec/Decoders/UnknownDescribedTypeDecoder.cs
@@ -18,6 +18,7 @@
 using System;
 using System.IO;
 using Apache.Qpid.Proton.Buffer;
+using Apache.Qpid.Proton.Codec.Decoders.Primitives;
 using Apache.Qpid.Proton.Types;
 
 namespace Apache.Qpid.Proton.Codec.Decoders
@@ -40,14 +41,42 @@ namespace Apache.Qpid.Proton.Codec.Decoders
       public override object ReadValue(IProtonBuffer buffer, IDecoderState state)
       {
          ITypeDecoder decoder = state.Decoder.ReadNextTypeDecoder(buffer, state);
-         object described = decoder.ReadValue(buffer, state);
 
-         return new UnknownDescribedType(descriptor, described);
+         if (decoder is not IPrimitiveTypeDecoder)
+         {
+            throw new DecodeException("The described value must be an AMQP primitive type.");
+         }
+
+         return new UnknownDescribedType(descriptor, decoder.ReadValue(buffer, state));
       }
 
       public override Array ReadArrayElements(IProtonBuffer buffer, IDecoderState state, int count)
       {
          ITypeDecoder decoder = state.Decoder.ReadNextTypeDecoder(buffer, state);
+
+         if (decoder is IPrimitiveTypeDecoder primitiveDecoder)
+         {
+            if (primitiveDecoder.IsZeroWidth)
+            {
+               if (count > state.MaxZeroWidthArrayElements)
+               {
+                  throw new DecodeException(
+                     "Array element count " + count + " is specified to be greater than limit for " +
+                     "zero sized encoded array types (" + state.MaxZeroWidthArrayElements + ")");
+               }
+            }
+            else if (count > buffer.ReadableBytes)
+            {
+               throw new DecodeException(
+                  "Array encoded length " + count + " is specified to be greater than the amount " +
+                  "of the remaining readable bytes (" + buffer.ReadableBytes + ")");
+            }
+         }
+         else
+         {
+            throw new DecodeException("The described value must be an AMQP primitive type.");
+         }
+
          UnknownDescribedType[] result = new UnknownDescribedType[count];
 
          for (int i = 0; i < count; ++i)
@@ -61,20 +90,57 @@ namespace Apache.Qpid.Proton.Codec.Decoders
 
       public override void SkipValue(IProtonBuffer buffer, IDecoderState state)
       {
-         state.Decoder.ReadNextTypeDecoder(buffer, state).SkipValue(buffer, state);
+         state.IncreaseDepth();
+
+         try
+         {
+            state.Decoder.ReadNextTypeDecoder(buffer, state).SkipValue(buffer, state);
+         }
+         finally
+         {
+            state.DecreaseDepth();
+         }
       }
 
       public override object ReadValue(Stream stream, IStreamDecoderState state)
       {
          IStreamTypeDecoder decoder = state.Decoder.ReadNextTypeDecoder(stream, state);
-         object described = decoder.ReadValue(stream, state);
 
-         return new UnknownDescribedType(descriptor, described);
+         if (decoder is not IPrimitiveTypeDecoder)
+         {
+            throw new DecodeException("The described value must be an AMQP primitive type.");
+         }
+
+         return new UnknownDescribedType(descriptor, decoder.ReadValue(stream, state));
       }
 
       public override Array ReadArrayElements(Stream stream, IStreamDecoderState state, int count)
       {
          IStreamTypeDecoder decoder = state.Decoder.ReadNextTypeDecoder(stream, state);
+
+         if (decoder is IPrimitiveTypeDecoder primitiveDecoder)
+         {
+            if (primitiveDecoder.IsZeroWidth)
+            {
+               if (count > state.MaxZeroWidthArrayElements)
+               {
+                  throw new DecodeException(
+                     "Array element count " + count + " is specified to be greater than limit for " +
+                     "zero sized encoded array types (" + state.MaxZeroWidthArrayElements + ")");
+               }
+            }
+            else if (count > state.MaxArraySize)
+            {
+               throw new DecodeException(
+                  "Array encoded length " + count + " is specified to be greater than the amount " +
+                  "of the configured max array length (" + state.MaxArraySize + ")");
+            }
+         }
+         else
+         {
+            throw new DecodeException("The described value must be an AMQP primitive type.");
+         }
+
          UnknownDescribedType[] result = new UnknownDescribedType[count];
 
          for (int i = 0; i < count; ++i)
@@ -88,7 +154,16 @@ namespace Apache.Qpid.Proton.Codec.Decoders
 
       public override void SkipValue(Stream stream, IStreamDecoderState state)
       {
-         state.Decoder.ReadNextTypeDecoder(stream, state).SkipValue(stream, state);
+         state.IncreaseDepth();
+
+         try
+         {
+            state.Decoder.ReadNextTypeDecoder(stream, state).SkipValue(stream, state);
+         }
+         finally
+         {
+            state.DecreaseDepth();
+         }
       }
    }
 }
diff --git a/src/Proton/Codec/Encoders/AbstractDescribedMapTypeEncoder.cs b/src/Proton/Codec/Encoders/AbstractDescribedMapTypeEncoder.cs
index 5ed21e8..9031771 100644
--- a/src/Proton/Codec/Encoders/AbstractDescribedMapTypeEncoder.cs
+++ b/src/Proton/Codec/Encoders/AbstractDescribedMapTypeEncoder.cs
@@ -40,14 +40,6 @@ namespace Apache.Qpid.Proton.Codec.Encoders
          return EncodingCodes.Map32;
       }
 
-      /// <summary>
-      /// Returns false when the value to be encoded has no Map body and can be
-      /// written as a Null body type instead of a Map type.
-      /// </summary>
-      /// <param name="value">The value that is encoded as a map type</param>
-      /// <returns>if the map type needs a map body or can be null</returns>
-      protected abstract bool HasMap(M value);
-
       /// <summary>
       /// Gets the number of elements that will result when this type is encoded
       /// into an AMQP Map type.
@@ -78,27 +70,20 @@ namespace Apache.Qpid.Proton.Codec.Encoders
 
          state.Encoder.WriteUnsignedLong(buffer, state, DescriptorCode);
 
-         if (HasMap(value))
-         {
-            int count = GetMapEntries(value);
-            EncodingCodes encodingCode = GetMapEncoding(value);
-
-            buffer.EnsureWritable(sizeof(byte));
-            buffer.WriteUnsignedByte(((byte)encodingCode));
-
-            switch (encodingCode)
-            {
-               case EncodingCodes.Map8:
-                  WriteSmallType(buffer, state, value, count);
-                  break;
-               case EncodingCodes.Map32:
-                  WriteLargeType(buffer, state, value, count);
-                  break;
-            }
-         }
-         else
+         int count = GetMapEntries(value);
+         EncodingCodes encodingCode = GetMapEncoding(value);
+
+         buffer.EnsureWritable(sizeof(byte));
+         buffer.WriteUnsignedByte(((byte)encodingCode));
+
+         switch (encodingCode)
          {
-            state.Encoder.WriteNull(buffer, state);
+            case EncodingCodes.Map8:
+               WriteSmallType(buffer, state, value, count);
+               break;
+            case EncodingCodes.Map32:
+               WriteLargeType(buffer, state, value, count);
+               break;
          }
       }
 
diff --git a/src/Proton/Codec/Encoders/Messaging/ApplicationPropertiesTypeEncoder.cs b/src/Proton/Codec/Encoders/Messaging/ApplicationPropertiesTypeEncoder.cs
index 03f439a..e4a54ca 100644
--- a/src/Proton/Codec/Encoders/Messaging/ApplicationPropertiesTypeEncoder.cs
+++ b/src/Proton/Codec/Encoders/Messaging/ApplicationPropertiesTypeEncoder.cs
@@ -30,12 +30,12 @@ namespace Apache.Qpid.Proton.Codec.Encoders.Messaging
 
       protected override int GetMapEntries(ApplicationProperties value)
       {
-         return value?.Value?.Count ?? 0;
-      }
+         if (value == null || value.Value == null)
+         {
+            throw new EncodeException("ApplicationProperties must have an assigned Map payload");
+         }
 
-      protected override bool HasMap(ApplicationProperties value)
-      {
-         return value?.Value != null;
+         return value.Value.Count;
       }
 
       protected override void WriteMapEntries(IProtonBuffer buffer, IEncoderState state, ApplicationProperties value)
diff --git a/src/Proton/Codec/Encoders/Messaging/DeliveryAnnotationsTypeEncoder.cs b/src/Proton/Codec/Encoders/Messaging/DeliveryAnnotationsTypeEncoder.cs
index f630c6b..585ec9a 100644
--- a/src/Proton/Codec/Encoders/Messaging/DeliveryAnnotationsTypeEncoder.cs
+++ b/src/Proton/Codec/Encoders/Messaging/DeliveryAnnotationsTypeEncoder.cs
@@ -30,12 +30,12 @@ namespace Apache.Qpid.Proton.Codec.Encoders.Messaging
 
       protected override int GetMapEntries(DeliveryAnnotations value)
       {
-         return value?.Value?.Count ?? 0;
-      }
+         if (value == null || value.Value == null)
+         {
+            throw new EncodeException("DeliveryAnnotations must have an assigned Map payload");
+         }
 
-      protected override bool HasMap(DeliveryAnnotations value)
-      {
-         return value?.Value != null;
+         return value.Value.Count;
       }
 
       protected override void WriteMapEntries(IProtonBuffer buffer, IEncoderState state, DeliveryAnnotations value)
diff --git a/src/Proton/Codec/Encoders/Messaging/FooterTypeEncoder.cs b/src/Proton/Codec/Encoders/Messaging/FooterTypeEncoder.cs
index b784300..d5d8f5e 100644
--- a/src/Proton/Codec/Encoders/Messaging/FooterTypeEncoder.cs
+++ b/src/Proton/Codec/Encoders/Messaging/FooterTypeEncoder.cs
@@ -30,12 +30,12 @@ namespace Apache.Qpid.Proton.Codec.Encoders.Messaging
 
       protected override int GetMapEntries(Footer value)
       {
-         return value?.Value?.Count ?? 0;
-      }
+         if (value == null || value.Value == null)
+         {
+            throw new EncodeException("Footer must have an assigned Map payload");
+         }
 
-      protected override bool HasMap(Footer value)
-      {
-         return value?.Value != null;
+         return value.Value.Count;
       }
 
       protected override void WriteMapEntries(IProtonBuffer buffer, IEncoderState state, Footer value)
diff --git a/src/Proton/Codec/Encoders/Messaging/MessageAnnotationsTypeEncoder.cs b/src/Proton/Codec/Encoders/Messaging/MessageAnnotationsTypeEncoder.cs
index a2ae4f3..e07fb2d 100644
--- a/src/Proton/Codec/Encoders/Messaging/MessageAnnotationsTypeEncoder.cs
+++ b/src/Proton/Codec/Encoders/Messaging/MessageAnnotationsTypeEncoder.cs
@@ -30,12 +30,12 @@ namespace Apache.Qpid.Proton.Codec.Encoders.Messaging
 
       protected override int GetMapEntries(MessageAnnotations value)
       {
-         return value?.Value?.Count ?? 0;
-      }
+         if (value == null || value.Value == null)
+         {
+            throw new EncodeException("MessageAnnotations must have an assigned Map payload");
+         }
 
-      protected override bool HasMap(MessageAnnotations value)
-      {
-         return value?.Value != null;
+         return value.Value.Count;
       }
 
       protected override void WriteMapEntries(IProtonBuffer buffer, IEncoderState state, MessageAnnotations value)
diff --git a/src/Proton/Codec/Encoders/ProtonEncoder.cs b/src/Proton/Codec/Encoders/ProtonEncoder.cs
index b167ac0..72c0e70 100644
--- a/src/Proton/Codec/Encoders/ProtonEncoder.cs
+++ b/src/Proton/Codec/Encoders/ProtonEncoder.cs
@@ -26,6 +26,12 @@ namespace Apache.Qpid.Proton.Codec.Encoders
 {
    public sealed class ProtonEncoder : IEncoder
    {
+      public enum EncoderMode
+      {
+         Sasl,
+         Default
+      }
+
       // The encoders for primitives are fixed and cannot be altered by users who want
       // to register custom encoders, these encoders are stateless so they can be safely
       // made static to reduce overhead of creating and destroying this type.
@@ -91,6 +97,24 @@ namespace Apache.Qpid.Proton.Codec.Encoders
          [deliveryTagEncoder.EncodesType] = deliveryTagEncoder
       };
 
+      private readonly Func<string, Symbol> stringToSymbolSupplier;
+
+      public ProtonEncoder() : this(EncoderMode.Default)
+      {
+      }
+
+      public ProtonEncoder(EncoderMode mode)
+      {
+         if (mode == EncoderMode.Sasl)
+         {
+            stringToSymbolSupplier = Symbol.SaslLookup;
+         }
+         else
+         {
+            stringToSymbolSupplier = Symbol.Lookup;
+         }
+      }
+
       public IEncoderState NewEncoderState()
       {
          return new ProtonEncoderState(this);
@@ -270,7 +294,7 @@ namespace Apache.Qpid.Proton.Codec.Encoders
          }
          else
          {
-            symbolEncoder.WriteType(buffer, state, Symbol.Lookup(value));
+            symbolEncoder.WriteType(buffer, state, stringToSymbolSupplier.Invoke(value));
          }
       }
 
diff --git a/src/Proton/Codec/Encoders/ProtonEncoderFactory.cs b/src/Proton/Codec/Encoders/ProtonEncoderFactory.cs
index 0067517..fc5e91d 100644
--- a/src/Proton/Codec/Encoders/ProtonEncoderFactory.cs
+++ b/src/Proton/Codec/Encoders/ProtonEncoderFactory.cs
@@ -29,7 +29,7 @@ namespace Apache.Qpid.Proton.Codec.Encoders
    {
       public static ProtonEncoder Create()
       {
-         ProtonEncoder encoder = new();
+         ProtonEncoder encoder = new(ProtonEncoder.EncoderMode.Default);
 
          AddMessagingTypeEncoders(encoder);
          AddTransactionTypeEncoders(encoder);
@@ -40,7 +40,7 @@ namespace Apache.Qpid.Proton.Codec.Encoders
 
       public static ProtonEncoder CreateSasl()
       {
-         return AddSaslTypeEncoders(new ProtonEncoder());
+         return AddSaslTypeEncoders(new ProtonEncoder(ProtonEncoder.EncoderMode.Sasl));
       }
 
       private static ProtonEncoder AddMessagingTypeEncoders(ProtonEncoder encoder)
diff --git a/src/Proton/Codec/IDecoderState.cs b/src/Proton/Codec/IDecoderState.cs
index b29e516..97927be 100644
--- a/src/Proton/Codec/IDecoderState.cs
+++ b/src/Proton/Codec/IDecoderState.cs
@@ -15,6 +15,7 @@
  * limitations under the License.
  */
 
+using System;
 using Apache.Qpid.Proton.Buffer;
 
 namespace Apache.Qpid.Proton.Codec
@@ -37,6 +38,51 @@ namespace Apache.Qpid.Proton.Codec
       /// </summary>
       IDecoder Decoder { get; }
 
+      /// <summary>
+      /// The configured maximum number of elements that can be decoded from an array
+      /// encoded with the zero width AMQP types (Null, UInt0. ULong0, List0, Boolean_False
+      /// and Boolean_True). These are uncommon encodings and can lead to small encodings
+      /// with large memory costs at decode which makes them discouraged for normal use.
+      /// A value of zero (the default) should be handled by the implementation as meaning
+      /// that only an array of zero width types with zero elements can be decoded.
+      /// </summary>
+      uint MaxZeroWidthArrayElements
+      {
+         get => 0;
+         set => throw new NotImplementedException("Default implementation cannot set an array width  limit");
+      }
+
+      /// <summary>
+      /// Access the configured maximum depth that nested types such as Lists, Maps and Arrays
+      /// can have before a DecodeException is thrown to allow the decoder to error
+      /// in cases where the depth of encoding exceeds what the environment is thought to be
+      /// able to support.
+      /// </summary>
+      uint DepthLimit
+      {
+         get => uint.MaxValue;
+         set => throw new NotSupportedException("Depth limits not supported by default");
+      }
+
+      /// <summary>
+      /// During decode of AMQP types which can be comprised of a nesting of other AMQP types
+      /// the such as Lists, Maps and Arrays, the depth is increased to track the amount of
+      /// type nesting that comprises the type being decoded. Implementations can use this
+      /// value to impose limits on the depth of nested objects within complex types and throw
+      /// an DecodeException if that depth value is reached.
+      /// </summary>
+      void IncreaseDepth()
+      {
+      }
+
+      /// <summary>
+      /// Called once decoding of one level of a nested type completes to reduce to the previous
+      /// level before proceeding to the next element on the current level if any.
+      /// </summary>
+      void DecreaseDepth()
+      {
+      }
+
       /// <summary>
       /// Decodes an encoded UTF-8 string value from the given buffer.  The number of bytes
       /// that comprise the encoding is provided by the caller, the decoder should not read
diff --git a/src/Proton/Codec/IStreamDecoderState.cs b/src/Proton/Codec/IStreamDecoderState.cs
index 8c26368..2812cc2 100644
--- a/src/Proton/Codec/IStreamDecoderState.cs
+++ b/src/Proton/Codec/IStreamDecoderState.cs
@@ -15,6 +15,7 @@
  * limitations under the License.
  */
 
+using System;
 using System.IO;
 
 namespace Apache.Qpid.Proton.Codec
@@ -26,6 +27,12 @@ namespace Apache.Qpid.Proton.Codec
    /// </summary>
    public interface IStreamDecoderState
    {
+      /// <summary>
+      /// A default used to limit reads from a stream for types that encode a count or
+      /// size of encoded elements.
+      /// </summary>
+      protected static readonly uint DefaultMaxAllocationLimit = int.MaxValue - 8;
+
       /// <summary>
       /// Resets the decoder after a complete decode operation freeing any held
       /// resources and preparing for a new decode operation.
@@ -47,5 +54,104 @@ namespace Apache.Qpid.Proton.Codec
       /// <returns>A string value decoded from the UTF-8 bytes</returns>
       string DecodeUtf8(Stream stream, int length);
 
+      /// <summary>
+      /// The configured maximum number of elements that can be decoded from an array
+      /// encoded with the zero width AMQP types (Null, UInt0. ULong0, List0, Boolean_False
+      /// and Boolean_True). These are uncommon encodings and can lead to small encodings
+      /// with large memory costs at decode which makes them discouraged for normal use.
+      /// It is recommended that for implementations that implement this limit configuration
+      /// the default be zero meaning zero width array encodings are disabled and will always
+      /// throw a NotImplementedException.
+      /// </summary>
+      uint MaxZeroWidthArrayElements
+      {
+         get => 0;
+         set => throw new NotImplementedException("Default implementation cannot set an array width limit");
+      }
+
+      /// <summary>
+      /// The configured maximum size of an encoded string that can be decoded before an error is thrown.
+      /// </summary>
+      uint MaxStringSize
+      {
+         get => DefaultMaxAllocationLimit;
+         set => throw new NotImplementedException("Default implementation cannot set an configurable size limit");
+      }
+
+      /// <summary>
+      /// The configured maximum size of an encoded array that can be decoded before an error is thrown.
+      /// </summary>
+      uint MaxArraySize
+      {
+         get => DefaultMaxAllocationLimit;
+         set => throw new NotImplementedException("Default implementation cannot set an configurable size limit");
+      }
+
+      /// <summary>
+      /// The configured maximum size of an encoded Binary that can be decoded before an error is thrown.
+      /// </summary>
+      uint MaxBinarySize
+      {
+         get => DefaultMaxAllocationLimit;
+         set => throw new NotImplementedException("Default implementation cannot set an configurable size limit");
+      }
+
+      /// <summary>
+      /// The configured maximum size of an encoded Symbol that can be decoded before an error is thrown.
+      /// </summary>
+      uint MaxSymbolSize
+      {
+         get => DefaultMaxAllocationLimit;
+         set => throw new NotImplementedException("Default implementation cannot set an configurable size limit");
+      }
+
+      /// <summary>
+      /// The configured maximum encoded list size that can be decoded before an error is thrown.
+      /// </summary>
+      uint MaxListSize
+      {
+         get => DefaultMaxAllocationLimit;
+         set => throw new NotImplementedException("Default implementation cannot set an configurable size limit");
+      }
+
+      /// <summary>
+      /// The configured maximum encoded map size that can be decoded before an error is thrown.
+      /// </summary>
+      uint MaxMapSize
+      {
+         get => DefaultMaxAllocationLimit;
+         set => throw new NotImplementedException("Default implementation cannot set an configurable size limit");
+      }
+
+      /// <summary>
+      /// Access the configured maximum depth that nested types such as Lists, Maps and Arrays
+      /// can have before a DecodeException is thrown to allow the decoder to error
+      /// in cases where the depth of encoding exceeds what the environment is thought to be
+      /// able to support.
+      /// </summary>
+      uint DepthLimit
+      {
+         get => uint.MaxValue;
+         set => throw new NotSupportedException("Depth limits not supported by default");
+      }
+
+      /// <summary>
+      /// During decode of AMQP types which can be comprised of a nesting of other AMQP types
+      /// the such as Lists, Maps and Arrays, the depth is increased to track the amount of
+      /// type nesting that comprises the type being decoded. Implementations can use this
+      /// value to impose limits on the depth of nested objects within complex types and throw
+      /// an DecodeException if that depth value is reached.
+      /// </summary>
+      void IncreaseDepth()
+      {
+      }
+
+      /// <summary>
+      /// Called once decoding of one level of a nested type completes to reduce to the previous
+      /// level before proceeding to the next element on the current level if any.
+      /// </summary>
+      void DecreaseDepth()
+      {
+      }
    }
 }
diff --git a/src/Proton/Engine/Exceptions/ProtocolViolationException.cs b/src/Proton/Engine/Exceptions/ProtocolViolationException.cs
index b4fb1f0..b4985a2 100644
--- a/src/Proton/Engine/Exceptions/ProtocolViolationException.cs
+++ b/src/Proton/Engine/Exceptions/ProtocolViolationException.cs
@@ -17,6 +17,7 @@
 
 using System;
 using Apache.Qpid.Proton.Types;
+using Apache.Qpid.Proton.Types.Transport;
 
 namespace Apache.Qpid.Proton.Engine.Exceptions
 {
@@ -100,7 +101,7 @@ namespace Apache.Qpid.Proton.Engine.Exceptions
       /// <summary>
       /// Returns the symbolic error condition that describes the violation error.
       /// </summary>
-      public Symbol ErrorCondition { get; internal set; }
+      public Symbol ErrorCondition { get; internal set; } = AmqpError.INTERNAL_ERROR;
 
    }
 }
\ No newline at end of file
diff --git a/src/Proton/Engine/IEngineConfiguration.cs b/src/Proton/Engine/IEngineConfiguration.cs
index 4b7d533..ce9f9ae 100644
--- a/src/Proton/Engine/IEngineConfiguration.cs
+++ b/src/Proton/Engine/IEngineConfiguration.cs
@@ -15,6 +15,7 @@
  * limitations under the License.
  */
 
+using System;
 using Apache.Qpid.Proton.Buffer;
 
 namespace Apache.Qpid.Proton.Engine
@@ -42,5 +43,18 @@ namespace Apache.Qpid.Proton.Engine
       /// </summary>
       bool TraceFrames { get; set; }
 
+      /// <summary>
+      /// Sets the configured maximum number of Transfer frames that can make up a single completed
+      /// incoming delivery. If the delivery is not completed within this number of transfer frames the
+      /// engine may either close the associated link or the connection in its entirety. An engine
+      /// implementation may opt not to implement this feature in which case the value should be fixed
+      /// at zero and any assignment should be ignored. If the value is configured as zero then the
+      /// behavior should be to treat that as no limit was assigned.
+      /// </summary>
+      uint MaxTransfersPerDelivery
+      {
+         get => 0;
+         set => throw new NotSupportedException("Default configuration does not support assigning a max transfers value");
+      }
    }
 }
diff --git a/src/Proton/Engine/Implementation/ProtonConstants.cs b/src/Proton/Engine/Implementation/ProtonConstants.cs
index c051421..ac078cb 100644
--- a/src/Proton/Engine/Implementation/ProtonConstants.cs
+++ b/src/Proton/Engine/Implementation/ProtonConstants.cs
@@ -27,6 +27,12 @@ namespace Apache.Qpid.Proton.Engine.Implementation
       /// </summary>
       public static readonly uint MinMaxAmqpFrameSize = 512;
 
+      /// <summary>
+      /// The engine default for the maximum number of transfer performatives allowed
+      /// before a delivery must be completed or the engine will consider it an error.
+      /// </summary>
+      public static readonly uint DefaultMaxTransfersPerDelivery = ushort.MaxValue;
+
       /**
        * The default AMQP max frame size used by the engine and connection if none is set
        * by the client or remote peer.
diff --git a/src/Proton/Engine/Implementation/ProtonEngineConfiguration.cs b/src/Proton/Engine/Implementation/ProtonEngineConfiguration.cs
index 610d6e9..3ad354a 100644
--- a/src/Proton/Engine/Implementation/ProtonEngineConfiguration.cs
+++ b/src/Proton/Engine/Implementation/ProtonEngineConfiguration.cs
@@ -78,6 +78,8 @@ namespace Apache.Qpid.Proton.Engine.Implementation
 
       public uint InboundMaxFrameSize => effectiveMaxInboundFrameSize;
 
+      public uint MaxTransfersPerDelivery { get; set; } = ProtonConstants.DefaultMaxTransfersPerDelivery;
+
       #region Internal Engine API
 
       internal void RecomputeEffectiveFrameSizeLimits()
diff --git a/src/Proton/Engine/Implementation/ProtonFrameDecodingHandler.cs b/src/Proton/Engine/Implementation/ProtonFrameDecodingHandler.cs
index 1f49ba5..2929059 100644
--- a/src/Proton/Engine/Implementation/ProtonFrameDecodingHandler.cs
+++ b/src/Proton/Engine/Implementation/ProtonFrameDecodingHandler.cs
@@ -381,7 +381,15 @@ namespace Apache.Qpid.Proton.Engine.Implementation
             if (frameBodySize > 0)
             {
                long startReadIndex = input.ReadOffset;
-               val = handler.decoder.ReadObject(input, handler.decoderState);
+
+               try
+               {
+                  val = handler.decoder.ReadObject(input, handler.decoderState);
+               }
+               finally
+               {
+                  handler.decoderState.Reset();
+               }
 
                // Copy the payload portion of the incoming bytes for now as the incoming may be
                // from a wrapped pooled buffer and for now we have no way of retaining or otherwise
diff --git a/src/Proton/Engine/Implementation/ProtonIncomingDelivery.cs b/src/Proton/Engine/Implementation/ProtonIncomingDelivery.cs
index fb7c186..3f8f325 100644
--- a/src/Proton/Engine/Implementation/ProtonIncomingDelivery.cs
+++ b/src/Proton/Engine/Implementation/ProtonIncomingDelivery.cs
@@ -259,8 +259,6 @@ namespace Apache.Qpid.Proton.Engine.Implementation
 
       internal bool IsFirstTransfer => transferCount <= 1;
 
-      internal uint IncrementAndGetTransferCount() => ++transferCount;
-
       internal uint DeliveryId => deliveryId;
 
       internal ProtonIncomingDelivery Aborted()
@@ -307,19 +305,24 @@ namespace Apache.Qpid.Proton.Engine.Implementation
 
       internal ProtonIncomingDelivery AppendTransferPayload(IProtonBuffer buffer)
       {
-         if (payload == null)
-         {
-            payload = buffer;
-         }
-         else if (aggregate != null)
-         {
-            aggregate.Append(buffer);
-         }
-         else
+         ++transferCount;
+
+         if (buffer != null)
          {
-            IProtonBuffer previous = payload;
+            if (payload == null)
+            {
+               payload = buffer;
+            }
+            else if (aggregate != null)
+            {
+               aggregate.Append(buffer);
+            }
+            else
+            {
+               IProtonBuffer previous = payload;
 
-            payload = aggregate = IProtonCompositeBuffer.Compose(ProtonByteBufferAllocator.Instance, previous, buffer);
+               payload = aggregate = IProtonCompositeBuffer.Compose(ProtonByteBufferAllocator.Instance, previous, buffer);
+            }
          }
 
          return this;
diff --git a/src/Proton/Engine/Implementation/ProtonReceiver.cs b/src/Proton/Engine/Implementation/ProtonReceiver.cs
index e641172..c60ffbf 100644
--- a/src/Proton/Engine/Implementation/ProtonReceiver.cs
+++ b/src/Proton/Engine/Implementation/ProtonReceiver.cs
@@ -39,7 +39,7 @@ namespace Apache.Qpid.Proton.Engine.Implementation
       private Action<IIncomingDelivery> deliveryUpdatedEventHandler = null;
 
       private readonly ProtonSessionIncomingWindow sessionWindow;
-      private readonly LinkedSplayedDictionary<uint, ProtonIncomingDelivery> unsettled = new();
+      private readonly UnsettledDictionary<ProtonIncomingDelivery> unsettled = new(delivery => delivery.DeliveryId);
 
       private uint? currentDeliveryId;
 
@@ -154,23 +154,22 @@ namespace Apache.Qpid.Proton.Engine.Implementation
 
          IList<uint> toRemove = new List<uint>();
 
-         foreach (KeyValuePair<uint, ProtonIncomingDelivery> delivery in unsettled)
+         unsettled.ForEach((deliveryId, delivery) =>
          {
-            if (filter.Invoke(delivery.Value))
+            if (filter.Invoke(delivery))
             {
                if (state != null)
                {
-                  delivery.Value.LocalState(state);
+                  delivery.LocalState(state);
                }
                if (settle)
                {
-                  delivery.Value.LocallySettled();
-                  toRemove.Add(delivery.Key);
+                  delivery.LocallySettled();
+                  toRemove.Add(deliveryId);
                }
-
-               sessionWindow.ProcessDisposition(this, delivery.Value);
+               sessionWindow.ProcessDisposition(this, delivery);
             }
-         }
+         });
 
          if (toRemove.Count > 0)
          {
@@ -285,8 +284,6 @@ namespace Apache.Qpid.Proton.Engine.Implementation
             currentDeliveryId = transfer.DeliveryId;
          }
 
-         delivery.IncrementAndGetTransferCount();
-
          if (transfer.HasState())
          {
             delivery.RemoteState = transfer.DeliveryState;
@@ -297,12 +294,10 @@ namespace Apache.Qpid.Proton.Engine.Implementation
             delivery.RemotelySettled();
          }
 
-         if (payload != null)
-         {
-            delivery.AppendTransferPayload(payload);
-         }
+         delivery.AppendTransferPayload(payload);
 
          bool done = transfer.Aborted || !transfer.More;
+
          if (done)
          {
             CreditState.DecrementCredit();
@@ -318,6 +313,14 @@ namespace Apache.Qpid.Proton.Engine.Implementation
                delivery.Completed();
             }
          }
+         else if (delivery.TransferCount >= engine.Configuration.MaxTransfersPerDelivery)
+         {
+            engine.EngineFailed(new ProtocolViolationException(LinkError.TRANSFER_LIMIT_EXCEEDED,
+                "Delivery not completed within configured max Transfers per delivery value: " +
+                engine.Configuration.MaxTransfersPerDelivery));
+
+            return;
+         }
 
          if (transfer.Aborted)
          {
diff --git a/src/Proton/Engine/Implementation/ProtonSessionIncomingWindow.cs b/src/Proton/Engine/Implementation/ProtonSessionIncomingWindow.cs
index d400153..42a7667 100644
--- a/src/Proton/Engine/Implementation/ProtonSessionIncomingWindow.cs
+++ b/src/Proton/Engine/Implementation/ProtonSessionIncomingWindow.cs
@@ -52,7 +52,7 @@ namespace Apache.Qpid.Proton.Engine.Implementation
       private uint maxFrameSize;
       private uint incomingBytes;
 
-      private readonly SplayedDictionary<uint, ProtonIncomingDelivery> unsettled = new();
+      private readonly UnsettledDictionary<ProtonIncomingDelivery> unsettled = new(delivery => delivery.DeliveryId);
 
       public ProtonSessionIncomingWindow(ProtonSession session)
       {
@@ -133,15 +133,22 @@ namespace Apache.Qpid.Proton.Engine.Implementation
       /// <returns></returns>
       internal Transfer HandleTransfer(IProtonLink link, Transfer transfer, IProtonBuffer payload)
       {
-         incomingBytes += payload != null ? (uint)payload.ReadableBytes : 0;
-         incomingWindow--;
-         nextIncomingId++;
+         if (incomingWindow-- == 0)
+         {
+            engine.EngineFailed(new ProtocolViolationException(SessionError.WINDOW_VIOLATION,
+                "Sender violated session incoming window limit: " + link.Handle));
+         }
+         else
+         {
+            incomingBytes += payload != null ? (uint)payload.ReadableBytes : 0;
+            nextIncomingId++;
 
-         link.RemoteTransfer(transfer, payload, out ProtonIncomingDelivery delivery);
+            link.RemoteTransfer(transfer, payload, out ProtonIncomingDelivery delivery);
 
-         if (!delivery.IsSettled && !delivery.IsRemotelySettled && delivery.IsFirstTransfer)
-         {
-            unsettled.Add(delivery.DeliveryId, delivery);
+            if (!delivery.IsSettled && !delivery.IsRemotelySettled && delivery.IsFirstTransfer)
+            {
+               unsettled.Add(delivery.DeliveryId, delivery);
+            }
          }
 
          return transfer;
@@ -159,7 +166,7 @@ namespace Apache.Qpid.Proton.Engine.Implementation
 
          if (disposition.HasLast() && disposition.Last != first)
          {
-            HandleRangedDisposition(disposition);
+            HandleRangedDisposition(disposition, unsettled);
          }
          else
          {
@@ -177,27 +184,22 @@ namespace Apache.Qpid.Proton.Engine.Implementation
          return disposition;
       }
 
-      private void HandleRangedDisposition(Disposition disposition)
+      private static void HandleRangedDisposition(Disposition disposition, UnsettledDictionary<ProtonIncomingDelivery> unsettled)
       {
-         uint first = disposition.First;
-         uint last = disposition.Last;
-         bool settled = disposition.Settled;
-
-         uint index = first;
-
-         do
+         if (disposition.Settled)
          {
-            if (unsettled.TryGetValue(index, out ProtonIncomingDelivery delivery))
+            unsettled.RemoveEach(disposition.First, disposition.Last, (delivery) =>
             {
-               if (settled)
-               {
-                  unsettled.Remove(index);
-               }
-
                ((IProtonLink)delivery.Link).RemoteDisposition(disposition, delivery);
-            }
+            });
+         }
+         else
+         {
+            unsettled.ForEach(disposition.First, disposition.Last, (delivery) =>
+            {
+               ((IProtonLink)delivery.Link).RemoteDisposition(disposition, delivery);
+            });
          }
-         while (index++ != last);
       }
 
       internal uint UpdateIncomingWindow()
diff --git a/src/Proton/Engine/Implementation/ProtonSessionOutgoingWindow.cs b/src/Proton/Engine/Implementation/ProtonSessionOutgoingWindow.cs
index 2ad654b..23967c5 100644
--- a/src/Proton/Engine/Implementation/ProtonSessionOutgoingWindow.cs
+++ b/src/Proton/Engine/Implementation/ProtonSessionOutgoingWindow.cs
@@ -52,7 +52,7 @@ namespace Apache.Qpid.Proton.Engine.Implementation
       private uint remoteIncomingWindow;
       private uint remoteNextIncomingId = 0;
 
-      private readonly SplayedDictionary<uint, ProtonOutgoingDelivery> unsettled = new();
+      private readonly UnsettledDictionary<ProtonOutgoingDelivery> unsettled = new(delivery => delivery.DeliveryId);
 
       public ProtonSessionOutgoingWindow(ProtonSession session)
       {
@@ -233,7 +233,7 @@ namespace Apache.Qpid.Proton.Engine.Implementation
 
          if (disposition.HasLast() && disposition.Last != first)
          {
-            HandleRangedDisposition(disposition);
+            HandleRangedDisposition(disposition, unsettled);
          }
          else
          {
@@ -250,25 +250,22 @@ namespace Apache.Qpid.Proton.Engine.Implementation
          return disposition;
       }
 
-      private void HandleRangedDisposition(Disposition disposition)
+      private static void HandleRangedDisposition(Disposition disposition, UnsettledDictionary<ProtonOutgoingDelivery> unsettled)
       {
-         uint first = disposition.First;
-         uint last = disposition.Last;
-
-         uint index = first;
-
-         do
+         if (disposition.Settled)
          {
-            if (unsettled.TryGetValue(index, out ProtonOutgoingDelivery delivery))
+            unsettled.RemoveEach(disposition.First, disposition.Last, (delivery) =>
             {
-               if (disposition.Settled)
-               {
-                  unsettled.Remove(first);
-               }
                ((IProtonLink)delivery.Link).RemoteDisposition(disposition, delivery);
-            }
+            });
+         }
+         else
+         {
+            unsettled.ForEach(disposition.First, disposition.Last, (delivery) =>
+            {
+               ((IProtonLink)delivery.Link).RemoteDisposition(disposition, delivery);
+            });
          }
-         while (index++ != last);
       }
 
       #endregion
diff --git a/src/Proton/Engine/Implementation/ProtonTransactionManager.cs b/src/Proton/Engine/Implementation/ProtonTransactionManager.cs
index 317c771..8a6823e 100644
--- a/src/Proton/Engine/Implementation/ProtonTransactionManager.cs
+++ b/src/Proton/Engine/Implementation/ProtonTransactionManager.cs
@@ -335,7 +335,7 @@ namespace Apache.Qpid.Proton.Engine.Implementation
          {
             IProtonBuffer payload = delivery.ReadAll();
 
-            AmqpValue container = (AmqpValue)payloadDecoder.ReadObject(payload, payloadDecoder.CachedDecoderState);
+            AmqpValue container = payloadDecoder.ReadObject<AmqpValue>(payload, payloadDecoder.CachedDecoderState);
 
             if (container.Value is Declare)
             {
diff --git a/src/Proton/Engine/Implementation/Sasl/ProtonDefaultSaslClientAuthenticator.cs b/src/Proton/Engine/Implementation/Sasl/ProtonDefaultSaslClientAuthenticator.cs
index a314e1f..0e395f2 100644
--- a/src/Proton/Engine/Implementation/Sasl/ProtonDefaultSaslClientAuthenticator.cs
+++ b/src/Proton/Engine/Implementation/Sasl/ProtonDefaultSaslClientAuthenticator.cs
@@ -31,7 +31,7 @@ namespace Apache.Qpid.Proton.Engine.Implementation.Sasl
    {
       public static readonly ProtonDefaultSaslClientAuthenticator Instance = new();
 
-      private readonly Symbol ANONYMOUS = Symbol.Lookup("ANONYMOUS");
+      private readonly Symbol ANONYMOUS = Symbol.SaslLookup("ANONYMOUS");
 
       public void HandleSaslChallenge(ISaslClientContext context, IProtonBuffer challenge)
       {
@@ -40,7 +40,6 @@ namespace Apache.Qpid.Proton.Engine.Implementation.Sasl
 
       public void HandleSaslMechanisms(ISaslClientContext context, Symbol[] mechanisms)
       {
-
          if (mechanisms != null && Array.Find(mechanisms, element => ANONYMOUS.Equals(element)) != null)
          {
             context.SendChosenMechanism(ANONYMOUS, null, ProtonByteBufferAllocator.Instance.Allocate(0, 0));
diff --git a/src/Proton/Engine/Implementation/Sasl/ProtonDefaultSaslServerAuthenticator .cs b/src/Proton/Engine/Implementation/Sasl/ProtonDefaultSaslServerAuthenticator .cs
index 9411516..c01d298 100644
--- a/src/Proton/Engine/Implementation/Sasl/ProtonDefaultSaslServerAuthenticator .cs	
+++ b/src/Proton/Engine/Implementation/Sasl/ProtonDefaultSaslServerAuthenticator .cs	
@@ -33,7 +33,7 @@ namespace Apache.Qpid.Proton.Engine.Implementation.Sasl
    {
       public static readonly ProtonDefaultSaslServerAuthenticator Instance = new();
 
-      private static readonly Symbol[] PLAIN = { Symbol.Lookup("PLAIN") };
+      private static readonly Symbol[] PLAIN = { Symbol.SaslLookup("PLAIN") };
 
       public void HandleSaslHeader(ISaslServerContext context, AmqpHeader header)
       {
diff --git a/src/Proton/Engine/Sasl/Client/AnonymousMechanism.cs b/src/Proton/Engine/Sasl/Client/AnonymousMechanism.cs
index 118d6d1..8fbc05b 100644
--- a/src/Proton/Engine/Sasl/Client/AnonymousMechanism.cs
+++ b/src/Proton/Engine/Sasl/Client/AnonymousMechanism.cs
@@ -24,7 +24,7 @@ namespace Apache.Qpid.Proton.Engine.Sasl.Client
    /// </summary>
    public sealed class AnonymousMechanism : AbstractMechanism
    {
-      public static readonly Symbol ANONYMOUS = Symbol.Lookup("ANONYMOUS");
+      public static readonly Symbol ANONYMOUS = Symbol.SaslLookup("ANONYMOUS");
 
       public override Symbol Name => ANONYMOUS;
 
diff --git a/src/Proton/Engine/Sasl/Client/CramMD5Mechanism.cs b/src/Proton/Engine/Sasl/Client/CramMD5Mechanism.cs
index b8189f9..90d97a2 100644
--- a/src/Proton/Engine/Sasl/Client/CramMD5Mechanism.cs
+++ b/src/Proton/Engine/Sasl/Client/CramMD5Mechanism.cs
@@ -27,7 +27,7 @@ namespace Apache.Qpid.Proton.Engine.Sasl.Client
    /// </summary>
    public sealed class CramMD5Mechanism : AbstractMechanism
    {
-      public static readonly Symbol CRAM_MD5 = Symbol.Lookup("CRAM-MD5");
+      public static readonly Symbol CRAM_MD5 = Symbol.SaslLookup("CRAM-MD5");
 
       private bool sentResponse;
 
diff --git a/src/Proton/Engine/Sasl/Client/ExternalMechanism.cs b/src/Proton/Engine/Sasl/Client/ExternalMechanism.cs
index 0cafa91..3884311 100644
--- a/src/Proton/Engine/Sasl/Client/ExternalMechanism.cs
+++ b/src/Proton/Engine/Sasl/Client/ExternalMechanism.cs
@@ -24,7 +24,7 @@ namespace Apache.Qpid.Proton.Engine.Sasl.Client
    /// </summary>
    public sealed class ExternalMechanism : AbstractMechanism
    {
-      public static readonly Symbol EXTERNAL = Symbol.Lookup("EXTERNAL");
+      public static readonly Symbol EXTERNAL = Symbol.SaslLookup("EXTERNAL");
 
       public override Symbol Name => EXTERNAL;
 
diff --git a/src/Proton/Engine/Sasl/Client/IMechanism.cs b/src/Proton/Engine/Sasl/Client/IMechanism.cs
index 57f6561..9d1fd48 100644
--- a/src/Proton/Engine/Sasl/Client/IMechanism.cs
+++ b/src/Proton/Engine/Sasl/Client/IMechanism.cs
@@ -81,7 +81,7 @@ namespace Apache.Qpid.Proton.Engine.Sasl.Client
       /// authentication error if that mechanism matches the highest priority value offered by the remote
       /// peer.
       /// </remarks>
-      /// <returns>true if the mechanim should used without it having been requested</returns>
+      /// <returns>true if the mechanism should used without it having been requested</returns>
       bool IsEnabledByDefault();
 
    }
diff --git a/src/Proton/Engine/Sasl/Client/PlainMechanism.cs b/src/Proton/Engine/Sasl/Client/PlainMechanism.cs
index 2ee6d3c..a7f9eb8 100644
--- a/src/Proton/Engine/Sasl/Client/PlainMechanism.cs
+++ b/src/Proton/Engine/Sasl/Client/PlainMechanism.cs
@@ -27,7 +27,7 @@ namespace Apache.Qpid.Proton.Engine.Sasl.Client
    /// </summary>
    public sealed class PlainMechanism : AbstractMechanism
    {
-      public static readonly Symbol PLAIN = Symbol.Lookup("PLAIN");
+      public static readonly Symbol PLAIN = Symbol.SaslLookup("PLAIN");
 
       public override Symbol Name => PLAIN;
 
diff --git a/src/Proton/Engine/Sasl/Client/SaslMechanismSelector.cs b/src/Proton/Engine/Sasl/Client/SaslMechanismSelector.cs
index b7622ad..96ed2ea 100644
--- a/src/Proton/Engine/Sasl/Client/SaslMechanismSelector.cs
+++ b/src/Proton/Engine/Sasl/Client/SaslMechanismSelector.cs
@@ -46,7 +46,7 @@ namespace Apache.Qpid.Proton.Engine.Sasl.Client
       /// <param name="allowed">An enumeration of SASL mechanism names</param>
       public SaslMechanismSelector(IEnumerable<string> allowed)
       {
-         this.allowedMechanisms = allowed != null ? StringUtils.ToSymbolSet(allowed) : new HashSet<Symbol>();
+         this.allowedMechanisms = allowed != null ? StringUtils.ToSaslSymbolSet(allowed) : new HashSet<Symbol>();
       }
 
       /// <summary>
diff --git a/src/Proton/Engine/Sasl/Client/ScramSHA1Mechanism.cs b/src/Proton/Engine/Sasl/Client/ScramSHA1Mechanism.cs
index 7e50ff5..963095f 100644
--- a/src/Proton/Engine/Sasl/Client/ScramSHA1Mechanism.cs
+++ b/src/Proton/Engine/Sasl/Client/ScramSHA1Mechanism.cs
@@ -26,7 +26,7 @@ namespace Apache.Qpid.Proton.Engine.Sasl.Client
    /// </summary>
    public sealed class ScramSHA1Mechanism : AbstractScramSHAMechanism
    {
-      public static readonly Symbol SCRAM_SHA_1 = Symbol.Lookup("SCRAM-SHA-1");
+      public static readonly Symbol SCRAM_SHA_1 = Symbol.SaslLookup("SCRAM-SHA-1");
 
       public ScramSHA1Mechanism() : this(Guid.NewGuid().ToString())
       {
diff --git a/src/Proton/Engine/Sasl/Client/ScramSHA256Mechanism.cs b/src/Proton/Engine/Sasl/Client/ScramSHA256Mechanism.cs
index 129a622..53b35f4 100644
--- a/src/Proton/Engine/Sasl/Client/ScramSHA256Mechanism.cs
+++ b/src/Proton/Engine/Sasl/Client/ScramSHA256Mechanism.cs
@@ -26,7 +26,7 @@ namespace Apache.Qpid.Proton.Engine.Sasl.Client
    /// </summary>
    public sealed class ScramSHA256Mechanism : AbstractScramSHAMechanism
    {
-      public static readonly Symbol SCRAM_SHA_256 = Symbol.Lookup("SCRAM-SHA-256");
+      public static readonly Symbol SCRAM_SHA_256 = Symbol.SaslLookup("SCRAM-SHA-256");
 
       public ScramSHA256Mechanism() : this(Guid.NewGuid().ToString())
       {
diff --git a/src/Proton/Engine/Sasl/Client/ScramSHA512Mechanism.cs b/src/Proton/Engine/Sasl/Client/ScramSHA512Mechanism.cs
index f84c706..19f1fe5 100644
--- a/src/Proton/Engine/Sasl/Client/ScramSHA512Mechanism.cs
+++ b/src/Proton/Engine/Sasl/Client/ScramSHA512Mechanism.cs
@@ -26,7 +26,7 @@ namespace Apache.Qpid.Proton.Engine.Sasl.Client
    /// </summary>
    public sealed class ScramSHA512Mechanism : AbstractScramSHAMechanism
    {
-      public static readonly Symbol SCRAM_SHA_512 = Symbol.Lookup("SCRAM-SHA-512");
+      public static readonly Symbol SCRAM_SHA_512 = Symbol.SaslLookup("SCRAM-SHA-512");
 
       public ScramSHA512Mechanism() : this(Guid.NewGuid().ToString())
       {
diff --git a/src/Proton/Engine/Sasl/Client/XOauth2Mechanism.cs b/src/Proton/Engine/Sasl/Client/XOauth2Mechanism.cs
index c94a691..8a1d432 100644
--- a/src/Proton/Engine/Sasl/Client/XOauth2Mechanism.cs
+++ b/src/Proton/Engine/Sasl/Client/XOauth2Mechanism.cs
@@ -31,7 +31,7 @@ namespace Apache.Qpid.Proton.Engine.Sasl.Client
    /// </remarks>
    public sealed class XOauth2Mechanism : AbstractMechanism
    {
-      public static readonly Symbol XOAUTH2 = Symbol.Lookup("XOAUTH2");
+      public static readonly Symbol XOAUTH2 = Symbol.SaslLookup("XOAUTH2");
 
       private readonly Regex ACCESS_TOKEN_PATTERN = new("^[\\x20-\\x7F]+$", RegexOptions.Compiled);
 
diff --git a/src/Proton/Types/Security/SaslChallenge.cs b/src/Proton/Types/Security/SaslChallenge.cs
index 02df9f0..e8366e7 100644
--- a/src/Proton/Types/Security/SaslChallenge.cs
+++ b/src/Proton/Types/Security/SaslChallenge.cs
@@ -24,7 +24,7 @@ namespace Apache.Qpid.Proton.Types.Security
    public sealed class SaslChallenge : ISaslPerformative
    {
       public static readonly ulong DescriptorCode = 0x0000000000000042UL;
-      public static readonly Symbol DescriptorSymbol = Symbol.Lookup("amqp:sasl-challenge:list");
+      public static readonly Symbol DescriptorSymbol = Symbol.SaslLookup("amqp:sasl-challenge:list");
 
       private IProtonBuffer challenge;
 
diff --git a/src/Proton/Types/Security/SaslInit.cs b/src/Proton/Types/Security/SaslInit.cs
index d8ac09b..0107deb 100644
--- a/src/Proton/Types/Security/SaslInit.cs
+++ b/src/Proton/Types/Security/SaslInit.cs
@@ -24,7 +24,7 @@ namespace Apache.Qpid.Proton.Types.Security
    public class SaslInit : ISaslPerformative
    {
       public static readonly ulong DescriptorCode = 0x0000000000000041UL;
-      public static readonly Symbol DescriptorSymbol = Symbol.Lookup("amqp:sasl-init:list");
+      public static readonly Symbol DescriptorSymbol = Symbol.SaslLookup("amqp:sasl-init:list");
 
       private Symbol mechanism;
       private IProtonBuffer initialResponse;
diff --git a/src/Proton/Types/Security/SaslMechanisms.cs b/src/Proton/Types/Security/SaslMechanisms.cs
index 5fa051d..dd51851 100644
--- a/src/Proton/Types/Security/SaslMechanisms.cs
+++ b/src/Proton/Types/Security/SaslMechanisms.cs
@@ -22,7 +22,7 @@ namespace Apache.Qpid.Proton.Types.Security
    public class SaslMechanisms : ISaslPerformative
    {
       public static readonly ulong DescriptorCode = 0x0000000000000040UL;
-      public static readonly Symbol DescriptorSymbol = Symbol.Lookup("amqp:sasl-mechanisms:list");
+      public static readonly Symbol DescriptorSymbol = Symbol.SaslLookup("amqp:sasl-mechanisms:list");
 
       private Symbol[] mechanisms;
 
diff --git a/src/Proton/Types/Security/SaslOutcome.cs b/src/Proton/Types/Security/SaslOutcome.cs
index 97c29a7..029ea1f 100644
--- a/src/Proton/Types/Security/SaslOutcome.cs
+++ b/src/Proton/Types/Security/SaslOutcome.cs
@@ -23,7 +23,7 @@ namespace Apache.Qpid.Proton.Types.Security
    public class SaslOutcome : ISaslPerformative
    {
       public static readonly ulong DescriptorCode = 0x0000000000000044UL;
-      public static readonly Symbol DescriptorSymbol = Symbol.Lookup("amqp:sasl-outcome:list");
+      public static readonly Symbol DescriptorSymbol = Symbol.SaslLookup("amqp:sasl-outcome:list");
 
       public SaslOutcome() { }
 
diff --git a/src/Proton/Types/Security/SaslResponse.cs b/src/Proton/Types/Security/SaslResponse.cs
index eaaf5ef..1b8894e 100644
--- a/src/Proton/Types/Security/SaslResponse.cs
+++ b/src/Proton/Types/Security/SaslResponse.cs
@@ -24,7 +24,7 @@ namespace Apache.Qpid.Proton.Types.Security
    public class SaslResponse : ISaslPerformative
    {
       public static readonly ulong DescriptorCode = 0x0000000000000043UL;
-      public static readonly Symbol DescriptorSymbol = Symbol.Lookup("amqp:sasl-response:list");
+      public static readonly Symbol DescriptorSymbol = Symbol.SaslLookup("amqp:sasl-response:list");
 
       private IProtonBuffer response;
 
diff --git a/src/Proton/Types/Symbol.cs b/src/Proton/Types/Symbol.cs
index 9d3acc7..96e1cf9 100644
--- a/src/Proton/Types/Symbol.cs
+++ b/src/Proton/Types/Symbol.cs
@@ -24,32 +24,42 @@ namespace Apache.Qpid.Proton.Types
 {
    public sealed class Symbol : IEquatable<Symbol>, IComparable, IComparable<Symbol>
    {
-      private static readonly ConcurrentDictionary<IProtonBuffer, Symbol> buffersToSymbols = new();
-      private static readonly ConcurrentDictionary<string, Symbol> stringsToSymbols = new();
+      // Prevents the Symbol cache from growing overly large if abused by creating overly
+      // large Symbols which would all be stored in the symbol cache. The SASL symbol cache
+      // is kept considerably smaller since there shouldn't be many entries used for the
+      // mechanisms and descriptor symbols as compared to the main application level cache.
 
-      private static readonly Symbol EMPTY_SYMBOL = new();
+      private static readonly uint MaxSymbolCacheEntries = 8192;
+      private static readonly uint MaxCachedSymbolSize = 64;
 
-      // Prevents the Symbol cache from growing overly large if abused by creating overly
-      // large Symbols which would all be stored in the symbol cache.
-      private static readonly uint MAX_CACHED_SYMBOL_SIZE = 64;
+      private static readonly uint MaxSaslSymbolCacheEntries = 128;
+      private static readonly uint MaxCachedSaslSymbolSize = 32;
+
+      private static readonly SymbolCache CachedSymbols = new SymbolCache(MaxSymbolCacheEntries, MaxCachedSymbolSize);
+      private static readonly SymbolCache CachedSaslSymbols = new SymbolCache(MaxSaslSymbolCacheEntries, MaxCachedSaslSymbolSize);
+
+      private static readonly Symbol EMPTY_SYMBOL = new();
 
       // Lazy allocated based on calls to stringify the given Symbol
       private string symbolString;
 
       private readonly IProtonBuffer underlying;
       private readonly int hashCode;
+      private readonly SymbolCache symbolCache;
 
       private Symbol()
       {
          underlying = ProtonByteBufferAllocator.Instance.Allocate(0, 0);
          symbolString = "";
          hashCode = 32;
+         symbolCache = null;
       }
 
-      private Symbol(IProtonBuffer buffer)
+      private Symbol(IProtonBuffer buffer, SymbolCache cache)
       {
          underlying = buffer;
          hashCode = buffer.GetHashCode();
+         symbolCache = cache;
       }
 
       /// <summary>
@@ -82,19 +92,7 @@ namespace Apache.Qpid.Proton.Types
          }
          else
          {
-            if (!stringsToSymbols.TryGetValue(value, out Symbol symbol))
-            {
-               symbol = Lookup(ProtonByteBufferAllocator.Instance.Wrap(Encoding.ASCII.GetBytes(value)));
-
-               if (symbol.Length <= MAX_CACHED_SYMBOL_SIZE)
-               {
-                  // Try and keep the Symbol instance consistent with the one that is stored
-                  // in the buffer to symbol dictionary.
-                  stringsToSymbols[value] = symbol;
-               }
-            }
-
-            return symbol;
+            return CachedSymbols.Lookup(value);
          }
       }
 
@@ -131,29 +129,69 @@ namespace Apache.Qpid.Proton.Types
          }
          else
          {
-            if (!buffersToSymbols.TryGetValue(value, out Symbol symbol))
-            {
-               if (copyOnCreate)
-               {
-                  long symbolSize = value.ReadableBytes;
-                  IProtonBuffer copy = ProtonByteBufferAllocator.Instance.Allocate(symbolSize, symbolSize);
-                  value.CopyInto(value.ReadOffset, copy, 0, symbolSize);
-                  copy.WriteOffset = symbolSize;
-                  value = copy;
-               }
+            return CachedSymbols.Lookup(value, copyOnCreate);
+         }
+      }
 
-               symbol = new Symbol(value);
+      /// <summary>
+      /// Lookup or create a singleton instance of the given Symbol that has the
+      /// matching name to the string value provided. This method looks in the
+      /// smaller SASL symbol cache for the matching Symbol value.
+      /// </summary>
+      /// <param name="value">the stringified symbol name</param>
+      /// <returns>A singleton instance of the named Symbol</returns>
+      public static Symbol SaslLookup(string value)
+      {
+         if (value == null)
+         {
+            return null;
+         }
+         else if (value.Length == 0)
+         {
+            return EMPTY_SYMBOL;
+         }
+         else
+         {
+            return CachedSaslSymbols.Lookup(value);
+         }
+      }
 
-               if (symbol.Length <= MAX_CACHED_SYMBOL_SIZE)
-               {
-                  if (!buffersToSymbols.TryAdd(value, symbol))
-                  {
-                     symbol = buffersToSymbols[value];
-                  }
-               }
-            }
+      /// <summary>
+      /// Lookup or create a singleton instance of the given Symbol that has the
+      /// matching byte contents as the given buffer, if none exists a new Symbol
+      /// is created using the given buffer which is not copied but used directly.
+      /// This method looks in the smaller SASL Symbol cache for the matching value.
+      /// </summary>
+      /// <param name="value">the stringified symbol name</param>
+      /// <returns>A singleton instance of the named Symbol</returns>
+      public static Symbol SaslLookup(IProtonBuffer value)
+      {
+         return SaslLookup(value, false);
+      }
 
-            return symbol;
+      /// <summary>
+      /// Lookup or create a singleton instance of the given Symbol that has the
+      /// matching byte contents as the given buffer, if none exists a new Symbol
+      /// is created using the given buffer which is not copied if the provided
+      /// boolean option requests it. This method looks in the smaller SASL Symbol
+      /// cache for the matching value.
+      /// </summary>
+      /// <param name="value">the stringified symbol name</param>
+      /// <param name="copyOnCreate">should the given buffer be copied if a Symbol is created</param>
+      /// <returns>A singleton instance of the named Symbol</returns>
+      public static Symbol SaslLookup(IProtonBuffer value, bool copyOnCreate)
+      {
+         if (value == null)
+         {
+            return null;
+         }
+         else if (!value.IsReadable)
+         {
+            return EMPTY_SYMBOL;
+         }
+         else
+         {
+            return CachedSaslSymbols.Lookup(value, copyOnCreate);
          }
       }
 
@@ -180,14 +218,7 @@ namespace Apache.Qpid.Proton.Types
       {
          if (symbolString == null && underlying.IsReadable)
          {
-            symbolString = underlying.ToString(Encoding.ASCII);
-            if (symbolString.Length <= MAX_CACHED_SYMBOL_SIZE)
-            {
-               if (!stringsToSymbols.TryAdd(symbolString, this))
-               {
-                  symbolString = stringsToSymbols[symbolString].symbolString;
-               }
-            }
+            symbolCache.ToString(this);
          }
 
          return symbolString ?? "";
@@ -227,5 +258,117 @@ namespace Apache.Qpid.Proton.Types
       {
          return CompareTo(other as Symbol);
       }
+
+      private static Symbol CreateSymbol(SymbolCache cache, IProtonBuffer buffer, bool copyOnCreate)
+      {
+            if (copyOnCreate)
+            {
+               long symbolSize = buffer.ReadableBytes;
+               IProtonBuffer copy = ProtonByteBufferAllocator.Instance.Allocate(symbolSize, symbolSize);
+               buffer.CopyInto(buffer.ReadOffset, copy, 0, symbolSize);
+               copy.WriteOffset = symbolSize;
+               buffer = copy;
+            }
+
+            return new Symbol(buffer, cache);
+      }
+
+      private sealed class SymbolCache
+      {
+         private readonly ConcurrentDictionary<IProtonBuffer, Symbol> buffersToSymbols = new();
+         private readonly ConcurrentDictionary<string, Symbol> stringsToSymbols = new();
+
+         private readonly uint maxCachedSymbols;
+         private readonly uint maxCachedSymbolSize;
+
+         public SymbolCache(uint maxCachedSymbols, uint maxCachedSymbolSize)
+         {
+            this.maxCachedSymbols = maxCachedSymbols;
+            this.maxCachedSymbolSize = maxCachedSymbolSize;
+         }
+
+         public Symbol Lookup(string value)
+         {
+            if (!stringsToSymbols.TryGetValue(value, out Symbol symbol))
+            {
+               symbol = Lookup(ProtonByteBufferAllocator.Instance.Wrap(Encoding.ASCII.GetBytes(value)));
+
+               if (symbol.symbolString == null)
+               {
+                  // In case of a new Symbol object being created we want to ensure the String value
+                  // is loaded now since we know what it is and future calls won't need to access the
+                  // cache for no reason.
+                  symbol.symbolString = value;
+               }
+
+               if (symbol.Length <= maxCachedSymbolSize && stringsToSymbols.Count < maxCachedSymbols)
+               {
+                  // Try and keep the Symbol instance consistent with the one that is stored
+                  // in the buffer to symbol dictionary.
+                  stringsToSymbols[value] = symbol;
+               }
+            }
+
+            return symbol;
+         }
+
+         public Symbol Lookup(IProtonBuffer value)
+         {
+            return Lookup(value, false);
+         }
+
+         public Symbol Lookup(IProtonBuffer value, bool copyOnCreate)
+         {
+            bool canCache = value.ReadableBytes <= maxCachedSymbolSize;
+
+            if (canCache)
+            {
+               if (!buffersToSymbols.TryGetValue(value, out Symbol symbol))
+               {
+                  // Lock to prevent the cache from possibly growing beyond the max cache size
+                  // due to races on early fills from multiple connection threads.
+                  lock (EMPTY_SYMBOL)
+                  {
+                     if (!buffersToSymbols.TryGetValue(value, out symbol))
+                     {
+                        symbol = CreateSymbol(this, value, copyOnCreate);
+
+                        if (buffersToSymbols.Count < maxCachedSymbols)
+                        {
+                           if (!buffersToSymbols.TryAdd(value, symbol))
+                           {
+                              symbol = buffersToSymbols[value];
+                           }
+                        }
+                     }
+                  }
+               }
+
+               return symbol;
+            }
+            else
+            {
+               return CreateSymbol(this, value, copyOnCreate);
+            }
+         }
+
+         public string ToString(Symbol symbol)
+         {
+            if (symbol.symbolString == null && symbol.underlying.IsReadable)
+            {
+               symbol.symbolString = symbol.underlying.ToString(Encoding.ASCII);
+
+               if (symbol.symbolString.Length <= maxCachedSymbolSize && stringsToSymbols.Count < maxCachedSymbols)
+               {
+                  if (!stringsToSymbols.TryAdd(symbol.symbolString, symbol))
+                  {
+                     symbol.symbolString = stringsToSymbols[symbol.symbolString].symbolString;
+                  }
+               }
+            }
+
+            return symbol.symbolString ?? "";
+         }
+      }
    }
 }
\ No newline at end of file
diff --git a/src/Proton/Types/UnknownDescribedType.cs b/src/Proton/Types/UnknownDescribedType.cs
index 5e58186..967847b 100644
--- a/src/Proton/Types/UnknownDescribedType.cs
+++ b/src/Proton/Types/UnknownDescribedType.cs
@@ -24,7 +24,7 @@ namespace Apache.Qpid.Proton.Types
    /// </summary>
    public sealed class UnknownDescribedType : IDescribedType
    {
-      internal UnknownDescribedType(object descriptor, object described)
+      public UnknownDescribedType(object descriptor, object described)
       {
          Descriptor = descriptor;
          Described = described;
diff --git a/src/Proton/Utilities/StringUtils.cs b/src/Proton/Utilities/StringUtils.cs
index 842848a..5a6631f 100644
--- a/src/Proton/Utilities/StringUtils.cs
+++ b/src/Proton/Utilities/StringUtils.cs
@@ -53,6 +53,28 @@ namespace Apache.Qpid.Proton.Utilities
          return result;
       }
 
+      /// <summary>
+      /// Given an array of string objects, convert them to a matching array of
+      /// their Symbol equivalent values using the SASL Symbol cache as the source.
+      /// </summary>
+      /// <param name="stringArray"></param>
+      /// <returns>A new Symbol array with values that correspond to the input stings</returns>
+      public static Symbol[] ToSaslSymbolArray(string[] stringArray)
+      {
+         Symbol[] result = null;
+
+         if (stringArray != null)
+         {
+            result = new Symbol[stringArray.Length];
+            for (int i = 0; i < stringArray.Length; ++i)
+            {
+               result[i] = Symbol.SaslLookup(stringArray[i]);
+            }
+         }
+
+         return result;
+      }
+
       /// <summary>
       /// Given an array of Symbol objects, convert them to a matching array of
       /// their string equivalent values.
@@ -100,6 +122,32 @@ namespace Apache.Qpid.Proton.Utilities
          return result;
       }
 
+      /// <summary>
+      /// Converts an enumeration of string values into a set of Symbol values.
+      /// Uses the SASL Symbol cache as the source of cached value.
+      /// </summary>
+      /// <param name="strings">an enumeration of string values</param>
+      /// <returns>a set of Symbol value that match the input strings</returns>
+      public static ISet<Symbol> ToSaslSymbolSet(in IEnumerable<string> strings)
+      {
+         ISet<Symbol> result;
+
+         if (strings != null)
+         {
+            result = new HashSet<Symbol>();
+            foreach (string value in strings)
+            {
+               result.Add(Symbol.SaslLookup(value));
+            }
+         }
+         else
+         {
+            result = null;
+         }
+
+         return result;
+      }
+
       /// <summary>
       /// Converts an enumeration of Symbol values into a set of string values.
       /// </summary>
diff --git a/src/Proton/Utilities/UnsettledDictionary.cs b/src/Proton/Utilities/UnsettledDictionary.cs
... 14406 lines suppressed ...


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