Repository: smithy-lang/smithy
Pull request #2206 (2024-03-21 06:50:19)

Split sparse collection tests to new operations
#### Background
* What do these changes do? 
Create new operations in protocol tests for sparse tests.
* Why are they important?
Makes it easier to support sparse vs dense collections when working with protocols.

#### Testing
* How did you test these changes?
Ran generated tests in AWS SDK for JSv3

---
By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license.



Patch:
diff --git a/.github/workflows/make-docs.yml b/.github/workflows/make-docs.yml
index d79e73b0e..4edae6653 100644
--- a/.github/workflows/make-docs.yml
+++ b/.github/workflows/make-docs.yml
@@ -78,6 +78,7 @@ jobs:
         path: /tmp/smithy-docs
     - name: Copy artifacts
       run: |
+        git pull
         cp -R /tmp/smithy-docs/* .
         git config --global user.name "smithy-automation"
         git config --global user.email "github-smithy-automation@amazon.com"
diff --git a/.gitignore b/.gitignore
index 4b608569e..ed1c10be1 100644
--- a/.gitignore
+++ b/.gitignore
@@ -35,5 +35,8 @@ build/
 */*/out/
 /wrapper
 
+# jdt-ls
+.metadata
+
 # Smithy
 .smithy.lsp.log
diff --git a/build.gradle b/build.gradle
index d82e89e4f..567e5816d 100644
--- a/build.gradle
+++ b/build.gradle
@@ -25,7 +25,7 @@ plugins {
     id "com.github.spotbugs" version "6.0.8"
     id "io.codearte.nexus-staging" version "0.30.0"
     id "me.champeau.jmh" version "0.7.2"
-    id "com.github.johnrengelman.shadow" version "8.1.1"
+    id "com.github.johnrengelman.shadow" version "7.1.2"
     id "org.jreleaser" version "1.11.0"
 }
 
diff --git a/docs/source-2.0/additional-specs/index.rst b/docs/source-2.0/additional-specs/index.rst
index cf3743324..e9c8638b4 100644
--- a/docs/source-2.0/additional-specs/index.rst
+++ b/docs/source-2.0/additional-specs/index.rst
@@ -15,6 +15,7 @@ start with ``smithy.*`` where "*" is anything other than ``api``.
     waiters
     mqtt
     rules-engine/index
+    protocols/index
 
 .. seealso::
 
diff --git a/docs/source-2.0/additional-specs/protocols/index.rst b/docs/source-2.0/additional-specs/protocols/index.rst
new file mode 100644
index 000000000..9d41bef00
--- /dev/null
+++ b/docs/source-2.0/additional-specs/protocols/index.rst
@@ -0,0 +1,10 @@
+.. _protocols:
+
+=========
+Protocols
+=========
+
+.. toctree::
+    :maxdepth: 1
+
+    smithy-rpc-v2
diff --git a/docs/source-2.0/additional-specs/protocols/smithy-rpc-v2.rst b/docs/source-2.0/additional-specs/protocols/smithy-rpc-v2.rst
new file mode 100644
index 000000000..2b2e4664b
--- /dev/null
+++ b/docs/source-2.0/additional-specs/protocols/smithy-rpc-v2.rst
@@ -0,0 +1,484 @@
+.. _smithy-rpc-v2-cbor:
+
+===========================
+Smithy RPC v2 CBOR protocol
+===========================
+
+This specification defines the ``smithy.protocols#rpcv2Cbor`` protocol. This
+protocol is used to expose services that serialize RPC payloads as CBOR.
+
+.. smithy-trait:: smithy.protocols#rpcv2Cbor
+.. _smithy.protocols#rpcv2Cbor-trait:
+
+
+------------------------------------
+``smithy.protocols#rpcv2Cbor`` trait
+------------------------------------
+
+Summary
+    Adds support for an RPC-based protocol over HTTP that sends requests and
+    responses with CBOR payloads.
+Trait selector
+    ``service``
+Value type
+    Structure
+See
+    `Protocol tests <https://github.com/smithy-lang/smithy/tree/__smithy_version__/smithy-protocol-tests/model/rpcv2Cbor>`_
+
+``smithy.protocols#rpcv2Cbor`` is a structure that supports the following
+members:
+
+.. list-table::
+    :header-rows: 1
+    :widths: 10 20 70
+
+    * - Property
+      - Type
+      - Description
+    * - http
+      - ``[string]``
+      - The priority ordered list of supported HTTP protocol versions.
+    * - eventStreamHttp
+      - ``[string]``
+      - The priority ordered list of supported HTTP protocol versions
+        that are required when using :ref:`event streams <event-streams>`
+        with the service. If not set, this value defaults to the value
+        of the ``http`` member. Any entry in ``eventStreamHttp`` MUST
+        also appear in ``http``.
+
+Each entry in ``http`` and ``eventStreamHttp`` SHOULD be a valid
+`Application-Layer Protocol Negotiation (ALPN) Protocol ID`_ (for example,
+``http/1.1``, ``h2``, etc). Clients SHOULD pick the first protocol in the
+list they understand when connecting to a service. A client SHOULD assume
+that a service supports ``http/1.1`` when no ``http`` or ``eventStreamHttp``
+values are provided.
+
+The following example defines a service that uses
+``smithy.protocols#rpcv2Cbor``.
+
+.. code-block:: smithy
+
+    $version: "2"
+
+    namespace smithy.example
+
+    use smithy.protocols#rpcv2Cbor
+
+    @rpcv2Cbor
+    service MyService {
+        version: "2020-07-02"
+    }
+
+The following example defines a service that requires the use of
+``h2`` when using event streams.
+
+.. code-block:: smithy
+
+    $version: "2"
+
+    namespace smithy.example
+
+    use smithy.protocols#rpcv2Cbor
+
+    @rpcv2Cbor(
+        http: ["h2", "http/1.1"],
+        eventStreamHttp: ["h2"]
+    )
+    service MyService {
+        version: "2020-02-05"
+    }
+
+The following example defines a service that requires the use of
+``h2`` or ``http/1.1`` when using event streams, where ``h2`` is
+preferred over ``http/1.1``.
+
+.. code-block:: smithy
+
+    $version: "2"
+
+    namespace smithy.example
+
+    use smithy.protocols#rpcv2Cbor
+
+    @rpcv2Cbor(
+        http: ["h2", "http/1.1"],
+        eventStreamHttp: ["h2", "http/1.1"]
+    )
+    service MyService {
+        version: "2020-02-05"
+    }
+
+The following example defines a service that requires the use of
+``h2`` for all requests, including event streams.
+
+.. code-block:: smithy
+
+    $version: "2"
+
+    namespace smithy.example
+
+    use smithy.protocols#rpcv2Cbor
+
+    @rpcv2Cbor(http: ["h2"])
+    service MyService {
+        version: "2020-02-05"
+    }
+
+
+----------------
+Supported traits
+----------------
+
+The ``smithy.protocols#rpcv2Cbor`` protocol supports the following traits
+that affect serialization:
+
+.. list-table::
+    :header-rows: 1
+    :widths: 20 80
+
+    * - Trait
+      - Description
+    * - :ref:`cors <cors-trait>`
+      - Indicates that the service supports CORS.
+    * - :ref:`endpoint <endpoint-trait>`
+      - Configures a custom operation endpoint.
+    * - :ref:`hostLabel <hostLabel-trait>`
+      - Binds a top-level operation input structure member to a label in
+        the hostPrefix of an endpoint trait.
+    * - :ref:`httpError <httpError-trait>`
+      - A ``client`` error has a default status code of ``400``, and a
+        ``server`` error has a default status code of ``500``. The
+        ``httpError`` trait is used to define a custom status code.
+    * - :ref:`requestCompression <requestCompression-trait>`
+      - Indicates that an operation supports compressing requests from clients
+        to services.
+
+
+------------------
+Protocol behaviors
+------------------
+
+Implementations of the ``smithy.protocols#rpcv2Cbor`` protocol comply with the
+following rules:
+
+~~~~~~~~
+Requests
+~~~~~~~~
+ 
+Every request for the ``rpcv2Cbor`` protocol MUST contain a ``Smithy-Protocol``
+header with the value of ``rpc-v2-cbor``.
+
+Every request for the ``rpcv2Cbor`` protocol MUST be sent using the HTTP
+``POST`` method. :ref:`HTTP binding traits <http-traits>` MUST be ignored when
+building ``rpcv2Cbor`` requests if they are present.
+
+Every request for the ``rpcv2Cbor`` protocol MUST be sent to a URL with the
+following form: ``{prefix?}/service/{serviceName}/operation/{operationName}``
+
+* The Smithy RPCv2 CBOR protocol will only use the last four segments of the
+    URL when routing requests. For example, a service could use a ``v1`` prefix
+    in the URL path, which would not affect the operation a request is routed
+    to: ``v1/service/FooService/operation/BarOperation``
+* The ``serviceName`` segment MUST be replaced by the :token:`shape name <smithy:Identifier>`
+    of the service's :ref:`shape-id` in the Smithy model. The ``serviceName``
+    produced by client implementations MUST NOT contain the namespace of the
+    ``service`` shape. Service implementations SHOULD accept an absolute shape
+    ID as the content of this segment with the ``#`` character replaced with a
+    ``.`` character, routing it the same as if only the name was specified. For
+    example, if the ``service``'s absolute shape ID is
+    ``com.example#TheService``, a service should accept both ``TheService`` and
+    ``com.example.TheService`` as values for the ``serviceName`` segment.
+* The ``operationName`` segment MUST be replaced by the name of the ``operation``
+    shape in the Smithy. The ``operationName`` produced by client
+    implementations MUST NOT contain the namespace of the ``operation`` shape.
+    Service implementations MUST NOT accept an absolute shape ID as the content
+    of this segment.
+
+Requests for the ``rpcv2Cbor`` protocol MUST use the following behavior for
+setting a ``Content-Type`` header:
+
+* Buffered RPC requests: the value of the ``Content-Type`` header MUST be
+    ``application/cbor``.
+* Event streaming requests: the value of the ``Content-Type`` header MUST be
+    ``application/vnd.amazon.eventstream``.
+* Requests for operations with no defined input type (as in, they target the
+    ``Unit`` shape) MUST NOT contain bodies in their HTTP requests. The
+    ``Content-Type`` for the serialization format MUST NOT be set.
+
+Requests for the ``rpcv2Cbor`` protocol MUST NOT contain an ``X-Amz-Target`` or
+``X-Amzn-Target`` header. An ``rpcv2Cbor`` request is malformed if it contains
+either of these headers. Server-side implementations MUST reject such requests
+for security reasons.
+
+Buffered RPC requests for the ``rpcv2Cbor`` protocol SHOULD include a
+``Content-Length`` header. Event streaming requests MUST NOT specify a content
+length (instead using ``Transfer-Encoding: chunked`` on HTTP/1.1).
+
+Event streaming requests for the ``rpcv2Cbor`` protocol MUST include an
+``Accept`` header set to the value ``application/vnd.amazon.eventstream``.
+Other forms of content streaming MAY be added in the future, utilizing
+different values for ``Accept``.
+
+In summary, the ``rpcv2Cbor`` protocol defines behavior for the following
+headers for requests:
+
+.. list-table::
+    :header-rows: 1
+    :widths: 18 18 64
+
+    * - Header
+      - Status
+      - Description
+    * - ``Smithy-Protocol``
+      - Required
+      - The value of ``rpc-v2-cbor``.
+    * - ``Content-Type``
+      - Required with request bodies
+      - The value of ``application/cbor``. For event streaming requests, this
+        is ``application/vnd.amazon.eventstream``.
+    * - ``Content-Length``
+      - Conditional
+      - The standard ``Content-Length`` header defined by :rfc:`7230#section-3.3.2`.
+        For event streaming requests, this MUST NOT be set.
+    * - ``Accept``
+      - Conditional
+      - For event streaming requests, to the value ``application/vnd.amazon.eventstream``.
+
+
+~~~~~~~~~
+Responses
+~~~~~~~~~
+
+The status code for successful ``rpcv2Cbor`` responses MUST be ``200``.
+ 
+The status code for error ``rpcv2Cbor`` MUST be determined by the following
+steps:
+
+1. If the :ref:`httpError <httpError-trait>` trait is set on the error shape,
+   its value is used.
+2. If the :ref:`error <error-trait>` trait is set to ``server``, the value
+   MUST be ``500``.
+3. Otherwise, the value MUST be ``400``.
+
+Every response for the ``rpcv2Cbor`` protocol MUST contain a ``Smithy-Protocol``
+header with the value of ``rpc-v2-cbor``. The response MUST match the value of
+the ``Smithy-Protocol`` header from the request.
+
+Responses for the ``rpcv2Cbor`` protocol MUST use the following behavior for
+setting a ``Content-Type`` header:
+
+* Buffered RPC responses: the value of the ``Content-Type`` header MUST be
+  ``application/cbor``.
+* Event streaming responses: the value of the ``Content-Type`` header MUST be
+  ``application/vnd.amazon.eventstream``.
+* Responses for operations with no defined output type (as in, they target the
+  ``Unit`` shape) MUST NOT contain bodies in their HTTP responses. The
+  ``Content-Type`` for the serialization format MUST NOT be set.
+
+Buffered RPC responses for the ``rpcv2Cbor`` protocol SHOULD include a
+``Content-Length`` header. Event streaming responses SHOULD NOT specify a
+content length (instead using ``Transfer-Encoding: chunked`` on HTTP/1.1).
+
+Responses for the ``rpcv2Cbor`` protocol SHOULD NOT contain the
+``X-Amzn-ErrorType`` header. Type information is always serialized in the
+payload. Clients MUST ignore this header. See `Operation error serialization`_
+for information on the serialization of error responses.
+
+In summary, the ``rpcv2Cbor`` protocol defines behavior for the following
+headers for responses:
+
+.. list-table::
+    :header-rows: 1
+    :widths: 18 18 64
+
+    * - Header
+      - Status
+      - Description
+    * - ``Smithy-Protocol``
+      - Required
+      - The value of ``rpc-v2-cbor``.
+    * - ``Content-Type``
+      - Required with request bodies
+      - The value of ``application/cbor``. For event streaming requests, this
+        is ``application/vnd.amazon.eventstream``.
+    * - ``Content-Length``
+      - Conditional
+      - The standard ``Content-Length`` header defined by :rfc:`7230#section-3.3.2`.
+        For event streaming requests, this SHOULD NOT be set.
+
+
+-------------------
+Shape serialization
+-------------------
+
+The ``smithy.protocols#rpcv2Cbor`` protocol serializes all shapes into a CBOR
+document body with no HTTP bindings supported. The following table shows how
+to convert each shape type:
+
+.. list-table::
+    :header-rows: 1
+    :widths: 25 75
+
+    * - Smithy type
+      - CBOR representation
+    * - ``blob``
+      - :rfc:`Major type 2 (byte string) <8949#section-3.1-2.5>`
+    * - ``boolean``
+      - :rfc:`Major type 7 (simple) <8949#section-3.1-2.15>`
+    * - ``byte``
+      - :rfc:`Major type 0 (unsigned integer) <8949#section-3.1-2.1>` or
+        :rfc:`major type 1 (negative integer) <8949#section-3.1-2.3>`
+    * - ``short``
+      - :rfc:`Major type 0 (unsigned integer) <8949#section-3.1-2.1>` or
+        :rfc:`major type 1 (negative integer) <8949#section-3.1-2.3>`
+    * - ``integer``
+      - :rfc:`Major type 0 (unsigned integer) <8949#section-3.1-2.1>` or
+        :rfc:`major type 1 (negative integer) <8949#section-3.1-2.3>`
+    * - ``long``
+      - :rfc:`Major type 0 (unsigned integer) <8949#section-3.1-2.1>` or
+        :rfc:`major type 1 (negative integer) <8949#section-3.1-2.3>`
+    * - ``float``
+      - :rfc:`Major type 7 (float) <8949#section-3.1-2.15>`
+    * - ``double``
+      - :rfc:`Major type 7 (float) <8949#section-3.1-2.15>`
+    * - ``bigDecimal``
+      - :rfc:`Major type 6 <8949#section-3.1-2.13>`, value
+        :rfc:`tags 2 (unsigned bignum) or 3 (negative bignum) <8949#section-3.4>`
+    * - ``bigInteger``
+      - :rfc:`Major type 6 <8949#section-3.1-2.13>`, value
+        :rfc:`tag 4 (decimal fraction) <8949#section-3.4>`
+    * - ``string``
+      - :rfc:`Major type 3 (text string) <8949#section-3.1-2.7>`
+    * - ``timestamp``
+      - See `Timestamp type serialization`_.
+    * - ``document``
+      - Document types are not currently supported in this protocol.
+    * - ``list``
+      - :rfc:`Major type 4 (array of data items) <8949#section-3.1-2.9>`
+    * - ``map``
+      - :rfc:`Major type 5 (map of pairs of data items) <8949#section-3.1-2.11>`,
+        each key/value pair is a pair in the type
+    * - ``structure``
+      - :rfc:`Major type 5 (map of pairs of data items) <8949#section-3.1-2.11>`,
+        each member is the key to a pair in the type
+    * - ``union``
+      - :rfc:`Major type 5 (map of pairs of data items) <8949#section-3.1-2.11>`,
+        each member is the key to a pair in the type.
+
+        A union is serialized identically as a ``structure``
+        shape, but only a single member can be set to a non-null value.
+        Deserializers MUST ignore an unrecognized ``__type`` member if present.
+
+Values that are ``null`` MUST be omitted from wire contents where not subject
+to `default value serialization`_ rules.
+
+If an implementation does not support arbitrary precision (``bigInteger`` and
+``bigDecimal`` Smithy types), it MUST fail when attempting to deserialize a
+value of that type.
+
+.. note::
+    The ``undefined`` CBOR value, CBOR :rfc:`major type 7 <8949#section-3.1-2.15>`
+    :rfc:`value 23 <8949#section-3.3>`, is not supported. Clients SHOULD NOT
+    serialize to this CBOR value and SHOULD deserialize this CBOR value to
+    ``null``. Servers MUST NOT serialize to this CBOR value and MUST
+    deserialize this CBOR value to ``null``.
+
+~~~~~~~~~~~~~~~~~~~~~~~~~~
+Numeric type serialization
+~~~~~~~~~~~~~~~~~~~~~~~~~~
+ 
+Numeric types (``byte``, ``short``, ``integer``, ``long``, ``float``, and ``double``)
+SHOULD be serialized into the smallest possible CBOR type representation that
+can contain its value. For example, a field modeled as a ``long`` with a value of
+1 should be sent in a single byte.
+
+Floating-point numeric types (``float`` and ``double``) MAY be serialized into
+non-floating-point numeric types (``byte``, ``short``, ``integer``, and ``long``) if
+and only if the conversion would not cause a loss of precision. For example, a
+field modeled as a ``float`` with a value of 256 may be sent as an integer.
+
+As support for half-precision floating-point values is inconsistent across
+implementations, floating-point numeric types SHOULD NOT serialize into a
+half-precision (16 bit) numeric CBOR values. Implementations MUST be capable of
+deserializing half-precision numeric CBOR values (including -Infinity,
+Infinity, and NAN) into their Smithy type representation.
+
+Numeric types MUST be deserialized into their Smithy type representation. For
+example, a field modeled as a ``long`` with a single byte value of 1 must be
+deserialized into a long.
+
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+Timestamp type serialization
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Timestamps are serialized as :rfc:`major type 6 <8949#section-3.1-2.13>`, value
+:rfc:`tag 1 (epoch-based date/time) <8949#section-3.4>` to indicate the tagged
+value is an epoch timestamp. Values are either :rfc:`major type 0 (unsigned integer) <8949#section-3.1-2.1>`
+or :rfc:`major type 1 (negative integer) <8949#section-3.1-2.3>` for integer
+values or :rfc:`major type 7 <8949#section-3.1-2.15>`,
+:rfc:`value 25 (half-precision float), 26 (single-precision float), or 27 (double-precision float) <8949#section-3.3>`
+for floating-point values.
+
+As support for half-precision floating-point values is inconsistent across
+implementations, timestamp types SHOULD NOT serialize into a half-precision
+(16 bit) numeric CBOR value for the tagged value.
+
+This protocol uses ``epoch-seconds``, also known as Unix timestamps, with
+millisecond (1/1000th of a second) resolution. The :ref:`timestampFormat <timestampFormat-trait>`
+MUST NOT be respected to customize timestamp serialization.
+
+
+~~~~~~~~~~~~~~~~~~~~~~~~~~~
+Default value serialization
+~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+To avoid information disclosure, service serializers SHOULD omit the default
+value of structure members that are marked with the :ref:`internal trait <internal-trait>`.
+
+Client deserializers SHOULD attempt to error correct structures that omit a
+:ref:`required <required-trait>` member by filling in a default zero value for
+the member. Error correction allows clients to continue to function while the
+server is in error.
+
+----------------------------------
+Operation response deserialization
+----------------------------------
+ 
+Clients MUST use the following rules to interpret responses and determine how
+to deserialize them:
+
+1. If the response does not have the same ``Smithy-Protocol`` header as the
+    request, it MUST be considered malformed. Clients who receive a malformed
+    response MUST handle it (i.e. throw a reasonable exception) based solely on
+    the HTTP response code. No attempt should be made to interpret the response
+    body or headers.
+2. If the response code is ``200``, the request is successful and the response
+    payload SHALL be deserialized as the ``output`` shape defined on the
+    operation.
+3. Finally, if the response code is not ``200``, the response payload is an
+    exception and SHALL be deserialized according to `Operation error serialization`_
+
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+Operation error serialization
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Error responses in the ``rpcv2Cbor`` protocol MUST be serialized identically to
+standard responses with one additional component to distinguish which error is
+contained: a body field named ``__type``. This value of this component MUST
+contain the error’s absolute :ref:`shape-id`.
+
+By default, all error shapes have a ``message`` field containing an
+error-specific message meant for human consumers of API errors. Services MUST
+support generating this field and serializing it when responding. Clients MUST
+support generating this field and deserializing it from responses.
+
+The ``Code`` response body field and ``code`` response body field MUST NOT be
+used to distinguish which error is contained in a response.
+
+Clients who receive a malformed error response MUST handle it based solely on
+the HTTP response code (i.e. throw a reasonable exception). No attempt should
+be made to interpret the response body or headers.
+
+.. include:: ../../aws/protocols/error-rename-simple.rst.template
+
+.. _`Application-Layer Protocol Negotiation (ALPN) Protocol ID`: https://www.iana.org/assignments/tls-extensiontype-values/tls-extensiontype-values.xhtml#alpn-protocol-ids
diff --git a/docs/source-2.0/aws/protocols/aws-query-protocol.rst b/docs/source-2.0/aws/protocols/aws-query-protocol.rst
index 2ccc4574c..b91703442 100644
--- a/docs/source-2.0/aws/protocols/aws-query-protocol.rst
+++ b/docs/source-2.0/aws/protocols/aws-query-protocol.rst
@@ -549,19 +549,20 @@ The following example defines an error that uses a custom "Code" of
 
 Summary
     When using the :ref:`awsQuery <aws.protocols#awsQuery-trait>` protocol,
-    custom ``Code`` and ``HTTP response code`` values can be defined for an error response via
-    the :ref:`awsQueryError <aws.protocols#awsQueryError-trait>` trait.
-
-    The ``awsQueryCompatible`` trait allows services to backward compatibly migrate from ``awsQuery`` to
-    :ref:`awsJson1_0 <aws.protocols#awsJson1_0-trait>` without removing values defined in the ``awsQueryError`` trait.
-
-    This trait adds the ``x-amzn-query-error`` header in the form of ``Code;Fault`` to error responses.
-    ``Code`` is the value defined in the :ref:`awsQueryError <aws.protocols#awsQueryError-trait>`,
-    and ``Fault`` is one of ``Sender`` or ``Receiver``.
-
+    custom ``Code`` and ``HTTP response code`` values can be defined for an
+    error response via the :ref:`awsQueryError <aws.protocols#awsQueryError-trait>`
+    trait.
+
+    The ``awsQueryCompatible`` trait allows services to backward compatibly
+    migrate from ``awsQuery`` to :ref:`awsJson1_0 <aws.protocols#awsJson1_0-trait>`
+    without removing values defined in the ``awsQueryError`` trait.
+
+    This trait adds the ``x-amzn-query-error`` header in the form of
+    ``Code;Fault`` to error responses. ``Code`` is the value defined in the
+    :ref:`awsQueryError <aws.protocols#awsQueryError-trait>`, and ``Fault`` is
+    one of ``Sender`` or ``Receiver``.
 Trait selector
     ``service [trait|awsJson1_0]``
-
 Value type
     Annotation trait
 
@@ -587,6 +588,11 @@ Value type
         message: String
     }
 
+.. important::
+
+    AWS client implementations of the ``smithy.protocols#rpcv2Cbor`` protocol
+    MUST support the ``aws.protocols#awsQueryCompatible`` trait.
+
 
 .. _awsQuery-compliance-tests:
 
diff --git a/settings.gradle b/settings.gradle
index bc0abdddc..9605f4c7e 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -34,3 +34,5 @@ include ":smithy-smoke-test-traits"
 include ":smithy-syntax"
 include ":smithy-aws-endpoints"
 include ":smithy-aws-smoke-test-model"
+include ":smithy-protocol-traits"
+include ":smithy-protocol-tests"
diff --git a/smithy-model/src/main/resources/software/amazon/smithy/model/loader/prelude.smithy b/smithy-model/src/main/resources/software/amazon/smithy/model/loader/prelude.smithy
index 0c5261227..f4cc38035 100644
--- a/smithy-model/src/main/resources/software/amazon/smithy/model/loader/prelude.smithy
+++ b/smithy-model/src/main/resources/software/amazon/smithy/model/loader/prelude.smithy
@@ -309,6 +309,7 @@ structure httpApiKeyAuth {
 ///
 /// `traitValidators` is a map of validation event IDs to validators to apply to a shape.
 /// Selectors are used to identify shapes that are incompatible with a constrained trait.
+/// The shape with the targeted trait applied MUST be a valid entry point for the selector.
 ///
 /// The following example defines a protocol that does not support document types. Each matching member found in the
 /// closure of an attached shape emits a validation event:
@@ -317,7 +318,7 @@ structure httpApiKeyAuth {
 /// @trait(selector: "service")
 /// @traitValidators(
 ///     "myCustomProtocol.NoDocuments": {
-///         selector: "member :test(> document)"
+///         selector: "~> member :test(> document)"
 ///         message: "This protocol does not support document types"
 ///     }
 /// )
diff --git a/smithy-protocol-traits/README.md b/smithy-protocol-traits/README.md
new file mode 100644
index 000000000..51d00fe62
--- /dev/null
+++ b/smithy-protocol-traits/README.md
@@ -0,0 +1,7 @@
+# Smithy protocols traits
+
+This module provides the implementation of protocols traits for Smithy.
+
+Protocols:
+
+* RPC v2 - An RPC protocol with support for multiple wire formats.
diff --git a/smithy-protocol-traits/build.gradle b/smithy-protocol-traits/build.gradle
new file mode 100644
index 000000000..c269a7e00
--- /dev/null
+++ b/smithy-protocol-traits/build.gradle
@@ -0,0 +1,16 @@
+/*
+ * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+description = "This module provides the implementation of protocol traits for Smithy."
+
+ext {
+    displayName = "Smithy :: Protocol Traits"
+    moduleName = "software.amazon.smithy.protocol.traits"
+}
+
+dependencies {
+    api project(":smithy-utils")
+    api project(":smithy-model")
+}
diff --git a/smithy-protocol-traits/src/main/java/software/amazon/smithy/protocol/traits/Rpcv2CborTrait.java b/smithy-protocol-traits/src/main/java/software/amazon/smithy/protocol/traits/Rpcv2CborTrait.java
new file mode 100644
index 000000000..af9a5ee89
--- /dev/null
+++ b/smithy-protocol-traits/src/main/java/software/amazon/smithy/protocol/traits/Rpcv2CborTrait.java
@@ -0,0 +1,148 @@
+/*
+ * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package software.amazon.smithy.protocol.traits;
+
+import java.util.ArrayList;
+import java.util.List;
+import software.amazon.smithy.model.node.Node;
+import software.amazon.smithy.model.node.ObjectNode;
+import software.amazon.smithy.model.shapes.ShapeId;
+import software.amazon.smithy.model.traits.AbstractTrait;
+import software.amazon.smithy.model.traits.AbstractTraitBuilder;
+import software.amazon.smithy.model.traits.Trait;
+import software.amazon.smithy.utils.ListUtils;
+import software.amazon.smithy.utils.ToSmithyBuilder;
+
+public final class Rpcv2CborTrait extends AbstractTrait implements ToSmithyBuilder<Rpcv2CborTrait> {
+
+    public static final ShapeId ID = ShapeId.from("smithy.protocols#rpcv2Cbor");
+
+    private static final String HTTP = "http";
+    private static final String EVENT_STREAM_HTTP = "eventStreamHttp";
+
+    private final List<String> http;
+    private final List<String> eventStreamHttp;
+
+    private Rpcv2CborTrait(Builder builder) {
+        super(ID, builder.getSourceLocation());
+        http = ListUtils.copyOf(builder.http);
+        eventStreamHttp = ListUtils.copyOf(builder.eventStreamHttp);
+    }
+
+    /**
+     * Creates a new {@code Builder}.
+     */
+    public static Builder builder() {
+        return new Builder();
+    }
+
+    /**
+     * Updates the builder from a Node.
+     *
+     * @param node Node object that must be a valid {@code ObjectNode}.
+     * @return Returns the updated builder.
+     */
+    public static Rpcv2CborTrait fromNode(Node node) {
+        Builder builder = builder().sourceLocation(node);
+        ObjectNode objectNode = node.expectObjectNode();
+        objectNode.getArrayMember(HTTP).map(values -> Node.loadArrayOfString(HTTP, values))
+                .ifPresent(builder::http);
+        objectNode.getArrayMember(EVENT_STREAM_HTTP).map(values -> Node.loadArrayOfString(EVENT_STREAM_HTTP, values))
+                .ifPresent(builder::eventStreamHttp);
+        return builder.build();
+    }
+
+    /**
+     * Gets the priority ordered list of supported HTTP protocol versions.
+     *
+     * @return Returns the supported HTTP protocol versions.
+     */
+    public List<String> getHttp() {
+        return http;
+    }
+
+    /**
+     * Gets the priority ordered list of supported HTTP protocol versions that are required when
+     * using event streams.
+     *
+     * @return Returns the supported event stream HTTP protocol versions.
+     */
+    public List<String> getEventStreamHttp() {
+        return eventStreamHttp;
+    }
+
+    @Override
+    protected Node createNode() {
+        ObjectNode.Builder builder = Node.objectNodeBuilder().sourceLocation(getSourceLocation());
+        if (!getHttp().isEmpty()) {
+            builder.withMember(HTTP, Node.fromStrings(getHttp()));
+        }
+        if (!getEventStreamHttp().isEmpty()) {
+            builder.withMember(EVENT_STREAM_HTTP, Node.fromStrings(getEventStreamHttp()));
+        }
+        return builder.build();
+    }
+
+    @Override
+    public Builder toBuilder() {
+        return builder().http(http).eventStreamHttp(eventStreamHttp);
+    }
+
+    /**
+     * Builder for creating a {@code Rpcv2CborTrait}.
+     */
+    public static final class Builder extends AbstractTraitBuilder<Rpcv2CborTrait, Builder> {
+
+        private final List<String> http = new ArrayList<>();
+        private final List<String> eventStreamHttp = new ArrayList<>();
+
+        @Override
+        public Rpcv2CborTrait build() {
+            return new Rpcv2CborTrait(this);
+        }
+
+        /**
+         * Sets the list of supported HTTP protocols.
+         *
+         * @param http HTTP protocols to set and replace.
+         * @return Returns the builder.
+         */
+        public Builder http(List<String> http) {
+            this.http.clear();
+            this.http.addAll(http);
+            return this;
+        }
+
+        /**
+         * Sets the list of supported event stream HTTP protocols.
+         *
+         * @param eventStreamHttp Event stream HTTP protocols to set and replace.
+         * @return Returns the builder.
+         */
+        public Builder eventStreamHttp(List<String> eventStreamHttp) {
+            this.eventStreamHttp.clear();
+            this.eventStreamHttp.addAll(eventStreamHttp);
+            return this;
+        }
+    }
+
+    /**
+     * Implements the {@code AbstractTrait.Provider}.
+     */
+    public static final class Provider extends AbstractTrait.Provider {
+
+        public Provider() {
+            super(ID);
+        }
+
+        @Override
+        public Trait createTrait(ShapeId target, Node value) {
+            Rpcv2CborTrait result = fromNode(value);
+            result.setNodeCache(value);
+            return result;
+        }
+    }
+}
diff --git a/smithy-protocol-traits/src/main/java/software/amazon/smithy/protocol/traits/Rpcv2CborTraitValidator.java b/smithy-protocol-traits/src/main/java/software/amazon/smithy/protocol/traits/Rpcv2CborTraitValidator.java
new file mode 100644
index 000000000..e8049bd95
--- /dev/null
+++ b/smithy-protocol-traits/src/main/java/software/amazon/smithy/protocol/traits/Rpcv2CborTraitValidator.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package software.amazon.smithy.protocol.traits;
+
+import java.util.ArrayList;
+import java.util.List;
+import software.amazon.smithy.model.Model;
+import software.amazon.smithy.model.shapes.ServiceShape;
+import software.amazon.smithy.model.validation.AbstractValidator;
+import software.amazon.smithy.model.validation.ValidationEvent;
+import software.amazon.smithy.utils.SmithyInternalApi;
+
+/**
+ * Validates models implementing the {@code Rpcv2CborTrait} against its constraints by:
+ *
+ * - Ensuring that every entry in {@code eventStreamHttp} also appears in the {@code http} property
+ *   of a protocol trait.
+ */
+@SmithyInternalApi
+public final class Rpcv2CborTraitValidator extends AbstractValidator {
+
+    @Override
+    public List<ValidationEvent> validate(Model model) {
+        List<ValidationEvent> events = new ArrayList<>();
+        for (ServiceShape serviceShape : model.getServiceShapesWithTrait(Rpcv2CborTrait.class)) {
+            Rpcv2CborTrait protocolTrait = serviceShape.expectTrait(Rpcv2CborTrait.class);
+
+            List<String> invalid = new ArrayList<>(protocolTrait.getEventStreamHttp());
+            invalid.removeAll(protocolTrait.getHttp());
+            if (!invalid.isEmpty()) {
+                events.add(error(serviceShape, protocolTrait,
+                        String.format("The following values of the `eventStreamHttp` property do "
+                                + "not also appear in the `http` property of the %s protocol "
+                                + "trait: %s", protocolTrait.toShapeId(), invalid)));
+            }
+        }
+        return events;
+    }
+}
diff --git a/smithy-protocol-traits/src/main/java/software/amazon/smithy/protocol/traits/package-info.java b/smithy-protocol-traits/src/main/java/software/amazon/smithy/protocol/traits/package-info.java
new file mode 100644
index 000000000..38e4b7cf8
--- /dev/null
+++ b/smithy-protocol-traits/src/main/java/software/amazon/smithy/protocol/traits/package-info.java
@@ -0,0 +1,12 @@
+/*
+ * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+/**
+ * Defines protocols for Smithy.
+ */
+@SmithyUnstableApi
+package software.amazon.smithy.protocol.traits;
+
+import software.amazon.smithy.utils.SmithyUnstableApi;
diff --git a/smithy-protocol-traits/src/main/resources/META-INF/services/software.amazon.smithy.model.traits.TraitService b/smithy-protocol-traits/src/main/resources/META-INF/services/software.amazon.smithy.model.traits.TraitService
new file mode 100644
index 000000000..807dd17ce
--- /dev/null
+++ b/smithy-protocol-traits/src/main/resources/META-INF/services/software.amazon.smithy.model.traits.TraitService
@@ -0,0 +1,1 @@
+software.amazon.smithy.protocol.traits.Rpcv2CborTrait$Provider
diff --git a/smithy-protocol-traits/src/main/resources/META-INF/services/software.amazon.smithy.model.validation.Validator b/smithy-protocol-traits/src/main/resources/META-INF/services/software.amazon.smithy.model.validation.Validator
new file mode 100644
index 000000000..d764d47b9
--- /dev/null
+++ b/smithy-protocol-traits/src/main/resources/META-INF/services/software.amazon.smithy.model.validation.Validator
@@ -0,0 +1,1 @@
+software.amazon.smithy.protocol.traits.Rpcv2CborTraitValidator
diff --git a/smithy-protocol-traits/src/main/resources/META-INF/smithy/manifest b/smithy-protocol-traits/src/main/resources/META-INF/smithy/manifest
new file mode 100644
index 000000000..d3ce2c739
--- /dev/null
+++ b/smithy-protocol-traits/src/main/resources/META-INF/smithy/manifest
@@ -0,0 +1,1 @@
+smithy.protocols.rpcv2.smithy
diff --git a/smithy-protocol-traits/src/main/resources/META-INF/smithy/smithy.protocols.rpcv2.smithy b/smithy-protocol-traits/src/main/resources/META-INF/smithy/smithy.protocols.rpcv2.smithy
new file mode 100644
index 000000000..68c672b7b
--- /dev/null
+++ b/smithy-protocol-traits/src/main/resources/META-INF/smithy/smithy.protocols.rpcv2.smithy
@@ -0,0 +1,37 @@
+$version: "2.0"
+
+namespace smithy.protocols
+
+use smithy.api#cors
+use smithy.api#endpoint
+use smithy.api#hostLabel
+use smithy.api#httpError
+
+/// An RPC-based protocol that serializes CBOR payloads.
+@trait(selector: "service")
+@protocolDefinition(traits: [
+    cors
+    endpoint
+    hostLabel
+    httpError
+])
+@traitValidators(
+    "rpcv2Cbor.NoDocuments": {
+         selector: "service ~> member :test(> document)"
+         message: "This protocol does not support document types"
+    }
+)
+structure rpcv2Cbor {
+    /// Priority ordered list of supported HTTP protocol versions.
+    http: StringList
+
+    /// Priority ordered list of supported HTTP protocol versions
+    /// that are required when using event streams.
+    eventStreamHttp: StringList
+}
+
+/// A list of String shapes.
+@private
+list StringList {
+    member: String
+}


Test patch:
diff --git a/docs/source-1.0/spec/http-protocol-compliance-tests.rst b/docs/source-1.0/spec/http-protocol-compliance-tests.rst
index 796d26dc4..a2654d0ab 100644
--- a/docs/source-1.0/spec/http-protocol-compliance-tests.rst
+++ b/docs/source-1.0/spec/http-protocol-compliance-tests.rst
@@ -207,8 +207,10 @@ that support the following members:
         to parse and validate the expected data against generated data.
     * - params
       - ``document``
-      - Defines the input parameters used to generate the HTTP request. These
-        parameters MUST be compatible with the input of the operation.
+      - For clients, defines the input parameters used to generate the HTTP
+        request. For servers, defines the input parameters extracted from the
+        HTTP request. These parameters MUST be compatible with the input of the
+        operation.
 
         Parameter values that contain binary data MUST be defined using
         values that can be represented in plain text (for example, use "foo"
@@ -391,9 +393,10 @@ structures that support the following members:
         base64 encoded.
     * - params
       - ``document``
-      - Defines the output or error parameters used to generate the HTTP
-        response. These parameters MUST be compatible with the targeted
-        operation's output or the targeted error structure.
+      - For clients, defines the output or error parameters extracted from the
+        HTTP response. For servers, defines the output or error parameters used
+        to generate the HTTP response. These parameters MUST be compatible with
+        the targeted operation's output or the targeted error structure.
 
         Parameter values that contain binary data MUST be defined using
         values that can be represented in plain text (for example, use "foo"
diff --git a/docs/source-2.0/additional-specs/http-protocol-compliance-tests.rst b/docs/source-2.0/additional-specs/http-protocol-compliance-tests.rst
index da92e4f56..5a0703bc5 100644
--- a/docs/source-2.0/additional-specs/http-protocol-compliance-tests.rst
+++ b/docs/source-2.0/additional-specs/http-protocol-compliance-tests.rst
@@ -208,8 +208,10 @@ that support the following members:
         to parse and validate the expected data against generated data.
     * - params
       - ``document``
-      - Defines the input parameters used to generate the HTTP request. These
-        parameters MUST be compatible with the input of the operation.
+      - For clients, defines the input parameters used to generate the HTTP
+        request. For servers, defines the input parameters extracted from the
+        HTTP request. These parameters MUST be compatible with the input of the
+        operation.
 
         Parameter values that contain binary data MUST be defined using
         values that can be represented in plain text (for example, use "foo"
@@ -393,9 +395,10 @@ structures that support the following members:
         base64 encoded.
     * - params
       - ``document``
-      - Defines the output or error parameters used to generate the HTTP
-        response. These parameters MUST be compatible with the targeted
-        operation's output or the targeted error structure.
+      - For clients, defines the output or error parameters extracted from the
+        HTTP response. For servers, defines the output or error parameters used
+        to generate the HTTP response. These parameters MUST be compatible with
+        the targeted operation's output or the targeted error structure.
 
         Parameter values that contain binary data MUST be defined using
         values that can be represented in plain text (for example, use "foo"
diff --git a/smithy-aws-protocol-tests/build.gradle b/smithy-aws-protocol-tests/build.gradle
index 2a4b95bb3..279f4b8a1 100644
--- a/smithy-aws-protocol-tests/build.gradle
+++ b/smithy-aws-protocol-tests/build.gradle
@@ -27,6 +27,7 @@ ext {
 dependencies {
     implementation project(path: ":smithy-cli", configuration: "shadow")
     implementation project(":smithy-protocol-test-traits")
+    implementation project(":smithy-protocol-traits")
     implementation project(":smithy-aws-traits")
     api project(":smithy-validation-model")
 }
diff --git a/smithy-aws-protocol-tests/model/awsJson1_1/main.smithy b/smithy-aws-protocol-tests/model/awsJson1_1/main.smithy
index 380b2ab8a..9503e7df7 100644
--- a/smithy-aws-protocol-tests/model/awsJson1_1/main.smithy
+++ b/smithy-aws-protocol-tests/model/awsJson1_1/main.smithy
@@ -27,6 +27,7 @@ service JsonProtocol {
         PutAndGetInlineDocuments,
         JsonEnums,
         NullOperation,
+        SparseNullsOperation,
         GreetingWithErrors,
         JsonUnions,
 
diff --git a/smithy-aws-protocol-tests/model/awsJson1_1/null.smithy b/smithy-aws-protocol-tests/model/awsJson1_1/null.smithy
index b2d07aaca..308c34dba 100644
--- a/smithy-aws-protocol-tests/model/awsJson1_1/null.smithy
+++ b/smithy-aws-protocol-tests/model/awsJson1_1/null.smithy
@@ -44,52 +44,6 @@ use smithy.test#httpResponseTests
         method: "POST",
         uri: "/",
         appliesTo: "server",
-    },
-    {
-        id: "AwsJson11MapsSerializeNullValues",
-        documentation: "Serializes null values in maps",
-        protocol: awsJson1_1,
-        body: """
-            {
-                "sparseStringMap": {
-                    "foo": null
-                }
-            }""",
-        bodyMediaType: "application/json",
-        headers: {
-            "Content-Type": "application/x-amz-json-1.1",
-            "X-Amz-Target": "JsonProtocol.NullOperation",
-        },
-        params: {
-            "sparseStringMap": {
-                "foo": null
-            }
-        },
-        method: "POST",
-        uri: "/",
-    },
-    {
-        id: "AwsJson11ListsSerializeNull",
-        documentation: "Serializes null values in lists",
-        protocol: awsJson1_1,
-        body: """
-            {
-                "sparseStringList": [
-                    null
-                ]
-            }""",
-        bodyMediaType: "application/json",
-        headers: {
-            "Content-Type": "application/x-amz-json-1.1",
-            "X-Amz-Target": "JsonProtocol.NullOperation",
-        },
-        params: {
-            "sparseStringList": [
-                null
-            ]
-        },
-        method: "POST",
-        uri: "/",
     }
 ])
 @httpResponseTests([
@@ -119,53 +73,111 @@ use smithy.test#httpResponseTests
             string: null
         },
         appliesTo: "server",
-    },
+    }
+])
+operation NullOperation {
+    input: NullOperationInputOutput
+    output: NullOperationInputOutput
+}
+
+structure NullOperationInputOutput {
+    string: String
+}
+
+@httpRequestTests([
     {
-        id: "AwsJson11MapsDeserializeNullValues",
-        documentation: "Deserializes null values in maps",
-        protocol: awsJson1_1,
-        code: 200,
+        id: "AwsJson11SparseMapsSerializeNullValues"
+        documentation: "Serializes null values in maps"
+        protocol: awsJson1_1
         body: """
             {
                 "sparseStringMap": {
                     "foo": null
                 }
-            }""",
-        bodyMediaType: "application/json",
-        headers: {"Content-Type": "application/x-amz-json-1.1"},
+            }"""
+        bodyMediaType: "application/json"
+        headers: {
+            "Content-Type": "application/x-amz-json-1.1"
+            "X-Amz-Target": "JsonProtocol.SparseNullsOperation"
+        }
         params: {
             "sparseStringMap": {
                 "foo": null
             }
-        },
+        }
+        method: "POST"
+        uri: "/"
     },
     {
-        id: "AwsJson11ListsDeserializeNull",
-        documentation: "Deserializes null values in lists",
-        protocol: awsJson1_1,
-        code: 200,
+        id: "AwsJson11SparseListsSerializeNull"
+        documentation: "Serializes null values in lists"
+        protocol: awsJson1_1
         body: """
             {
                 "sparseStringList": [
                     null
                 ]
-            }""",
-        bodyMediaType: "application/json",
-        headers: {"Content-Type": "application/x-amz-json-1.1"},
+            }"""
+        bodyMediaType: "application/json"
+        headers: {
+            "Content-Type": "application/x-amz-json-1.1"
+            "X-Amz-Target": "JsonProtocol.SparseNullsOperation"
+        }
         params: {
             "sparseStringList": [
                 null
             ]
-        },
+        }
+        method: "POST"
+        uri: "/"
     }
 ])
-operation NullOperation {
-    input: NullOperationInputOutput,
-    output: NullOperationInputOutput,
+@httpResponseTests([
+    {
+        id: "AwsJson11SparseMapsDeserializeNullValues"
+        documentation: "Deserializes null values in maps"
+        protocol: awsJson1_1
+        code: 200
+        body: """
+            {
+                "sparseStringMap": {
+                    "foo": null
+                }
+            }"""
+        bodyMediaType: "application/json"
+        headers: {"Content-Type": "application/x-amz-json-1.1"}
+        params: {
+            "sparseStringMap": {
+                "foo": null
+            }
+        }
+    }
+    {
+        id: "AwsJson11SparseListsDeserializeNull"
+        documentation: "Deserializes null values in lists"
+        protocol: awsJson1_1
+        code: 200
+        body: """
+            {
+                "sparseStringList": [
+                    null
+                ]
+            }"""
+        bodyMediaType: "application/json"
+        headers: {"Content-Type": "application/x-amz-json-1.1"}
+        params: {
+            "sparseStringList": [
+                null
+            ]
+        }
+    }
+])
+operation SparseNullsOperation {
+    input: SparseNullsOperationInputOutput
+    output: SparseNullsOperationInputOutput
 }
 
-structure NullOperationInputOutput {
-    string: String,
-    sparseStringList: SparseStringList,
-    sparseStringMap: SparseStringMap,
+structure SparseNullsOperationInputOutput {
+    sparseStringList: SparseStringList
+    sparseStringMap: SparseStringMap
 }
diff --git a/smithy-aws-protocol-tests/model/restJson1/json-lists.smithy b/smithy-aws-protocol-tests/model/restJson1/json-lists.smithy
index d05874da6..af76a3307 100644
--- a/smithy-aws-protocol-tests/model/restJson1/json-lists.smithy
+++ b/smithy-aws-protocol-tests/model/restJson1/json-lists.smithy
@@ -159,25 +159,6 @@ apply JsonLists @httpRequestTests([
         params: {
             stringList: []
         }
-    },
-    {
-          id: "RestJsonListsSerializeNull",
-          documentation: "Serializes null values in lists",
-          protocol: restJson1,
-          method: "PUT",
-          uri: "/JsonLists",
-          body: """
-                {
-                    "sparseStringList": [
-                        null,
-                        "hi"
-                    ]
-                }""",
-          bodyMediaType: "application/json",
-          headers: {"Content-Type": "application/json"},
-          params: {
-              sparseStringList: [null, "hi"]
-          }
     }
 ])
 
@@ -305,32 +286,12 @@ apply JsonLists @httpResponseTests([
         params: {
             stringList: []
         }
-    },
-    {
-          id: "RestJsonListsSerializeNull",
-          documentation: "Serializes null values in sparse lists",
-          protocol: restJson1,
-          code: 200,
-          body: """
-                {
-                    "sparseStringList": [
-                        null,
-                        "hi"
-                    ]
-                }""",
-          bodyMediaType: "application/json",
-          headers: {"Content-Type": "application/json"},
-          params: {
-              sparseStringList: [null, "hi"]
-          }
     }
 ])
 
 structure JsonListsInputOutput {
     stringList: StringList,
 
-    sparseStringList: SparseStringList,
-
     stringSet: StringSet,
 
     integerList: IntegerList,
@@ -360,3 +321,55 @@ structure StructureListMember {
     @jsonName("other")
     b: String,
 }
+
+@httpRequestTests([
+    {
+        id: "RestJsonSparseListsSerializeNull"
+        documentation: "Serializes null values in sparse lists"
+        protocol: restJson1
+        method: "PUT"
+        uri: "/SparseJsonLists"
+        body: """
+                {
+                    "sparseStringList": [
+                        null,
+                        "hi"
+                    ]
+                }"""
+        bodyMediaType: "application/json"
+        headers: {"Content-Type": "application/json"}
+        params: {
+            sparseStringList: [null, "hi"]
+        }
+    }
+])
+@httpResponseTests([
+    {
+        id: "RestJsonSparseListsSerializeNull"
+        documentation: "Serializes null values in sparse lists"
+        protocol: restJson1
+        code: 200
+        body: """
+                {
+                    "sparseStringList": [
+                        null,
+                        "hi"
+                    ]
+                }"""
+        bodyMediaType: "application/json"
+        headers: {"Content-Type": "application/json"}
+        params: {
+            sparseStringList: [null, "hi"]
+        }
+    }
+])
+@idempotent
+@http(uri: "/SparseJsonLists", method: "PUT")
+operation SparseJsonLists {
+    input: SparseJsonListsInputOutput
+    output: SparseJsonListsInputOutput
+}
+
+structure SparseJsonListsInputOutput {
+    sparseStringList: SparseStringList
+}
diff --git a/smithy-aws-protocol-tests/model/restJson1/json-maps.smithy b/smithy-aws-protocol-tests/model/restJson1/json-maps.smithy
index ca7de6e16..814c38a8f 100644
--- a/smithy-aws-protocol-tests/model/restJson1/json-maps.smithy
+++ b/smithy-aws-protocol-tests/model/restJson1/json-maps.smithy
@@ -35,8 +35,86 @@ apply JsonMaps @httpRequestTests([
                       "baz": {
                           "hi": "bye"
                       }
-                  },
-                  "sparseStructMap": {
+                  }
+              }""",
+        bodyMediaType: "application/json",
+        headers: {
+            "Content-Type": "application/json"
+        },
+        params: {
+            "denseStructMap": {
+                "foo": {
+                    "hi": "there"
+                },
+                "baz": {
+                    "hi": "bye"
+                }
+            }
+        }
+    },
+    {
+        id: "RestJsonSerializesZeroValuesInMaps",
+        documentation: "Ensure that 0 and false are sent over the wire in all maps and lists",
+        protocol: restJson1,
+        method: "POST",
+        uri: "/JsonMaps",
+        body: """
+            {
+                "denseNumberMap": {
+                    "x": 0
+                },
+                "denseBooleanMap": {
+                    "x": false
+                }
+            }""",
+        bodyMediaType: "application/json",
+        headers: {
+            "Content-Type": "application/json"
+        },
+        params: {
+            "denseNumberMap": {
+                "x": 0
+            },
+            "denseBooleanMap": {
+                "x": false
+            },
+        }
+    },
+    {
+        id: "RestJsonSerializesDenseSetMap",
+        documentation: "A request that contains a dense map of sets.",
+        protocol: restJson1,
+        method: "POST",
+        uri: "/JsonMaps",
+        body: """
+            {
+                "denseSetMap": {
+                    "x": [],
+                    "y": ["a", "b"]
+                }
+            }""",
+        bodyMediaType: "application/json",
+        headers: {
+            "Content-Type": "application/json"
+        },
+        params: {
+            "denseSetMap": {
+                "x": [],
+                "y": ["a", "b"]
+            }
+        }
+    }
+])
+
+apply JsonMaps @httpResponseTests([
+    {
+        id: "RestJsonJsonMaps",
+        documentation: "Deserializes JSON maps",
+        protocol: restJson1,
+        code: 200,
+        body: """
+              {
+                  "denseStructMap": {
                       "foo": {
                           "hi": "there"
                       },
@@ -57,7 +135,151 @@ apply JsonMaps @httpRequestTests([
                 "baz": {
                     "hi": "bye"
                 }
+            }
+        }
+    },
+    {
+        id: "RestJsonDeserializesZeroValuesInMaps",
+        documentation: "Ensure that 0 and false are sent over the wire in all maps and lists",
+        protocol: restJson1,
+        code: 200,
+        body: """
+            {
+                "denseNumberMap": {
+                    "x": 0
+                },
+                "denseBooleanMap": {
+                    "x": false
+                }
+            }""",
+        bodyMediaType: "application/json",
+        headers: {
+            "Content-Type": "application/json"
+        },
+        params: {
+            "denseNumberMap": {
+                "x": 0
+            },
+            "denseBooleanMap": {
+                "x": false
             },
+        }
+    },
+    {
+        id: "RestJsonDeserializesDenseSetMap",
+        documentation: "A response that contains a dense map of sets.",
+        protocol: restJson1,
+        code: 200,
+        body: """
+            {
+                "denseSetMap": {
+                    "x": [],
+                    "y": ["a", "b"]
+                }
+            }""",
+        bodyMediaType: "application/json",
+        headers: {
+            "Content-Type": "application/json"
+        },
+        params: {
+            "denseSetMap": {
+                "x": [],
+                "y": ["a", "b"]
+            }
+        }
+    },
+    {
+        id: "RestJsonDeserializesDenseSetMapAndSkipsNull",
+        documentation: """
+            Clients SHOULD tolerate seeing a null value in a dense map, and they SHOULD
+            drop the null key-value pair.""",
+        protocol: restJson1,
+        appliesTo: "client",
+        code: 200,
+        body: """
+            {
+                "denseSetMap": {
+                    "x": [],
+                    "y": ["a", "b"],
+                    "z": null
+                }
+            }""",
+        bodyMediaType: "application/json",
+        headers: {
+            "Content-Type": "application/json"
+        },
+        params: {
+            "denseSetMap": {
+                "x": [],
+                "y": ["a", "b"]
+            }
+        }
+    }
+])
+
+structure JsonMapsInputOutput {
+    denseStructMap: DenseStructMap
+    denseNumberMap: DenseNumberMap
+    denseBooleanMap: DenseBooleanMap
+    denseStringMap: DenseStringMap
+    denseSetMap: DenseSetMap
+}
+
+map DenseStructMap {
+    key: String,
+    value: GreetingStruct
+}
+
+map DenseBooleanMap {
+    key: String,
+    value: Boolean
+}
+
+map DenseNumberMap {
+    key: String,
+    value: Integer
+}
+
+map DenseStringMap {
+    key: String,
+    value: String
+}
+
+map DenseSetMap {
+    key: String,
+    value: StringSet
+}
+
+/// This example tests sparse map serialization.
+@http(uri: "/SparseJsonMaps", method: "POST")
+operation SparseJsonMaps {
+    input: SparseJsonMapsInputOutput
+    output: SparseJsonMapsInputOutput
+}
+
+apply SparseJsonMaps @httpRequestTests([
+    {
+        id: "RestJsonSparseJsonMaps",
+        documentation: "Serializes JSON maps",
+        protocol: restJson1,
+        method: "POST",
+        uri: "/SparseJsonMaps",
+        body: """
+              {
+                  "sparseStructMap": {
+                      "foo": {
+                          "hi": "there"
+                      },
+                      "baz": {
+                          "hi": "bye"
+                      }
+                  }
+              }""",
+        bodyMediaType: "application/json",
+        headers: {
+            "Content-Type": "application/json"
+        },
+        params: {
             "sparseStructMap": {
                 "foo": {
                     "hi": "there"
@@ -69,11 +291,11 @@ apply JsonMaps @httpRequestTests([
         }
     },
     {
-        id: "RestJsonSerializesNullMapValues",
+        id: "RestJsonSerializesSparseNullMapValues",
         documentation: "Serializes JSON map values in sparse maps",
         protocol: restJson1,
         method: "POST",
-        uri: "/JsonMaps",
+        uri: "/SparseJsonMaps",
         body: """
             {
                 "sparseBooleanMap": {
@@ -109,22 +331,16 @@ apply JsonMaps @httpRequestTests([
         }
     },
     {
-        id: "RestJsonSerializesZeroValuesInMaps",
+        id: "RestJsonSerializesZeroValuesInSparseMaps",
         documentation: "Ensure that 0 and false are sent over the wire in all maps and lists",
         protocol: restJson1,
         method: "POST",
-        uri: "/JsonMaps",
+        uri: "/SparseJsonMaps",
         body: """
             {
-                "denseNumberMap": {
-                    "x": 0
-                },
                 "sparseNumberMap": {
                     "x": 0
                 },
-                "denseBooleanMap": {
-                    "x": false
-                },
                 "sparseBooleanMap": {
                     "x": false
                 }
@@ -134,15 +350,9 @@ apply JsonMaps @httpRequestTests([
             "Content-Type": "application/json"
         },
         params: {
-            "denseNumberMap": {
-                "x": 0
-            },
             "sparseNumberMap": {
                 "x": 0
             },
-            "denseBooleanMap": {
-                "x": false
-            },
             "sparseBooleanMap": {
                 "x": false
             }
@@ -153,7 +363,7 @@ apply JsonMaps @httpRequestTests([
         documentation: "A request that contains a sparse map of sets",
         protocol: restJson1,
         method: "POST",
-        uri: "/JsonMaps",
+        uri: "/SparseJsonMaps",
         body: """
             {
                 "sparseSetMap": {
@@ -172,36 +382,12 @@ apply JsonMaps @httpRequestTests([
             }
         }
     },
-    {
-        id: "RestJsonSerializesDenseSetMap",
-        documentation: "A request that contains a dense map of sets.",
-        protocol: restJson1,
-        method: "POST",
-        uri: "/JsonMaps",
-        body: """
-            {
-                "denseSetMap": {
-                    "x": [],
-                    "y": ["a", "b"]
-                }
-            }""",
-        bodyMediaType: "application/json",
-        headers: {
-            "Content-Type": "application/json"
-        },
-        params: {
-            "denseSetMap": {
-                "x": [],
-                "y": ["a", "b"]
-            }
-        }
-    },
     {
         id: "RestJsonSerializesSparseSetMapAndRetainsNull",
         documentation: "A request that contains a sparse map of sets.",
         protocol: restJson1,
         method: "POST",
-        uri: "/JsonMaps",
+        uri: "/SparseJsonMaps",
         body: """
             {
                 "sparseSetMap": {
@@ -224,22 +410,14 @@ apply JsonMaps @httpRequestTests([
     }
 ])
 
-apply JsonMaps @httpResponseTests([
+apply SparseJsonMaps @httpResponseTests([
     {
-        id: "RestJsonJsonMaps",
+        id: "RestJsonSparseJsonMaps",
         documentation: "Deserializes JSON maps",
         protocol: restJson1,
         code: 200,
         body: """
               {
-                  "denseStructMap": {
-                      "foo": {
-                          "hi": "there"
-                      },
-                      "baz": {
-                          "hi": "bye"
-                      }
-                  },
                   "sparseStructMap": {
                       "foo": {
                           "hi": "there"
@@ -254,14 +432,6 @@ apply JsonMaps @httpResponseTests([
             "Content-Type": "application/json"
         },
         params: {
-            "denseStructMap": {
-                "foo": {
-                    "hi": "there"
-                },
-                "baz": {
-                    "hi": "bye"
-                }
-            },
             "sparseStructMap": {
                 "foo": {
                     "hi": "there"
@@ -273,7 +443,7 @@ apply JsonMaps @httpResponseTests([
         }
     },
     {
-        id: "RestJsonDeserializesNullMapValues",
+        id: "RestJsonDeserializesSparseNullMapValues",
         documentation: "Deserializes null JSON map values",
         protocol: restJson1,
         code: 200,
@@ -312,21 +482,15 @@ apply JsonMaps @httpResponseTests([
         }
     },
     {
-        id: "RestJsonDeserializesZeroValuesInMaps",
+        id: "RestJsonDeserializesZeroValuesInSparseMaps",
         documentation: "Ensure that 0 and false are sent over the wire in all maps and lists",
         protocol: restJson1,
         code: 200,
         body: """
             {
-                "denseNumberMap": {
-                    "x": 0
-                },
                 "sparseNumberMap": {
                     "x": 0
                 },
-                "denseBooleanMap": {
-                    "x": false
-                },
                 "sparseBooleanMap": {
                     "x": false
                 }
@@ -336,15 +500,9 @@ apply JsonMaps @httpResponseTests([
             "Content-Type": "application/json"
         },
         params: {
-            "denseNumberMap": {
-                "x": 0
-            },
             "sparseNumberMap": {
                 "x": 0
             },
-            "denseBooleanMap": {
-                "x": false
-            },
             "sparseBooleanMap": {
                 "x": false
             }
@@ -373,29 +531,6 @@ apply JsonMaps @httpResponseTests([
             }
         }
     },
-    {
-        id: "RestJsonDeserializesDenseSetMap",
-        documentation: "A response that contains a dense map of sets.",
-        protocol: restJson1,
-        code: 200,
-        body: """
-            {
-                "denseSetMap": {
-                    "x": [],
-                    "y": ["a", "b"]
-                }
-            }""",
-        bodyMediaType: "application/json",
-        headers: {
-            "Content-Type": "application/json"
-        },
-        params: {
-            "denseSetMap": {
-                "x": [],
-                "y": ["a", "b"]
-            }
-        }
-    },
     {
         id: "RestJsonDeserializesSparseSetMapAndRetainsNull",
         documentation: "A response that contains a sparse map of sets.",
@@ -420,52 +555,15 @@ apply JsonMaps @httpResponseTests([
                 "z": null
             }
         }
-    },
-    {
-        id: "RestJsonDeserializesDenseSetMapAndSkipsNull",
-        documentation: """
-            Clients SHOULD tolerate seeing a null value in a dense map, and they SHOULD
-            drop the null key-value pair.""",
-        protocol: restJson1,
-        appliesTo: "client",
-        code: 200,
-        body: """
-            {
-                "denseSetMap": {
-                    "x": [],
-                    "y": ["a", "b"],
-                    "z": null
-                }
-            }""",
-        bodyMediaType: "application/json",
-        headers: {
-            "Content-Type": "application/json"
-        },
-        params: {
-            "denseSetMap": {
-                "x": [],
-                "y": ["a", "b"]
-            }
-        }
     }
 ])
 
-structure JsonMapsInputOutput {
-    denseStructMap: DenseStructMap,
-    sparseStructMap: SparseStructMap,
-    denseNumberMap: DenseNumberMap,
-    denseBooleanMap: DenseBooleanMap,
-    denseStringMap: DenseStringMap,
-    sparseNumberMap: SparseNumberMap,
-    sparseBooleanMap: SparseBooleanMap,
-    sparseStringMap: SparseStringMap,
-    denseSetMap: DenseSetMap,
-    sparseSetMap: SparseSetMap,
-}
-
-map DenseStructMap {
-    key: String,
-    value: GreetingStruct
+structure SparseJsonMapsInputOutput {
+    sparseStructMap: SparseStructMap
+    sparseNumberMap: SparseNumberMap
+    sparseBooleanMap: SparseBooleanMap
+    sparseStringMap: SparseStringMap
+    sparseSetMap: SparseSetMap
 }
 
 @sparse
@@ -474,21 +572,6 @@ map SparseStructMap {
     value: GreetingStruct
 }
 
-map DenseBooleanMap {
-    key: String,
-    value: Boolean
-}
-
-map DenseNumberMap {
-    key: String,
-    value: Integer
-}
-
-map DenseStringMap {
-    key: String,
-    value: String
-}
-
 @sparse
 map SparseBooleanMap {
     key: String,
@@ -501,11 +584,6 @@ map SparseNumberMap {
     value: Integer
 }
 
-map DenseSetMap {
-    key: String,
-    value: StringSet
-}
-
 @sparse
 map SparseSetMap {
     key: String,
diff --git a/smithy-aws-protocol-tests/model/restJson1/main.smithy b/smithy-aws-protocol-tests/model/restJson1/main.smithy
index f0a638594..e5ae6f979 100644
--- a/smithy-aws-protocol-tests/model/restJson1/main.smithy
+++ b/smithy-aws-protocol-tests/model/restJson1/main.smithy
@@ -81,7 +81,9 @@ service RestJson {
         JsonIntEnums,
         RecursiveShapes,
         JsonLists,
+        SparseJsonLists,
         JsonMaps,
+        SparseJsonMaps,
         JsonBlobs,
 
         // Documents
diff --git a/smithy-aws-protocol-tests/model/shared-types.smithy b/smithy-aws-protocol-tests/model/shared-types.smithy
index 4e88e464e..b4e31ba4d 100644
--- a/smithy-aws-protocol-tests/model/shared-types.smithy
+++ b/smithy-aws-protocol-tests/model/shared-types.smithy
@@ -55,6 +55,10 @@ list NestedStringList {
     member: StringList,
 }
 
+list ShortList {
+    member: Short,
+}
+
 list IntegerList {
     member: Integer,
 }
@@ -64,6 +68,10 @@ list IntegerSet {
     member: Integer,
 }
 
+list FloatList {
+    member: Float,
+}
+
 list DoubleList {
     member: Double,
 }
@@ -81,11 +89,19 @@ list TimestampList {
     member: Timestamp,
 }
 
+list BlobList {
+    member: Blob,
+}
+
 @uniqueItems
 list BlobSet {
     member: Blob,
 }
 
+list ByteList {
+    member: Byte,
+}
+
 @uniqueItems
 list ByteSet {
     member: Byte,
@@ -95,6 +111,11 @@ list ShortSet {
     member: Short,
 }
 
+@uniqueItems
+list LongList {
+    member: Long,
+}
+
 @uniqueItems
 list LongSet {
     member: Long,
@@ -105,6 +126,10 @@ list TimestampSet {
     member: Timestamp,
 }
 
+list DateTimeList {
+    member: DateTime,
+}
+
 @uniqueItems
 list DateTimeSet {
     member: DateTime,
@@ -192,10 +217,10 @@ list IntegerEnumList {
 
 @uniqueItems
 list IntegerEnumSet {
-     member: IntegerEnum
+    member: IntegerEnum
 }
 
 map IntegerEnumMap {
-     key: String,
-     value: IntegerEnum
+    key: String,
+    value: IntegerEnum
 }
diff --git a/smithy-protocol-tests/build.gradle b/smithy-protocol-tests/build.gradle
new file mode 100644
index 000000000..fb711a704
--- /dev/null
+++ b/smithy-protocol-tests/build.gradle
@@ -0,0 +1,28 @@
+/*
+ * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+plugins {
+    id "software.amazon.smithy.gradle.smithy-jar" version "$smithyGradleVersion"
+}
+
+description = "Defines protocol tests for Smithy HTTP protocols."
+
+ext {
+    displayName = "Smithy :: Protocol Tests"
+    moduleName = "software.amazon.smithy.protocoltests"
+}
+
+dependencies {
+    implementation project(path: ":smithy-cli", configuration: "shadow")
+    implementation project(":smithy-protocol-test-traits")
+    implementation project(":smithy-protocol-traits")
+    api project(":smithy-validation-model")
+}
+
+tasks["sourcesJar"].dependsOn("smithyJarStaging")
+
+smithy {
+    format.set(false)
+}
diff --git a/smithy-protocol-tests/model/rpcv2Cbor/cbor-lists.smithy b/smithy-protocol-tests/model/rpcv2Cbor/cbor-lists.smithy
new file mode 100644
index 000000000..c87ebb3bb
--- /dev/null
+++ b/smithy-protocol-tests/model/rpcv2Cbor/cbor-lists.smithy
@@ -0,0 +1,412 @@
+// This file defines test cases that serialize lists in JSON documents.
+
+$version: "2.0"
+
+namespace smithy.protocoltests.rpcv2Cbor
+
+use smithy.protocoltests.shared#BooleanList
+use smithy.protocoltests.shared#BlobList
+use smithy.protocoltests.shared#FooEnumList
+use smithy.protocoltests.shared#IntegerEnumList
+use smithy.protocoltests.shared#IntegerList
+use smithy.protocoltests.shared#NestedStringList
+use smithy.protocoltests.shared#SparseStringList
+use smithy.protocoltests.shared#SparseStringMap
+use smithy.protocoltests.shared#StringList
+use smithy.protocoltests.shared#StringSet
+use smithy.protocoltests.shared#TimestampList
+use smithy.protocols#rpcv2Cbor
+use smithy.test#httpRequestTests
+use smithy.test#httpResponseTests
+use smithy.framework#ValidationException
+
+/// This test case serializes JSON lists for the following cases for both
+/// input and output:
+///
+/// 1. Normal lists.
+/// 2. Normal sets.
+/// 3. Lists of lists.
+/// 4. Lists of structures.
+@idempotent
+operation RpcV2CborLists {
+    input: RpcV2CborListInputOutput,
+    output: RpcV2CborListInputOutput,
+    errors: [ValidationException]
+}
+
+apply RpcV2CborLists @httpRequestTests([
+    {
+        id: "RpcV2CborLists",
+        documentation: "Serializes RpcV2 Cbor lists",
+        protocol: rpcv2Cbor,
+        method: "POST",
+        uri: "/service/RpcV2Protocol/operation/RpcV2CborLists",
+        body: "v2pzdHJpbmdMaXN0gmNmb29jYmFyaXN0cmluZ1NldIJjZm9vY2JhcmtpbnRlZ2VyTGlzdIIBAmtib29sZWFuTGlzdIL19G10aW1lc3RhbXBMaXN0gsH7QdTX+/OAAADB+0HU1/vzgAAAaGVudW1MaXN0gmNGb29hMGtpbnRFbnVtTGlzdIIBAnBuZXN0ZWRTdHJpbmdMaXN0goJjZm9vY2JhcoJjYmF6Y3F1eG1zdHJ1Y3R1cmVMaXN0gqJhYWExYWJhMqJhYWEzYWJhNGhibG9iTGlzdIJDZm9vQ2Jhcv8="
+        bodyMediaType: "application/cbor",
+        headers: {
+            "smithy-protocol": "rpc-v2-cbor",
+            "Accept": "application/cbor",
+            "Content-Type": "application/cbor"
+        },
+        params: {
+            "stringList": [
+                "foo",
+                "bar"
+            ],
+            "stringSet": [
+                "foo",
+                "bar"
+            ],
+            "integerList": [
+                1,
+                2
+            ],
+            "booleanList": [
+                true,
+                false
+            ],
+            "timestampList": [
+                1398796238,
+                1398796238
+            ],
+            "enumList": [
+                "Foo",
+                "0"
+            ],
+            "intEnumList": [
+                1,
+                2
+            ],
+            "nestedStringList": [
+                [
+                    "foo",
+                    "bar"
+                ],
+                [
+                    "baz",
+                    "qux"
+                ]
+            ],
+            "structureList": [
+                {
+                    "a": "1",
+                    "b": "2"
+                },
+                {
+                    "a": "3",
+                    "b": "4"
+                }
+            ],
+            "blobList" : [
+                "foo",
+                "bar"
+            ]
+        }
+    },
+    {
+        id: "RpcV2CborListsEmpty",
+        documentation: "Serializes empty JSON lists",
+        tags: ["client-indefinite"]
+        protocol: rpcv2Cbor,
+        method: "POST",
+        uri: "/service/RpcV2Protocol/operation/RpcV2CborLists",
+        body: "v2pzdHJpbmdMaXN0n///",
+        bodyMediaType: "application/cbor",
+        headers: {
+            "smithy-protocol": "rpc-v2-cbor",
+            "Accept": "application/cbor",
+            "Content-Type": "application/cbor"
+        },
+        params: {
+            stringList: []
+        }
+    },
+    {
+        id: "RpcV2CborListsEmptyUsingDefiniteLength",
+        documentation: "Serializes empty JSON definite length lists",
+        tags: ["client-definite"]
+        protocol: rpcv2Cbor,
+        method: "POST",
+        uri: "/service/RpcV2Protocol/operation/RpcV2CborLists",
+        body: "oWpzdHJpbmdMaXN0gA=="
+        bodyMediaType: "application/cbor",
+        headers: {
+            "smithy-protocol": "rpc-v2-cbor",
+            "Accept": "application/cbor",
+            "Content-Type": "application/cbor"
+        },
+        params: {
+            stringList: []
+        }
+    },
+    {
+        id: "RpcV2CborIndefiniteStringInsideIndefiniteList",
+        documentation: "Can deserialize indefinite length text strings inside an indefinite length list",
+        protocol: rpcv2Cbor,
+        method: "POST",
+        uri: "/service/RpcV2Protocol/operation/RpcV2CborLists",
+        body: "v2pzdHJpbmdMaXN0n394HUFuIGV4YW1wbGUgaW5kZWZpbml0ZSBzdHJpbmcsdyB3aGljaCB3aWxsIGJlIGNodW5rZWQsbiBvbiBlYWNoIGNvbW1h/394NUFub3RoZXIgZXhhbXBsZSBpbmRlZmluaXRlIHN0cmluZyB3aXRoIG9ubHkgb25lIGNodW5r/3ZUaGlzIGlzIGEgcGxhaW4gc3RyaW5n//8="
+        bodyMediaType: "application/cbor",
+        headers: {
+            "smithy-protocol": "rpc-v2-cbor",
+            "Accept": "application/cbor",
+            "Content-Type": "application/cbor"
+        },
+        params: {
+            stringList: ["An example indefinite string, which will be chunked, on each comma", "Another example indefinite string with only one chunk", "This is a plain string"]
+        }
+        appliesTo: "server"
+    },
+    {
+        id: "RpcV2CborIndefiniteStringInsideDefiniteList",
+        documentation: "Can deserialize indefinite length text strings inside a definite length list",
+        protocol: rpcv2Cbor,
+        method: "POST",
+        uri: "/service/RpcV2Protocol/operation/RpcV2CborLists",
+        body: "oWpzdHJpbmdMaXN0g394HUFuIGV4YW1wbGUgaW5kZWZpbml0ZSBzdHJpbmcsdyB3aGljaCB3aWxsIGJlIGNodW5rZWQsbiBvbiBlYWNoIGNvbW1h/394NUFub3RoZXIgZXhhbXBsZSBpbmRlZmluaXRlIHN0cmluZyB3aXRoIG9ubHkgb25lIGNodW5r/3ZUaGlzIGlzIGEgcGxhaW4gc3RyaW5n"
+        bodyMediaType: "application/cbor",
+        headers: {
+            "smithy-protocol": "rpc-v2-cbor",
+            "Accept": "application/cbor",
+            "Content-Type": "application/cbor"
+        },
+        params: {
+            stringList: ["An example indefinite string, which will be chunked, on each comma", "Another example indefinite string with only one chunk", "This is a plain string"]
+        },
+        appliesTo: "server"
+    }
+])
+
+apply RpcV2CborLists @httpResponseTests([
+    {
+        id: "RpcV2CborLists",
+        documentation: "Serializes RpcV2 Cbor lists",
+        protocol: rpcv2Cbor,
+        code: 200,
+        body: "v2pzdHJpbmdMaXN0n2Nmb29jYmFy/2lzdHJpbmdTZXSfY2Zvb2NiYXL/a2ludGVnZXJMaXN0nwEC/2tib29sZWFuTGlzdJ/19P9tdGltZXN0YW1wTGlzdJ/B+0HU1/vzgAAAwftB1Nf784AAAP9oZW51bUxpc3SfY0Zvb2Ew/2tpbnRFbnVtTGlzdJ8BAv9wbmVzdGVkU3RyaW5nTGlzdJ+fY2Zvb2NiYXL/n2NiYXpjcXV4//9tc3RydWN0dXJlTGlzdJ+/YWFhMWFiYTL/v2FhYTNhYmE0//9oYmxvYkxpc3SfQ2Zvb0NiYXL//w=="
+        bodyMediaType: "application/cbor",
+        headers: {
+            "smithy-protocol": "rpc-v2-cbor",
+            "Content-Type": "application/cbor"
+        },
+        params: {
+            "stringList": [
+                "foo",
+                "bar"
+            ],
+            "stringSet": [
+                "foo",
+                "bar"
+            ],
+            "integerList": [
+                1,
+                2
+            ],
+            "booleanList": [
+                true,
+                false
+            ],
+            "timestampList": [
+                1398796238,
+                1398796238
+            ],
+            "enumList": [
+                "Foo",
+                "0"
+            ],
+            "intEnumList": [
+                1,
+                2
+            ],
+            "nestedStringList": [
+                [
+                    "foo",
+                    "bar"
+                ],
+                [
+                    "baz",
+                    "qux"
+                ]
+            ],
+            "structureList": [
+                {
+                    "a": "1",
+                    "b": "2"
+                },
+                {
+                    "a": "3",
+                    "b": "4"
+                }
+            ],
+            "blobList": [
+                "foo",
+                "bar"
+            ]
+        }
+    },
+    {
+        id: "RpcV2CborListsEmpty",
+        documentation: "Serializes empty RpcV2 Cbor lists",
+        protocol: rpcv2Cbor,
+        code: 200,
+        body: "v2pzdHJpbmdMaXN0n///",
+        bodyMediaType: "application/cbor",
+        headers: {
+            "smithy-protocol": "rpc-v2-cbor",
+            "Content-Type": "application/cbor"
+        },
+        params: {
+            stringList: []
+        }
+    },
+    {
+        id: "RpcV2CborIndefiniteStringInsideIndefiniteListCanDeserialize",
+        documentation: "Can deserialize indefinite length text strings inside an indefinite length list",
+        protocol: rpcv2Cbor,
+        code: 200,
+        body: "v2pzdHJpbmdMaXN0n394HUFuIGV4YW1wbGUgaW5kZWZpbml0ZSBzdHJpbmcsdyB3aGljaCB3aWxsIGJlIGNodW5rZWQsbiBvbiBlYWNoIGNvbW1h/394NUFub3RoZXIgZXhhbXBsZSBpbmRlZmluaXRlIHN0cmluZyB3aXRoIG9ubHkgb25lIGNodW5r/3ZUaGlzIGlzIGEgcGxhaW4gc3RyaW5n//8="
+        bodyMediaType: "application/cbor",
+        headers: {
+            "smithy-protocol": "rpc-v2-cbor",
+            "Content-Type": "application/cbor"
+        },
+        params: {
+            stringList: ["An example indefinite string, which will be chunked, on each comma", "Another example indefinite string with only one chunk", "This is a plain string"]
+        }
+        appliesTo: "client"
+    },
+    {
+        id: "RpcV2CborIndefiniteStringInsideDefiniteListCanDeserialize",
+        documentation: "Can deserialize indefinite length text strings inside a definite length list",
+        protocol: rpcv2Cbor,
+        code: 200,
+        body: "oWpzdHJpbmdMaXN0g394HUFuIGV4YW1wbGUgaW5kZWZpbml0ZSBzdHJpbmcsdyB3aGljaCB3aWxsIGJlIGNodW5rZWQsbiBvbiBlYWNoIGNvbW1h/394NUFub3RoZXIgZXhhbXBsZSBpbmRlZmluaXRlIHN0cmluZyB3aXRoIG9ubHkgb25lIGNodW5r/3ZUaGlzIGlzIGEgcGxhaW4gc3RyaW5n"
+        bodyMediaType: "application/cbor",
+        headers: {
+            "smithy-protocol": "rpc-v2-cbor",
+            "Content-Type": "application/cbor"
+        },
+        params: {
+            stringList: ["An example indefinite string, which will be chunked, on each comma", "Another example indefinite string with only one chunk", "This is a plain string"]
+        },
+        appliesTo: "client"
+    }
+])
+
+structure RpcV2CborListInputOutput {
+    stringList: StringList,
+
+    stringSet: StringSet,
+
+    integerList: IntegerList,
+
+    booleanList: BooleanList,
+
+    timestampList: TimestampList,
+
+    enumList: FooEnumList,
+
+    intEnumList: IntegerEnumList,
+
+    nestedStringList: NestedStringList,
+
+    structureList: StructureList
+
+    blobList: BlobList
+}
+
+list StructureList {
+    member: StructureListMember,
+}
+
+structure StructureListMember {
+    a: String,
+    b: String,
+}
+
+
+@httpRequestTests([
+    {
+        id: "RpcV2CborSparseMapsSerializeNullValues"
+        documentation: "Serializes null values in maps"
+        protocol: rpcv2Cbor
+        bodyMediaType: "application/cbor",
+        headers: {
+            "smithy-protocol": "rpc-v2-cbor",
+            "Accept": "application/cbor",
+            "Content-Type": "application/cbor"
+        },
+        body: "v29zcGFyc2VTdHJpbmdNYXC/Y2Zvb/b//w=="
+        params: {
+            "sparseStringMap": {
+                "foo": null
+            }
+        }
+        method: "POST"
+        uri: "/service/RpcV2Protocol/operation/SparseNullsOperation",
+    },
+    {
+        id: "RpcV2CborSparseListsSerializeNull"
+        documentation: "Serializes null values in lists"
+        protocol: rpcv2Cbor
+        bodyMediaType: "application/cbor",
+        headers: {
+            "smithy-protocol": "rpc-v2-cbor",
+            "Accept": "application/cbor",
+            "Content-Type": "application/cbor"
+        },
+        body: "v3BzcGFyc2VTdHJpbmdMaXN0n/b//w=="
+        params: {
+            "sparseStringList": [
+                null
+            ]
+        }
+        method: "POST"
+        uri: "/service/RpcV2Protocol/operation/SparseNullsOperation",
+    }
+])
+@httpResponseTests([
+    {
+        id: "RpcV2CborSparseMapsDeserializeNullValues"
+        documentation: "Deserializes null values in maps"
+        protocol: rpcv2Cbor,
+        code: 200,
+        body: "v29zcGFyc2VTdHJpbmdNYXC/Y2Zvb/b//w=="
+        bodyMediaType: "application/cbor",
+        headers: {
+            "smithy-protocol": "rpc-v2-cbor",
+            "Content-Type": "application/cbor"
+        },
+        params: {
+            "sparseStringMap": {
+                "foo": null
+            }
+        }
+    }
+    {
+        id: "RpcV2CborSparseListsDeserializeNull"
+        documentation: "Deserializes null values in lists"
+        protocol: rpcv2Cbor,
+        code: 200,
+        body: "v3BzcGFyc2VTdHJpbmdMaXN0n/b//w=="
+        bodyMediaType: "application/cbor",
+        headers: {
+            "smithy-protocol": "rpc-v2-cbor",
+            "Content-Type": "application/cbor"
+        },
+        params: {
+            "sparseStringList": [
+                null
+            ]
+        }
+    }
+])
+operation SparseNullsOperation {
+    input: SparseNullsOperationInputOutput
+    output: SparseNullsOperationInputOutput
+}
+
+structure SparseNullsOperationInputOutput {
+    sparseStringList: SparseStringList
+    sparseStringMap: SparseStringMap
+}
diff --git a/smithy-protocol-tests/model/rpcv2Cbor/cbor-maps.smithy b/smithy-protocol-tests/model/rpcv2Cbor/cbor-maps.smithy
new file mode 100644
index 000000000..ba21ff62a
--- /dev/null
+++ b/smithy-protocol-tests/model/rpcv2Cbor/cbor-maps.smithy
@@ -0,0 +1,471 @@
+$version: "2.0"
+
+namespace smithy.protocoltests.rpcv2Cbor
+
+use smithy.protocoltests.shared#FooEnumMap
+use smithy.protocoltests.shared#GreetingStruct
+use smithy.protocoltests.shared#SparseStringMap
+use smithy.protocoltests.shared#StringSet
+use smithy.test#httpRequestTests
+use smithy.test#httpResponseTests
+use smithy.protocols#rpcv2Cbor
+use smithy.framework#ValidationException
+
+/// The example tests basic map serialization.
+operation RpcV2CborDenseMaps {
+    input: RpcV2CborDenseMapsInputOutput,
+    output: RpcV2CborDenseMapsInputOutput,
+    errors: [ValidationException]
+}
+
+apply RpcV2CborDenseMaps @httpRequestTests([
+    {
+        id: "RpcV2CborMaps",
+        documentation: "Serializes maps",
+        protocol: rpcv2Cbor,
+        method: "POST",
+        uri: "/service/RpcV2Protocol/operation/RpcV2CborDenseMaps",
+        body: "oW5kZW5zZVN0cnVjdE1hcKJjZm9voWJoaWV0aGVyZWNiYXqhYmhpY2J5ZQ=="
+        bodyMediaType: "application/cbor",
+        headers: {
+            "smithy-protocol": "rpc-v2-cbor",
+            "Accept": "application/cbor",
+            "Content-Type": "application/cbor"
+        },
+        params: {
+            "denseStructMap": {
+                "foo": {
+                    "hi": "there"
+                },
+                "baz": {
+                    "hi": "bye"
+                }
+            }
+        }
+    },
+    {
+        id: "RpcV2CborSerializesZeroValuesInMaps",
+        documentation: "Ensure that 0 and false are sent over the wire in all maps and lists",
+        protocol: rpcv2Cbor,
+        method: "POST",
+        uri: "/service/RpcV2Protocol/operation/RpcV2CborDenseMaps",
+        body: "om5kZW5zZU51bWJlck1hcKFheABvZGVuc2VCb29sZWFuTWFwoWF49A==",
+        bodyMediaType: "application/cbor",
+        headers: {
+            "smithy-protocol": "rpc-v2-cbor",
+            "Accept": "application/cbor",
+            "Content-Type": "application/cbor"
+        },
+        params: {
+            "denseNumberMap": {
+                "x": 0
+            },
+            "denseBooleanMap": {
+                "x": false
+            }
+        }
+    },
+    {
+        id: "RpcV2CborSerializesDenseSetMap",
+        documentation: "A request that contains a dense map of sets.",
+        protocol: rpcv2Cbor,
+        method: "POST",
+        uri: "/service/RpcV2Protocol/operation/RpcV2CborDenseMaps",
+        body: "oWtkZW5zZVNldE1hcKJheIBheYJhYWFi",
+        bodyMediaType: "application/cbor",
+        headers: {
+            "smithy-protocol": "rpc-v2-cbor",
+            "Accept": "application/cbor",
+            "Content-Type": "application/cbor"
+        },
+        params: {
+            "denseSetMap": {
+                "x": [],
+                "y": ["a", "b"]
+            }
+        }
+    },
+])
+
+apply RpcV2CborDenseMaps @httpResponseTests([
+    {
+        id: "RpcV2CborMaps",
+        documentation: "Deserializes maps",
+        protocol: rpcv2Cbor,
+        code: 200,
+        body: "oW5kZW5zZVN0cnVjdE1hcKJjZm9voWJoaWV0aGVyZWNiYXqhYmhpY2J5ZQ==",
+        bodyMediaType: "application/cbor",
+        headers: {
+            "smithy-protocol": "rpc-v2-cbor",
+            "Content-Type": "application/cbor"
+        },
+        params: {
+            "denseStructMap": {
+                "foo": {
+                    "hi": "there"
+                },
+                "baz": {
+                    "hi": "bye"
+                }
+            }
+        }
+    },
+    {
+        id: "RpcV2CborDeserializesZeroValuesInMaps",
+        documentation: "Ensure that 0 and false are sent over the wire in all maps and lists",
+        protocol: rpcv2Cbor,
+        code: 200,
+        body: "om5kZW5zZU51bWJlck1hcKFheABvZGVuc2VCb29sZWFuTWFwoWF49A==",
+        bodyMediaType: "application/cbor",
+        headers: {
+            "smithy-protocol": "rpc-v2-cbor",
+            "Content-Type": "application/cbor"
+        },
+        params: {
+            "denseNumberMap": {
+                "x": 0
+            },
+            "denseBooleanMap": {
+                "x": false
+            }
+        }
+    },
+    {
+        id: "RpcV2CborDeserializesDenseSetMap",
+        documentation: "A response that contains a dense map of sets",
+        protocol: rpcv2Cbor,
+        code: 200,
+        body: "oWtkZW5zZVNldE1hcKJheIBheYJhYWFi",
+        bodyMediaType: "application/cbor",
+        headers: {
+            "smithy-protocol": "rpc-v2-cbor",
+            "Content-Type": "application/cbor"
+        },
+        params: {
+            "denseSetMap": {
+                "x": [],
+                "y": ["a", "b"]
+            }
+        }
+    },
+    {
+        id: "RpcV2CborDeserializesDenseSetMapAndSkipsNull",
+        documentation: """
+            Clients SHOULD tolerate seeing a null value in a dense map, and they SHOULD
+            drop the null key-value pair.""",
+        protocol: rpcv2Cbor,
+        appliesTo: "client",
+        code: 200,
+        body: "oWtkZW5zZVNldE1hcKNheIBheYJhYWFiYXr2",
+        bodyMediaType: "application/cbor",
+        headers: {
+            "smithy-protocol": "rpc-v2-cbor",
+            "Content-Type": "application/cbor"
+        },
+        params: {
+            "denseSetMap": {
+                "x": [],
+                "y": ["a", "b"],
+                "z": null
+            }
+        }
+    }
+])
+
+structure RpcV2CborDenseMapsInputOutput {
+    denseStructMap: DenseStructMap,
+    denseNumberMap: DenseNumberMap,
+    denseBooleanMap: DenseBooleanMap,
+    denseStringMap: DenseStringMap,
+    denseSetMap: DenseSetMap,
+}
+
+map DenseStructMap {
+    key: String,
+    value: GreetingStruct
+}
+
+map DenseBooleanMap {
+    key: String,
+    value: Boolean
+}
+
+map DenseNumberMap {
+    key: String,
+    value: Integer
+}
+
+map DenseStringMap {
+    key: String,
+    value: String
+}
+
+map DenseSetMap {
+    key: String,
+    value: StringSet
+}
+
+
+apply RpcV2CborSparseMaps @httpRequestTests([
+    {
+        id: "RpcV2CborSparseMaps",
+        documentation: "Serializes sparse maps",
+        protocol: rpcv2Cbor,
+        method: "POST",
+        uri: "/service/RpcV2Protocol/operation/RpcV2CborSparseMaps",
+        body: "v29zcGFyc2VTdHJ1Y3RNYXC/Y2Zvb79iaGlldGhlcmX/Y2Jher9iaGljYnll////",
+        bodyMediaType: "application/cbor",
+        headers: {
+            "smithy-protocol": "rpc-v2-cbor",
+            "Accept": "application/cbor",
+            "Content-Type": "application/cbor"
+        },
+        params: {
+            "sparseStructMap": {
+                "foo": {
+                    "hi": "there"
+                },
+                "baz": {
+                    "hi": "bye"
+                }
+            }
+        }
+    },
+    {
+        id: "RpcV2CborSerializesNullMapValues",
+        documentation: "Serializes null map values in sparse maps",
+        protocol: rpcv2Cbor,
+        method: "POST",
+        uri: "/service/RpcV2Protocol/operation/RpcV2CborSparseMaps",
+        body: "v3BzcGFyc2VCb29sZWFuTWFwv2F49v9vc3BhcnNlTnVtYmVyTWFwv2F49v9vc3BhcnNlU3RyaW5nTWFwv2F49v9vc3BhcnNlU3RydWN0TWFwv2F49v//"
+        bodyMediaType: "application/cbor",
+        headers: {
+            "smithy-protocol": "rpc-v2-cbor",
+            "Accept": "application/cbor",
+            "Content-Type": "application/cbor"
+        },
+        params: {
+            "sparseBooleanMap": {
+                "x": null
+            },
+            "sparseNumberMap": {
+                "x": null
+            },
+            "sparseStringMap": {
+                "x": null
+            },
+            "sparseStructMap": {
+                "x": null
+            }
+        }
+    },
+    {
+        id: "RpcV2CborSerializesSparseSetMap",
+        documentation: "A request that contains a sparse map of sets",
+        protocol: rpcv2Cbor,
+        method: "POST",
+        uri: "/service/RpcV2Protocol/operation/RpcV2CborSparseMaps",
+        body: "v2xzcGFyc2VTZXRNYXC/YXif/2F5n2FhYWL///8="
+        bodyMediaType: "application/cbor",
+        headers: {
+            "smithy-protocol": "rpc-v2-cbor",
+            "Accept": "application/cbor",
+            "Content-Type": "application/cbor"
+        },
+        params: {
+            "sparseSetMap": {
+                "x": [],
+                "y": ["a", "b"]
+            }
+        }
+    },
+    {
+        id: "RpcV2CborSerializesSparseSetMapAndRetainsNull",
+        documentation: "A request that contains a sparse map of sets.",
+        protocol: rpcv2Cbor,
+        method: "POST",
+        uri: "/service/RpcV2Protocol/operation/RpcV2CborSparseMaps",
+        body: "v2xzcGFyc2VTZXRNYXC/YXif/2F5n2FhYWL/YXr2//8=",
+        bodyMediaType: "application/cbor",
+        headers: {
+            "smithy-protocol": "rpc-v2-cbor",
+            "Accept": "application/cbor",
+            "Content-Type": "application/cbor"
+        },
+        params: {
+            "sparseSetMap": {
+                "x": [],
+                "y": ["a", "b"],
+                "z": null
+            }
+        }
+    },
+    {
+        id: "RpcV2CborSerializesZeroValuesInSparseMaps",
+        documentation: "Ensure that 0 and false are sent over the wire in all maps and lists",
+        protocol: rpcv2Cbor,
+        method: "POST",
+        uri: "/service/RpcV2Protocol/operation/RpcV2CborSparseMaps",
+        body: "v29zcGFyc2VOdW1iZXJNYXC/YXgA/3BzcGFyc2VCb29sZWFuTWFwv2F49P//"
+        bodyMediaType: "application/cbor",
+        headers: {
+            "smithy-protocol": "rpc-v2-cbor",
+            "Accept": "application/cbor",
+            "Content-Type": "application/cbor"
+        },
+        params: {
+            "sparseNumberMap": {
+                "x": 0
+            },
+            "sparseBooleanMap": {
+                "x": false
+            }
+        }
+    }
+])
+
+apply RpcV2CborSparseMaps @httpResponseTests([
+    {
+        id: "RpcV2CborSparseJsonMaps",
+        documentation: "Deserializes sparse maps",
+        protocol: rpcv2Cbor,
+        code: 200,
+        body: "v29zcGFyc2VTdHJ1Y3RNYXC/Y2Zvb79iaGlldGhlcmX/Y2Jher9iaGljYnll////",
+        bodyMediaType: "application/cbor",
+        headers: {
+            "smithy-protocol": "rpc-v2-cbor",
+            "Content-Type": "application/cbor"
+        },
+        params: {
+            "sparseStructMap": {
+                "foo": {
+                    "hi": "there"
+                },
+                "baz": {
+                    "hi": "bye"
+                }
+            }
+        }
+    },
+    {
+        id: "RpcV2CborDeserializesNullMapValues",
+        documentation: "Deserializes null map values",
+        protocol: rpcv2Cbor,
+        code: 200,
+        body: "v3BzcGFyc2VCb29sZWFuTWFwv2F49v9vc3BhcnNlTnVtYmVyTWFwv2F49v9vc3BhcnNlU3RyaW5nTWFwv2F49v9vc3BhcnNlU3RydWN0TWFwv2F49v//"
+        bodyMediaType: "application/cbor",
+        headers: {
+            "smithy-protocol": "rpc-v2-cbor",
+            "Content-Type": "application/cbor"
+        },
+        params: {
+            "sparseBooleanMap": {
+                "x": null
+            },
+            "sparseNumberMap": {
+                "x": null
+            },
+            "sparseStringMap": {
+                "x": null
+            },
+            "sparseStructMap": {
+                "x": null
+            }
+        }
+    },
+    {
+        id: "RpcV2CborDeserializesSparseSetMap",
+        documentation: "A response that contains a sparse map of sets",
+        protocol: rpcv2Cbor,
+        code: 200,
+        body: "v2xzcGFyc2VTZXRNYXC/YXmfYWFhYv9heJ////8=",
+        bodyMediaType: "application/cbor",
+        headers: {
+            "smithy-protocol": "rpc-v2-cbor",
+            "Content-Type": "application/cbor"
+        },
+        params: {
+            "sparseSetMap": {
+                "x": [],
+                "y": ["a", "b"]
+            }
+        }
+    },
+    {
+        id: "RpcV2CborDeserializesSparseSetMapAndRetainsNull",
+        documentation: "A response that contains a sparse map of sets with a null",
+        protocol: rpcv2Cbor,
+        code: 200,
+        body: "v2xzcGFyc2VTZXRNYXC/YXif/2F5n2FhYWL/YXr2//8=",
+        bodyMediaType: "application/cbor",
+        headers: {
+            "smithy-protocol": "rpc-v2-cbor",
+            "Content-Type": "application/cbor"
+        },
+        params: {
+            "sparseSetMap": {
+                "x": [],
+                "y": ["a", "b"],
+                "z": null
+            }
+        }
+    },
+    {
+        id: "RpcV2CborDeserializesZeroValuesInSparseMaps",
+        documentation: "Ensure that 0 and false are sent over the wire in all maps and lists",
+        protocol: rpcv2Cbor,
+        code: 200,
+        body: "v29zcGFyc2VOdW1iZXJNYXC/YXgA/3BzcGFyc2VCb29sZWFuTWFwv2F49P//"
+        bodyMediaType: "application/cbor",
+        headers: {
+            "smithy-protocol": "rpc-v2-cbor",
+            "Content-Type": "application/cbor"
+        },
+        params: {
+            "sparseNumberMap": {
+                "x": 0
+            },
+            "sparseBooleanMap": {
+                "x": false
+            }
+        }
+    }
+])
+
+operation RpcV2CborSparseMaps {
+    input: RpcV2CborSparseMapsInputOutput
+    output: RpcV2CborSparseMapsInputOutput
+    errors: [ValidationException]
+}
+
+structure RpcV2CborSparseMapsInputOutput {
+    sparseStructMap: SparseStructMap,
+    sparseNumberMap: SparseNumberMap,
+    sparseBooleanMap: SparseBooleanMap,
+    sparseStringMap: SparseStringMap,
+    sparseSetMap: SparseSetMap,
+}
+
+@sparse
+map SparseStructMap {
+    key: String,
+    value: GreetingStruct
+}
+
+@sparse
+map SparseBooleanMap {
+    key: String,
+    value: Boolean
+}
+
+@sparse
+map SparseNumberMap {
+    key: String,
+    value: Integer
+}
+
+@sparse
+map SparseSetMap {
+    key: String,
+    value: StringSet
+}
diff --git a/smithy-protocol-tests/model/rpcv2Cbor/cbor-structs.smithy b/smithy-protocol-tests/model/rpcv2Cbor/cbor-structs.smithy
new file mode 100644
index 000000000..effaefc00
--- /dev/null
+++ b/smithy-protocol-tests/model/rpcv2Cbor/cbor-structs.smithy
@@ -0,0 +1,540 @@
+$version: "2.0"
+
+namespace smithy.protocoltests.rpcv2Cbor
+
+use smithy.protocols#rpcv2Cbor
+use smithy.test#httpRequestTests
+use smithy.test#httpResponseTests
+
+@httpRequestTests([
+    {
+        id: "RpcV2CborSimpleScalarProperties",
+        protocol: rpcv2Cbor,
+        documentation: "Serializes simple scalar properties",
+        headers: {
+            "smithy-protocol": "rpc-v2-cbor",
+            "Accept": "application/cbor",
+            "Content-Type": "application/cbor"
+        }
+        method: "POST",
+        bodyMediaType: "application/cbor",
+        uri: "/service/RpcV2Protocol/operation/SimpleScalarProperties",
+        body: "v2lieXRlVmFsdWUFa2RvdWJsZVZhbHVl+z/+OVgQYk3TcWZhbHNlQm9vbGVhblZhbHVl9GpmbG9hdFZhbHVl+kDz989saW50ZWdlclZhbHVlGQEAaWxvbmdWYWx1ZRkmkWpzaG9ydFZhbHVlGSaqa3N0cmluZ1ZhbHVlZnNpbXBsZXB0cnVlQm9vbGVhblZhbHVl9WlibG9iVmFsdWVDZm9v/w=="
+        params: {
+            byteValue: 5,
+            doubleValue: 1.889,
+            falseBooleanValue: false,
+            floatValue: 7.624,
+            integerValue: 256,
+            longValue: 9873,
+            shortValue: 9898,
+            stringValue: "simple",
+            trueBooleanValue: true,
+            blobValue: "foo"
+        }
+    },
+    {
+        id: "RpcV2CborSimpleScalarPropertiesUsingIndefiniteLength",
+        protocol: rpcv2Cbor,
+        documentation: """
+            The server should be capable of deserializing simple scalar properties
+            encoded using a map with a definite length. The server should also be able to parse
+            a key encoded using an indefinite length string.""",
+        headers: {
+            "smithy-protocol": "rpc-v2-cbor",
+            "Accept": "application/cbor",
+            "Content-Type": "application/cbor"
+        }
+        method: "POST",
+        bodyMediaType: "application/cbor",
+        uri: "/service/RpcV2Protocol/operation/SimpleScalarProperties",
+        body: "qmlieXRlVmFsdWUFf2Zkb3VibGVlVmFsdWX/+z/+OVgQYk3Tf2VmYWxzZWdCb29sZWFuZVZhbHVl//RqZmxvYXRWYWx1ZfpA8/fPbGludGVnZXJWYWx1ZRkBAGlsb25nVmFsdWUZJpFqc2hvcnRWYWx1ZRkmqn9mc3RyaW5nZVZhbHVl/2ZzaW1wbGVwdHJ1ZUJvb2xlYW5WYWx1ZfVpYmxvYlZhbHVlQ2Zvbw=="
+        params: {
+            byteValue: 5,
+            doubleValue: 1.889,
+            falseBooleanValue: false,
+            floatValue: 7.624,
+            integerValue: 256,
+            longValue: 9873,
+            shortValue: 9898,
+            stringValue: "simple",
+            trueBooleanValue: true,
+            blobValue: "foo"
+        },
+        appliesTo: "server"
+    },
+    {
+        id: "RpcV2CborClientDoesntSerializeNullStructureValues",
+        documentation: "RpcV2 Cbor should not serialize null structure values",
+        protocol: rpcv2Cbor,
+        method: "POST",
+        uri: "/service/RpcV2Protocol/operation/SimpleScalarProperties",
+        body: "v/8=",
+        bodyMediaType: "application/cbor",
+        headers: {
+            "smithy-protocol": "rpc-v2-cbor",
+            "Accept": "application/cbor",
+            "Content-Type": "application/cbor"
+        }
+        params: {
+            stringValue: null
+        },
+        appliesTo: "client"
+    },
+    {
+        id: "RpcV2CborServerDoesntDeSerializeNullStructureValues",
+        documentation: "RpcV2 Cbor should not deserialize null structure values",
+        protocol: rpcv2Cbor,
+        method: "POST",
+        uri: "/service/RpcV2Protocol/operation/SimpleScalarProperties",
+        body: "v2tzdHJpbmdWYWx1Zfb/",
+        bodyMediaType: "application/cbor",
+        headers: {
+            "smithy-protocol": "rpc-v2-cbor",
+            "Accept": "application/cbor",
+            "Content-Type": "application/cbor"
+        }
+        params: {},
+        appliesTo: "server"
+    },
+    {
+        id: "RpcV2CborSupportsNaNFloatInputs",
+        protocol: rpcv2Cbor,
+        documentation: "Supports handling NaN float values.",
+        headers: {
+            "smithy-protocol": "rpc-v2-cbor",
+            "Accept": "application/cbor",
+            "Content-Type": "application/cbor"
+        }
+        method: "POST",
+        bodyMediaType: "application/cbor",
+        uri: "/service/RpcV2Protocol/operation/SimpleScalarProperties",
+        body: "v2tkb3VibGVWYWx1Zft/+AAAAAAAAGpmbG9hdFZhbHVl+n/AAAD/"
+        params: {
+            doubleValue: "NaN",
+            floatValue: "NaN"
+        }
+    },
+    {
+        id: "RpcV2CborSupportsInfinityFloatInputs",
+        protocol: rpcv2Cbor,
+        documentation: "Supports handling Infinity float values.",
+        headers: {
+            "smithy-protocol": "rpc-v2-cbor",
+            "Accept": "application/cbor",
+            "Content-Type": "application/cbor"
+        }
+        method: "POST",
+        bodyMediaType: "application/cbor",
+        uri: "/service/RpcV2Protocol/operation/SimpleScalarProperties",
+        body: "v2tkb3VibGVWYWx1Zft/8AAAAAAAAGpmbG9hdFZhbHVl+n+AAAD/"
+        params: {
+            doubleValue: "Infinity",
+            floatValue: "Infinity"
+        }
+    },
+    {
+        id: "RpcV2CborSupportsNegativeInfinityFloatInputs",
+        protocol: rpcv2Cbor,
+        documentation: "Supports handling Infinity float values.",
+        headers: {
+            "smithy-protocol": "rpc-v2-cbor",
+            "Accept": "application/cbor",
+            "Content-Type": "application/cbor"
+        }
+        method: "POST",
+        bodyMediaType: "application/cbor",
+        uri: "/service/RpcV2Protocol/operation/SimpleScalarProperties",
+        body: "v2tkb3VibGVWYWx1Zfv/8AAAAAAAAGpmbG9hdFZhbHVl+v+AAAD/"
+        params: {
+            doubleValue: "-Infinity",
+            floatValue: "-Infinity"
+        }
+    },
+    {
+        id: "RpcV2CborIndefiniteLengthStringsCanBeDeserialized",
+        protocol: rpcv2Cbor,
+        documentation: "The server should be capable of deserializing indefinite length text strings.",
+        headers: {
+            "smithy-protocol": "rpc-v2-cbor",
+            "Accept": "application/cbor",
+            "Content-Type": "application/cbor"
+        }
+        method: "POST",
+        bodyMediaType: "application/cbor",
+        uri: "/service/RpcV2Protocol/operation/SimpleScalarProperties",
+        body: "oWtzdHJpbmdWYWx1ZX94HUFuIGV4YW1wbGUgaW5kZWZpbml0ZSBzdHJpbmcscSBjaHVua2VkIG9uIGNvbW1h/w=="
+        params: {
+            stringValue: "An example indefinite string, chunked on comma"
+        },
+        appliesTo: "server"
+    },
+    {
+        id: "RpcV2CborIndefiniteLengthByteStringsCanBeDeserialized",
+        protocol: rpcv2Cbor,
+        documentation: "The server should be capable of deserializing indefinite length byte strings.",
+        headers: {
+            "smithy-protocol": "rpc-v2-cbor",
+            "Accept": "application/cbor",
+            "Content-Type": "application/cbor"
+        }
+        method: "POST",
+        bodyMediaType: "application/cbor",
+        uri: "/service/RpcV2Protocol/operation/SimpleScalarProperties",
+        body: "oWlibG9iVmFsdWVfWCJBbiBleGFtcGxlIGluZGVmaW5pdGUtYnl0ZSBzdHJpbmcsUSBjaHVua2VkIG9uIGNvbW1h/w=="
+        params: {
+            blobValue: "An example indefinite-byte string, chunked on comma"
+        },
+        appliesTo: "server"
+    },
+    {
+        id: "RpcV2CborSupportsUpcastingData",
+        protocol: rpcv2Cbor,
+        documentation: "Supports upcasting from a smaller byte representation of the same data type.",
+        headers: {
+            "smithy-protocol": "rpc-v2-cbor",
+            "Accept": "application/cbor",
+            "Content-Type": "application/cbor"
+        }
+        method: "POST",
+        bodyMediaType: "application/cbor",
+        uri: "/service/RpcV2Protocol/operation/SimpleScalarProperties",
+        body: "v2tkb3VibGVWYWx1Zfk+AGpmbG9hdFZhbHVl+UegbGludGVnZXJWYWx1ZRg4aWxvbmdWYWx1ZRkBAGpzaG9ydFZhbHVlCv8="
+        params: {
+            doubleValue: 1.5,
+            floatValue: 7.625,
+            integerValue: 56,
+            longValue: 256,
+            shortValue: 10
+        },
+        appliesTo: "server"
+    },
+    {
+        id: "RpcV2CborExtraFieldsInTheBodyShouldBeSkippedByServers",
+        protocol: rpcv2Cbor,
+        documentation: """
+            The server should skip over additional fields that are not part of the structure. This allows a
+            client generated against a newer Smithy model to be able to communicate with a server that is
+            generated against an older Smithy model.""",
+        headers: {
+            "smithy-protocol": "rpc-v2-cbor",
+            "Accept": "application/cbor",
+            "Content-Type": "application/cbor"
+        }
+        method: "POST",
+        bodyMediaType: "application/cbor",
+        uri: "/service/RpcV2Protocol/operation/SimpleScalarProperties",
+        body: "v2lieXRlVmFsdWUFa2RvdWJsZVZhbHVl+z/+OVgQYk3TcWZhbHNlQm9vbGVhblZhbHVl9GpmbG9hdFZhbHVl+kDz989rZXh0cmFPYmplY3S/c2luZGVmaW5pdGVMZW5ndGhNYXC/a3dpdGhBbkFycmF5nwECA///cWRlZmluaXRlTGVuZ3RoTWFwo3J3aXRoQURlZmluaXRlQXJyYXmDAQIDeB1hbmRTb21lSW5kZWZpbml0ZUxlbmd0aFN0cmluZ3gfdGhhdCBoYXMsIGJlZW4gY2h1bmtlZCBvbiBjb21tYWxub3JtYWxTdHJpbmdjZm9vanNob3J0VmFsdWUZJw9uc29tZU90aGVyRmllbGR2dGhpcyBzaG91bGQgYmUgc2tpcHBlZP9saW50ZWdlclZhbHVlGQEAaWxvbmdWYWx1ZRkmkWpzaG9ydFZhbHVlGSaqa3N0cmluZ1ZhbHVlZnNpbXBsZXB0cnVlQm9vbGVhblZhbHVl9WlibG9iVmFsdWVDZm9v/w==",
+        params: {
+            byteValue: 5,
+            doubleValue: 1.889,
+            falseBooleanValue: false,
+            floatValue: 7.624,
+            integerValue: 256,
+            longValue: 9873,
+            shortValue: 9898,
+            stringValue: "simple",
+            trueBooleanValue: true,
+            blobValue: "foo"
+        },
+        appliesTo: "server"
+    }
+])
+@httpResponseTests([
+    {
+        id: "RpcV2CborSimpleScalarProperties",
+        protocol: rpcv2Cbor,
+        documentation: "Serializes simple scalar properties",
+        headers: {
+            "smithy-protocol": "rpc-v2-cbor",
+            "Content-Type": "application/cbor"
+        }
+        bodyMediaType: "application/cbor",
+        body: "v3B0cnVlQm9vbGVhblZhbHVl9XFmYWxzZUJvb2xlYW5WYWx1ZfRpYnl0ZVZhbHVlBWtkb3VibGVWYWx1Zfs//jlYEGJN02pmbG9hdFZhbHVl+kDz989saW50ZWdlclZhbHVlGQEAanNob3J0VmFsdWUZJqprc3RyaW5nVmFsdWVmc2ltcGxlaWJsb2JWYWx1ZUNmb2//"
+        code: 200,
+        params: {
+            trueBooleanValue: true,
+            falseBooleanValue: false,
+            byteValue: 5,
+            doubleValue: 1.889,
+            floatValue: 7.624,
+            integerValue: 256,
+            shortValue: 9898,
+            stringValue: "simple",
+            blobValue: "foo"
+        }
+    },
+    {
+        id: "RpcV2CborSimpleScalarPropertiesUsingDefiniteLength",
+        protocol: rpcv2Cbor,
+        documentation: "Deserializes simple scalar properties encoded using a map with definite length",
+        headers: {
+            "smithy-protocol": "rpc-v2-cbor",
+            "Content-Type": "application/cbor"
+        }
+        bodyMediaType: "application/cbor",
+        body: "qXB0cnVlQm9vbGVhblZhbHVl9XFmYWxzZUJvb2xlYW5WYWx1ZfRpYnl0ZVZhbHVlBWtkb3VibGVWYWx1Zfs//jlYEGJN02pmbG9hdFZhbHVl+kDz989saW50ZWdlclZhbHVlGQEAanNob3J0VmFsdWUZJqprc3RyaW5nVmFsdWVmc2ltcGxlaWJsb2JWYWx1ZUNmb28="
+        code: 200,
+        params: {
+            trueBooleanValue: true,
+            falseBooleanValue: false,
+            byteValue: 5,
+            doubleValue: 1.889,
+            floatValue: 7.624,
+            integerValue: 256,
+            shortValue: 9898,
+            stringValue: "simple",
+            blobValue: "foo"
+        },
+        appliesTo: "client"
+    },
+    {
+        id: "RpcV2CborClientDoesntDeserializeNullStructureValues",
+        documentation: "RpcV2 Cbor should not deserialize null structure values",
+        protocol: rpcv2Cbor,
+        body: "v2tzdHJpbmdWYWx1Zfb/",
+        code: 200,
+        bodyMediaType: "application/cbor",
+        headers: {
+            "smithy-protocol": "rpc-v2-cbor",
+            "Content-Type": "application/cbor"
+        }
+        params: {}
+        appliesTo: "client"
+    },
+    {
+        id: "RpcV2CborServerDoesntSerializeNullStructureValues",
+        documentation: "RpcV2 Cbor should not serialize null structure values",
+        protocol: rpcv2Cbor,
+        body: "v/8=",
+        code: 200,
+        bodyMediaType: "application/cbor",
+        headers: {
+            "smithy-protocol": "rpc-v2-cbor",
+            "Content-Type": "application/cbor"
+        }
+        params: {
+            stringValue: null
+        },
+        appliesTo: "server"
+    },
+    {
+        id: "RpcV2CborSupportsNaNFloatOutputs",
+        protocol: rpcv2Cbor,
+        documentation: "Supports handling NaN float values.",
+        headers: {
+            "smithy-protocol": "rpc-v2-cbor",
+            "Content-Type": "application/cbor"
+        },
+        code: 200,
+        bodyMediaType: "application/cbor",
+        body: "v2tkb3VibGVWYWx1Zft/+AAAAAAAAGpmbG9hdFZhbHVl+n/AAAD/"
+        params: {
+            doubleValue: "NaN",
+            floatValue: "NaN"
+        }
+    },
+    {
+        id: "RpcV2CborSupportsInfinityFloatOutputs",
+        protocol: rpcv2Cbor,
+        documentation: "Supports handling Infinity float values.",
+        headers: {
+            "smithy-protocol": "rpc-v2-cbor",
+            "Content-Type": "application/cbor"
+        },
+        code: 200,
+        bodyMediaType: "application/cbor",
+        body: "v2tkb3VibGVWYWx1Zft/8AAAAAAAAGpmbG9hdFZhbHVl+n+AAAD/"
+        params: {
+            doubleValue: "Infinity",
+            floatValue: "Infinity"
+        }
+    },
+    {
+        id: "RpcV2CborSupportsNegativeInfinityFloatOutputs",
+        protocol: rpcv2Cbor,
+        documentation: "Supports handling Negative Infinity float values.",
+        headers: {
+            "smithy-protocol": "rpc-v2-cbor",
+            "Content-Type": "application/cbor"
+        },
+        code: 200,
+        bodyMediaType: "application/cbor",
+        body: "v2tkb3VibGVWYWx1Zfv/8AAAAAAAAGpmbG9hdFZhbHVl+v+AAAD/"
+        params: {
+            doubleValue: "-Infinity",
+            floatValue: "-Infinity"
+        }
+    },
+    {
+        id: "RpcV2CborSupportsUpcastingDataOnDeserialize",
+        protocol: rpcv2Cbor,
+        documentation: "Supports upcasting from a smaller byte representation of the same data type.",
+        headers: {
+            "smithy-protocol": "rpc-v2-cbor",
+            "Content-Type": "application/cbor"
+        },
+        code: 200,
+        bodyMediaType: "application/cbor",
+        body: "v2tkb3VibGVWYWx1Zfk+AGpmbG9hdFZhbHVl+UegbGludGVnZXJWYWx1ZRg4aWxvbmdWYWx1ZRkBAGpzaG9ydFZhbHVlCv8="
+        params: {
+            doubleValue: 1.5,
+            floatValue: 7.625,
+            integerValue: 56,
+            longValue: 256,
+            shortValue: 10
+        },
+        appliesTo: "client"
+    },
+    {
+        id: "RpcV2CborExtraFieldsInTheBodyShouldBeSkippedByClients",
+        protocol: rpcv2Cbor,
+        documentation: """
+            The client should skip over additional fields that are not part of the structure. This allows a
+            client generated against an older Smithy model to be able to communicate with a server that is
+            generated against a newer Smithy model.""",
+        headers: {
+            "smithy-protocol": "rpc-v2-cbor",
+            "Content-Type": "application/cbor"
+        }
+        code: 200,
+        bodyMediaType: "application/cbor",
+        body: "v2lieXRlVmFsdWUFa2RvdWJsZVZhbHVl+z/+OVgQYk3TcWZhbHNlQm9vbGVhblZhbHVl9GpmbG9hdFZhbHVl+kDz989rZXh0cmFPYmplY3S/c2luZGVmaW5pdGVMZW5ndGhNYXC/a3dpdGhBbkFycmF5nwECA///cWRlZmluaXRlTGVuZ3RoTWFwo3J3aXRoQURlZmluaXRlQXJyYXmDAQIDeB1hbmRTb21lSW5kZWZpbml0ZUxlbmd0aFN0cmluZ3gfdGhhdCBoYXMsIGJlZW4gY2h1bmtlZCBvbiBjb21tYWxub3JtYWxTdHJpbmdjZm9vanNob3J0VmFsdWUZJw9uc29tZU90aGVyRmllbGR2dGhpcyBzaG91bGQgYmUgc2tpcHBlZP9saW50ZWdlclZhbHVlGQEAaWxvbmdWYWx1ZRkmkWpzaG9ydFZhbHVlGSaqa3N0cmluZ1ZhbHVlZnNpbXBsZXB0cnVlQm9vbGVhblZhbHVl9WlibG9iVmFsdWVDZm9v/w==",
+        params: {
+            byteValue: 5,
+            doubleValue: 1.889,
+            falseBooleanValue: false,
+            floatValue: 7.624,
+            integerValue: 256,
+            longValue: 9873,
+            shortValue: 9898,
+            stringValue: "simple",
+            trueBooleanValue: true,
+            blobValue: "foo"
+        },
+        appliesTo: "client"
+    }
+])
+operation SimpleScalarProperties {
+    input: SimpleScalarStructure,
+    output: SimpleScalarStructure
+}
+
+apply RecursiveShapes @httpRequestTests([
+    {
+        id: "RpcV2CborRecursiveShapes",
+        documentation: "Serializes recursive structures",
+        protocol: rpcv2Cbor,
+        method: "POST",
+        uri: "/service/RpcV2Protocol/operation/RecursiveShapes",
+        body: "v2ZuZXN0ZWS/Y2Zvb2RGb28xZm5lc3RlZL9jYmFyZEJhcjFvcmVjdXJzaXZlTWVtYmVyv2Nmb29kRm9vMmZuZXN0ZWS/Y2JhcmRCYXIy//////8=",
+        bodyMediaType: "application/cbor",
+        headers: {
+            "smithy-protocol": "rpc-v2-cbor",
+            "Accept": "application/cbor",
+            "Content-Type": "application/cbor"
+        },
+        params: {
+            nested: {
+                foo: "Foo1",
+                nested: {
+                    bar: "Bar1",
+                    recursiveMember: {
+                        foo: "Foo2",
+                        nested: {
+                            bar: "Bar2"
+                        }
+                    }
+                }
+            }
+        }
+    }
+])
+
+apply RecursiveShapes @httpResponseTests([
+    {
+        id: "RpcV2CborRecursiveShapes",
+        documentation: "Serializes recursive structures",
+        protocol: rpcv2Cbor,
+        code: 200,
+        body: "v2ZuZXN0ZWS/Y2Zvb2RGb28xZm5lc3RlZL9jYmFyZEJhcjFvcmVjdXJzaXZlTWVtYmVyv2Nmb29kRm9vMmZuZXN0ZWS/Y2JhcmRCYXIy//////8=",
+        bodyMediaType: "application/cbor",
+        headers: {
+            "smithy-protocol": "rpc-v2-cbor",
+            "Content-Type": "application/cbor"
+        },
+        params: {
+            nested: {
+                foo: "Foo1",
+                nested: {
+                    bar: "Bar1",
+                    recursiveMember: {
+                        foo: "Foo2",
+                        nested: {
+                            bar: "Bar2"
+                        }
+                    }
+                }
+            }
+        }
+    },
+    {
+        id: "RpcV2CborRecursiveShapesUsingDefiniteLength",
+        documentation: "Deserializes recursive structures encoded using a map with definite length",
+        protocol: rpcv2Cbor,
+        code: 200,
+        body: "oWZuZXN0ZWSiY2Zvb2RGb28xZm5lc3RlZKJjYmFyZEJhcjFvcmVjdXJzaXZlTWVtYmVyomNmb29kRm9vMmZuZXN0ZWShY2JhcmRCYXIy"
+        bodyMediaType: "application/cbor",
+        headers: {
+            "smithy-protocol": "rpc-v2-cbor",
+            "Content-Type": "application/cbor"
+        },
+        params: {
+            nested: {
+                foo: "Foo1",
+                nested: {
+                    bar: "Bar1",
+                    recursiveMember: {
+                        foo: "Foo2",
+                        nested: {
+                            bar: "Bar2"
+                        }
+                    }
+                }
+            }
+        },
+        appliesTo: "client"
+    }
+])
+
+operation RecursiveShapes {
+    input: RecursiveShapesInputOutput,
+    output: RecursiveShapesInputOutput
+}
+
+structure RecursiveShapesInputOutput {
+    nested: RecursiveShapesInputOutputNested1
+}
+
+structure RecursiveShapesInputOutputNested1 {
+    foo: String,
+    nested: RecursiveShapesInputOutputNested2
+}
+
+structure RecursiveShapesInputOutputNested2 {
+    bar: String,
+    recursiveMember: RecursiveShapesInputOutputNested1,
+}
+
+structure SimpleScalarStructure {
+    trueBooleanValue: Boolean,
+    falseBooleanValue: Boolean,
+    byteValue: Byte,
+    doubleValue: Double,
+    floatValue: Float,
+    integerValue: Integer,
+    longValue: Long,
+    shortValue: Short,
+    stringValue: String,
+    blobValue: Blob
+}
diff --git a/smithy-protocol-tests/model/rpcv2Cbor/defaults.smithy b/smithy-protocol-tests/model/rpcv2Cbor/defaults.smithy
new file mode 100644
index 000000000..8d2296442
--- /dev/null
+++ b/smithy-protocol-tests/model/rpcv2Cbor/defaults.smithy
@@ -0,0 +1,338 @@
+$version: "2.0"
+
+namespace smithy.protocoltests.rpcv2Cbor
+
+use smithy.protocols#rpcv2Cbor
+use smithy.test#httpRequestTests
+use smithy.test#httpResponseTests
+use smithy.framework#ValidationException
+
+apply OperationWithDefaults @httpRequestTests([
+    {
+        id: "RpcV2CborClientPopulatesDefaultValuesInInput"
+        documentation: "Client populates default values in input."
+        protocol: rpcv2Cbor
+        appliesTo: "client"
+        tags: ["defaults"]
+        method: "POST"
+        uri: "/service/RpcV2Protocol/operation/OperationWithDefaults",
+        headers: {
+            "smithy-protocol": "rpc-v2-cbor",
+            "Accept": "application/cbor",
+            "Content-Type": "application/cbor"
+        },
+        bodyMediaType: "application/cbor"
+        body: "v21kZWZhdWx0U3RyaW5nYmhpbmRlZmF1bHRCb29sZWFu9WtkZWZhdWx0TGlzdIBwZGVmYXVsdFRpbWVzdGFtcMH7AAAAAAAAAABrZGVmYXVsdEJsb2JDYWJja2RlZmF1bHRCeXRlAWxkZWZhdWx0U2hvcnQBbmRlZmF1bHRJbnRlZ2VyCmtkZWZhdWx0TG9uZxhkbGRlZmF1bHRGbG9hdPo/gAAAbWRlZmF1bHREb3VibGX7P/AAAAAAAABqZGVmYXVsdE1hcKBrZGVmYXVsdEVudW1jRk9PbmRlZmF1bHRJbnRFbnVtAWtlbXB0eVN0cmluZ2BsZmFsc2VCb29sZWFu9GllbXB0eUJsb2JAaHplcm9CeXRlAGl6ZXJvU2hvcnQAa3plcm9JbnRlZ2VyAGh6ZXJvTG9uZwBpemVyb0Zsb2F0+gAAAABqemVyb0RvdWJsZfsAAAAAAAAAAP8="
+        params: {
+            defaults: {}
+        }
+    }
+    {
+        id: "RpcV2CborClientSkipsTopLevelDefaultValuesInInput"
+        documentation: "Client skips top level default values in input."
+        appliesTo: "client"
+        tags: ["defaults"]
+        protocol: rpcv2Cbor
+        method: "POST"
+        bodyMediaType: "application/cbor"
+        uri: "/service/RpcV2Protocol/operation/OperationWithDefaults",
+        headers: {
+            "smithy-protocol": "rpc-v2-cbor",
+            "Accept": "application/cbor",
+            "Content-Type": "application/cbor"
+        },
+        body: "v/8="
+        params: {
+        }
+    }
+    {
+        id: "RpcV2CborClientUsesExplicitlyProvidedMemberValuesOverDefaults"
+        documentation: "Client uses explicitly provided member values over defaults"
+        appliesTo: "client"
+        tags: ["defaults"]
+        protocol: rpcv2Cbor
+        method: "POST"
+        bodyMediaType: "application/cbor"
+        uri: "/service/RpcV2Protocol/operation/OperationWithDefaults",
+        headers: {
+            "smithy-protocol": "rpc-v2-cbor",
+            "Accept": "application/cbor",
+            "Content-Type": "application/cbor"
+        },
+        body: "v2hkZWZhdWx0c7dtZGVmYXVsdFN0cmluZ2NieWVuZGVmYXVsdEJvb2xlYW71a2RlZmF1bHRMaXN0gWFhcGRlZmF1bHRUaW1lc3RhbXDB+z/wAAAAAAAAa2RlZmF1bHRCbG9iQmhpa2RlZmF1bHRCeXRlAmxkZWZhdWx0U2hvcnQCbmRlZmF1bHRJbnRlZ2VyFGtkZWZhdWx0TG9uZxjIbGRlZmF1bHRGbG9hdPpAAAAAbWRlZmF1bHREb3VibGX7QAAAAAAAAABqZGVmYXVsdE1hcKFkbmFtZWRKYWNra2RlZmF1bHRFbnVtY0JBUm5kZWZhdWx0SW50RW51bQJrZW1wdHlTdHJpbmdjZm9vbGZhbHNlQm9vbGVhbvVpZW1wdHlCbG9iQmhpaHplcm9CeXRlAWl6ZXJvU2hvcnQBa3plcm9JbnRlZ2VyAWh6ZXJvTG9uZwFpemVyb0Zsb2F0+j+AAABqemVyb0RvdWJsZfs/8AAAAAAAAP8="
+        params: {
+            defaults: {
+                defaultString: "bye",
+                defaultBoolean: true,
+                defaultList: ["a"],
+                defaultTimestamp: 1,
+                defaultBlob: "hi",
+                defaultByte: 2,
+                defaultShort: 2,
+                defaultInteger: 20,
+                defaultLong: 200,
+                defaultFloat: 2.0,
+                defaultDouble: 2.0,
+                defaultMap: {name: "Jack"},
+                defaultEnum: "BAR",
+                defaultIntEnum: 2,
+                emptyString: "foo",
+                falseBoolean: true,
+                emptyBlob: "hi",
+                zeroByte: 1,
+                zeroShort: 1,
+                zeroInteger: 1,
+                zeroLong: 1,
+                zeroFloat: 1.0,
+                zeroDouble: 1.0
+            }
+        }
+    }
+    {
+        id: "RpcV2CborServerPopulatesDefaultsWhenMissingInRequestBody"
+        documentation: "Server populates default values when missing in request body."
+        appliesTo: "server"
+        tags: ["defaults"]
+        protocol: rpcv2Cbor
+        method: "POST"
+        bodyMediaType: "application/cbor"
+        uri: "/service/RpcV2Protocol/operation/OperationWithDefaults",
+        headers: {
+            "smithy-protocol": "rpc-v2-cbor",
+            "Accept": "application/cbor",
+            "Content-Type": "application/cbor"
+        },
+        body: "v2hkZWZhdWx0c6D/"
+        params: {
+            defaults: {
+                defaultString: "hi",
+                defaultBoolean: true,
+                defaultList: [],
+                defaultTimestamp: 0,
+                defaultBlob: "abc",
+                defaultByte: 1,
+                defaultShort: 1,
+                defaultInteger: 10,
+                defaultLong: 100,
+                defaultFloat: 1.0,
+                defaultDouble: 1.0,
+                defaultMap: {},
+                defaultEnum: "FOO",
+                defaultIntEnum: 1,
+                emptyString: "",
+                falseBoolean: false,
+                emptyBlob: "",
+                zeroByte: 0,
+                zeroShort: 0,
+                zeroInteger: 0,
+                zeroLong: 0,
+                zeroFloat: 0.0,
+                zeroDouble: 0.0
+            },
+            topLevelDefault: "hi",
+            otherTopLevelDefault: 0
+        }
+    }
+    {
+        id: "RpcV2CborClientUsesExplicitlyProvidedValuesInTopLevel"
+        documentation: "Any time a value is provided for a member in the top level of input, it is used, regardless of if its the default."
+        appliesTo: "client"
+        tags: ["defaults"]
+        protocol: rpcv2Cbor
+        method: "POST"
+        bodyMediaType: "application/cbor"
+        uri: "/service/RpcV2Protocol/operation/OperationWithDefaults",
+        headers: {
+            "smithy-protocol": "rpc-v2-cbor",
+            "Accept": "application/cbor",
+            "Content-Type": "application/cbor"
+        },
+        body: "v290b3BMZXZlbERlZmF1bHRiaGl0b3RoZXJUb3BMZXZlbERlZmF1bHQA/w=="
+        params: {
+            topLevelDefault: "hi",
+            otherTopLevelDefault: 0
+        }
+    }
+    {
+        id: "RpcV2CborClientIgnoresNonTopLevelDefaultsOnMembersWithClientOptional"
+        documentation: "Typically, non top-level members would have defaults filled in, but if they have the clientOptional trait, the defaults should be ignored."
+        appliesTo: "client"
+        tags: ["defaults"]
+        protocol: rpcv2Cbor
+        method: "POST"
+        bodyMediaType: "application/cbor"
+        uri: "/service/RpcV2Protocol/operation/OperationWithDefaults",
+        headers: {
+            "smithy-protocol": "rpc-v2-cbor",
+            "Accept": "application/cbor",
+            "Content-Type": "application/cbor"
+        },
+        body: "v3ZjbGllbnRPcHRpb25hbERlZmF1bHRzoP8="
+        params: {
+            clientOptionalDefaults: {}
+        }
+    }
+])
+
+apply OperationWithDefaults @httpResponseTests([
+    {
+        id: "RpcV2CborClientPopulatesDefaultsValuesWhenMissingInResponse"
+        documentation: "Client populates default values when missing in response."
+        appliesTo: "client"
+        tags: ["defaults"]
+        protocol: rpcv2Cbor
+        code: 200
+        bodyMediaType: "application/cbor"
+        headers: {
+            "smithy-protocol": "rpc-v2-cbor",
+            "Content-Type": "application/cbor"
+        },
+        body: "v/8="
+        params: {
+            defaultString: "hi"
+            defaultBoolean: true
+            defaultList: []
+            defaultTimestamp: 0
+            defaultBlob: "abc"
+            defaultByte: 1
+            defaultShort: 1
+            defaultInteger: 10
+            defaultLong: 100
+            defaultFloat: 1.0
+            defaultDouble: 1.0
+            defaultMap: {}
+            defaultEnum: "FOO"
+            defaultIntEnum: 1
+            emptyString: ""
+            falseBoolean: false
+            emptyBlob: ""
+            zeroByte: 0
+            zeroShort: 0
+            zeroInteger: 0
+            zeroLong: 0
+            zeroFloat: 0.0
+            zeroDouble: 0.0
+        }
+    }
+    {
+        id: "RpcV2CborClientIgnoresDefaultValuesIfMemberValuesArePresentInResponse"
+        documentation: "Client ignores default values if member values are present in the response."
+        appliesTo: "client"
+        tags: ["defaults"]
+        protocol: rpcv2Cbor
+        code: 200
+        bodyMediaType: "application/cbor"
+        headers: {
+            "smithy-protocol": "rpc-v2-cbor",
+            "Content-Type": "application/cbor"
+        },
+        body: "v21kZWZhdWx0U3RyaW5nY2J5ZW5kZWZhdWx0Qm9vbGVhbvRrZGVmYXVsdExpc3SBYWFwZGVmYXVsdFRpbWVzdGFtcMH7QAAAAAAAAABrZGVmYXVsdEJsb2JCaGlrZGVmYXVsdEJ5dGUCbGRlZmF1bHRTaG9ydAJuZGVmYXVsdEludGVnZXIUa2RlZmF1bHRMb25nGMhsZGVmYXVsdEZsb2F0+kAAAABtZGVmYXVsdERvdWJsZftAAAAAAAAAAGpkZWZhdWx0TWFwoWRuYW1lZEphY2trZGVmYXVsdEVudW1jQkFSbmRlZmF1bHRJbnRFbnVtAmtlbXB0eVN0cmluZ2Nmb29sZmFsc2VCb29sZWFu9WllbXB0eUJsb2JCaGloemVyb0J5dGUBaXplcm9TaG9ydAFremVyb0ludGVnZXIBaHplcm9Mb25nAWl6ZXJvRmxvYXT6P4AAAGp6ZXJvRG91Ymxl+z/wAAAAAAAA/w=="
+        params: {
+            defaultString: "bye",
+            defaultBoolean: false,
+            defaultList: ["a"],
+            defaultTimestamp: 2,
+            defaultBlob: "hi",
+            defaultByte: 2,
+            defaultShort: 2,
+            defaultInteger: 20,
+            defaultLong: 200,
+            defaultFloat: 2.0,
+            defaultDouble: 2.0,
+            defaultMap: {name: "Jack"},
+            defaultEnum: "BAR",
+            defaultIntEnum: 2,
+            emptyString: "foo",
+            falseBoolean: true,
+            emptyBlob: "hi",
+            zeroByte: 1,
+            zeroShort: 1,
+            zeroInteger: 1,
+            zeroLong: 1,
+            zeroFloat: 1.0,
+            zeroDouble: 1.0
+        }
+    }
+    {
+        id: "RpcV2CborServerPopulatesDefaultsInResponseWhenMissingInParams"
+        documentation: "Server populates default values in response when missing in params."
+        appliesTo: "server"
+        tags: ["defaults"]
+        protocol: rpcv2Cbor
+        code: 200
+        bodyMediaType: "application/cbor"
+        headers: {
+            "smithy-protocol": "rpc-v2-cbor",
+            "Content-Type": "application/cbor"
+        },
+        body: "v21kZWZhdWx0U3RyaW5nYmhpbmRlZmF1bHRCb29sZWFu9WtkZWZhdWx0TGlzdIBwZGVmYXVsdFRpbWVzdGFtcMH7AAAAAAAAAABrZGVmYXVsdEJsb2JDYWJja2RlZmF1bHRCeXRlAWxkZWZhdWx0U2hvcnQBbmRlZmF1bHRJbnRlZ2VyCmtkZWZhdWx0TG9uZxhkbGRlZmF1bHRGbG9hdPo/gAAAbWRlZmF1bHREb3VibGX7P/AAAAAAAABqZGVmYXVsdE1hcKBrZGVmYXVsdEVudW1jRk9PbmRlZmF1bHRJbnRFbnVtAWtlbXB0eVN0cmluZ2BsZmFsc2VCb29sZWFu9GllbXB0eUJsb2JAaHplcm9CeXRlAGl6ZXJvU2hvcnQAa3plcm9JbnRlZ2VyAGh6ZXJvTG9uZwBpemVyb0Zsb2F0+gAAAABqemVyb0RvdWJsZfsAAAAAAAAAAP8="
+        params: {}
+    }
+])
+
+operation OperationWithDefaults {
+    input := {
+        defaults: Defaults
+        clientOptionalDefaults: ClientOptionalDefaults
+        topLevelDefault: String = "hi" // Client should ignore default values in input shape
+        otherTopLevelDefault: Integer = 0
+    }
+
+    output := with [DefaultsMixin] {}
+
+    errors: [ValidationException]
+}
+
+structure Defaults with [DefaultsMixin] {}
+
+structure ClientOptionalDefaults {
+    @clientOptional
+    member: Integer = 0
+}
+
+@mixin
+structure DefaultsMixin {
+    defaultString: String = "hi"
+    defaultBoolean: Boolean = true
+    defaultList: TestStringList = []
+    defaultTimestamp: Timestamp = 0
+    defaultBlob: Blob = "abc"
+    defaultByte: Byte = 1
+    defaultShort: Short = 1
+    defaultInteger: Integer = 10
+    defaultLong: Long = 100
+    defaultFloat: Float = 1.0
+    defaultDouble: Double = 1.0
+    defaultMap: TestStringMap = {}
+    defaultEnum: TestEnum = "FOO"
+    defaultIntEnum: TestIntEnum = 1
+    emptyString: String = ""
+    falseBoolean: Boolean = false
+    emptyBlob: Blob = ""
+    zeroByte: Byte = 0
+    zeroShort: Short = 0
+    zeroInteger: Integer = 0
+    zeroLong: Long = 0
+    zeroFloat: Float = 0.0
+    zeroDouble: Double = 0.0
+}
+
+list TestStringList {
+    member: String
+}
+
+map TestStringMap {
+    key: String
+    value: String
+}
+
+enum TestEnum {
+    FOO
+    BAR
+    BAZ
+}
+
+intEnum TestIntEnum {
+    ONE = 1
+    TWO = 2
+}
diff --git a/smithy-protocol-tests/model/rpcv2Cbor/empty-input-output.smithy b/smithy-protocol-tests/model/rpcv2Cbor/empty-input-output.smithy
new file mode 100644
index 000000000..f178b9ee0
--- /dev/null
+++ b/smithy-protocol-tests/model/rpcv2Cbor/empty-input-output.smithy
@@ -0,0 +1,207 @@
+$version: "2.0"
+
+namespace smithy.protocoltests.rpcv2Cbor
+
+use smithy.protocols#rpcv2Cbor
+use smithy.test#httpRequestTests
+use smithy.test#httpResponseTests
+
+
+@httpRequestTests([
+    {
+        id: "no_input",
+        protocol: rpcv2Cbor,
+        documentation: "Body is empty and no Content-Type header if no input",
+        headers: {
+            "smithy-protocol": "rpc-v2-cbor",
+            "Accept": "application/cbor",
+        },
+        forbidHeaders: [
+            "Content-Type",
+            "X-Amz-Target"
+        ]
+        method: "POST",
+        uri: "/service/RpcV2Protocol/operation/NoInputOutput",
+        body: ""
+    },
+    {
+        id: "NoInputServerAllowsEmptyCbor",
+        protocol: rpcv2Cbor,
+        documentation: "Servers should accept CBOR empty struct if no input.",
+        headers: {
+            "smithy-protocol": "rpc-v2-cbor",
+            "Accept": "application/cbor",
+            "Content-Type": "application/cbor"
+        }
+        method: "POST",
+        uri: "/service/RpcV2Protocol/operation/NoInputOutput",
+        body: "v/8=",
+        appliesTo: "server"
+    },
+    {
+        id: "NoInputServerAllowsEmptyBody",
+        protocol: rpcv2Cbor,
+        documentation: """
+            Servers should accept an empty body if there is no input. Additionally,
+            they should not raise an error if the `Accept` header is set."""
+        headers: {
+            "smithy-protocol": "rpc-v2-cbor",
+            "Accept": "application/cbor",
+            "Content-Type": "application/cbor"
+        }
+        method: "POST",
+        uri: "/service/RpcV2Protocol/operation/NoInputOutput",
+        body: "",
+        appliesTo: "server"
+    }
+])
+@httpResponseTests([
+    {
+        id: "no_output",
+        protocol: rpcv2Cbor,
+        documentation: "A `Content-Type` header should not be set if the response body is empty.",
+        body: "",
+        bodyMediaType: "application/cbor",
+        headers: {
+            "smithy-protocol": "rpc-v2-cbor",
+        },
+        forbidHeaders: [
+            "Content-Type"
+        ]
+        code: 200,
+    },
+    {
+        id: "NoOutputClientAllowsEmptyCbor",
+        protocol: rpcv2Cbor,
+        documentation: "Clients should accept a CBOR empty struct if there is no output.",
+        headers: {
+            "smithy-protocol": "rpc-v2-cbor",
+            "Content-Type": "application/cbor"
+        }
+        code: 200,
+        bodyMediaType: "application/cbor",
+        body: "v/8=",
+        appliesTo: "client",
+    },
+    {
+        id: "NoOutputClientAllowsEmptyBody",
+        protocol: rpcv2Cbor,
+        documentation: """
+            Clients should accept an empty body if there is no output and
+            should not raise an error if the `Content-Type` header is set.""",
+        headers: {
+            "smithy-protocol": "rpc-v2-cbor",
+            "Content-Type": "application/cbor"
+        }
+        code: 200,
+        bodyMediaType: "application/cbor",
+        body: "",
+        appliesTo: "client",
+    }
+])
+operation NoInputOutput {}
+
+
+@httpRequestTests([
+    {
+        id: "empty_input",
+        protocol: rpcv2Cbor,
+        documentation: "When Input structure is empty we write CBOR equivalent of {}",
+        headers: {
+            "smithy-protocol": "rpc-v2-cbor",
+            "Accept": "application/cbor",
+            "Content-Type": "application/cbor"
+        },
+        forbidHeaders: [
+            "X-Amz-Target"
+        ]
+        method: "POST",
+        uri: "/service/RpcV2Protocol/operation/EmptyInputOutput",
+        bodyMediaType: "application/cbor",
+        body: "v/8=",
+    },
+    {
+        id: "empty_input_no_body",
+        protocol: rpcv2Cbor,
+        documentation: "When Input structure is empty the server should accept an empty body",
+        headers: {
+            "smithy-protocol": "rpc-v2-cbor",
+            "Accept": "application/cbor",
+            "Content-Type": "application/cbor"
+        },
+        method: "POST",
+        uri: "/service/RpcV2Protocol/operation/EmptyInputOutput",
+        bodyMediaType: "application/cbor",
+        body: "",
+        appliesTo: "server"
+    },
+])
+@httpResponseTests([
+    {
+        id: "empty_output",
+        protocol: rpcv2Cbor,
+        documentation: "When output structure is empty we write CBOR equivalent of {}",
+        body: "v/8=",
+        bodyMediaType: "application/cbor",
+        headers: {
+            "smithy-protocol": "rpc-v2-cbor",
+            "Content-Type": "application/cbor"
+        }
+        code: 200,
+    },
+    {
+        id: "empty_output_no_body",
+        protocol: rpcv2Cbor,
+        documentation: "When output structure is empty the client should accept an empty body",
+        body: "",
+        bodyMediaType: "application/cbor",
+        headers: {
+            "smithy-protocol": "rpc-v2-cbor",
+            "Content-Type": "application/cbor"
+        }
+        code: 200,
+        appliesTo: "client"
+    },
+])
+operation EmptyInputOutput {
+    input: EmptyStructure,
+    output: EmptyStructure
+}
+
+@httpRequestTests([
+    {
+        id: "optional_input",
+        protocol: rpcv2Cbor,
+        documentation: "When input is empty we write CBOR equivalent of {}",
+        headers: {
+            "smithy-protocol": "rpc-v2-cbor",
+            "Accept": "application/cbor",
+            "Content-Type": "application/cbor"
+        },
+        forbidHeaders: [
+            "X-Amz-Target"
+        ]
+        method: "POST",
+        uri: "/service/RpcV2Protocol/operation/OptionalInputOutput",
+        bodyMediaType: "application/cbor",
+        body: "v/8=",
+    },
+])
+@httpResponseTests([
+    {
+        id: "optional_output",
+        protocol: rpcv2Cbor,
+        documentation: "When output is empty we write CBOR equivalent of {}",
+        body: "v/8=",
+        bodyMediaType: "application/cbor",
+        headers: {
+            "smithy-protocol": "rpc-v2-cbor",
+            "Content-Type": "application/cbor"
+        }
+        code: 200,
+    },
+])
+operation OptionalInputOutput {
+    input: SimpleStructure,
+    output: SimpleStructure
+}
diff --git a/smithy-protocol-tests/model/rpcv2Cbor/errors.smithy b/smithy-protocol-tests/model/rpcv2Cbor/errors.smithy
new file mode 100644
index 000000000..d9f0c1783
--- /dev/null
+++ b/smithy-protocol-tests/model/rpcv2Cbor/errors.smithy
@@ -0,0 +1,92 @@
+$version: "2.0"
+
+namespace smithy.protocoltests.rpcv2Cbor
+
+use smithy.test#httpRequestTests
+use smithy.test#httpResponseTests
+use smithy.protocols#rpcv2Cbor
+
+/// This operation has three possible return values:
+///
+/// 1. A successful response in the form of GreetingWithErrorsOutput
+/// 2. An InvalidGreeting error.
+/// 3. A ComplexError error.
+///
+/// Implementations must be able to successfully take a response and
+/// properly deserialize successful and error responses.
+@idempotent
+operation GreetingWithErrors {
+    output: GreetingWithErrorsOutput,
+    errors: [InvalidGreeting, ComplexError]
+}
+
+structure GreetingWithErrorsOutput {
+    greeting: String,
+}
+
+/// This error is thrown when an invalid greeting value is provided.
+@error("client")
+structure InvalidGreeting {
+    Message: String,
+}
+
+apply InvalidGreeting @httpResponseTests([
+    {
+        id: "RpcV2CborInvalidGreetingError",
+        documentation: "Parses simple RpcV2 Cbor errors",
+        protocol: rpcv2Cbor,
+        params: {
+            Message: "Hi"
+        },
+        code: 400,
+        headers: {
+            "smithy-protocol": "rpc-v2-cbor",
+            "Content-Type": "application/cbor"
+        },
+        body: "v2ZfX3R5cGV4LnNtaXRoeS5wcm90b2NvbHRlc3RzLnJwY3YyQ2JvciNJbnZhbGlkR3JlZXRpbmdnTWVzc2FnZWJIaf8=",
+        bodyMediaType: "application/cbor",
+    },
+])
+
+/// This error is thrown when a request is invalid.
+@error("client")
+structure ComplexError {
+    TopLevel: String,
+    Nested: ComplexNestedErrorData,
+}
+
+structure ComplexNestedErrorData {
+    Foo: String,
+}
+
+apply ComplexError @httpResponseTests([
+    {
+        id: "RpcV2CborComplexError",
+        documentation: "Parses a complex error with no message member",
+        protocol: rpcv2Cbor,
+        params: {
+            TopLevel: "Top level",
+            Nested: {
+                Foo: "bar"
+            }
+        },
+        code: 400,
+        headers: {
+            "smithy-protocol": "rpc-v2-cbor",
+            "Content-Type": "application/cbor"
+        },
+        body: "v2ZfX3R5cGV4K3NtaXRoeS5wcm90b2NvbHRlc3RzLnJwY3YyQ2JvciNDb21wbGV4RXJyb3JoVG9wTGV2ZWxpVG9wIGxldmVsZk5lc3RlZL9jRm9vY2Jhcv//",
+        bodyMediaType: "application/cbor"
+    },
+    {
+        id: "RpcV2CborEmptyComplexError",
+        protocol: rpcv2Cbor,
+        code: 400,
+        headers: {
+            "smithy-protocol": "rpc-v2-cbor",
+            "Content-Type": "application/cbor"
+        },
+        body: "v2ZfX3R5cGV4K3NtaXRoeS5wcm90b2NvbHRlc3RzLnJwY3YyQ2JvciNDb21wbGV4RXJyb3L/",
+        bodyMediaType: "application/cbor"
+    },
+])
diff --git a/smithy-protocol-tests/model/rpcv2Cbor/fractional-seconds.smithy b/smithy-protocol-tests/model/rpcv2Cbor/fractional-seconds.smithy
new file mode 100644
index 000000000..faad52e90
--- /dev/null
+++ b/smithy-protocol-tests/model/rpcv2Cbor/fractional-seconds.smithy
@@ -0,0 +1,34 @@
+$version: "2.0"
+
+namespace smithy.protocoltests.rpcv2Cbor
+
+use smithy.protocols#rpcv2Cbor
+use smithy.protocoltests.shared#DateTime
+use smithy.test#httpResponseTests
+
+// These tests verify that clients can parse `DateTime` timestamps with fractional seconds.
+@tags(["client-only"])
+operation FractionalSeconds {
+    output: FractionalSecondsOutput
+}
+
+apply FractionalSeconds @httpResponseTests([
+    {
+        id: "RpcV2CborDateTimeWithFractionalSeconds",
+        documentation: "Ensures that clients can correctly parse timestamps with fractional seconds",
+        protocol: rpcv2Cbor,
+        code: 200,
+        body: "v2hkYXRldGltZcH7Qcw32zgPvnf/",
+        headers: {
+            "smithy-protocol": "rpc-v2-cbor",
+            "Content-Type": "application/cbor"
+        },
+        params: { datetime: 946845296.123 }
+        bodyMediaType: "application/cbor",
+        appliesTo: "client"
+    }
+])
+
+structure FractionalSecondsOutput {
+    datetime: DateTime
+}
diff --git a/smithy-protocol-tests/model/rpcv2Cbor/main.smithy b/smithy-protocol-tests/model/rpcv2Cbor/main.smithy
new file mode 100644
index 000000000..9d10e723e
--- /dev/null
+++ b/smithy-protocol-tests/model/rpcv2Cbor/main.smithy
@@ -0,0 +1,35 @@
+$version: "2.0"
+
+namespace smithy.protocoltests.rpcv2Cbor
+
+use smithy.protocols#rpcv2Cbor
+use smithy.test#httpRequestTests
+use smithy.test#httpResponseTests
+
+@rpcv2Cbor
+@title("RpcV2 Protocol Service")
+service RpcV2Protocol {
+    version: "2020-07-14",
+    operations: [
+        NoInputOutput,
+        EmptyInputOutput,
+        OptionalInputOutput,
+        SimpleScalarProperties,
+        RpcV2CborLists,
+        RpcV2CborDenseMaps,
+        RpcV2CborSparseMaps,
+        RecursiveShapes,
+        GreetingWithErrors,
+        FractionalSeconds,
+        OperationWithDefaults,
+        SparseNullsOperation
+    ]
+}
+
+structure EmptyStructure {
+
+}
+
+structure SimpleStructure {
+    value: String,
+}
diff --git a/smithy-protocol-tests/model/shared-types.smithy b/smithy-protocol-tests/model/shared-types.smithy
new file mode 100644
index 000000000..5340018cc
--- /dev/null
+++ b/smithy-protocol-tests/model/shared-types.smithy
@@ -0,0 +1,215 @@
+// This file contains shared types that are used throughout the test cases.
+//
+// Anything that is generic enough that it could potentially be reused
+// should be defined in this file. However, things like input or output
+// structures or other test-case specific shapes should be defined closer to
+// the test case and in its same file.
+
+$version: "2.0"
+
+namespace smithy.protocoltests.shared
+
+list StringList {
+    member: String,
+}
+
+@sparse
+list SparseStringList {
+    member: String
+}
+
+@uniqueItems
+list StringSet {
+    member: String,
+}
+
+map StringMap {
+    key: String,
+    value: String,
+}
+
+map StringListMap {
+    key: String,
+    value: StringList
+}
+
+@sparse
+map SparseStringMap {
+    key: String,
+    value: String,
+}
+
+/// A list of lists of strings.
+list NestedStringList {
+    member: StringList,
+}
+
+list ShortList {
+    member: Short,
+}
+
+list IntegerList {
+    member: Integer,
+}
+
+@uniqueItems
+list IntegerSet {
+    member: Integer,
+}
+
+list FloatList {
+    member: Float,
+}
+
+list DoubleList {
+    member: Double,
+}
+
+list BooleanList {
+    member: Boolean,
+}
+
+@uniqueItems
+list BooleanSet {
+    member: Boolean,
+}
+
+list TimestampList {
+    member: Timestamp,
+}
+
+list BlobList {
+    member: Blob,
+}
+
+@uniqueItems
+list BlobSet {
+    member: Blob,
+}
+
+list ByteList {
+    member: Byte,
+}
+
+@uniqueItems
+list ByteSet {
+    member: Byte,
+}
+@uniqueItems
+list ShortSet {
+    member: Short,
+}
+
+@uniqueItems
+list LongList {
+    member: Long,
+}
+
+@uniqueItems
+list LongSet {
+    member: Long,
+}
+
+@uniqueItems
+list TimestampSet {
+    member: Timestamp,
+}
+
+list DateTimeList {
+    member: DateTime,
+}
+
+@uniqueItems
+list DateTimeSet {
+    member: DateTime,
+}
+
+@uniqueItems
+list HttpDateSet {
+    member: HttpDate,
+}
+
+@uniqueItems
+list ListSet {
+    member: StringList
+}
+
+@uniqueItems
+list StructureSet {
+    member: GreetingStruct
+}
+
+@uniqueItems
+list UnionSet {
+    member: FooUnion
+}
+
+union FooUnion {
+    string: String
+    integer: Integer
+}
+
+enum FooEnum {
+    FOO = "Foo"
+    BAZ = "Baz"
+    BAR = "Bar"
+    ONE = "1"
+    ZERO = "0"
+}
+
+list FooEnumList {
+    member: FooEnum,
+}
+
+@uniqueItems
+list FooEnumSet {
+    member: FooEnum,
+}
+
+map FooEnumMap {
+    key: String,
+    value: FooEnum,
+}
+
+@timestampFormat("date-time")
+timestamp DateTime
+
+@timestampFormat("epoch-seconds")
+timestamp EpochSeconds
+
+@timestampFormat("http-date")
+timestamp HttpDate
+
+@mediaType("text/plain")
+blob TextPlainBlob
+
+@mediaType("image/jpeg")
+blob JpegBlob
+
+structure GreetingStruct {
+    hi: String
+}
+
+list GreetingList {
+    member: GreetingStruct
+}
+
+intEnum IntegerEnum {
+    A = 1
+    B = 2
+    C = 3
+}
+
+list IntegerEnumList {
+    member: IntegerEnum
+}
+
+@uniqueItems
+list IntegerEnumSet {
+    member: IntegerEnum
+}
+
+map IntegerEnumMap {
+    key: String,
+    value: IntegerEnum
+}
diff --git a/smithy-protocol-traits/src/test/java/software/amazon/smithy/protocol/traits/Rpcv2CborTraitTest.java b/smithy-protocol-traits/src/test/java/software/amazon/smithy/protocol/traits/Rpcv2CborTraitTest.java
new file mode 100644
index 000000000..5897863b2
--- /dev/null
+++ b/smithy-protocol-traits/src/test/java/software/amazon/smithy/protocol/traits/Rpcv2CborTraitTest.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package software.amazon.smithy.protocol.traits;
+
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+import software.amazon.smithy.model.node.Node;
+import software.amazon.smithy.model.shapes.ShapeId;
+import software.amazon.smithy.model.traits.Trait;
+import software.amazon.smithy.model.traits.TraitFactory;
+import java.util.Optional;
+
+public class Rpcv2CborTraitTest {
+
+    @Test
+    public void loadsTraitWithDefaults() {
+        Node node = Node.objectNode();
+        TraitFactory provider = TraitFactory.createServiceFactory();
+        Optional<Trait> trait = provider.createTrait(Rpcv2CborTrait.ID, ShapeId.from("ns.foo#foo"), node);
+
+        Assertions.assertTrue(trait.isPresent());
+        Assertions.assertTrue(trait.get() instanceof Rpcv2CborTrait);
+        Rpcv2CborTrait smithyRpcV2Trait = (Rpcv2CborTrait) trait.get();
+        Assertions.assertEquals(smithyRpcV2Trait.toNode(), node);
+    }
+}
diff --git a/smithy-protocol-traits/src/test/java/software/amazon/smithy/protocol/traits/TestRunnerTest.java b/smithy-protocol-traits/src/test/java/software/amazon/smithy/protocol/traits/TestRunnerTest.java
new file mode 100644
index 000000000..8c29bfe12
--- /dev/null
+++ b/smithy-protocol-traits/src/test/java/software/amazon/smithy/protocol/traits/TestRunnerTest.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package software.amazon.smithy.protocol.traits;
+
+import java.util.concurrent.Callable;
+import java.util.stream.Stream;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.MethodSource;
+import software.amazon.smithy.model.validation.testrunner.SmithyTestCase;
+import software.amazon.smithy.model.validation.testrunner.SmithyTestSuite;
+
+public class TestRunnerTest {
+    @ParameterizedTest(name = "{0}")
+    @MethodSource("source")
+    public void testRunner(String filename, Callable<SmithyTestCase.Result> callable)
+            throws Exception {
+        callable.call();
+    }
+
+    public static Stream<?> source() {
+        return SmithyTestSuite.defaultParameterizedTestSource(TestRunnerTest.class);
+    }
+}
diff --git a/smithy-protocol-traits/src/test/resources/software/amazon/smithy/protocol/traits/errorfiles/eventStreamHttp-matches-http.errors b/smithy-protocol-traits/src/test/resources/software/amazon/smithy/protocol/traits/errorfiles/eventStreamHttp-matches-http.errors
new file mode 100644
index 000000000..5f569df65
--- /dev/null
+++ b/smithy-protocol-traits/src/test/resources/software/amazon/smithy/protocol/traits/errorfiles/eventStreamHttp-matches-http.errors
@@ -0,0 +1,3 @@
+[ERROR] smithy.example#InvalidService1: The following values of the `eventStreamHttp` property do not also appear in the `http` property of the smithy.protocols#rpcv2Cbor protocol trait: [http/1.1] | Rpcv2CborTrait
+[ERROR] smithy.example#InvalidService2: The following values of the `eventStreamHttp` property do not also appear in the `http` property of the smithy.protocols#rpcv2Cbor protocol trait: [http/1.1] | Rpcv2CborTrait
+[ERROR] smithy.example#InvalidService3: The following values of the `eventStreamHttp` property do not also appear in the `http` property of the smithy.protocols#rpcv2Cbor protocol trait: [http/1.1, h2c] | Rpcv2CborTrait
diff --git a/smithy-protocol-traits/src/test/resources/software/amazon/smithy/protocol/traits/errorfiles/eventStreamHttp-matches-http.smithy b/smithy-protocol-traits/src/test/resources/software/amazon/smithy/protocol/traits/errorfiles/eventStreamHttp-matches-http.smithy
new file mode 100644
index 000000000..5f0968710
--- /dev/null
+++ b/smithy-protocol-traits/src/test/resources/software/amazon/smithy/protocol/traits/errorfiles/eventStreamHttp-matches-http.smithy
@@ -0,0 +1,40 @@
+$version: "2.0"
+
+namespace smithy.example
+
+use smithy.protocols#rpcv2Cbor
+
+@rpcv2Cbor(http: ["h2", "http/1.1"], eventStreamHttp: ["h2"])
+service ValidService1 {
+    version: "2023-02-10"
+}
+
+@rpcv2Cbor(http: ["h2"], eventStreamHttp: ["h2"])
+service ValidService2 {
+    version: "2023-02-10"
+}
+
+@rpcv2Cbor(http: [], eventStreamHttp: [])
+service ValidService3 {
+    version: "2023-02-10"
+}
+
+@rpcv2Cbor(http: ["http/1.1"], eventStreamHttp: [])
+service ValidService4 {
+    version: "2023-02-10"
+}
+
+@rpcv2Cbor(eventStreamHttp: ["http/1.1"])
+service InvalidService1 {
+    version: "2023-02-10"
+}
+
+@rpcv2Cbor(http: ["h2"], eventStreamHttp: ["http/1.1"])
+service InvalidService2 {
+    version: "2023-02-10"
+}
+
+@rpcv2Cbor(http: ["h2"], eventStreamHttp: ["h2", "http/1.1", "h2c"])
+service InvalidService3 {
+    version: "2023-02-10"
+}
diff --git a/smithy-protocol-traits/src/test/resources/software/amazon/smithy/protocol/traits/errorfiles/no-document-support.errors b/smithy-protocol-traits/src/test/resources/software/amazon/smithy/protocol/traits/errorfiles/no-document-support.errors
new file mode 100644
index 000000000..1062699e1
--- /dev/null
+++ b/smithy-protocol-traits/src/test/resources/software/amazon/smithy/protocol/traits/errorfiles/no-document-support.errors
@@ -0,0 +1,1 @@
+[ERROR] smithy.example#DocumentOperationInput$document: Found an incompatible shape when validating the constraints of the `smithy.protocols#rpcv2Cbor` trait attached to `smithy.example#DocumentService`: This protocol does not support document types | rpcv2Cbor.NoDocuments
diff --git a/smithy-protocol-traits/src/test/resources/software/amazon/smithy/protocol/traits/errorfiles/no-document-support.smithy b/smithy-protocol-traits/src/test/resources/software/amazon/smithy/protocol/traits/errorfiles/no-document-support.smithy
new file mode 100644
index 000000000..10ed73af3
--- /dev/null
+++ b/smithy-protocol-traits/src/test/resources/software/amazon/smithy/protocol/traits/errorfiles/no-document-support.smithy
@@ -0,0 +1,19 @@
+$version: "2.0"
+
+namespace smithy.example
+
+use smithy.protocols#rpcv2Cbor
+
+@rpcv2Cbor
+service DocumentService {
+    version: "2023-02-10"
+    operations: [
+        DocumentOperation
+    ]
+}
+
+operation DocumentOperation {
+    input := {
+        document: Document
+    }
+}