Repository: smithy-lang/smithy
Pull request #2212 (2024-03-25 17:45:57)

Add Smithy RPC v2 CBOR protocol
#### Background
* What do these changes do? 
Adds a new Smithy wire protocol, Smithy RPC v2 CBOR (`@smithy.protocol#rpcv2Cbor`), that is an RPC protocol using [the CBOR data format](https://www.rfc-editor.org/rfc/rfc8949.html) for payload transmission.

* Why are they important?
CBOR is a more performant encoding, both in time to encode and the resulting payload size, than both JSON and XML. 

#### Testing
* How did you test these changes?
Spec builds and reads correctly. Protocol trait is used by services in the new `smithy-protocol-tests` package, which both aws/smithy-go (client) and smithy-lang/smithy-rs (service) pass.

---
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/.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/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/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/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
+    }
+}