From 39fed0add787ba0870bda8f38b49141115ae6107 Mon Sep 17 00:00:00 2001 From: pulimsr Date: Mon, 20 Jul 2026 13:45:24 -0400 Subject: [PATCH 1/4] CborShapeSerializer --- .../client/schema/CborShapeSerializer.h | 6 +- .../client/schema/CborShapeSerializer.cpp | 267 ++++++ .../client/schema/CborShapeSerializerTest.cpp | 804 ++++++++++++++++++ 3 files changed, 1075 insertions(+), 2 deletions(-) create mode 100644 src/aws-cpp-sdk-core/source/smithy/client/schema/CborShapeSerializer.cpp create mode 100644 tests/aws-cpp-sdk-core-tests/smithy/client/schema/CborShapeSerializerTest.cpp diff --git a/src/aws-cpp-sdk-core/include/smithy/client/schema/CborShapeSerializer.h b/src/aws-cpp-sdk-core/include/smithy/client/schema/CborShapeSerializer.h index 351b29b0aba..4e35476fd2a 100644 --- a/src/aws-cpp-sdk-core/include/smithy/client/schema/CborShapeSerializer.h +++ b/src/aws-cpp-sdk-core/include/smithy/client/schema/CborShapeSerializer.h @@ -1,5 +1,6 @@ #pragma once +#include #include #include #include @@ -7,8 +8,9 @@ namespace smithy { namespace schema { -class SMITHY_API CborShapeSerialize final : public ShapeSerializer { +class SMITHY_API CborShapeSerializer final : public ShapeSerializer { public: + using SerializerOutcome = Aws::Utils::Outcome>; CborShapeSerializer(); ~CborShapeSerializer(); @@ -35,7 +37,7 @@ class SMITHY_API CborShapeSerialize final : public ShapeSerializer { bool BeginNestedStructure(const Schema& schema) override; void EndNestedStructure() override; - Aws::String GetPayload() const; + SerializerOutcome GetPayload(); private: class Impl; diff --git a/src/aws-cpp-sdk-core/source/smithy/client/schema/CborShapeSerializer.cpp b/src/aws-cpp-sdk-core/source/smithy/client/schema/CborShapeSerializer.cpp new file mode 100644 index 00000000000..0fb25d08a27 --- /dev/null +++ b/src/aws-cpp-sdk-core/source/smithy/client/schema/CborShapeSerializer.cpp @@ -0,0 +1,267 @@ +/** + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0. + */ +#include +#include +#include +#include + +#include +#include + +using namespace smithy::schema; +using namespace Aws::Utils; +using SerializerOutcome = Aws::Utils::Outcome>; + +namespace { +constexpr int MAX_DEPTH = 500; + +constexpr unsigned char CBOR_INDEFINITE_MAP = 0xBF; +constexpr unsigned char CBOR_BREAK = 0xFF; +constexpr unsigned char CBOR_FALSE = 0xF4; +constexpr unsigned char CBOR_TRUE = 0xF5; +constexpr unsigned char CBOR_NULL = 0xF6; + +constexpr unsigned char TYPE_POSINT = 0x00; +constexpr unsigned char TYPE_NEGINT = 0x20; +constexpr unsigned char TYPE_BYTESTRING = 0x40; +constexpr unsigned char TYPE_TEXTSTRING = 0x60; +constexpr unsigned char TYPE_ARRAY = 0x80; +constexpr unsigned char TYPE_TAG = 0xC0; +constexpr unsigned char TYPE_SIMPLE = 0xE0; + +constexpr unsigned char ADDITIONAL_ONE_BYTE = 24; +constexpr unsigned char ADDITIONAL_TWO_BYTES = 25; +constexpr unsigned char ADDITIONAL_FOUR_BYTES = 26; +constexpr unsigned char ADDITIONAL_EIGHT_BYTES = 27; +} // namespace + +class CborShapeSerializer::Impl { + public: + Impl() { m_buf.reserve(8192); } + + bool BeginStructure(const Schema&) { + if (m_depth + 1 >= MAX_DEPTH) { + m_errorMessage = "Maximum nesting depth exceeded"; + return false; + } + m_buf += static_cast(CBOR_INDEFINITE_MAP); + m_depth++; + m_isMap[m_depth] = false; + m_isList[m_depth] = false; + return true; + } + + void EndStructure() { + m_buf += static_cast(CBOR_BREAK); + m_depth--; + } + + void WriteBoolean(const Schema& schema, bool value) { + WriteFieldName(schema); + m_buf += static_cast(value ? CBOR_TRUE : CBOR_FALSE); + } + + void WriteInteger(const Schema& schema, int value) { + WriteFieldName(schema); + WriteIntValue(static_cast(value)); + } + + void WriteLong(const Schema& schema, int64_t value) { + WriteFieldName(schema); + WriteIntValue(value); + } + + void WriteDouble(const Schema& schema, double value) { + WriteFieldName(schema); + WriteDoubleValue(value); + } + + void WriteString(const Schema& schema, const Aws::String& value) { + WriteFieldName(schema); + WriteTextString(value); + } + + void WriteTimestamp(const Schema& schema, const DateTime& value) { + WriteFieldName(schema); + double seconds = value.SecondsWithMSPrecision(); + WriteTagAndLength(TYPE_TAG, 1); + double intPart; + if (std::modf(seconds, &intPart) == 0.0) { + WriteIntValue(static_cast(intPart)); + } else { + WriteDoubleValue(seconds); + } + } + + void WriteBlob(const Schema& schema, const ByteBuffer& value) { + WriteFieldName(schema); + WriteTagAndLength(TYPE_BYTESTRING, static_cast(value.GetLength())); + m_buf.append(reinterpret_cast(value.GetUnderlyingData()), value.GetLength()); + } + + void WriteEnum(const Schema& schema, int value) { WriteInteger(schema, value); } + + void WriteNull(const Schema& schema) { + WriteFieldName(schema); + m_buf += static_cast(CBOR_NULL); + } + + bool BeginList(const Schema& schema, size_t count) { + if (m_depth + 1 >= MAX_DEPTH) { + m_errorMessage = "Maximum nesting depth exceeded"; + return false; + } + WriteFieldName(schema); + WriteTagAndLength(TYPE_ARRAY, static_cast(count)); + m_depth++; + m_isMap[m_depth] = false; + m_isList[m_depth] = true; + return true; + } + + void EndList() { m_depth--; } + + bool BeginMap(const Schema& schema, size_t) { + if (m_depth + 1 >= MAX_DEPTH) { + m_errorMessage = "Maximum nesting depth exceeded"; + return false; + } + WriteFieldName(schema); + m_buf += static_cast(CBOR_INDEFINITE_MAP); + m_depth++; + m_isMap[m_depth] = true; + m_isList[m_depth] = false; + return true; + } + + void WriteMapKey(const Aws::String& key) { m_currentMapKey = key; } + + void EndMap() { + m_buf += static_cast(CBOR_BREAK); + m_depth--; + } + + bool BeginNestedStructure(const Schema& schema) { + if (m_depth + 1 >= MAX_DEPTH) { + m_errorMessage = "Maximum nesting depth exceeded"; + return false; + } + WriteFieldName(schema); + m_buf += static_cast(CBOR_INDEFINITE_MAP); + m_depth++; + m_isMap[m_depth] = false; + m_isList[m_depth] = false; + return true; + } + + void EndNestedStructure() { + m_buf += static_cast(CBOR_BREAK); + m_depth--; + } + + SerializerOutcome GetPayload() { + if (m_finalized || !m_errorMessage.empty()) { + return Aws::Client::AWSError( + Aws::Client::CoreErrors::INTERNAL_FAILURE, "SerializationException", + !m_errorMessage.empty() ? m_errorMessage : "Serializer has already been finalized", false); + } + m_finalized = true; + return std::move(m_buf); + } + + private: + Aws::String m_buf; + int m_depth = 0; + Aws::Array m_isMap{}; + Aws::Array m_isList{}; + Aws::String m_currentMapKey; + bool m_finalized = false; + Aws::String m_errorMessage; + + void WriteFieldName(const Schema& schema) { + if (m_isList[m_depth]) { + return; + } + if (m_depth > 0 && m_isMap[m_depth]) { + WriteTextString(m_currentMapKey); + } else { + WriteTextString(schema.GetMemberName()); + } + } + + void WriteTextString(const Aws::String& str) { + WriteTagAndLength(TYPE_TEXTSTRING, static_cast(str.size())); + m_buf.append(str.data(), str.size()); + } + + void WriteIntValue(int64_t value) { + if (value >= 0) { + WriteTagAndLength(TYPE_POSINT, static_cast(value)); + } else { + WriteTagAndLength(TYPE_NEGINT, static_cast(-(value + 1))); + } + } + + void WriteDoubleValue(double value) { + m_buf += static_cast(TYPE_SIMPLE | ADDITIONAL_EIGHT_BYTES); + uint64_t bits; + std::memcpy(&bits, &value, sizeof(bits)); + for (int i = 56; i >= 0; i -= 8) { + m_buf += static_cast((bits >> i) & 0xFF); + } + } + + void WriteTagAndLength(unsigned char majorType, uint64_t value) { + if (value <= 23) { + m_buf += static_cast(majorType | static_cast(value)); + } else if (value <= 0xFF) { + m_buf += static_cast(majorType | ADDITIONAL_ONE_BYTE); + m_buf += static_cast(value & 0xFF); + } else if (value <= 0xFFFF) { + m_buf += static_cast(majorType | ADDITIONAL_TWO_BYTES); + m_buf += static_cast((value >> 8) & 0xFF); + m_buf += static_cast(value & 0xFF); + } else if (value <= 0xFFFFFFFF) { + m_buf += static_cast(majorType | ADDITIONAL_FOUR_BYTES); + m_buf += static_cast((value >> 24) & 0xFF); + m_buf += static_cast((value >> 16) & 0xFF); + m_buf += static_cast((value >> 8) & 0xFF); + m_buf += static_cast(value & 0xFF); + } else { + m_buf += static_cast(majorType | ADDITIONAL_EIGHT_BYTES); + m_buf += static_cast((value >> 56) & 0xFF); + m_buf += static_cast((value >> 48) & 0xFF); + m_buf += static_cast((value >> 40) & 0xFF); + m_buf += static_cast((value >> 32) & 0xFF); + m_buf += static_cast((value >> 24) & 0xFF); + m_buf += static_cast((value >> 16) & 0xFF); + m_buf += static_cast((value >> 8) & 0xFF); + m_buf += static_cast(value & 0xFF); + } + } +}; + +CborShapeSerializer::CborShapeSerializer() : m_impl(Aws::MakeUnique("CborShapeSerializer")) {} +CborShapeSerializer::~CborShapeSerializer() = default; + +bool CborShapeSerializer::BeginStructure(const Schema& schema) { return m_impl->BeginStructure(schema); } +void CborShapeSerializer::EndStructure() { m_impl->EndStructure(); } +void CborShapeSerializer::WriteBoolean(const Schema& schema, bool value) { m_impl->WriteBoolean(schema, value); } +void CborShapeSerializer::WriteInteger(const Schema& schema, int value) { m_impl->WriteInteger(schema, value); } +void CborShapeSerializer::WriteLong(const Schema& schema, int64_t value) { m_impl->WriteLong(schema, value); } +void CborShapeSerializer::WriteDouble(const Schema& schema, double value) { m_impl->WriteDouble(schema, value); } +void CborShapeSerializer::WriteString(const Schema& schema, const Aws::String& value) { m_impl->WriteString(schema, value); } +void CborShapeSerializer::WriteTimestamp(const Schema& schema, const DateTime& value) { m_impl->WriteTimestamp(schema, value); } +void CborShapeSerializer::WriteBlob(const Schema& schema, const ByteBuffer& value) { m_impl->WriteBlob(schema, value); } +void CborShapeSerializer::WriteEnum(const Schema& schema, int value) { m_impl->WriteEnum(schema, value); } +void CborShapeSerializer::WriteNull(const Schema& schema) { m_impl->WriteNull(schema); } +bool CborShapeSerializer::BeginList(const Schema& schema, size_t count) { return m_impl->BeginList(schema, count); } +void CborShapeSerializer::EndList() { m_impl->EndList(); } +bool CborShapeSerializer::BeginMap(const Schema& schema, size_t count) { return m_impl->BeginMap(schema, count); } +void CborShapeSerializer::WriteMapKey(const Aws::String& key) { m_impl->WriteMapKey(key); } +void CborShapeSerializer::EndMap() { m_impl->EndMap(); } +bool CborShapeSerializer::BeginNestedStructure(const Schema& schema) { return m_impl->BeginNestedStructure(schema); } +void CborShapeSerializer::EndNestedStructure() { m_impl->EndNestedStructure(); } +CborShapeSerializer::SerializerOutcome CborShapeSerializer::GetPayload() { return m_impl->GetPayload(); } diff --git a/tests/aws-cpp-sdk-core-tests/smithy/client/schema/CborShapeSerializerTest.cpp b/tests/aws-cpp-sdk-core-tests/smithy/client/schema/CborShapeSerializerTest.cpp new file mode 100644 index 00000000000..ebbadc9f567 --- /dev/null +++ b/tests/aws-cpp-sdk-core-tests/smithy/client/schema/CborShapeSerializerTest.cpp @@ -0,0 +1,804 @@ +/** + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0. + */ +#include +#include +#include +#include + +#include + +using namespace smithy::schema; + +class CborShapeSerializerTest : public Aws::Testing::AwsCppSdkGTestSuite {}; + +namespace { +Aws::String Bytes(const char* data, size_t len) { return Aws::String(data, len); } +} // namespace + +// --- Scalars --- + +TEST_F(CborShapeSerializerTest, EmptyStructure) { + CborShapeSerializer s; + Schema root; + s.BeginStructure(root); + s.EndStructure(); + auto outcome = s.GetPayload(); + ASSERT_TRUE(outcome.IsSuccess()); + // indefinite map start + break + EXPECT_EQ(outcome.GetResult(), Bytes("\xBF\xFF", 2)); +} + +TEST_F(CborShapeSerializerTest, BooleanTrue) { + CborShapeSerializer s; + Schema root; + Schema member("enabled", ShapeType::Boolean); + s.BeginStructure(root); + s.WriteBoolean(member, true); + s.EndStructure(); + auto outcome = s.GetPayload(); + ASSERT_TRUE(outcome.IsSuccess()); + const auto& payload = outcome.GetResult(); + // BF + text("enabled") + true + FF + // text "enabled" = 67 65 6E 61 62 6C 65 64 + Aws::String expected; + expected += '\xBF'; + expected += '\x67'; // text string, length 7 + expected += "enabled"; + expected += '\xF5'; // true + expected += '\xFF'; + EXPECT_EQ(payload, expected); +} + +TEST_F(CborShapeSerializerTest, BooleanFalse) { + CborShapeSerializer s; + Schema root; + Schema member("ok", ShapeType::Boolean); + s.BeginStructure(root); + s.WriteBoolean(member, false); + s.EndStructure(); + auto outcome = s.GetPayload(); + ASSERT_TRUE(outcome.IsSuccess()); + const auto& payload = outcome.GetResult(); + Aws::String expected; + expected += '\xBF'; + expected += '\x62'; // text string, length 2 + expected += "ok"; + expected += '\xF4'; // false + expected += '\xFF'; + EXPECT_EQ(payload, expected); +} + +TEST_F(CborShapeSerializerTest, IntegerSmall) { + CborShapeSerializer s; + Schema root; + Schema member("n", ShapeType::Integer); + s.BeginStructure(root); + s.WriteInteger(member, 1); + s.EndStructure(); + auto outcome = s.GetPayload(); + ASSERT_TRUE(outcome.IsSuccess()); + const auto& payload = outcome.GetResult(); + Aws::String expected; + expected += '\xBF'; + expected += '\x61'; // text string, length 1 + expected += 'n'; + expected += '\x01'; // positive integer 1 + expected += '\xFF'; + EXPECT_EQ(payload, expected); +} + +TEST_F(CborShapeSerializerTest, IntegerOneByte) { + CborShapeSerializer s; + Schema root; + Schema member("n", ShapeType::Integer); + s.BeginStructure(root); + s.WriteInteger(member, 42); + s.EndStructure(); + auto outcome = s.GetPayload(); + ASSERT_TRUE(outcome.IsSuccess()); + const auto& payload = outcome.GetResult(); + Aws::String expected; + expected += '\xBF'; + expected += '\x61'; + expected += 'n'; + expected += '\x18'; // additional info 24 (1-byte follows) + expected += '\x2A'; // 42 + expected += '\xFF'; + EXPECT_EQ(payload, expected); +} + +TEST_F(CborShapeSerializerTest, IntegerNegative) { + CborShapeSerializer s; + Schema root; + Schema member("n", ShapeType::Integer); + s.BeginStructure(root); + s.WriteInteger(member, -1); + s.EndStructure(); + auto outcome = s.GetPayload(); + ASSERT_TRUE(outcome.IsSuccess()); + const auto& payload = outcome.GetResult(); + Aws::String expected; + expected += '\xBF'; + expected += '\x61'; + expected += 'n'; + expected += '\x20'; // negative integer, value 0 => encodes -1 + expected += '\xFF'; + EXPECT_EQ(payload, expected); +} + +TEST_F(CborShapeSerializerTest, IntegerNegativeLarge) { + CborShapeSerializer s; + Schema root; + Schema member("n", ShapeType::Integer); + s.BeginStructure(root); + s.WriteInteger(member, -100); + s.EndStructure(); + auto outcome = s.GetPayload(); + ASSERT_TRUE(outcome.IsSuccess()); + const auto& payload = outcome.GetResult(); + Aws::String expected; + expected += '\xBF'; + expected += '\x61'; + expected += 'n'; + expected += '\x38'; // major type 1, additional 24 (1-byte follows) + expected += '\x63'; // 99 = 0x63 (encodes -100) + expected += '\xFF'; + EXPECT_EQ(payload, expected); +} + +TEST_F(CborShapeSerializerTest, LongValue) { + CborShapeSerializer s; + Schema root; + Schema member("big", ShapeType::Long); + s.BeginStructure(root); + s.WriteLong(member, 1000000LL); + s.EndStructure(); + auto outcome = s.GetPayload(); + ASSERT_TRUE(outcome.IsSuccess()); + const auto& payload = outcome.GetResult(); + Aws::String expected; + expected += '\xBF'; + expected += '\x63'; // text string, length 3 + expected += "big"; + // 1000000 = 0x000F4240 -> 4-byte encoding + expected += '\x1A'; // major type 0, additional 26 (4-byte follows) + expected += '\x00'; + expected += '\x0F'; + expected += '\x42'; + expected += '\x40'; + expected += '\xFF'; + EXPECT_EQ(payload, expected); +} + +TEST_F(CborShapeSerializerTest, DoubleValue) { + CborShapeSerializer s; + Schema root; + Schema member("d", ShapeType::Double); + s.BeginStructure(root); + s.WriteDouble(member, 3.14); + s.EndStructure(); + auto outcome = s.GetPayload(); + ASSERT_TRUE(outcome.IsSuccess()); + const auto& payload = outcome.GetResult(); + // Check prefix: BF + text("d") + FB (double marker) + ASSERT_GE(payload.size(), 4u); + EXPECT_EQ(static_cast(payload[0]), 0xBF); + EXPECT_EQ(static_cast(payload[1]), 0x61); + EXPECT_EQ(payload[2], 'd'); + EXPECT_EQ(static_cast(payload[3]), 0xFB); // double (type 7, additional 27) + // 8 bytes of IEEE 754 double + break + EXPECT_EQ(payload.size(), 13u); + EXPECT_EQ(static_cast(payload[12]), 0xFF); + // Verify actual double bytes + double reconstructed; + uint64_t bits = 0; + for (int i = 0; i < 8; i++) { + bits = (bits << 8) | static_cast(payload[4 + i]); + } + std::memcpy(&reconstructed, &bits, sizeof(reconstructed)); + EXPECT_DOUBLE_EQ(reconstructed, 3.14); +} + +TEST_F(CborShapeSerializerTest, StringValue) { + CborShapeSerializer s; + Schema root; + Schema member("name", ShapeType::String); + s.BeginStructure(root); + s.WriteString(member, "hello"); + s.EndStructure(); + auto outcome = s.GetPayload(); + ASSERT_TRUE(outcome.IsSuccess()); + const auto& payload = outcome.GetResult(); + Aws::String expected; + expected += '\xBF'; + expected += '\x64'; // text string, length 4 + expected += "name"; + expected += '\x65'; // text string, length 5 + expected += "hello"; + expected += '\xFF'; + EXPECT_EQ(payload, expected); +} + +TEST_F(CborShapeSerializerTest, EmptyString) { + CborShapeSerializer s; + Schema root; + Schema member("s", ShapeType::String); + s.BeginStructure(root); + s.WriteString(member, ""); + s.EndStructure(); + auto outcome = s.GetPayload(); + ASSERT_TRUE(outcome.IsSuccess()); + const auto& payload = outcome.GetResult(); + Aws::String expected; + expected += '\xBF'; + expected += '\x61'; // text string, length 1 + expected += 's'; + expected += '\x60'; // text string, length 0 + expected += '\xFF'; + EXPECT_EQ(payload, expected); +} + +TEST_F(CborShapeSerializerTest, BlobValue) { + CborShapeSerializer s; + Schema root; + Schema member("data", ShapeType::Blob); + s.BeginStructure(root); + unsigned char raw[] = {0xDE, 0xAD, 0xBE, 0xEF}; + Aws::Utils::ByteBuffer buf(raw, 4); + s.WriteBlob(member, buf); + s.EndStructure(); + auto outcome = s.GetPayload(); + ASSERT_TRUE(outcome.IsSuccess()); + const auto& payload = outcome.GetResult(); + Aws::String expected; + expected += '\xBF'; + expected += '\x64'; // text string, length 4 + expected += "data"; + expected += '\x44'; // byte string, length 4 + expected += '\xDE'; + expected += '\xAD'; + expected += '\xBE'; + expected += '\xEF'; + expected += '\xFF'; + EXPECT_EQ(payload, expected); +} + +TEST_F(CborShapeSerializerTest, NullValue) { + CborShapeSerializer s; + Schema root; + Schema member("item", ShapeType::String); + s.BeginStructure(root); + s.WriteNull(member); + s.EndStructure(); + auto outcome = s.GetPayload(); + ASSERT_TRUE(outcome.IsSuccess()); + const auto& payload = outcome.GetResult(); + Aws::String expected; + expected += '\xBF'; + expected += '\x64'; // text string, length 4 + expected += "item"; + expected += '\xF6'; // null + expected += '\xFF'; + EXPECT_EQ(payload, expected); +} + +TEST_F(CborShapeSerializerTest, TimestampIntegerSeconds) { + CborShapeSerializer s; + Schema root; + Schema member("ts", ShapeType::Timestamp); + s.BeginStructure(root); + // DateTime(int64_t) takes milliseconds; 1234567000ms = 1234567 seconds (no fractional part) + Aws::Utils::DateTime dt(static_cast(1234567000)); + s.WriteTimestamp(member, dt); + s.EndStructure(); + auto outcome = s.GetPayload(); + ASSERT_TRUE(outcome.IsSuccess()); + const auto& payload = outcome.GetResult(); + Aws::String expected; + expected += '\xBF'; + expected += '\x62'; // text string, length 2 + expected += "ts"; + expected += '\xC1'; // tag 1 + // 1234567 = 0x12D687 -> 4-byte encoding + expected += '\x1A'; // major type 0, additional 26 (4-byte uint) + expected += '\x00'; + expected += '\x12'; + expected += '\xD6'; + expected += '\x87'; + expected += '\xFF'; + EXPECT_EQ(payload, expected); +} + +TEST_F(CborShapeSerializerTest, TimestampFractionalSeconds) { + CborShapeSerializer s; + Schema root; + Schema member("ts", ShapeType::Timestamp); + s.BeginStructure(root); + Aws::Utils::DateTime dt(1234567890.5); + s.WriteTimestamp(member, dt); + s.EndStructure(); + auto outcome = s.GetPayload(); + ASSERT_TRUE(outcome.IsSuccess()); + const auto& payload = outcome.GetResult(); + // Should use tag 1 + double encoding + ASSERT_GE(payload.size(), 5u); + EXPECT_EQ(static_cast(payload[0]), 0xBF); + // After key "ts", expect tag 1 + double marker + // key: 62 74 73 => positions [1..3] + // tag 1: C1 => position [4] + // double: FB + 8 bytes => positions [5..13] + // break: FF => position [14] + EXPECT_EQ(payload.size(), 15u); + EXPECT_EQ(static_cast(payload[4]), 0xC1); + EXPECT_EQ(static_cast(payload[5]), 0xFB); + EXPECT_EQ(static_cast(payload[14]), 0xFF); +} + +// --- Multiple fields --- + +TEST_F(CborShapeSerializerTest, MultipleScalars) { + CborShapeSerializer s; + Schema root; + Schema m1("a", ShapeType::Boolean); + Schema m2("b", ShapeType::Integer); + Schema m3("c", ShapeType::String); + s.BeginStructure(root); + s.WriteBoolean(m1, true); + s.WriteInteger(m2, 7); + s.WriteString(m3, "x"); + s.EndStructure(); + auto outcome = s.GetPayload(); + ASSERT_TRUE(outcome.IsSuccess()); + const auto& payload = outcome.GetResult(); + Aws::String expected; + expected += '\xBF'; + expected += '\x61'; // text "a" + expected += 'a'; + expected += '\xF5'; // true + expected += '\x61'; // text "b" + expected += 'b'; + expected += '\x07'; // integer 7 + expected += '\x61'; // text "c" + expected += 'c'; + expected += '\x61'; // text "x" (length 1) + expected += 'x'; + expected += '\xFF'; + EXPECT_EQ(payload, expected); +} + +// --- Nested structures --- + +TEST_F(CborShapeSerializerTest, NestedStructure) { + CborShapeSerializer s; + Schema root; + Schema nested("meta", ShapeType::Structure); + Schema inner("key", ShapeType::String); + s.BeginStructure(root); + s.BeginNestedStructure(nested); + s.WriteString(inner, "val"); + s.EndNestedStructure(); + s.EndStructure(); + auto outcome = s.GetPayload(); + ASSERT_TRUE(outcome.IsSuccess()); + const auto& payload = outcome.GetResult(); + Aws::String expected; + expected += '\xBF'; // outer map start + expected += '\x64'; // text "meta" length 4 + expected += "meta"; + expected += '\xBF'; // nested map start + expected += '\x63'; // text "key" length 3 + expected += "key"; + expected += '\x63'; // text "val" length 3 + expected += "val"; + expected += '\xFF'; // nested map end + expected += '\xFF'; // outer map end + EXPECT_EQ(payload, expected); +} + +TEST_F(CborShapeSerializerTest, DeeplyNestedStructure) { + CborShapeSerializer s; + Schema root; + Schema l1("l1", ShapeType::Structure); + Schema l2("l2", ShapeType::Structure); + Schema leaf("v", ShapeType::Integer); + s.BeginStructure(root); + s.BeginNestedStructure(l1); + s.BeginNestedStructure(l2); + s.WriteInteger(leaf, 99); + s.EndNestedStructure(); + s.EndNestedStructure(); + s.EndStructure(); + auto outcome = s.GetPayload(); + ASSERT_TRUE(outcome.IsSuccess()); + const auto& payload = outcome.GetResult(); + Aws::String expected; + expected += '\xBF'; + expected += '\x62'; // "l1" + expected += "l1"; + expected += '\xBF'; + expected += '\x62'; // "l2" + expected += "l2"; + expected += '\xBF'; + expected += '\x61'; // "v" + expected += 'v'; + expected += '\x18'; // integer 99 (1-byte) + expected += '\x63'; // 99 = 0x63 + expected += '\xFF'; + expected += '\xFF'; + expected += '\xFF'; + EXPECT_EQ(payload, expected); +} + +// --- Lists --- + +TEST_F(CborShapeSerializerTest, ListOfIntegers) { + CborShapeSerializer s; + Schema root; + Schema listMember("nums", ShapeType::List); + Schema elem("member", ShapeType::Integer); + s.BeginStructure(root); + s.BeginList(listMember, 3); + s.WriteInteger(elem, 1); + s.WriteInteger(elem, 2); + s.WriteInteger(elem, 3); + s.EndList(); + s.EndStructure(); + auto outcome = s.GetPayload(); + ASSERT_TRUE(outcome.IsSuccess()); + const auto& payload = outcome.GetResult(); + Aws::String expected; + expected += '\xBF'; + expected += '\x64'; // text "nums" length 4 + expected += "nums"; + expected += '\x83'; // array of length 3 + expected += '\x01'; // integer 1 + expected += '\x02'; // integer 2 + expected += '\x03'; // integer 3 + expected += '\xFF'; + EXPECT_EQ(payload, expected); +} + +TEST_F(CborShapeSerializerTest, EmptyList) { + CborShapeSerializer s; + Schema root; + Schema listMember("items", ShapeType::List); + s.BeginStructure(root); + s.BeginList(listMember, 0); + s.EndList(); + s.EndStructure(); + auto outcome = s.GetPayload(); + ASSERT_TRUE(outcome.IsSuccess()); + const auto& payload = outcome.GetResult(); + Aws::String expected; + expected += '\xBF'; + expected += '\x65'; // text "items" length 5 + expected += "items"; + expected += '\x80'; // array of length 0 + expected += '\xFF'; + EXPECT_EQ(payload, expected); +} + +TEST_F(CborShapeSerializerTest, ListOfStrings) { + CborShapeSerializer s; + Schema root; + Schema listMember("tags", ShapeType::List); + Schema elem("member", ShapeType::String); + s.BeginStructure(root); + s.BeginList(listMember, 2); + s.WriteString(elem, "ab"); + s.WriteString(elem, "cd"); + s.EndList(); + s.EndStructure(); + auto outcome = s.GetPayload(); + ASSERT_TRUE(outcome.IsSuccess()); + const auto& payload = outcome.GetResult(); + Aws::String expected; + expected += '\xBF'; + expected += '\x64'; // text "tags" length 4 + expected += "tags"; + expected += '\x82'; // array of length 2 + expected += '\x62'; // text "ab" length 2 + expected += "ab"; + expected += '\x62'; // text "cd" length 2 + expected += "cd"; + expected += '\xFF'; + EXPECT_EQ(payload, expected); +} + +TEST_F(CborShapeSerializerTest, ListOfStructures) { + CborShapeSerializer s; + Schema root; + Schema listMember("items", ShapeType::List); + Schema structElem("member", ShapeType::Structure); + Schema field("id", ShapeType::Integer); + s.BeginStructure(root); + s.BeginList(listMember, 2); + s.BeginNestedStructure(structElem); + s.WriteInteger(field, 1); + s.EndNestedStructure(); + s.BeginNestedStructure(structElem); + s.WriteInteger(field, 2); + s.EndNestedStructure(); + s.EndList(); + s.EndStructure(); + auto outcome = s.GetPayload(); + ASSERT_TRUE(outcome.IsSuccess()); + const auto& payload = outcome.GetResult(); + Aws::String expected; + expected += '\xBF'; + expected += '\x65'; // "items" + expected += "items"; + expected += '\x82'; // array of 2 + expected += '\xBF'; // nested struct 1 + expected += '\x62'; // "id" + expected += "id"; + expected += '\x01'; // integer 1 + expected += '\xFF'; // end struct 1 + expected += '\xBF'; // nested struct 2 + expected += '\x62'; // "id" + expected += "id"; + expected += '\x02'; // integer 2 + expected += '\xFF'; // end struct 2 + expected += '\xFF'; // end outer map + EXPECT_EQ(payload, expected); +} + +// --- Maps --- + +TEST_F(CborShapeSerializerTest, MapOfStrings) { + CborShapeSerializer s; + Schema root; + Schema mapMember("headers", ShapeType::Map); + Schema valSchema("value", ShapeType::String); + s.BeginStructure(root); + s.BeginMap(mapMember, 2); + s.WriteMapKey("foo"); + s.WriteString(valSchema, "bar"); + s.WriteMapKey("baz"); + s.WriteString(valSchema, "qux"); + s.EndMap(); + s.EndStructure(); + auto outcome = s.GetPayload(); + ASSERT_TRUE(outcome.IsSuccess()); + const auto& payload = outcome.GetResult(); + Aws::String expected; + expected += '\xBF'; + expected += '\x67'; // text "headers" length 7 + expected += "headers"; + expected += '\xBF'; // inner indefinite map + expected += '\x63'; // text "foo" length 3 + expected += "foo"; + expected += '\x63'; // text "bar" length 3 + expected += "bar"; + expected += '\x63'; // text "baz" + expected += "baz"; + expected += '\x63'; // text "qux" + expected += "qux"; + expected += '\xFF'; // inner map break + expected += '\xFF'; // outer map break + EXPECT_EQ(payload, expected); +} + +TEST_F(CborShapeSerializerTest, EmptyMap) { + CborShapeSerializer s; + Schema root; + Schema mapMember("tags", ShapeType::Map); + s.BeginStructure(root); + s.BeginMap(mapMember, 0); + s.EndMap(); + s.EndStructure(); + auto outcome = s.GetPayload(); + ASSERT_TRUE(outcome.IsSuccess()); + const auto& payload = outcome.GetResult(); + Aws::String expected; + expected += '\xBF'; + expected += '\x64'; // text "tags" length 4 + expected += "tags"; + expected += '\xBF'; // indefinite map (empty) + expected += '\xFF'; // break + expected += '\xFF'; // outer break + EXPECT_EQ(payload, expected); +} + +TEST_F(CborShapeSerializerTest, MapOfStructures) { + CborShapeSerializer s; + Schema root; + Schema mapMember("nodes", ShapeType::Map); + Schema valSchema("value", ShapeType::Structure); + Schema field("val", ShapeType::Integer); + s.BeginStructure(root); + s.BeginMap(mapMember, 1); + s.WriteMapKey("a"); + s.BeginNestedStructure(valSchema); + s.WriteInteger(field, 1); + s.EndNestedStructure(); + s.EndMap(); + s.EndStructure(); + auto outcome = s.GetPayload(); + ASSERT_TRUE(outcome.IsSuccess()); + const auto& payload = outcome.GetResult(); + Aws::String expected; + expected += '\xBF'; + expected += '\x65'; // "nodes" + expected += "nodes"; + expected += '\xBF'; // map start + expected += '\x61'; // key "a" + expected += 'a'; + expected += '\xBF'; // nested struct + expected += '\x63'; // "val" + expected += "val"; + expected += '\x01'; // integer 1 + expected += '\xFF'; // end nested struct + expected += '\xFF'; // end map + expected += '\xFF'; // end outer map + EXPECT_EQ(payload, expected); +} + +// --- Depth limit --- + +TEST_F(CborShapeSerializerTest, MaxDepthEnforcement) { + CborShapeSerializer s; + Schema root; + Schema nested("n", ShapeType::Structure); + s.BeginStructure(root); + for (int i = 0; i < 500; i++) { + s.BeginNestedStructure(nested); + } + auto outcome = s.GetPayload(); + ASSERT_FALSE(outcome.IsSuccess()); + EXPECT_NE(outcome.GetError().GetMessage().find("depth"), Aws::String::npos); +} + +// --- GetPayload error cases --- + +TEST_F(CborShapeSerializerTest, GetPayloadCalledTwice) { + CborShapeSerializer s; + Schema root; + s.BeginStructure(root); + s.EndStructure(); + auto outcome1 = s.GetPayload(); + ASSERT_TRUE(outcome1.IsSuccess()); + auto outcome2 = s.GetPayload(); + ASSERT_FALSE(outcome2.IsSuccess()); + EXPECT_NE(outcome2.GetError().GetMessage().find("finalized"), Aws::String::npos); +} + +// --- Enum --- + +TEST_F(CborShapeSerializerTest, EnumValue) { + CborShapeSerializer s; + Schema root; + Schema member("status", ShapeType::Enum); + s.BeginStructure(root); + s.WriteEnum(member, 3); + s.EndStructure(); + auto outcome = s.GetPayload(); + ASSERT_TRUE(outcome.IsSuccess()); + const auto& payload = outcome.GetResult(); + Aws::String expected; + expected += '\xBF'; + expected += '\x66'; // text "status" length 6 + expected += "status"; + expected += '\x03'; // integer 3 + expected += '\xFF'; + EXPECT_EQ(payload, expected); +} + +// --- Combinations --- + +TEST_F(CborShapeSerializerTest, StructureWithListAndMap) { + CborShapeSerializer s; + Schema root; + Schema strMember("name", ShapeType::String); + Schema listMember("tags", ShapeType::List); + Schema listElem("member", ShapeType::String); + Schema mapMember("meta", ShapeType::Map); + Schema mapVal("value", ShapeType::Integer); + + s.BeginStructure(root); + s.WriteString(strMember, "hi"); + s.BeginList(listMember, 1); + s.WriteString(listElem, "t1"); + s.EndList(); + s.BeginMap(mapMember, 1); + s.WriteMapKey("k"); + s.WriteInteger(mapVal, 5); + s.EndMap(); + s.EndStructure(); + + auto outcome = s.GetPayload(); + ASSERT_TRUE(outcome.IsSuccess()); + const auto& payload = outcome.GetResult(); + Aws::String expected; + expected += '\xBF'; + expected += '\x64'; // "name" + expected += "name"; + expected += '\x62'; // "hi" + expected += "hi"; + expected += '\x64'; // "tags" + expected += "tags"; + expected += '\x81'; // array of 1 + expected += '\x62'; // "t1" + expected += "t1"; + expected += '\x64'; // "meta" + expected += "meta"; + expected += '\xBF'; // map start + expected += '\x61'; // "k" + expected += 'k'; + expected += '\x05'; // integer 5 + expected += '\xFF'; // map end + expected += '\xFF'; // outer end + EXPECT_EQ(payload, expected); +} + +TEST_F(CborShapeSerializerTest, IntegerZero) { + CborShapeSerializer s; + Schema root; + Schema member("z", ShapeType::Integer); + s.BeginStructure(root); + s.WriteInteger(member, 0); + s.EndStructure(); + auto outcome = s.GetPayload(); + ASSERT_TRUE(outcome.IsSuccess()); + const auto& payload = outcome.GetResult(); + Aws::String expected; + expected += '\xBF'; + expected += '\x61'; // "z" + expected += 'z'; + expected += '\x00'; // integer 0 + expected += '\xFF'; + EXPECT_EQ(payload, expected); +} + +TEST_F(CborShapeSerializerTest, IntegerTwoByte) { + CborShapeSerializer s; + Schema root; + Schema member("n", ShapeType::Integer); + s.BeginStructure(root); + s.WriteInteger(member, 1000); + s.EndStructure(); + auto outcome = s.GetPayload(); + ASSERT_TRUE(outcome.IsSuccess()); + const auto& payload = outcome.GetResult(); + Aws::String expected; + expected += '\xBF'; + expected += '\x61'; + expected += 'n'; + // 1000 = 0x03E8 -> 2-byte encoding + expected += '\x19'; // major type 0, additional 25 (2 bytes follow) + expected += '\x03'; + expected += '\xE8'; + expected += '\xFF'; + EXPECT_EQ(payload, expected); +} + +TEST_F(CborShapeSerializerTest, SparseList) { + CborShapeSerializer s; + Schema root; + Schema listMember("items", ShapeType::List); + Schema elem("member", ShapeType::String); + s.BeginStructure(root); + s.BeginList(listMember, 3); + s.WriteString(elem, "a"); + s.WriteNull(elem); + s.WriteString(elem, "b"); + s.EndList(); + s.EndStructure(); + auto outcome = s.GetPayload(); + ASSERT_TRUE(outcome.IsSuccess()); + const auto& payload = outcome.GetResult(); + Aws::String expected; + expected += '\xBF'; + expected += '\x65'; // "items" + expected += "items"; + expected += '\x83'; // array of 3 + expected += '\x61'; // text "a" + expected += 'a'; + expected += '\xF6'; // null + expected += '\x61'; // text "b" + expected += 'b'; + expected += '\xFF'; + EXPECT_EQ(payload, expected); +} From cca6f0a5d1051492283f42b37fc777c8e0cecbed Mon Sep 17 00:00:00 2001 From: pulimsr Date: Tue, 21 Jul 2026 15:29:33 -0400 Subject: [PATCH 2/4] Using CRT CborEncoder and adding WriteFloat to ShapeSerializer interface --- .../client/schema/CborShapeSerializer.h | 1 + .../client/schema/JsonShapeSerializer.h | 1 + .../client/schema/QueryShapeSerializer.h | 46 --- .../smithy/client/schema/ShapeSerializer.h | 1 + .../smithy/client/schema/XmlShapeSerializer.h | 1 + .../client/schema/CborShapeSerializer.cpp | 121 ++----- .../client/schema/JsonShapeSerializer.cpp | 6 + .../client/schema/XmlShapeSerializer.cpp | 3 + .../client/schema/CborShapeSerializerTest.cpp | 342 +++++++++++++++++- .../client/schema/JsonShapeSerializerTest.cpp | 24 ++ .../client/schema/XmlShapeSerializerTest.cpp | 22 ++ 11 files changed, 423 insertions(+), 145 deletions(-) delete mode 100644 src/aws-cpp-sdk-core/include/smithy/client/schema/QueryShapeSerializer.h diff --git a/src/aws-cpp-sdk-core/include/smithy/client/schema/CborShapeSerializer.h b/src/aws-cpp-sdk-core/include/smithy/client/schema/CborShapeSerializer.h index 4e35476fd2a..e6350c9cb9b 100644 --- a/src/aws-cpp-sdk-core/include/smithy/client/schema/CborShapeSerializer.h +++ b/src/aws-cpp-sdk-core/include/smithy/client/schema/CborShapeSerializer.h @@ -20,6 +20,7 @@ class SMITHY_API CborShapeSerializer final : public ShapeSerializer { void WriteBoolean(const Schema& schema, bool value) override; void WriteInteger(const Schema& schema, int value) override; void WriteLong(const Schema& schema, int64_t value) override; + void WriteFloat(const Schema& schema, float value) override; void WriteDouble(const Schema& schema, double value) override; void WriteString(const Schema& schema, const Aws::String& value) override; void WriteTimestamp(const Schema& schema, const Aws::Utils::DateTime& value) override; diff --git a/src/aws-cpp-sdk-core/include/smithy/client/schema/JsonShapeSerializer.h b/src/aws-cpp-sdk-core/include/smithy/client/schema/JsonShapeSerializer.h index 8830bfd236c..2ef94ef1e37 100644 --- a/src/aws-cpp-sdk-core/include/smithy/client/schema/JsonShapeSerializer.h +++ b/src/aws-cpp-sdk-core/include/smithy/client/schema/JsonShapeSerializer.h @@ -20,6 +20,7 @@ class SMITHY_API JsonShapeSerializer final : public ShapeSerializer { void WriteBoolean(const Schema& schema, bool value) override; void WriteInteger(const Schema& schema, int value) override; void WriteLong(const Schema& schema, int64_t value) override; + void WriteFloat(const Schema& schema, float value) override; void WriteDouble(const Schema& schema, double value) override; void WriteString(const Schema& schema, const Aws::String& value) override; void WriteTimestamp(const Schema& schema, const Aws::Utils::DateTime& value) override; diff --git a/src/aws-cpp-sdk-core/include/smithy/client/schema/QueryShapeSerializer.h b/src/aws-cpp-sdk-core/include/smithy/client/schema/QueryShapeSerializer.h deleted file mode 100644 index 38c0b3962b5..00000000000 --- a/src/aws-cpp-sdk-core/include/smithy/client/schema/QueryShapeSerializer.h +++ /dev/null @@ -1,46 +0,0 @@ -#pragma once - -#include -#include -#include - -namespace smithy { -namespace schema { - -class SMITHY_API QueryShapeSerializer final : public ShapeSerializer { - public: - QueryShapeSerializer(const Aws::String& action, const Aws::String& version); - ~QueryShapeSerializer(); - - bool BeginStructure(const Schema& schema) override; - void EndStructure() override; - - void WriteBoolean(const Schema& schema, bool value) override; - void WriteInteger(const Schema& schema, int value) override; - void WriteLong(const Schema& schema, int64_t value) override; - void WriteDouble(const Schema& schema, double value) override; - void WriteString(const Schema& schema, const Aws::String& value) override; - void WriteTimestamp(const Schema& schema, const Aws::Utils::DateTime& value) override; - void WriteBlob(const Schema& schema, const Aws::Utils::ByteBuffer& value) override; - void WriteEnum(const Schema& schema, int value) override; - void WriteNull(const Schema& schema) override; - - bool BeginList(const Schema& schema, size_t count) override; - void EndList() override; - - bool BeginMap(const Schema& schema, size_t count) override; - void WriteMapKey(const Aws::String& key) override; - void EndMap() override; - - bool BeginNestedStructure(const Schema& schema) override; - void EndNestedStructure() override; - - Aws::String GetPayload() const; - - private: - class Impl; - Aws::UniquePtr m_impl; -}; - -} // namespace schema -} // namespace smithy diff --git a/src/aws-cpp-sdk-core/include/smithy/client/schema/ShapeSerializer.h b/src/aws-cpp-sdk-core/include/smithy/client/schema/ShapeSerializer.h index e8ecd0429e6..a9e218f7916 100644 --- a/src/aws-cpp-sdk-core/include/smithy/client/schema/ShapeSerializer.h +++ b/src/aws-cpp-sdk-core/include/smithy/client/schema/ShapeSerializer.h @@ -21,6 +21,7 @@ class SMITHY_API ShapeSerializer { virtual void WriteBoolean(const Schema& schema, bool value) = 0; virtual void WriteInteger(const Schema& schema, int value) = 0; virtual void WriteLong(const Schema& schema, int64_t value) = 0; + virtual void WriteFloat(const Schema& schema, float value) = 0; virtual void WriteDouble(const Schema& schema, double value) = 0; virtual void WriteString(const Schema& schema, const Aws::String& value) = 0; virtual void WriteTimestamp(const Schema& schema, const Aws::Utils::DateTime& value) = 0; diff --git a/src/aws-cpp-sdk-core/include/smithy/client/schema/XmlShapeSerializer.h b/src/aws-cpp-sdk-core/include/smithy/client/schema/XmlShapeSerializer.h index bbf730bf061..89672fa0e54 100644 --- a/src/aws-cpp-sdk-core/include/smithy/client/schema/XmlShapeSerializer.h +++ b/src/aws-cpp-sdk-core/include/smithy/client/schema/XmlShapeSerializer.h @@ -20,6 +20,7 @@ class SMITHY_API XmlShapeSerializer final : public ShapeSerializer { void WriteBoolean(const Schema& schema, bool value) override; void WriteInteger(const Schema& schema, int value) override; void WriteLong(const Schema& schema, int64_t value) override; + void WriteFloat(const Schema& schema, float value) override; void WriteDouble(const Schema& schema, double value) override; void WriteString(const Schema& schema, const Aws::String& value) override; void WriteTimestamp(const Schema& schema, const Aws::Utils::DateTime& value) override; diff --git a/src/aws-cpp-sdk-core/source/smithy/client/schema/CborShapeSerializer.cpp b/src/aws-cpp-sdk-core/source/smithy/client/schema/CborShapeSerializer.cpp index 0fb25d08a27..a8bc22c5b7a 100644 --- a/src/aws-cpp-sdk-core/source/smithy/client/schema/CborShapeSerializer.cpp +++ b/src/aws-cpp-sdk-core/source/smithy/client/schema/CborShapeSerializer.cpp @@ -5,48 +5,27 @@ #include #include #include +#include #include #include -#include using namespace smithy::schema; using namespace Aws::Utils; using SerializerOutcome = Aws::Utils::Outcome>; namespace { -constexpr int MAX_DEPTH = 500; - -constexpr unsigned char CBOR_INDEFINITE_MAP = 0xBF; -constexpr unsigned char CBOR_BREAK = 0xFF; -constexpr unsigned char CBOR_FALSE = 0xF4; -constexpr unsigned char CBOR_TRUE = 0xF5; -constexpr unsigned char CBOR_NULL = 0xF6; - -constexpr unsigned char TYPE_POSINT = 0x00; -constexpr unsigned char TYPE_NEGINT = 0x20; -constexpr unsigned char TYPE_BYTESTRING = 0x40; -constexpr unsigned char TYPE_TEXTSTRING = 0x60; -constexpr unsigned char TYPE_ARRAY = 0x80; -constexpr unsigned char TYPE_TAG = 0xC0; -constexpr unsigned char TYPE_SIMPLE = 0xE0; - -constexpr unsigned char ADDITIONAL_ONE_BYTE = 24; -constexpr unsigned char ADDITIONAL_TWO_BYTES = 25; -constexpr unsigned char ADDITIONAL_FOUR_BYTES = 26; -constexpr unsigned char ADDITIONAL_EIGHT_BYTES = 27; +constexpr int MAX_DEPTH = 1000; } // namespace class CborShapeSerializer::Impl { public: - Impl() { m_buf.reserve(8192); } - bool BeginStructure(const Schema&) { if (m_depth + 1 >= MAX_DEPTH) { m_errorMessage = "Maximum nesting depth exceeded"; return false; } - m_buf += static_cast(CBOR_INDEFINITE_MAP); + m_encoder.WriteIndefMapStart(); m_depth++; m_isMap[m_depth] = false; m_isList[m_depth] = false; @@ -54,13 +33,13 @@ class CborShapeSerializer::Impl { } void EndStructure() { - m_buf += static_cast(CBOR_BREAK); + m_encoder.WriteBreak(); m_depth--; } void WriteBoolean(const Schema& schema, bool value) { WriteFieldName(schema); - m_buf += static_cast(value ? CBOR_TRUE : CBOR_FALSE); + m_encoder.WriteBool(value); } void WriteInteger(const Schema& schema, int value) { @@ -73,39 +52,43 @@ class CborShapeSerializer::Impl { WriteIntValue(value); } + void WriteFloat(const Schema& schema, float value) { + WriteFieldName(schema); + m_encoder.WriteFloat(value); + } + void WriteDouble(const Schema& schema, double value) { WriteFieldName(schema); - WriteDoubleValue(value); + m_encoder.WriteFloat(value); } void WriteString(const Schema& schema, const Aws::String& value) { WriteFieldName(schema); - WriteTextString(value); + m_encoder.WriteText(Aws::Crt::ByteCursorFromArray(reinterpret_cast(value.data()), value.size())); } void WriteTimestamp(const Schema& schema, const DateTime& value) { WriteFieldName(schema); double seconds = value.SecondsWithMSPrecision(); - WriteTagAndLength(TYPE_TAG, 1); + m_encoder.WriteTag(1); double intPart; if (std::modf(seconds, &intPart) == 0.0) { WriteIntValue(static_cast(intPart)); } else { - WriteDoubleValue(seconds); + m_encoder.WriteFloat(seconds); } } void WriteBlob(const Schema& schema, const ByteBuffer& value) { WriteFieldName(schema); - WriteTagAndLength(TYPE_BYTESTRING, static_cast(value.GetLength())); - m_buf.append(reinterpret_cast(value.GetUnderlyingData()), value.GetLength()); + m_encoder.WriteBytes(Aws::Crt::ByteCursorFromArray(value.GetUnderlyingData(), value.GetLength())); } void WriteEnum(const Schema& schema, int value) { WriteInteger(schema, value); } void WriteNull(const Schema& schema) { WriteFieldName(schema); - m_buf += static_cast(CBOR_NULL); + m_encoder.WriteNull(); } bool BeginList(const Schema& schema, size_t count) { @@ -114,7 +97,7 @@ class CborShapeSerializer::Impl { return false; } WriteFieldName(schema); - WriteTagAndLength(TYPE_ARRAY, static_cast(count)); + m_encoder.WriteArrayStart(count); m_depth++; m_isMap[m_depth] = false; m_isList[m_depth] = true; @@ -123,13 +106,13 @@ class CborShapeSerializer::Impl { void EndList() { m_depth--; } - bool BeginMap(const Schema& schema, size_t) { + bool BeginMap(const Schema& schema, size_t count) { if (m_depth + 1 >= MAX_DEPTH) { m_errorMessage = "Maximum nesting depth exceeded"; return false; } WriteFieldName(schema); - m_buf += static_cast(CBOR_INDEFINITE_MAP); + m_encoder.WriteMapStart(count); m_depth++; m_isMap[m_depth] = true; m_isList[m_depth] = false; @@ -138,10 +121,7 @@ class CborShapeSerializer::Impl { void WriteMapKey(const Aws::String& key) { m_currentMapKey = key; } - void EndMap() { - m_buf += static_cast(CBOR_BREAK); - m_depth--; - } + void EndMap() { m_depth--; } bool BeginNestedStructure(const Schema& schema) { if (m_depth + 1 >= MAX_DEPTH) { @@ -149,7 +129,7 @@ class CborShapeSerializer::Impl { return false; } WriteFieldName(schema); - m_buf += static_cast(CBOR_INDEFINITE_MAP); + m_encoder.WriteIndefMapStart(); m_depth++; m_isMap[m_depth] = false; m_isList[m_depth] = false; @@ -157,7 +137,7 @@ class CborShapeSerializer::Impl { } void EndNestedStructure() { - m_buf += static_cast(CBOR_BREAK); + m_encoder.WriteBreak(); m_depth--; } @@ -168,11 +148,12 @@ class CborShapeSerializer::Impl { !m_errorMessage.empty() ? m_errorMessage : "Serializer has already been finalized", false); } m_finalized = true; - return std::move(m_buf); + auto encoded = m_encoder.GetEncodedData(); + return Aws::String(reinterpret_cast(encoded.ptr), encoded.len); } private: - Aws::String m_buf; + Aws::Crt::Cbor::CborEncoder m_encoder; int m_depth = 0; Aws::Array m_isMap{}; Aws::Array m_isList{}; @@ -185,60 +166,21 @@ class CborShapeSerializer::Impl { return; } if (m_depth > 0 && m_isMap[m_depth]) { - WriteTextString(m_currentMapKey); + WriteText(m_currentMapKey); } else { - WriteTextString(schema.GetMemberName()); + WriteText(schema.GetMemberName()); } } - void WriteTextString(const Aws::String& str) { - WriteTagAndLength(TYPE_TEXTSTRING, static_cast(str.size())); - m_buf.append(str.data(), str.size()); + void WriteText(const Aws::String& str) { + m_encoder.WriteText(Aws::Crt::ByteCursorFromArray(reinterpret_cast(str.data()), str.size())); } void WriteIntValue(int64_t value) { if (value >= 0) { - WriteTagAndLength(TYPE_POSINT, static_cast(value)); - } else { - WriteTagAndLength(TYPE_NEGINT, static_cast(-(value + 1))); - } - } - - void WriteDoubleValue(double value) { - m_buf += static_cast(TYPE_SIMPLE | ADDITIONAL_EIGHT_BYTES); - uint64_t bits; - std::memcpy(&bits, &value, sizeof(bits)); - for (int i = 56; i >= 0; i -= 8) { - m_buf += static_cast((bits >> i) & 0xFF); - } - } - - void WriteTagAndLength(unsigned char majorType, uint64_t value) { - if (value <= 23) { - m_buf += static_cast(majorType | static_cast(value)); - } else if (value <= 0xFF) { - m_buf += static_cast(majorType | ADDITIONAL_ONE_BYTE); - m_buf += static_cast(value & 0xFF); - } else if (value <= 0xFFFF) { - m_buf += static_cast(majorType | ADDITIONAL_TWO_BYTES); - m_buf += static_cast((value >> 8) & 0xFF); - m_buf += static_cast(value & 0xFF); - } else if (value <= 0xFFFFFFFF) { - m_buf += static_cast(majorType | ADDITIONAL_FOUR_BYTES); - m_buf += static_cast((value >> 24) & 0xFF); - m_buf += static_cast((value >> 16) & 0xFF); - m_buf += static_cast((value >> 8) & 0xFF); - m_buf += static_cast(value & 0xFF); + m_encoder.WriteUInt(static_cast(value)); } else { - m_buf += static_cast(majorType | ADDITIONAL_EIGHT_BYTES); - m_buf += static_cast((value >> 56) & 0xFF); - m_buf += static_cast((value >> 48) & 0xFF); - m_buf += static_cast((value >> 40) & 0xFF); - m_buf += static_cast((value >> 32) & 0xFF); - m_buf += static_cast((value >> 24) & 0xFF); - m_buf += static_cast((value >> 16) & 0xFF); - m_buf += static_cast((value >> 8) & 0xFF); - m_buf += static_cast(value & 0xFF); + m_encoder.WriteNegInt(static_cast(-(value + 1))); } } }; @@ -251,6 +193,7 @@ void CborShapeSerializer::EndStructure() { m_impl->EndStructure(); } void CborShapeSerializer::WriteBoolean(const Schema& schema, bool value) { m_impl->WriteBoolean(schema, value); } void CborShapeSerializer::WriteInteger(const Schema& schema, int value) { m_impl->WriteInteger(schema, value); } void CborShapeSerializer::WriteLong(const Schema& schema, int64_t value) { m_impl->WriteLong(schema, value); } +void CborShapeSerializer::WriteFloat(const Schema& schema, float value) { m_impl->WriteFloat(schema, value); } void CborShapeSerializer::WriteDouble(const Schema& schema, double value) { m_impl->WriteDouble(schema, value); } void CborShapeSerializer::WriteString(const Schema& schema, const Aws::String& value) { m_impl->WriteString(schema, value); } void CborShapeSerializer::WriteTimestamp(const Schema& schema, const DateTime& value) { m_impl->WriteTimestamp(schema, value); } diff --git a/src/aws-cpp-sdk-core/source/smithy/client/schema/JsonShapeSerializer.cpp b/src/aws-cpp-sdk-core/source/smithy/client/schema/JsonShapeSerializer.cpp index a438cb55ac3..a9eaacc7d23 100644 --- a/src/aws-cpp-sdk-core/source/smithy/client/schema/JsonShapeSerializer.cpp +++ b/src/aws-cpp-sdk-core/source/smithy/client/schema/JsonShapeSerializer.cpp @@ -59,6 +59,11 @@ class JsonShapeSerializer::Impl { m_buf += StringUtils::to_string(value); } + void WriteFloat(const Schema& schema, float value) { + WriteFieldName(schema); + m_buf += StringUtils::to_string(value); + } + void WriteDouble(const Schema& schema, double value) { WriteFieldName(schema); m_buf += StringUtils::to_string(value); @@ -208,6 +213,7 @@ void JsonShapeSerializer::EndStructure() { m_impl->EndStructure(); } void JsonShapeSerializer::WriteBoolean(const Schema& schema, bool value) { m_impl->WriteBoolean(schema, value); } void JsonShapeSerializer::WriteInteger(const Schema& schema, int value) { m_impl->WriteInteger(schema, value); } void JsonShapeSerializer::WriteLong(const Schema& schema, int64_t value) { m_impl->WriteLong(schema, value); } +void JsonShapeSerializer::WriteFloat(const Schema& schema, float value) { m_impl->WriteFloat(schema, value); } void JsonShapeSerializer::WriteDouble(const Schema& schema, double value) { m_impl->WriteDouble(schema, value); } void JsonShapeSerializer::WriteString(const Schema& schema, const Aws::String& value) { m_impl->WriteString(schema, value); } void JsonShapeSerializer::WriteTimestamp(const Schema& schema, const DateTime& value) { m_impl->WriteTimestamp(schema, value); } diff --git a/src/aws-cpp-sdk-core/source/smithy/client/schema/XmlShapeSerializer.cpp b/src/aws-cpp-sdk-core/source/smithy/client/schema/XmlShapeSerializer.cpp index 215dca95246..b39311c9dea 100644 --- a/src/aws-cpp-sdk-core/source/smithy/client/schema/XmlShapeSerializer.cpp +++ b/src/aws-cpp-sdk-core/source/smithy/client/schema/XmlShapeSerializer.cpp @@ -71,6 +71,8 @@ class XmlShapeSerializer::Impl { void WriteLong(const Schema& schema, int64_t value) { WrapValue(GetXmlName(schema), StringUtils::to_string(value)); } + void WriteFloat(const Schema& schema, float value) { WrapValue(GetXmlName(schema), StringUtils::to_string(value)); } + void WriteDouble(const Schema& schema, double value) { WrapValue(GetXmlName(schema), StringUtils::to_string(value)); } void WriteString(const Schema& schema, const Aws::String& value) { WrapEscapedValue(GetXmlName(schema), value); } @@ -392,6 +394,7 @@ void XmlShapeSerializer::EndStructure() { m_impl->EndStructure(); } void XmlShapeSerializer::WriteBoolean(const Schema& schema, bool value) { m_impl->WriteBoolean(schema, value); } void XmlShapeSerializer::WriteInteger(const Schema& schema, int value) { m_impl->WriteInteger(schema, value); } void XmlShapeSerializer::WriteLong(const Schema& schema, int64_t value) { m_impl->WriteLong(schema, value); } +void XmlShapeSerializer::WriteFloat(const Schema& schema, float value) { m_impl->WriteFloat(schema, value); } void XmlShapeSerializer::WriteDouble(const Schema& schema, double value) { m_impl->WriteDouble(schema, value); } void XmlShapeSerializer::WriteString(const Schema& schema, const Aws::String& value) { m_impl->WriteString(schema, value); } void XmlShapeSerializer::WriteTimestamp(const Schema& schema, const DateTime& value) { m_impl->WriteTimestamp(schema, value); } diff --git a/tests/aws-cpp-sdk-core-tests/smithy/client/schema/CborShapeSerializerTest.cpp b/tests/aws-cpp-sdk-core-tests/smithy/client/schema/CborShapeSerializerTest.cpp index ebbadc9f567..fc989d49dc6 100644 --- a/tests/aws-cpp-sdk-core-tests/smithy/client/schema/CborShapeSerializerTest.cpp +++ b/tests/aws-cpp-sdk-core-tests/smithy/client/schema/CborShapeSerializerTest.cpp @@ -567,7 +567,7 @@ TEST_F(CborShapeSerializerTest, MapOfStrings) { expected += '\xBF'; expected += '\x67'; // text "headers" length 7 expected += "headers"; - expected += '\xBF'; // inner indefinite map + expected += '\xA2'; // definite map of 2 entries expected += '\x63'; // text "foo" length 3 expected += "foo"; expected += '\x63'; // text "bar" length 3 @@ -576,7 +576,6 @@ TEST_F(CborShapeSerializerTest, MapOfStrings) { expected += "baz"; expected += '\x63'; // text "qux" expected += "qux"; - expected += '\xFF'; // inner map break expected += '\xFF'; // outer map break EXPECT_EQ(payload, expected); } @@ -596,8 +595,7 @@ TEST_F(CborShapeSerializerTest, EmptyMap) { expected += '\xBF'; expected += '\x64'; // text "tags" length 4 expected += "tags"; - expected += '\xBF'; // indefinite map (empty) - expected += '\xFF'; // break + expected += '\xA0'; // definite map of 0 entries expected += '\xFF'; // outer break EXPECT_EQ(payload, expected); } @@ -623,15 +621,14 @@ TEST_F(CborShapeSerializerTest, MapOfStructures) { expected += '\xBF'; expected += '\x65'; // "nodes" expected += "nodes"; - expected += '\xBF'; // map start + expected += '\xA1'; // definite map of 1 entry expected += '\x61'; // key "a" expected += 'a'; - expected += '\xBF'; // nested struct + expected += '\xBF'; // nested struct (indefinite) expected += '\x63'; // "val" expected += "val"; expected += '\x01'; // integer 1 expected += '\xFF'; // end nested struct - expected += '\xFF'; // end map expected += '\xFF'; // end outer map EXPECT_EQ(payload, expected); } @@ -643,7 +640,7 @@ TEST_F(CborShapeSerializerTest, MaxDepthEnforcement) { Schema root; Schema nested("n", ShapeType::Structure); s.BeginStructure(root); - for (int i = 0; i < 500; i++) { + for (int i = 0; i < 1000; i++) { s.BeginNestedStructure(nested); } auto outcome = s.GetPayload(); @@ -724,11 +721,10 @@ TEST_F(CborShapeSerializerTest, StructureWithListAndMap) { expected += "t1"; expected += '\x64'; // "meta" expected += "meta"; - expected += '\xBF'; // map start + expected += '\xA1'; // definite map of 1 entry expected += '\x61'; // "k" expected += 'k'; expected += '\x05'; // integer 5 - expected += '\xFF'; // map end expected += '\xFF'; // outer end EXPECT_EQ(payload, expected); } @@ -802,3 +798,329 @@ TEST_F(CborShapeSerializerTest, SparseList) { expected += '\xFF'; EXPECT_EQ(payload, expected); } + +// --- Additional coverage --- + +TEST_F(CborShapeSerializerTest, IntegerFourByte) { + CborShapeSerializer s; + Schema root; + Schema member("n", ShapeType::Integer); + s.BeginStructure(root); + s.WriteInteger(member, 70000); + s.EndStructure(); + auto outcome = s.GetPayload(); + ASSERT_TRUE(outcome.IsSuccess()); + const auto& payload = outcome.GetResult(); + Aws::String expected; + expected += '\xBF'; + expected += '\x61'; // "n" + expected += 'n'; + expected += '\x1A'; // posint, four-byte follows + expected += '\x00'; + expected += '\x01'; + expected += '\x11'; + expected += '\x70'; // 70000 = 0x00011170 + expected += '\xFF'; + EXPECT_EQ(payload, expected); +} + +TEST_F(CborShapeSerializerTest, LongEightByte) { + CborShapeSerializer s; + Schema root; + Schema member("n", ShapeType::Long); + s.BeginStructure(root); + s.WriteLong(member, 5000000000LL); + s.EndStructure(); + auto outcome = s.GetPayload(); + ASSERT_TRUE(outcome.IsSuccess()); + const auto& payload = outcome.GetResult(); + Aws::String expected; + expected += '\xBF'; + expected += '\x61'; // "n" + expected += 'n'; + expected += '\x1B'; // posint, eight-byte follows + expected += '\x00'; + expected += '\x00'; + expected += '\x00'; + expected += '\x01'; + expected += '\x2A'; + expected += '\x05'; + expected += '\xF2'; + expected += '\x00'; // 5000000000 = 0x000000012A05F200 + expected += '\xFF'; + EXPECT_EQ(payload, expected); +} + +TEST_F(CborShapeSerializerTest, LargeNegativeInteger) { + CborShapeSerializer s; + Schema root; + Schema member("n", ShapeType::Long); + s.BeginStructure(root); + s.WriteLong(member, -1000000LL); + s.EndStructure(); + auto outcome = s.GetPayload(); + ASSERT_TRUE(outcome.IsSuccess()); + const auto& payload = outcome.GetResult(); + Aws::String expected; + expected += '\xBF'; + expected += '\x61'; // "n" + expected += 'n'; + expected += '\x3A'; // negint, four-byte follows + expected += '\x00'; + expected += '\x0F'; + expected += '\x42'; + expected += '\x3F'; // -1000000 => negint encoding: -(n+1), so 999999 = 0x000F423F + expected += '\xFF'; + EXPECT_EQ(payload, expected); +} + +TEST_F(CborShapeSerializerTest, DoubleWholeNumberEncodedAsInt) { + // CRT encoder uses "smallest possible" — a double like 5.0 encodes as integer 5 + CborShapeSerializer s; + Schema root; + Schema member("d", ShapeType::Double); + s.BeginStructure(root); + s.WriteDouble(member, 5.0); + s.EndStructure(); + auto outcome = s.GetPayload(); + ASSERT_TRUE(outcome.IsSuccess()); + const auto& payload = outcome.GetResult(); + // Expect integer encoding (0x05), not double encoding (0xFB + 8 bytes) + Aws::String expected; + expected += '\xBF'; + expected += '\x61'; // "d" + expected += 'd'; + expected += '\x05'; // integer 5 (smallest possible) + expected += '\xFF'; + EXPECT_EQ(payload, expected); +} + +TEST_F(CborShapeSerializerTest, DoubleNegativeWholeNumberEncodedAsNegInt) { + // CRT encoder: -3.0 encodes as negint 2 (meaning -(2+1) = -3) + CborShapeSerializer s; + Schema root; + Schema member("d", ShapeType::Double); + s.BeginStructure(root); + s.WriteDouble(member, -3.0); + s.EndStructure(); + auto outcome = s.GetPayload(); + ASSERT_TRUE(outcome.IsSuccess()); + const auto& payload = outcome.GetResult(); + Aws::String expected; + expected += '\xBF'; + expected += '\x61'; // "d" + expected += 'd'; + expected += '\x22'; // negint 2 => -3 + expected += '\xFF'; + EXPECT_EQ(payload, expected); +} + +TEST_F(CborShapeSerializerTest, UnicodeString) { + CborShapeSerializer s; + Schema root; + Schema member("s", ShapeType::String); + s.BeginStructure(root); + s.WriteString(member, "\xC3\xA9\xC3\xA8"); // "éè" in UTF-8 (4 bytes) + s.EndStructure(); + auto outcome = s.GetPayload(); + ASSERT_TRUE(outcome.IsSuccess()); + const auto& payload = outcome.GetResult(); + Aws::String expected; + expected += '\xBF'; + expected += '\x61'; // "s" + expected += 's'; + expected += '\x64'; // text length 4 + expected += "\xC3\xA9\xC3\xA8"; + expected += '\xFF'; + EXPECT_EQ(payload, expected); +} + +TEST_F(CborShapeSerializerTest, NestedListInList) { + CborShapeSerializer s; + Schema root; + Schema outerList("data", ShapeType::List); + Schema innerList("member", ShapeType::List); + Schema elem("member", ShapeType::Integer); + s.BeginStructure(root); + s.BeginList(outerList, 2); + s.BeginList(innerList, 2); + s.WriteInteger(elem, 1); + s.WriteInteger(elem, 2); + s.EndList(); + s.BeginList(innerList, 1); + s.WriteInteger(elem, 3); + s.EndList(); + s.EndList(); + s.EndStructure(); + auto outcome = s.GetPayload(); + ASSERT_TRUE(outcome.IsSuccess()); + const auto& payload = outcome.GetResult(); + Aws::String expected; + expected += '\xBF'; + expected += '\x64'; // "data" + expected += "data"; + expected += '\x82'; // outer array of 2 + expected += '\x82'; // inner array of 2 + expected += '\x01'; // 1 + expected += '\x02'; // 2 + expected += '\x81'; // inner array of 1 + expected += '\x03'; // 3 + expected += '\xFF'; + EXPECT_EQ(payload, expected); +} + +TEST_F(CborShapeSerializerTest, MapWithMultipleEntries) { + CborShapeSerializer s; + Schema root; + Schema mapMember("m", ShapeType::Map); + Schema valSchema("value", ShapeType::Integer); + s.BeginStructure(root); + s.BeginMap(mapMember, 3); + s.WriteMapKey("x"); + s.WriteInteger(valSchema, 1); + s.WriteMapKey("y"); + s.WriteInteger(valSchema, 2); + s.WriteMapKey("z"); + s.WriteInteger(valSchema, 3); + s.EndMap(); + s.EndStructure(); + auto outcome = s.GetPayload(); + ASSERT_TRUE(outcome.IsSuccess()); + const auto& payload = outcome.GetResult(); + Aws::String expected; + expected += '\xBF'; + expected += '\x61'; // "m" + expected += 'm'; + expected += '\xA3'; // definite map of 3 + expected += '\x61'; // "x" + expected += 'x'; + expected += '\x01'; // 1 + expected += '\x61'; // "y" + expected += 'y'; + expected += '\x02'; // 2 + expected += '\x61'; // "z" + expected += 'z'; + expected += '\x03'; // 3 + expected += '\xFF'; + EXPECT_EQ(payload, expected); +} + +TEST_F(CborShapeSerializerTest, FloatValue) { + CborShapeSerializer s; + Schema root; + Schema member("f", ShapeType::Float); + s.BeginStructure(root); + s.WriteFloat(member, 1.5f); + s.EndStructure(); + auto outcome = s.GetPayload(); + ASSERT_TRUE(outcome.IsSuccess()); + const auto& payload = outcome.GetResult(); + // 1.5 can be represented exactly as float32, CRT encodes as single float (0xFA) + Aws::String expected; + expected += '\xBF'; + expected += '\x61'; // "f" + expected += 'f'; + expected += '\xFA'; // single-precision float marker + expected += '\x3F'; // 1.5f IEEE 754: 0x3FC00000 + expected += '\xC0'; + expected += '\x00'; + expected += '\x00'; + expected += '\xFF'; + EXPECT_EQ(payload, expected); +} + +TEST_F(CborShapeSerializerTest, FloatWholeNumberEncodedAsInt) { + // CRT "smallest possible": 7.0f encodes as integer 7 + CborShapeSerializer s; + Schema root; + Schema member("f", ShapeType::Float); + s.BeginStructure(root); + s.WriteFloat(member, 7.0f); + s.EndStructure(); + auto outcome = s.GetPayload(); + ASSERT_TRUE(outcome.IsSuccess()); + const auto& payload = outcome.GetResult(); + Aws::String expected; + expected += '\xBF'; + expected += '\x61'; // "f" + expected += 'f'; + expected += '\x07'; // integer 7 + expected += '\xFF'; + EXPECT_EQ(payload, expected); +} + +TEST_F(CborShapeSerializerTest, TimestampEpochZero) { + CborShapeSerializer s; + Schema root; + Schema member("ts", ShapeType::Timestamp); + s.BeginStructure(root); + Aws::Utils::DateTime dt(0.0); + s.WriteTimestamp(member, dt); + s.EndStructure(); + auto outcome = s.GetPayload(); + ASSERT_TRUE(outcome.IsSuccess()); + const auto& payload = outcome.GetResult(); + // Tag 1 + integer 0 + Aws::String expected; + expected += '\xBF'; + expected += '\x62'; // "ts" + expected += "ts"; + expected += '\xC1'; // tag 1 + expected += '\x00'; // integer 0 + expected += '\xFF'; + EXPECT_EQ(payload, expected); +} + +TEST_F(CborShapeSerializerTest, LargeBlob) { + CborShapeSerializer s; + Schema root; + Schema member("b", ShapeType::Blob); + s.BeginStructure(root); + Aws::Utils::ByteBuffer blob(300); + for (size_t i = 0; i < 300; i++) { + blob[i] = static_cast(i % 256); + } + s.WriteBlob(member, blob); + s.EndStructure(); + auto outcome = s.GetPayload(); + ASSERT_TRUE(outcome.IsSuccess()); + const auto& payload = outcome.GetResult(); + // Verify: BF + key "b" + bytestring header (two-byte length for 300) + 300 bytes + FF + ASSERT_GE(payload.size(), 306u); + EXPECT_EQ(static_cast(payload[0]), 0xBF); + EXPECT_EQ(static_cast(payload[1]), 0x61); // text len 1 + EXPECT_EQ(payload[2], 'b'); + EXPECT_EQ(static_cast(payload[3]), 0x59); // bytestring, two-byte length + EXPECT_EQ(static_cast(payload[4]), 0x01); // 300 = 0x012C + EXPECT_EQ(static_cast(payload[5]), 0x2C); + EXPECT_EQ(static_cast(payload[payload.size() - 1]), 0xFF); + EXPECT_EQ(payload.size(), 3 + 3 + 300 + 1u); // key + header + blob + break +} + +TEST_F(CborShapeSerializerTest, UnionAsStructure) { + // Unions serialize like a structure with exactly one field + CborShapeSerializer s; + Schema root; + Schema unionMember("result", ShapeType::Union); + Schema field("message", ShapeType::String); + s.BeginStructure(root); + s.BeginNestedStructure(unionMember); + s.WriteString(field, "ok"); + s.EndNestedStructure(); + s.EndStructure(); + auto outcome = s.GetPayload(); + ASSERT_TRUE(outcome.IsSuccess()); + const auto& payload = outcome.GetResult(); + Aws::String expected; + expected += '\xBF'; + expected += '\x66'; // "result" + expected += "result"; + expected += '\xBF'; // nested indefinite map (union) + expected += '\x67'; // "message" + expected += "message"; + expected += '\x62'; // "ok" + expected += "ok"; + expected += '\xFF'; // end union + expected += '\xFF'; // end outer + EXPECT_EQ(payload, expected); +} diff --git a/tests/aws-cpp-sdk-core-tests/smithy/client/schema/JsonShapeSerializerTest.cpp b/tests/aws-cpp-sdk-core-tests/smithy/client/schema/JsonShapeSerializerTest.cpp index ccb83818530..7ca1e8ffd89 100644 --- a/tests/aws-cpp-sdk-core-tests/smithy/client/schema/JsonShapeSerializerTest.cpp +++ b/tests/aws-cpp-sdk-core-tests/smithy/client/schema/JsonShapeSerializerTest.cpp @@ -465,3 +465,27 @@ TEST_F(JsonShapeSerializerTest, NoJsonNameUsesGetMemberName) { s.EndStructure(); EXPECT_NE(s.GetPayload().GetResult().find("\"fieldName\":\"value\""), Aws::String::npos); } + +TEST_F(JsonShapeSerializerTest, FloatValue) { + JsonShapeSerializer s; + Schema root; + Schema member("f", ShapeType::Float); + s.BeginStructure(root); + s.WriteFloat(member, 1.5f); + s.EndStructure(); + auto outcome = s.GetPayload(); + ASSERT_TRUE(outcome.IsSuccess()); + EXPECT_EQ(outcome.GetResult(), "{\"f\":1.5}"); +} + +TEST_F(JsonShapeSerializerTest, FloatNegativeValue) { + JsonShapeSerializer s; + Schema root; + Schema member("f", ShapeType::Float); + s.BeginStructure(root); + s.WriteFloat(member, -2.25f); + s.EndStructure(); + auto outcome = s.GetPayload(); + ASSERT_TRUE(outcome.IsSuccess()); + EXPECT_EQ(outcome.GetResult(), "{\"f\":-2.25}"); +} diff --git a/tests/aws-cpp-sdk-core-tests/smithy/client/schema/XmlShapeSerializerTest.cpp b/tests/aws-cpp-sdk-core-tests/smithy/client/schema/XmlShapeSerializerTest.cpp index b42c996db37..0918cc9f503 100644 --- a/tests/aws-cpp-sdk-core-tests/smithy/client/schema/XmlShapeSerializerTest.cpp +++ b/tests/aws-cpp-sdk-core-tests/smithy/client/schema/XmlShapeSerializerTest.cpp @@ -503,3 +503,25 @@ TEST_F(XmlShapeSerializerTest, FlattenedMap) { EXPECT_NE(payload.find("k1v1"), Aws::String::npos); EXPECT_NE(payload.find("k2v2"), Aws::String::npos); } + +TEST_F(XmlShapeSerializerTest, FloatValue) { + XmlShapeSerializer s; + Schema root("Root", ShapeType::Structure); + Schema member("temp", ShapeType::Float); + s.BeginStructure(root); + s.WriteFloat(member, 1.5f); + s.EndStructure(); + auto payload = s.GetPayload().GetResult(); + EXPECT_NE(payload.find("1.5"), Aws::String::npos); +} + +TEST_F(XmlShapeSerializerTest, FloatNegativeValue) { + XmlShapeSerializer s; + Schema root("Root", ShapeType::Structure); + Schema member("val", ShapeType::Float); + s.BeginStructure(root); + s.WriteFloat(member, -2.25f); + s.EndStructure(); + auto payload = s.GetPayload().GetResult(); + EXPECT_NE(payload.find("-2.25"), Aws::String::npos); +} From 1b3bc480032a7a8a22e6afaea797e7a1390f0c82 Mon Sep 17 00:00:00 2001 From: pulimsr Date: Tue, 21 Jul 2026 17:11:03 -0400 Subject: [PATCH 3/4] using CRT timestamp encoding and removing depth checking --- .../client/schema/CborShapeSerializer.cpp | 33 +++---------------- .../client/schema/CborShapeSerializerTest.cpp | 29 ++++------------ 2 files changed, 11 insertions(+), 51 deletions(-) diff --git a/src/aws-cpp-sdk-core/source/smithy/client/schema/CborShapeSerializer.cpp b/src/aws-cpp-sdk-core/source/smithy/client/schema/CborShapeSerializer.cpp index a8bc22c5b7a..20cb2917dc5 100644 --- a/src/aws-cpp-sdk-core/source/smithy/client/schema/CborShapeSerializer.cpp +++ b/src/aws-cpp-sdk-core/source/smithy/client/schema/CborShapeSerializer.cpp @@ -8,23 +8,17 @@ #include #include -#include - using namespace smithy::schema; using namespace Aws::Utils; using SerializerOutcome = Aws::Utils::Outcome>; namespace { -constexpr int MAX_DEPTH = 1000; +constexpr int MAX_DEPTH = 128; } // namespace class CborShapeSerializer::Impl { public: bool BeginStructure(const Schema&) { - if (m_depth + 1 >= MAX_DEPTH) { - m_errorMessage = "Maximum nesting depth exceeded"; - return false; - } m_encoder.WriteIndefMapStart(); m_depth++; m_isMap[m_depth] = false; @@ -69,14 +63,8 @@ class CborShapeSerializer::Impl { void WriteTimestamp(const Schema& schema, const DateTime& value) { WriteFieldName(schema); - double seconds = value.SecondsWithMSPrecision(); m_encoder.WriteTag(1); - double intPart; - if (std::modf(seconds, &intPart) == 0.0) { - WriteIntValue(static_cast(intPart)); - } else { - m_encoder.WriteFloat(seconds); - } + WriteIntValue(static_cast(value.Seconds())); } void WriteBlob(const Schema& schema, const ByteBuffer& value) { @@ -92,10 +80,6 @@ class CborShapeSerializer::Impl { } bool BeginList(const Schema& schema, size_t count) { - if (m_depth + 1 >= MAX_DEPTH) { - m_errorMessage = "Maximum nesting depth exceeded"; - return false; - } WriteFieldName(schema); m_encoder.WriteArrayStart(count); m_depth++; @@ -107,10 +91,6 @@ class CborShapeSerializer::Impl { void EndList() { m_depth--; } bool BeginMap(const Schema& schema, size_t count) { - if (m_depth + 1 >= MAX_DEPTH) { - m_errorMessage = "Maximum nesting depth exceeded"; - return false; - } WriteFieldName(schema); m_encoder.WriteMapStart(count); m_depth++; @@ -124,10 +104,6 @@ class CborShapeSerializer::Impl { void EndMap() { m_depth--; } bool BeginNestedStructure(const Schema& schema) { - if (m_depth + 1 >= MAX_DEPTH) { - m_errorMessage = "Maximum nesting depth exceeded"; - return false; - } WriteFieldName(schema); m_encoder.WriteIndefMapStart(); m_depth++; @@ -142,10 +118,10 @@ class CborShapeSerializer::Impl { } SerializerOutcome GetPayload() { - if (m_finalized || !m_errorMessage.empty()) { + if (m_finalized) { return Aws::Client::AWSError( Aws::Client::CoreErrors::INTERNAL_FAILURE, "SerializationException", - !m_errorMessage.empty() ? m_errorMessage : "Serializer has already been finalized", false); + "Serializer has already been finalized", false); } m_finalized = true; auto encoded = m_encoder.GetEncodedData(); @@ -159,7 +135,6 @@ class CborShapeSerializer::Impl { Aws::Array m_isList{}; Aws::String m_currentMapKey; bool m_finalized = false; - Aws::String m_errorMessage; void WriteFieldName(const Schema& schema) { if (m_isList[m_depth]) { diff --git a/tests/aws-cpp-sdk-core-tests/smithy/client/schema/CborShapeSerializerTest.cpp b/tests/aws-cpp-sdk-core-tests/smithy/client/schema/CborShapeSerializerTest.cpp index fc989d49dc6..c70bd43f531 100644 --- a/tests/aws-cpp-sdk-core-tests/smithy/client/schema/CborShapeSerializerTest.cpp +++ b/tests/aws-cpp-sdk-core-tests/smithy/client/schema/CborShapeSerializerTest.cpp @@ -312,6 +312,7 @@ TEST_F(CborShapeSerializerTest, TimestampIntegerSeconds) { } TEST_F(CborShapeSerializerTest, TimestampFractionalSeconds) { + // Fractional seconds are truncated to integer per rpcv2Cbor protocol CborShapeSerializer s; Schema root; Schema member("ts", ShapeType::Timestamp); @@ -322,18 +323,15 @@ TEST_F(CborShapeSerializerTest, TimestampFractionalSeconds) { auto outcome = s.GetPayload(); ASSERT_TRUE(outcome.IsSuccess()); const auto& payload = outcome.GetResult(); - // Should use tag 1 + double encoding - ASSERT_GE(payload.size(), 5u); - EXPECT_EQ(static_cast(payload[0]), 0xBF); - // After key "ts", expect tag 1 + double marker // key: 62 74 73 => positions [1..3] // tag 1: C1 => position [4] - // double: FB + 8 bytes => positions [5..13] - // break: FF => position [14] - EXPECT_EQ(payload.size(), 15u); + // integer 1234567890 (four-byte): 1A 49 96 02 D2 => positions [5..9] + // break: FF => position [10] + EXPECT_EQ(payload.size(), 11u); + EXPECT_EQ(static_cast(payload[0]), 0xBF); EXPECT_EQ(static_cast(payload[4]), 0xC1); - EXPECT_EQ(static_cast(payload[5]), 0xFB); - EXPECT_EQ(static_cast(payload[14]), 0xFF); + EXPECT_EQ(static_cast(payload[5]), 0x1A); + EXPECT_EQ(static_cast(payload[10]), 0xFF); } // --- Multiple fields --- @@ -635,19 +633,6 @@ TEST_F(CborShapeSerializerTest, MapOfStructures) { // --- Depth limit --- -TEST_F(CborShapeSerializerTest, MaxDepthEnforcement) { - CborShapeSerializer s; - Schema root; - Schema nested("n", ShapeType::Structure); - s.BeginStructure(root); - for (int i = 0; i < 1000; i++) { - s.BeginNestedStructure(nested); - } - auto outcome = s.GetPayload(); - ASSERT_FALSE(outcome.IsSuccess()); - EXPECT_NE(outcome.GetError().GetMessage().find("depth"), Aws::String::npos); -} - // --- GetPayload error cases --- TEST_F(CborShapeSerializerTest, GetPayloadCalledTwice) { From 72fe6c77934038bcbb1e26896f351f8b7392f934 Mon Sep 17 00:00:00 2001 From: pulimsr Date: Wed, 22 Jul 2026 13:24:19 -0400 Subject: [PATCH 4/4] Simplify CborShapeSerializer to pure CRT wrapper --- .../client/schema/CborShapeSerializer.cpp | 98 ++++--------------- .../client/schema/CborShapeSerializerTest.cpp | 53 ++++++++++ 2 files changed, 70 insertions(+), 81 deletions(-) diff --git a/src/aws-cpp-sdk-core/source/smithy/client/schema/CborShapeSerializer.cpp b/src/aws-cpp-sdk-core/source/smithy/client/schema/CborShapeSerializer.cpp index f429141903c..1dacae7c959 100644 --- a/src/aws-cpp-sdk-core/source/smithy/client/schema/CborShapeSerializer.cpp +++ b/src/aws-cpp-sdk-core/source/smithy/client/schema/CborShapeSerializer.cpp @@ -4,7 +4,6 @@ */ #include #include -#include #include #include @@ -12,110 +11,62 @@ using namespace smithy::schema; using namespace Aws::Utils; using SerializerOutcome = Aws::Utils::Outcome>; -namespace { -constexpr int MAX_DEPTH = 128; -} // namespace - class CborShapeSerializer::Impl { public: bool BeginStructure(const Schema&) { m_encoder.WriteIndefMapStart(); - m_depth++; - m_isMap[m_depth] = false; - m_isList[m_depth] = false; return true; } - void EndStructure() { - m_encoder.WriteBreak(); - m_depth--; - } + void EndStructure() { m_encoder.WriteBreak(); } - void WriteBoolean(const Schema& schema, bool value) { - WriteFieldName(schema); - m_encoder.WriteBool(value); - } + void WriteBoolean(const Schema&, bool value) { m_encoder.WriteBool(value); } - void WriteInteger(const Schema& schema, int value) { - WriteFieldName(schema); - WriteIntValue(value); - } + void WriteInteger(const Schema&, int value) { WriteIntValue(value); } - void WriteLong(const Schema& schema, int64_t value) { - WriteFieldName(schema); - WriteIntValue(value); - } + void WriteLong(const Schema&, int64_t value) { WriteIntValue(value); } - void WriteFloat(const Schema& schema, float value) { - WriteFieldName(schema); - m_encoder.WriteFloat(value); - } + void WriteFloat(const Schema&, float value) { m_encoder.WriteFloat(value); } - void WriteDouble(const Schema& schema, double value) { - WriteFieldName(schema); - m_encoder.WriteFloat(value); - } + void WriteDouble(const Schema&, double value) { m_encoder.WriteFloat(value); } - void WriteString(const Schema& schema, const Aws::String& value) { - WriteFieldName(schema); - m_encoder.WriteText(Aws::Crt::ByteCursorFromArray(reinterpret_cast(value.data()), value.size())); - } + void WriteString(const Schema&, const Aws::String& value) { WriteText(value); } - void WriteTimestamp(const Schema& schema, const DateTime& value) { - WriteFieldName(schema); + void WriteTimestamp(const Schema&, const DateTime& value) { m_encoder.WriteTag(1); WriteIntValue(value.Seconds()); } - void WriteBlob(const Schema& schema, const ByteBuffer& value) { - WriteFieldName(schema); + void WriteBlob(const Schema&, const ByteBuffer& value) { m_encoder.WriteBytes(Aws::Crt::ByteCursorFromArray(value.GetUnderlyingData(), value.GetLength())); } void WriteEnum(const Schema& schema, int value) { WriteInteger(schema, value); } - void WriteNull(const Schema& schema) { - WriteFieldName(schema); - m_encoder.WriteNull(); - } + void WriteNull(const Schema&) { m_encoder.WriteNull(); } - bool BeginList(const Schema& schema, size_t count) { - WriteFieldName(schema); + bool BeginList(const Schema&, size_t count) { m_encoder.WriteArrayStart(count); - m_depth++; - m_isMap[m_depth] = false; - m_isList[m_depth] = true; return true; } - void EndList() { m_depth--; } + void EndList() {} - bool BeginMap(const Schema& schema, size_t count) { - WriteFieldName(schema); + bool BeginMap(const Schema&, size_t count) { m_encoder.WriteMapStart(count); - m_depth++; - m_isMap[m_depth] = true; - m_isList[m_depth] = false; return true; } - void WriteMapKey(const Aws::String& key) { m_currentMapKey = key; } + void WriteMapKey(const Aws::String& key) { WriteText(key); } - void EndMap() { m_depth--; } + void EndMap() {} - bool BeginNestedStructure(const Schema& schema) { - WriteFieldName(schema); + bool BeginNestedStructure(const Schema&) { m_encoder.WriteIndefMapStart(); - m_depth++; - m_isMap[m_depth] = false; - m_isList[m_depth] = false; return true; } - void EndNestedStructure() { - m_encoder.WriteBreak(); - m_depth--; - } + void EndNestedStructure() { m_encoder.WriteBreak(); } SerializerOutcome GetPayload() { if (m_finalized) { @@ -130,23 +81,8 @@ class CborShapeSerializer::Impl { private: Aws::Crt::Cbor::CborEncoder m_encoder; - int m_depth = 0; - Aws::Array m_isMap{}; - Aws::Array m_isList{}; - Aws::String m_currentMapKey; bool m_finalized = false; - void WriteFieldName(const Schema& schema) { - if (m_isList[m_depth]) { - return; - } - if (m_depth > 0 && m_isMap[m_depth]) { - WriteText(m_currentMapKey); - } else { - WriteText(schema.GetMemberName()); - } - } - void WriteText(const Aws::String& str) { m_encoder.WriteText(Aws::Crt::ByteCursorFromArray(reinterpret_cast(str.data()), str.size())); } diff --git a/tests/aws-cpp-sdk-core-tests/smithy/client/schema/CborShapeSerializerTest.cpp b/tests/aws-cpp-sdk-core-tests/smithy/client/schema/CborShapeSerializerTest.cpp index c70bd43f531..a1f936d7088 100644 --- a/tests/aws-cpp-sdk-core-tests/smithy/client/schema/CborShapeSerializerTest.cpp +++ b/tests/aws-cpp-sdk-core-tests/smithy/client/schema/CborShapeSerializerTest.cpp @@ -35,6 +35,7 @@ TEST_F(CborShapeSerializerTest, BooleanTrue) { Schema root; Schema member("enabled", ShapeType::Boolean); s.BeginStructure(root); + s.WriteMapKey("enabled"); s.WriteBoolean(member, true); s.EndStructure(); auto outcome = s.GetPayload(); @@ -56,6 +57,7 @@ TEST_F(CborShapeSerializerTest, BooleanFalse) { Schema root; Schema member("ok", ShapeType::Boolean); s.BeginStructure(root); + s.WriteMapKey("ok"); s.WriteBoolean(member, false); s.EndStructure(); auto outcome = s.GetPayload(); @@ -75,6 +77,7 @@ TEST_F(CborShapeSerializerTest, IntegerSmall) { Schema root; Schema member("n", ShapeType::Integer); s.BeginStructure(root); + s.WriteMapKey("n"); s.WriteInteger(member, 1); s.EndStructure(); auto outcome = s.GetPayload(); @@ -94,6 +97,7 @@ TEST_F(CborShapeSerializerTest, IntegerOneByte) { Schema root; Schema member("n", ShapeType::Integer); s.BeginStructure(root); + s.WriteMapKey("n"); s.WriteInteger(member, 42); s.EndStructure(); auto outcome = s.GetPayload(); @@ -114,6 +118,7 @@ TEST_F(CborShapeSerializerTest, IntegerNegative) { Schema root; Schema member("n", ShapeType::Integer); s.BeginStructure(root); + s.WriteMapKey("n"); s.WriteInteger(member, -1); s.EndStructure(); auto outcome = s.GetPayload(); @@ -133,6 +138,7 @@ TEST_F(CborShapeSerializerTest, IntegerNegativeLarge) { Schema root; Schema member("n", ShapeType::Integer); s.BeginStructure(root); + s.WriteMapKey("n"); s.WriteInteger(member, -100); s.EndStructure(); auto outcome = s.GetPayload(); @@ -153,6 +159,7 @@ TEST_F(CborShapeSerializerTest, LongValue) { Schema root; Schema member("big", ShapeType::Long); s.BeginStructure(root); + s.WriteMapKey("big"); s.WriteLong(member, 1000000LL); s.EndStructure(); auto outcome = s.GetPayload(); @@ -177,6 +184,7 @@ TEST_F(CborShapeSerializerTest, DoubleValue) { Schema root; Schema member("d", ShapeType::Double); s.BeginStructure(root); + s.WriteMapKey("d"); s.WriteDouble(member, 3.14); s.EndStructure(); auto outcome = s.GetPayload(); @@ -206,6 +214,7 @@ TEST_F(CborShapeSerializerTest, StringValue) { Schema root; Schema member("name", ShapeType::String); s.BeginStructure(root); + s.WriteMapKey("name"); s.WriteString(member, "hello"); s.EndStructure(); auto outcome = s.GetPayload(); @@ -226,6 +235,7 @@ TEST_F(CborShapeSerializerTest, EmptyString) { Schema root; Schema member("s", ShapeType::String); s.BeginStructure(root); + s.WriteMapKey("s"); s.WriteString(member, ""); s.EndStructure(); auto outcome = s.GetPayload(); @@ -245,6 +255,7 @@ TEST_F(CborShapeSerializerTest, BlobValue) { Schema root; Schema member("data", ShapeType::Blob); s.BeginStructure(root); + s.WriteMapKey("data"); unsigned char raw[] = {0xDE, 0xAD, 0xBE, 0xEF}; Aws::Utils::ByteBuffer buf(raw, 4); s.WriteBlob(member, buf); @@ -270,6 +281,7 @@ TEST_F(CborShapeSerializerTest, NullValue) { Schema root; Schema member("item", ShapeType::String); s.BeginStructure(root); + s.WriteMapKey("item"); s.WriteNull(member); s.EndStructure(); auto outcome = s.GetPayload(); @@ -289,6 +301,7 @@ TEST_F(CborShapeSerializerTest, TimestampIntegerSeconds) { Schema root; Schema member("ts", ShapeType::Timestamp); s.BeginStructure(root); + s.WriteMapKey("ts"); // DateTime(int64_t) takes milliseconds; 1234567000ms = 1234567 seconds (no fractional part) Aws::Utils::DateTime dt(static_cast(1234567000)); s.WriteTimestamp(member, dt); @@ -317,6 +330,7 @@ TEST_F(CborShapeSerializerTest, TimestampFractionalSeconds) { Schema root; Schema member("ts", ShapeType::Timestamp); s.BeginStructure(root); + s.WriteMapKey("ts"); Aws::Utils::DateTime dt(1234567890.5); s.WriteTimestamp(member, dt); s.EndStructure(); @@ -343,8 +357,11 @@ TEST_F(CborShapeSerializerTest, MultipleScalars) { Schema m2("b", ShapeType::Integer); Schema m3("c", ShapeType::String); s.BeginStructure(root); + s.WriteMapKey("a"); s.WriteBoolean(m1, true); + s.WriteMapKey("b"); s.WriteInteger(m2, 7); + s.WriteMapKey("c"); s.WriteString(m3, "x"); s.EndStructure(); auto outcome = s.GetPayload(); @@ -374,7 +391,9 @@ TEST_F(CborShapeSerializerTest, NestedStructure) { Schema nested("meta", ShapeType::Structure); Schema inner("key", ShapeType::String); s.BeginStructure(root); + s.WriteMapKey("meta"); s.BeginNestedStructure(nested); + s.WriteMapKey("key"); s.WriteString(inner, "val"); s.EndNestedStructure(); s.EndStructure(); @@ -402,8 +421,11 @@ TEST_F(CborShapeSerializerTest, DeeplyNestedStructure) { Schema l2("l2", ShapeType::Structure); Schema leaf("v", ShapeType::Integer); s.BeginStructure(root); + s.WriteMapKey("l1"); s.BeginNestedStructure(l1); + s.WriteMapKey("l2"); s.BeginNestedStructure(l2); + s.WriteMapKey("v"); s.WriteInteger(leaf, 99); s.EndNestedStructure(); s.EndNestedStructure(); @@ -437,6 +459,7 @@ TEST_F(CborShapeSerializerTest, ListOfIntegers) { Schema listMember("nums", ShapeType::List); Schema elem("member", ShapeType::Integer); s.BeginStructure(root); + s.WriteMapKey("nums"); s.BeginList(listMember, 3); s.WriteInteger(elem, 1); s.WriteInteger(elem, 2); @@ -463,6 +486,7 @@ TEST_F(CborShapeSerializerTest, EmptyList) { Schema root; Schema listMember("items", ShapeType::List); s.BeginStructure(root); + s.WriteMapKey("items"); s.BeginList(listMember, 0); s.EndList(); s.EndStructure(); @@ -484,6 +508,7 @@ TEST_F(CborShapeSerializerTest, ListOfStrings) { Schema listMember("tags", ShapeType::List); Schema elem("member", ShapeType::String); s.BeginStructure(root); + s.WriteMapKey("tags"); s.BeginList(listMember, 2); s.WriteString(elem, "ab"); s.WriteString(elem, "cd"); @@ -512,11 +537,14 @@ TEST_F(CborShapeSerializerTest, ListOfStructures) { Schema structElem("member", ShapeType::Structure); Schema field("id", ShapeType::Integer); s.BeginStructure(root); + s.WriteMapKey("items"); s.BeginList(listMember, 2); s.BeginNestedStructure(structElem); + s.WriteMapKey("id"); s.WriteInteger(field, 1); s.EndNestedStructure(); s.BeginNestedStructure(structElem); + s.WriteMapKey("id"); s.WriteInteger(field, 2); s.EndNestedStructure(); s.EndList(); @@ -551,6 +579,7 @@ TEST_F(CborShapeSerializerTest, MapOfStrings) { Schema mapMember("headers", ShapeType::Map); Schema valSchema("value", ShapeType::String); s.BeginStructure(root); + s.WriteMapKey("headers"); s.BeginMap(mapMember, 2); s.WriteMapKey("foo"); s.WriteString(valSchema, "bar"); @@ -583,6 +612,7 @@ TEST_F(CborShapeSerializerTest, EmptyMap) { Schema root; Schema mapMember("tags", ShapeType::Map); s.BeginStructure(root); + s.WriteMapKey("tags"); s.BeginMap(mapMember, 0); s.EndMap(); s.EndStructure(); @@ -605,9 +635,11 @@ TEST_F(CborShapeSerializerTest, MapOfStructures) { Schema valSchema("value", ShapeType::Structure); Schema field("val", ShapeType::Integer); s.BeginStructure(root); + s.WriteMapKey("nodes"); s.BeginMap(mapMember, 1); s.WriteMapKey("a"); s.BeginNestedStructure(valSchema); + s.WriteMapKey("val"); s.WriteInteger(field, 1); s.EndNestedStructure(); s.EndMap(); @@ -654,6 +686,7 @@ TEST_F(CborShapeSerializerTest, EnumValue) { Schema root; Schema member("status", ShapeType::Enum); s.BeginStructure(root); + s.WriteMapKey("status"); s.WriteEnum(member, 3); s.EndStructure(); auto outcome = s.GetPayload(); @@ -680,10 +713,13 @@ TEST_F(CborShapeSerializerTest, StructureWithListAndMap) { Schema mapVal("value", ShapeType::Integer); s.BeginStructure(root); + s.WriteMapKey("name"); s.WriteString(strMember, "hi"); + s.WriteMapKey("tags"); s.BeginList(listMember, 1); s.WriteString(listElem, "t1"); s.EndList(); + s.WriteMapKey("meta"); s.BeginMap(mapMember, 1); s.WriteMapKey("k"); s.WriteInteger(mapVal, 5); @@ -719,6 +755,7 @@ TEST_F(CborShapeSerializerTest, IntegerZero) { Schema root; Schema member("z", ShapeType::Integer); s.BeginStructure(root); + s.WriteMapKey("z"); s.WriteInteger(member, 0); s.EndStructure(); auto outcome = s.GetPayload(); @@ -738,6 +775,7 @@ TEST_F(CborShapeSerializerTest, IntegerTwoByte) { Schema root; Schema member("n", ShapeType::Integer); s.BeginStructure(root); + s.WriteMapKey("n"); s.WriteInteger(member, 1000); s.EndStructure(); auto outcome = s.GetPayload(); @@ -761,6 +799,7 @@ TEST_F(CborShapeSerializerTest, SparseList) { Schema listMember("items", ShapeType::List); Schema elem("member", ShapeType::String); s.BeginStructure(root); + s.WriteMapKey("items"); s.BeginList(listMember, 3); s.WriteString(elem, "a"); s.WriteNull(elem); @@ -791,6 +830,7 @@ TEST_F(CborShapeSerializerTest, IntegerFourByte) { Schema root; Schema member("n", ShapeType::Integer); s.BeginStructure(root); + s.WriteMapKey("n"); s.WriteInteger(member, 70000); s.EndStructure(); auto outcome = s.GetPayload(); @@ -814,6 +854,7 @@ TEST_F(CborShapeSerializerTest, LongEightByte) { Schema root; Schema member("n", ShapeType::Long); s.BeginStructure(root); + s.WriteMapKey("n"); s.WriteLong(member, 5000000000LL); s.EndStructure(); auto outcome = s.GetPayload(); @@ -841,6 +882,7 @@ TEST_F(CborShapeSerializerTest, LargeNegativeInteger) { Schema root; Schema member("n", ShapeType::Long); s.BeginStructure(root); + s.WriteMapKey("n"); s.WriteLong(member, -1000000LL); s.EndStructure(); auto outcome = s.GetPayload(); @@ -865,6 +907,7 @@ TEST_F(CborShapeSerializerTest, DoubleWholeNumberEncodedAsInt) { Schema root; Schema member("d", ShapeType::Double); s.BeginStructure(root); + s.WriteMapKey("d"); s.WriteDouble(member, 5.0); s.EndStructure(); auto outcome = s.GetPayload(); @@ -886,6 +929,7 @@ TEST_F(CborShapeSerializerTest, DoubleNegativeWholeNumberEncodedAsNegInt) { Schema root; Schema member("d", ShapeType::Double); s.BeginStructure(root); + s.WriteMapKey("d"); s.WriteDouble(member, -3.0); s.EndStructure(); auto outcome = s.GetPayload(); @@ -905,6 +949,7 @@ TEST_F(CborShapeSerializerTest, UnicodeString) { Schema root; Schema member("s", ShapeType::String); s.BeginStructure(root); + s.WriteMapKey("s"); s.WriteString(member, "\xC3\xA9\xC3\xA8"); // "éè" in UTF-8 (4 bytes) s.EndStructure(); auto outcome = s.GetPayload(); @@ -927,6 +972,7 @@ TEST_F(CborShapeSerializerTest, NestedListInList) { Schema innerList("member", ShapeType::List); Schema elem("member", ShapeType::Integer); s.BeginStructure(root); + s.WriteMapKey("data"); s.BeginList(outerList, 2); s.BeginList(innerList, 2); s.WriteInteger(elem, 1); @@ -960,6 +1006,7 @@ TEST_F(CborShapeSerializerTest, MapWithMultipleEntries) { Schema mapMember("m", ShapeType::Map); Schema valSchema("value", ShapeType::Integer); s.BeginStructure(root); + s.WriteMapKey("m"); s.BeginMap(mapMember, 3); s.WriteMapKey("x"); s.WriteInteger(valSchema, 1); @@ -995,6 +1042,7 @@ TEST_F(CborShapeSerializerTest, FloatValue) { Schema root; Schema member("f", ShapeType::Float); s.BeginStructure(root); + s.WriteMapKey("f"); s.WriteFloat(member, 1.5f); s.EndStructure(); auto outcome = s.GetPayload(); @@ -1020,6 +1068,7 @@ TEST_F(CborShapeSerializerTest, FloatWholeNumberEncodedAsInt) { Schema root; Schema member("f", ShapeType::Float); s.BeginStructure(root); + s.WriteMapKey("f"); s.WriteFloat(member, 7.0f); s.EndStructure(); auto outcome = s.GetPayload(); @@ -1039,6 +1088,7 @@ TEST_F(CborShapeSerializerTest, TimestampEpochZero) { Schema root; Schema member("ts", ShapeType::Timestamp); s.BeginStructure(root); + s.WriteMapKey("ts"); Aws::Utils::DateTime dt(0.0); s.WriteTimestamp(member, dt); s.EndStructure(); @@ -1061,6 +1111,7 @@ TEST_F(CborShapeSerializerTest, LargeBlob) { Schema root; Schema member("b", ShapeType::Blob); s.BeginStructure(root); + s.WriteMapKey("b"); Aws::Utils::ByteBuffer blob(300); for (size_t i = 0; i < 300; i++) { blob[i] = static_cast(i % 256); @@ -1089,7 +1140,9 @@ TEST_F(CborShapeSerializerTest, UnionAsStructure) { Schema unionMember("result", ShapeType::Union); Schema field("message", ShapeType::String); s.BeginStructure(root); + s.WriteMapKey("result"); s.BeginNestedStructure(unionMember); + s.WriteMapKey("message"); s.WriteString(field, "ok"); s.EndNestedStructure(); s.EndStructure();