From d01ba4daa5ca7843d202099f75ccd934bc3b4379 Mon Sep 17 00:00:00 2001 From: pulimsr Date: Thu, 23 Jul 2026 14:27:47 -0400 Subject: [PATCH] adding the Cbor Deserializer --- .../client/schema/CborShapeDeserializer.h | 46 +++ .../smithy/client/schema/ShapeDeserializer.h | 44 ++ .../client/schema/CborShapeDeserializer.cpp | 159 +++++++ .../schema/CborShapeDeserializerTest.cpp | 391 ++++++++++++++++++ 4 files changed, 640 insertions(+) create mode 100644 src/aws-cpp-sdk-core/include/smithy/client/schema/CborShapeDeserializer.h create mode 100644 src/aws-cpp-sdk-core/include/smithy/client/schema/ShapeDeserializer.h create mode 100644 src/aws-cpp-sdk-core/source/smithy/client/schema/CborShapeDeserializer.cpp create mode 100644 tests/aws-cpp-sdk-core-tests/smithy/client/schema/CborShapeDeserializerTest.cpp diff --git a/src/aws-cpp-sdk-core/include/smithy/client/schema/CborShapeDeserializer.h b/src/aws-cpp-sdk-core/include/smithy/client/schema/CborShapeDeserializer.h new file mode 100644 index 00000000000..671c880fa61 --- /dev/null +++ b/src/aws-cpp-sdk-core/include/smithy/client/schema/CborShapeDeserializer.h @@ -0,0 +1,46 @@ +#pragma once + +#include +#include +#include + +namespace smithy { +namespace schema { + +class SMITHY_API CborShapeDeserializer final : public ShapeDeserializer { + public: + CborShapeDeserializer(const unsigned char* data, size_t length); + ~CborShapeDeserializer(); + + bool ReadBoolean() override; + int ReadInteger() override; + int64_t ReadLong() override; + float ReadFloat() override; + double ReadDouble() override; + Aws::String ReadString() override; + Aws::Utils::DateTime ReadTimestamp() override; + Aws::Utils::ByteBuffer ReadBlob() override; + int ReadEnum() override; + + void BeginStruct() override; + void EndStruct() override; + + size_t BeginList() override; + void EndList() override; + + size_t BeginMap() override; + void EndMap() override; + + Aws::String ReadKey() override; + bool IsBreak() override; + bool IsNull() override; + void ReadNull() override; + void SkipValue() override; + + private: + class Impl; + Aws::UniquePtr m_impl; +}; + +} // namespace schema +} // namespace smithy diff --git a/src/aws-cpp-sdk-core/include/smithy/client/schema/ShapeDeserializer.h b/src/aws-cpp-sdk-core/include/smithy/client/schema/ShapeDeserializer.h new file mode 100644 index 00000000000..e0d8216da6a --- /dev/null +++ b/src/aws-cpp-sdk-core/include/smithy/client/schema/ShapeDeserializer.h @@ -0,0 +1,44 @@ +#pragma once + +#include +#include +#include +#include + +#include + +namespace smithy { +namespace schema { + +class SMITHY_API ShapeDeserializer { + public: + virtual ~ShapeDeserializer() = default; + + virtual bool ReadBoolean() = 0; + virtual int ReadInteger() = 0; + virtual int64_t ReadLong() = 0; + virtual float ReadFloat() = 0; + virtual double ReadDouble() = 0; + virtual Aws::String ReadString() = 0; + virtual Aws::Utils::DateTime ReadTimestamp() = 0; + virtual Aws::Utils::ByteBuffer ReadBlob() = 0; + virtual int ReadEnum() = 0; + + virtual void BeginStruct() = 0; + virtual void EndStruct() = 0; + + virtual size_t BeginList() = 0; + virtual void EndList() = 0; + + virtual size_t BeginMap() = 0; + virtual void EndMap() = 0; + + virtual Aws::String ReadKey() = 0; + virtual bool IsBreak() = 0; + virtual bool IsNull() = 0; + virtual void ReadNull() = 0; + virtual void SkipValue() = 0; +}; + +} // namespace schema +} // namespace smithy diff --git a/src/aws-cpp-sdk-core/source/smithy/client/schema/CborShapeDeserializer.cpp b/src/aws-cpp-sdk-core/source/smithy/client/schema/CborShapeDeserializer.cpp new file mode 100644 index 00000000000..a4fb13760f4 --- /dev/null +++ b/src/aws-cpp-sdk-core/source/smithy/client/schema/CborShapeDeserializer.cpp @@ -0,0 +1,159 @@ +/** + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0. + */ +#include +#include + +using namespace smithy::schema; +using namespace Aws::Utils; +using namespace Aws::Crt::Cbor; + +class CborShapeDeserializer::Impl { + public: + Impl(const unsigned char* data, size_t length) + : m_decoder(Aws::Crt::ByteCursorFromArray(data, length)) {} + + bool ReadBoolean() { + auto val = m_decoder.PopNextBooleanVal(); + return val.has_value() ? val.value() : false; + } + + int ReadInteger() { return static_cast(ReadLong()); } + + int64_t ReadLong() { + auto type = m_decoder.PeekType(); + if (!type.has_value()) { + return 0; + } + if (*type == CborType::UInt) { + auto val = m_decoder.PopNextUnsignedIntVal(); + return val.has_value() ? static_cast(val.value()) : 0; + } + if (*type == CborType::NegInt) { + auto val = m_decoder.PopNextNegativeIntVal(); + return val.has_value() ? static_cast(-(static_cast(val.value()) + 1)) : 0; + } + return 0; + } + + float ReadFloat() { return static_cast(ReadDouble()); } + + double ReadDouble() { + auto type = m_decoder.PeekType(); + if (!type.has_value()) { + return 0.0; + } + if (*type == CborType::Float) { + auto val = m_decoder.PopNextFloatVal(); + return val.has_value() ? val.value() : 0.0; + } + // CRT "smallest possible" may encode doubles as integers + return static_cast(ReadLong()); + } + + Aws::String ReadString() { + auto val = m_decoder.PopNextTextVal(); + if (!val.has_value()) { + return {}; + } + return Aws::String(reinterpret_cast(val->ptr), val->len); + } + + DateTime ReadTimestamp() { + auto tag = m_decoder.PopNextTagVal(); + (void)tag; + return DateTime(static_cast(ReadLong())); + } + + ByteBuffer ReadBlob() { + auto val = m_decoder.PopNextBytesVal(); + if (!val.has_value()) { + return {}; + } + return ByteBuffer(val->ptr, val->len); + } + + int ReadEnum() { return ReadInteger(); } + + void BeginStruct() { + auto type = m_decoder.PeekType(); + if (type.has_value() && *type == CborType::IndefMapStart) { + m_decoder.ConsumeNextSingleElement(); + } else { + m_decoder.PopNextMapStart(); + } + } + + void EndStruct() { + // For indefinite maps, the break is consumed by the IsBreak/read loop + } + + size_t BeginList() { + auto type = m_decoder.PeekType(); + if (type.has_value() && *type == CborType::IndefArrayStart) { + m_decoder.ConsumeNextSingleElement(); + return 0; + } + auto size = m_decoder.PopNextArrayStart(); + return size.has_value() ? static_cast(size.value()) : 0; + } + + void EndList() {} + + size_t BeginMap() { + auto type = m_decoder.PeekType(); + if (type.has_value() && *type == CborType::IndefMapStart) { + m_decoder.ConsumeNextSingleElement(); + return 0; + } + auto size = m_decoder.PopNextMapStart(); + return size.has_value() ? static_cast(size.value()) : 0; + } + + void EndMap() {} + + Aws::String ReadKey() { return ReadString(); } + + bool IsBreak() { + auto type = m_decoder.PeekType(); + return type.has_value() && *type == CborType::Break; + } + + bool IsNull() { + auto type = m_decoder.PeekType(); + return type.has_value() && *type == CborType::Null; + } + + void ReadNull() { m_decoder.ConsumeNextSingleElement(); } + + void SkipValue() { m_decoder.ConsumeNextWholeDataItem(); } + + private: + CborDecoder m_decoder; +}; + +CborShapeDeserializer::CborShapeDeserializer(const unsigned char* data, size_t length) + : m_impl(Aws::MakeUnique("CborShapeDeserializer", data, length)) {} +CborShapeDeserializer::~CborShapeDeserializer() = default; + +bool CborShapeDeserializer::ReadBoolean() { return m_impl->ReadBoolean(); } +int CborShapeDeserializer::ReadInteger() { return m_impl->ReadInteger(); } +int64_t CborShapeDeserializer::ReadLong() { return m_impl->ReadLong(); } +float CborShapeDeserializer::ReadFloat() { return m_impl->ReadFloat(); } +double CborShapeDeserializer::ReadDouble() { return m_impl->ReadDouble(); } +Aws::String CborShapeDeserializer::ReadString() { return m_impl->ReadString(); } +DateTime CborShapeDeserializer::ReadTimestamp() { return m_impl->ReadTimestamp(); } +ByteBuffer CborShapeDeserializer::ReadBlob() { return m_impl->ReadBlob(); } +int CborShapeDeserializer::ReadEnum() { return m_impl->ReadEnum(); } +void CborShapeDeserializer::BeginStruct() { m_impl->BeginStruct(); } +void CborShapeDeserializer::EndStruct() { m_impl->EndStruct(); } +size_t CborShapeDeserializer::BeginList() { return m_impl->BeginList(); } +void CborShapeDeserializer::EndList() { m_impl->EndList(); } +size_t CborShapeDeserializer::BeginMap() { return m_impl->BeginMap(); } +void CborShapeDeserializer::EndMap() { m_impl->EndMap(); } +Aws::String CborShapeDeserializer::ReadKey() { return m_impl->ReadKey(); } +bool CborShapeDeserializer::IsBreak() { return m_impl->IsBreak(); } +bool CborShapeDeserializer::IsNull() { return m_impl->IsNull(); } +void CborShapeDeserializer::ReadNull() { m_impl->ReadNull(); } +void CborShapeDeserializer::SkipValue() { m_impl->SkipValue(); } diff --git a/tests/aws-cpp-sdk-core-tests/smithy/client/schema/CborShapeDeserializerTest.cpp b/tests/aws-cpp-sdk-core-tests/smithy/client/schema/CborShapeDeserializerTest.cpp new file mode 100644 index 00000000000..e17fdc28052 --- /dev/null +++ b/tests/aws-cpp-sdk-core-tests/smithy/client/schema/CborShapeDeserializerTest.cpp @@ -0,0 +1,391 @@ +/** + * 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 CborShapeDeserializerTest : public Aws::Testing::AwsCppSdkGTestSuite {}; + +// --- Scalars --- + +TEST_F(CborShapeDeserializerTest, BooleanTrue) { + CborShapeSerializer s; + Schema root; + s.BeginStructure(root); + s.WriteMapKey("enabled"); + s.WriteBoolean(root, true); + s.EndStructure(); + auto payload = s.GetPayload().GetResult(); + + CborShapeDeserializer d(reinterpret_cast(payload.data()), payload.size()); + d.BeginStruct(); + ASSERT_FALSE(d.IsBreak()); + EXPECT_EQ(d.ReadKey(), "enabled"); + EXPECT_TRUE(d.ReadBoolean()); + EXPECT_TRUE(d.IsBreak()); + d.EndStruct(); +} + +TEST_F(CborShapeDeserializerTest, BooleanFalse) { + CborShapeSerializer s; + Schema root; + s.BeginStructure(root); + s.WriteMapKey("ok"); + s.WriteBoolean(root, false); + s.EndStructure(); + auto payload = s.GetPayload().GetResult(); + + CborShapeDeserializer d(reinterpret_cast(payload.data()), payload.size()); + d.BeginStruct(); + EXPECT_EQ(d.ReadKey(), "ok"); + EXPECT_FALSE(d.ReadBoolean()); + EXPECT_TRUE(d.IsBreak()); + d.EndStruct(); +} + +TEST_F(CborShapeDeserializerTest, IntegerSmall) { + CborShapeSerializer s; + Schema root; + s.BeginStructure(root); + s.WriteMapKey("n"); + s.WriteInteger(root, 7); + s.EndStructure(); + auto payload = s.GetPayload().GetResult(); + + CborShapeDeserializer d(reinterpret_cast(payload.data()), payload.size()); + d.BeginStruct(); + EXPECT_EQ(d.ReadKey(), "n"); + EXPECT_EQ(d.ReadInteger(), 7); + EXPECT_TRUE(d.IsBreak()); + d.EndStruct(); +} + +TEST_F(CborShapeDeserializerTest, IntegerNegative) { + CborShapeSerializer s; + Schema root; + s.BeginStructure(root); + s.WriteMapKey("n"); + s.WriteInteger(root, -42); + s.EndStructure(); + auto payload = s.GetPayload().GetResult(); + + CborShapeDeserializer d(reinterpret_cast(payload.data()), payload.size()); + d.BeginStruct(); + EXPECT_EQ(d.ReadKey(), "n"); + EXPECT_EQ(d.ReadInteger(), -42); + EXPECT_TRUE(d.IsBreak()); + d.EndStruct(); +} + +TEST_F(CborShapeDeserializerTest, LongValue) { + CborShapeSerializer s; + Schema root; + s.BeginStructure(root); + s.WriteMapKey("big"); + s.WriteLong(root, 5000000000LL); + s.EndStructure(); + auto payload = s.GetPayload().GetResult(); + + CborShapeDeserializer d(reinterpret_cast(payload.data()), payload.size()); + d.BeginStruct(); + EXPECT_EQ(d.ReadKey(), "big"); + EXPECT_EQ(d.ReadLong(), 5000000000LL); + EXPECT_TRUE(d.IsBreak()); + d.EndStruct(); +} + +TEST_F(CborShapeDeserializerTest, DoubleValue) { + CborShapeSerializer s; + Schema root; + s.BeginStructure(root); + s.WriteMapKey("d"); + s.WriteDouble(root, 3.14); + s.EndStructure(); + auto payload = s.GetPayload().GetResult(); + + CborShapeDeserializer d(reinterpret_cast(payload.data()), payload.size()); + d.BeginStruct(); + EXPECT_EQ(d.ReadKey(), "d"); + EXPECT_DOUBLE_EQ(d.ReadDouble(), 3.14); + EXPECT_TRUE(d.IsBreak()); + d.EndStruct(); +} + +TEST_F(CborShapeDeserializerTest, DoubleWholeNumber) { + // CRT encodes 5.0 as integer 5 — deserializer must handle this + CborShapeSerializer s; + Schema root; + s.BeginStructure(root); + s.WriteMapKey("d"); + s.WriteDouble(root, 5.0); + s.EndStructure(); + auto payload = s.GetPayload().GetResult(); + + CborShapeDeserializer d(reinterpret_cast(payload.data()), payload.size()); + d.BeginStruct(); + EXPECT_EQ(d.ReadKey(), "d"); + EXPECT_DOUBLE_EQ(d.ReadDouble(), 5.0); + EXPECT_TRUE(d.IsBreak()); + d.EndStruct(); +} + +TEST_F(CborShapeDeserializerTest, FloatValue) { + CborShapeSerializer s; + Schema root; + s.BeginStructure(root); + s.WriteMapKey("f"); + s.WriteFloat(root, 1.5f); + s.EndStructure(); + auto payload = s.GetPayload().GetResult(); + + CborShapeDeserializer d(reinterpret_cast(payload.data()), payload.size()); + d.BeginStruct(); + EXPECT_EQ(d.ReadKey(), "f"); + EXPECT_FLOAT_EQ(d.ReadFloat(), 1.5f); + EXPECT_TRUE(d.IsBreak()); + d.EndStruct(); +} + +TEST_F(CborShapeDeserializerTest, StringValue) { + CborShapeSerializer s; + Schema root; + s.BeginStructure(root); + s.WriteMapKey("name"); + s.WriteString(root, "hello"); + s.EndStructure(); + auto payload = s.GetPayload().GetResult(); + + CborShapeDeserializer d(reinterpret_cast(payload.data()), payload.size()); + d.BeginStruct(); + EXPECT_EQ(d.ReadKey(), "name"); + EXPECT_EQ(d.ReadString(), "hello"); + EXPECT_TRUE(d.IsBreak()); + d.EndStruct(); +} + +TEST_F(CborShapeDeserializerTest, BlobValue) { + CborShapeSerializer s; + Schema root; + Aws::Utils::ByteBuffer blob(4); + blob[0] = 0xDE; blob[1] = 0xAD; blob[2] = 0xBE; blob[3] = 0xEF; + s.BeginStructure(root); + s.WriteMapKey("data"); + s.WriteBlob(root, blob); + s.EndStructure(); + auto payload = s.GetPayload().GetResult(); + + CborShapeDeserializer d(reinterpret_cast(payload.data()), payload.size()); + d.BeginStruct(); + EXPECT_EQ(d.ReadKey(), "data"); + auto result = d.ReadBlob(); + ASSERT_EQ(result.GetLength(), 4u); + EXPECT_EQ(result[0], 0xDE); + EXPECT_EQ(result[1], 0xAD); + EXPECT_EQ(result[2], 0xBE); + EXPECT_EQ(result[3], 0xEF); + EXPECT_TRUE(d.IsBreak()); + d.EndStruct(); +} + +TEST_F(CborShapeDeserializerTest, TimestampValue) { + CborShapeSerializer s; + Schema root; + Aws::Utils::DateTime dt(1234567890.0); // seconds since epoch + s.BeginStructure(root); + s.WriteMapKey("ts"); + s.WriteTimestamp(root, dt); + s.EndStructure(); + auto payload = s.GetPayload().GetResult(); + + CborShapeDeserializer d(reinterpret_cast(payload.data()), payload.size()); + d.BeginStruct(); + EXPECT_EQ(d.ReadKey(), "ts"); + auto result = d.ReadTimestamp(); + EXPECT_EQ(result.Seconds(), 1234567890); + EXPECT_TRUE(d.IsBreak()); + d.EndStruct(); +} + +TEST_F(CborShapeDeserializerTest, NullValue) { + CborShapeSerializer s; + Schema root; + s.BeginStructure(root); + s.WriteMapKey("item"); + s.WriteNull(root); + s.EndStructure(); + auto payload = s.GetPayload().GetResult(); + + CborShapeDeserializer d(reinterpret_cast(payload.data()), payload.size()); + d.BeginStruct(); + EXPECT_EQ(d.ReadKey(), "item"); + EXPECT_TRUE(d.IsNull()); + d.ReadNull(); + EXPECT_TRUE(d.IsBreak()); + d.EndStruct(); +} + +// --- Collections --- + +TEST_F(CborShapeDeserializerTest, ListOfIntegers) { + CborShapeSerializer s; + Schema root; + s.BeginStructure(root); + s.WriteMapKey("nums"); + s.BeginList(root, 3); + s.WriteInteger(root, 10); + s.WriteInteger(root, 20); + s.WriteInteger(root, 30); + s.EndList(); + s.EndStructure(); + auto payload = s.GetPayload().GetResult(); + + CborShapeDeserializer d(reinterpret_cast(payload.data()), payload.size()); + d.BeginStruct(); + EXPECT_EQ(d.ReadKey(), "nums"); + size_t count = d.BeginList(); + EXPECT_EQ(count, 3u); + EXPECT_EQ(d.ReadInteger(), 10); + EXPECT_EQ(d.ReadInteger(), 20); + EXPECT_EQ(d.ReadInteger(), 30); + d.EndList(); + EXPECT_TRUE(d.IsBreak()); + d.EndStruct(); +} + +TEST_F(CborShapeDeserializerTest, MapOfStrings) { + CborShapeSerializer s; + Schema root; + s.BeginStructure(root); + s.WriteMapKey("headers"); + s.BeginMap(root, 2); + s.WriteMapKey("foo"); + s.WriteString(root, "bar"); + s.WriteMapKey("baz"); + s.WriteString(root, "qux"); + s.EndMap(); + s.EndStructure(); + auto payload = s.GetPayload().GetResult(); + + CborShapeDeserializer d(reinterpret_cast(payload.data()), payload.size()); + d.BeginStruct(); + EXPECT_EQ(d.ReadKey(), "headers"); + size_t count = d.BeginMap(); + EXPECT_EQ(count, 2u); + EXPECT_EQ(d.ReadKey(), "foo"); + EXPECT_EQ(d.ReadString(), "bar"); + EXPECT_EQ(d.ReadKey(), "baz"); + EXPECT_EQ(d.ReadString(), "qux"); + d.EndMap(); + EXPECT_TRUE(d.IsBreak()); + d.EndStruct(); +} + +TEST_F(CborShapeDeserializerTest, NestedStructure) { + CborShapeSerializer s; + Schema root; + s.BeginStructure(root); + s.WriteMapKey("meta"); + s.BeginNestedStructure(root); + s.WriteMapKey("key"); + s.WriteString(root, "val"); + s.EndNestedStructure(); + s.EndStructure(); + auto payload = s.GetPayload().GetResult(); + + CborShapeDeserializer d(reinterpret_cast(payload.data()), payload.size()); + d.BeginStruct(); + EXPECT_EQ(d.ReadKey(), "meta"); + d.BeginStruct(); + EXPECT_EQ(d.ReadKey(), "key"); + EXPECT_EQ(d.ReadString(), "val"); + EXPECT_TRUE(d.IsBreak()); + d.EndStruct(); + EXPECT_TRUE(d.IsBreak()); + d.EndStruct(); +} + +// --- Skip unknown fields --- + +TEST_F(CborShapeDeserializerTest, SkipUnknownField) { + CborShapeSerializer s; + Schema root; + s.BeginStructure(root); + s.WriteMapKey("known"); + s.WriteInteger(root, 1); + s.WriteMapKey("unknown"); + s.WriteString(root, "skip me"); + s.WriteMapKey("also_known"); + s.WriteInteger(root, 2); + s.EndStructure(); + auto payload = s.GetPayload().GetResult(); + + CborShapeDeserializer d(reinterpret_cast(payload.data()), payload.size()); + d.BeginStruct(); + EXPECT_EQ(d.ReadKey(), "known"); + EXPECT_EQ(d.ReadInteger(), 1); + EXPECT_EQ(d.ReadKey(), "unknown"); + d.SkipValue(); + EXPECT_EQ(d.ReadKey(), "also_known"); + EXPECT_EQ(d.ReadInteger(), 2); + EXPECT_TRUE(d.IsBreak()); + d.EndStruct(); +} + +TEST_F(CborShapeDeserializerTest, SkipNestedUnknown) { + CborShapeSerializer s; + Schema root; + s.BeginStructure(root); + s.WriteMapKey("unknown_struct"); + s.BeginNestedStructure(root); + s.WriteMapKey("a"); + s.WriteInteger(root, 1); + s.WriteMapKey("b"); + s.WriteString(root, "nested"); + s.EndNestedStructure(); + s.WriteMapKey("known"); + s.WriteInteger(root, 42); + s.EndStructure(); + auto payload = s.GetPayload().GetResult(); + + CborShapeDeserializer d(reinterpret_cast(payload.data()), payload.size()); + d.BeginStruct(); + EXPECT_EQ(d.ReadKey(), "unknown_struct"); + d.SkipValue(); + EXPECT_EQ(d.ReadKey(), "known"); + EXPECT_EQ(d.ReadInteger(), 42); + EXPECT_TRUE(d.IsBreak()); + d.EndStruct(); +} + +// --- Multiple fields --- + +TEST_F(CborShapeDeserializerTest, MultipleScalars) { + CborShapeSerializer s; + Schema root; + s.BeginStructure(root); + s.WriteMapKey("a"); + s.WriteBoolean(root, true); + s.WriteMapKey("b"); + s.WriteInteger(root, 7); + s.WriteMapKey("c"); + s.WriteString(root, "x"); + s.EndStructure(); + auto payload = s.GetPayload().GetResult(); + + CborShapeDeserializer d(reinterpret_cast(payload.data()), payload.size()); + d.BeginStruct(); + EXPECT_EQ(d.ReadKey(), "a"); + EXPECT_TRUE(d.ReadBoolean()); + EXPECT_EQ(d.ReadKey(), "b"); + EXPECT_EQ(d.ReadInteger(), 7); + EXPECT_EQ(d.ReadKey(), "c"); + EXPECT_EQ(d.ReadString(), "x"); + EXPECT_TRUE(d.IsBreak()); + d.EndStruct(); +}