From 8a1ea3ed7ca6d8699e906b0e2be9044253058e31 Mon Sep 17 00:00:00 2001 From: pulimsr Date: Thu, 23 Jul 2026 14:27:47 -0400 Subject: [PATCH 1/3] 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(); +} From 108f206e7e67c35aa695a9f8f7eb2b2ae6240210 Mon Sep 17 00:00:00 2001 From: Snigdha Date: Mon, 27 Jul 2026 11:33:26 -0400 Subject: [PATCH 2/3] Add error handling, validation on timestamp for negative value and adding size_t on beginStruct for definite length maps --- .../client/schema/CborShapeDeserializer.h | 5 +- .../smithy/client/schema/ShapeDeserializer.h | 5 +- .../client/schema/CborShapeDeserializer.cpp | 98 ++++++++++++--- .../schema/CborShapeDeserializerTest.cpp | 115 ++++++++++++++++++ 4 files changed, 204 insertions(+), 19 deletions(-) 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 index 671c880fa61..7ad7e20f0e6 100644 --- a/src/aws-cpp-sdk-core/include/smithy/client/schema/CborShapeDeserializer.h +++ b/src/aws-cpp-sdk-core/include/smithy/client/schema/CborShapeDeserializer.h @@ -22,7 +22,7 @@ class SMITHY_API CborShapeDeserializer final : public ShapeDeserializer { Aws::Utils::ByteBuffer ReadBlob() override; int ReadEnum() override; - void BeginStruct() override; + size_t BeginStruct() override; void EndStruct() override; size_t BeginList() override; @@ -37,6 +37,9 @@ class SMITHY_API CborShapeDeserializer final : public ShapeDeserializer { void ReadNull() override; void SkipValue() override; + bool HasError() const override; + int GetLastError() const override; + private: class Impl; Aws::UniquePtr m_impl; 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 index e0d8216da6a..791cd47e995 100644 --- a/src/aws-cpp-sdk-core/include/smithy/client/schema/ShapeDeserializer.h +++ b/src/aws-cpp-sdk-core/include/smithy/client/schema/ShapeDeserializer.h @@ -24,7 +24,7 @@ class SMITHY_API ShapeDeserializer { virtual Aws::Utils::ByteBuffer ReadBlob() = 0; virtual int ReadEnum() = 0; - virtual void BeginStruct() = 0; + virtual size_t BeginStruct() = 0; virtual void EndStruct() = 0; virtual size_t BeginList() = 0; @@ -38,6 +38,9 @@ class SMITHY_API ShapeDeserializer { virtual bool IsNull() = 0; virtual void ReadNull() = 0; virtual void SkipValue() = 0; + + virtual bool HasError() const = 0; + virtual int GetLastError() const = 0; }; } // namespace schema 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 index a4fb13760f4..a19ee203b2b 100644 --- a/src/aws-cpp-sdk-core/source/smithy/client/schema/CborShapeDeserializer.cpp +++ b/src/aws-cpp-sdk-core/source/smithy/client/schema/CborShapeDeserializer.cpp @@ -12,11 +12,16 @@ using namespace Aws::Crt::Cbor; class CborShapeDeserializer::Impl { public: Impl(const unsigned char* data, size_t length) - : m_decoder(Aws::Crt::ByteCursorFromArray(data, length)) {} + : m_decoder(Aws::Crt::ByteCursorFromArray(data, length)), + m_errorCode(0) {} bool ReadBoolean() { auto val = m_decoder.PopNextBooleanVal(); - return val.has_value() ? val.value() : false; + if (!val.has_value()) { + SetError(); + return false; + } + return val.value(); } int ReadInteger() { return static_cast(ReadLong()); } @@ -24,16 +29,26 @@ class CborShapeDeserializer::Impl { int64_t ReadLong() { auto type = m_decoder.PeekType(); if (!type.has_value()) { + SetError(); return 0; } if (*type == CborType::UInt) { auto val = m_decoder.PopNextUnsignedIntVal(); - return val.has_value() ? static_cast(val.value()) : 0; + if (!val.has_value()) { + SetError(); + return 0; + } + return static_cast(val.value()); } if (*type == CborType::NegInt) { auto val = m_decoder.PopNextNegativeIntVal(); - return val.has_value() ? static_cast(-(static_cast(val.value()) + 1)) : 0; + if (!val.has_value()) { + SetError(); + return 0; + } + return static_cast(-(static_cast(val.value()) + 1)); } + SetError(); return 0; } @@ -42,19 +57,24 @@ class CborShapeDeserializer::Impl { double ReadDouble() { auto type = m_decoder.PeekType(); if (!type.has_value()) { + SetError(); return 0.0; } if (*type == CborType::Float) { auto val = m_decoder.PopNextFloatVal(); - return val.has_value() ? val.value() : 0.0; + if (!val.has_value()) { + SetError(); + return 0.0; + } + return val.value(); } - // CRT "smallest possible" may encode doubles as integers return static_cast(ReadLong()); } Aws::String ReadString() { auto val = m_decoder.PopNextTextVal(); if (!val.has_value()) { + SetError(); return {}; } return Aws::String(reinterpret_cast(val->ptr), val->len); @@ -62,13 +82,38 @@ class CborShapeDeserializer::Impl { DateTime ReadTimestamp() { auto tag = m_decoder.PopNextTagVal(); - (void)tag; - return DateTime(static_cast(ReadLong())); + if (!tag.has_value() || tag.value() != 1) { + SetError(); + return {}; + } + auto type = m_decoder.PeekType(); + if (!type.has_value()) { + SetError(); + return {}; + } + if (*type == CborType::Float) { + auto val = m_decoder.PopNextFloatVal(); + if (!val.has_value() || val.value() < 0.0) { + SetError(); + return {}; + } + return DateTime(val.value()); + } + if (*type == CborType::NegInt) { + SetError(); + return {}; + } + auto val = ReadLong(); + if (m_errorCode != 0) { + return {}; + } + return DateTime(static_cast(val)); } ByteBuffer ReadBlob() { auto val = m_decoder.PopNextBytesVal(); if (!val.has_value()) { + SetError(); return {}; } return ByteBuffer(val->ptr, val->len); @@ -76,18 +121,21 @@ class CborShapeDeserializer::Impl { int ReadEnum() { return ReadInteger(); } - void BeginStruct() { + size_t BeginStruct() { auto type = m_decoder.PeekType(); if (type.has_value() && *type == CborType::IndefMapStart) { m_decoder.ConsumeNextSingleElement(); - } else { - m_decoder.PopNextMapStart(); + return 0; } + auto size = m_decoder.PopNextMapStart(); + if (!size.has_value()) { + SetError(); + return 0; + } + return static_cast(size.value()); } - void EndStruct() { - // For indefinite maps, the break is consumed by the IsBreak/read loop - } + void EndStruct() {} size_t BeginList() { auto type = m_decoder.PeekType(); @@ -96,7 +144,11 @@ class CborShapeDeserializer::Impl { return 0; } auto size = m_decoder.PopNextArrayStart(); - return size.has_value() ? static_cast(size.value()) : 0; + if (!size.has_value()) { + SetError(); + return 0; + } + return static_cast(size.value()); } void EndList() {} @@ -108,7 +160,11 @@ class CborShapeDeserializer::Impl { return 0; } auto size = m_decoder.PopNextMapStart(); - return size.has_value() ? static_cast(size.value()) : 0; + if (!size.has_value()) { + SetError(); + return 0; + } + return static_cast(size.value()); } void EndMap() {} @@ -129,8 +185,14 @@ class CborShapeDeserializer::Impl { void SkipValue() { m_decoder.ConsumeNextWholeDataItem(); } + bool HasError() const { return m_errorCode != 0; } + int GetLastError() const { return m_errorCode; } + private: + void SetError() { m_errorCode = m_decoder.LastError(); } + CborDecoder m_decoder; + int m_errorCode; }; CborShapeDeserializer::CborShapeDeserializer(const unsigned char* data, size_t length) @@ -146,7 +208,7 @@ 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(); } +size_t CborShapeDeserializer::BeginStruct() { return m_impl->BeginStruct(); } void CborShapeDeserializer::EndStruct() { m_impl->EndStruct(); } size_t CborShapeDeserializer::BeginList() { return m_impl->BeginList(); } void CborShapeDeserializer::EndList() { m_impl->EndList(); } @@ -157,3 +219,5 @@ 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(); } +bool CborShapeDeserializer::HasError() const { return m_impl->HasError(); } +int CborShapeDeserializer::GetLastError() const { return m_impl->GetLastError(); } 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 index e17fdc28052..ee3f26c6fd7 100644 --- a/tests/aws-cpp-sdk-core-tests/smithy/client/schema/CborShapeDeserializerTest.cpp +++ b/tests/aws-cpp-sdk-core-tests/smithy/client/schema/CborShapeDeserializerTest.cpp @@ -389,3 +389,118 @@ TEST_F(CborShapeDeserializerTest, MultipleScalars) { EXPECT_TRUE(d.IsBreak()); d.EndStruct(); } + +// --- Error handling --- + +TEST_F(CborShapeDeserializerTest, ErrorOnEmptyPayload) { + const unsigned char empty[1] = {0}; + CborShapeDeserializer d(empty, 0); + EXPECT_FALSE(d.HasError()); + d.ReadBoolean(); + EXPECT_TRUE(d.HasError()); + EXPECT_NE(d.GetLastError(), 0); +} + +TEST_F(CborShapeDeserializerTest, ErrorOnTypeMismatch) { + CborShapeSerializer s; + Schema root; + s.BeginStructure(root); + s.WriteMapKey("val"); + 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(), "val"); + d.ReadInteger(); + EXPECT_TRUE(d.HasError()); +} + +TEST_F(CborShapeDeserializerTest, NoErrorOnValidPayload) { + 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_FALSE(d.HasError()); +} + +// --- BeginStruct returns size for definite-length maps --- + +TEST_F(CborShapeDeserializerTest, BeginStructDefiniteLengthMap) { + // Definite-length map with 2 entries: + // A2 -- map(2) + // 61 61 -- text(1) "a" + // 01 -- unsigned(1) + // 61 62 -- text(1) "b" + // 02 -- unsigned(2) + const unsigned char data[] = {0xA2, 0x61, 0x61, 0x01, 0x61, 0x62, 0x02}; + CborShapeDeserializer d(data, sizeof(data)); + size_t count = d.BeginStruct(); + EXPECT_EQ(count, 2u); + EXPECT_EQ(d.ReadKey(), "a"); + EXPECT_EQ(d.ReadInteger(), 1); + EXPECT_EQ(d.ReadKey(), "b"); + EXPECT_EQ(d.ReadInteger(), 2); + d.EndStruct(); + EXPECT_FALSE(d.HasError()); +} + +TEST_F(CborShapeDeserializerTest, BeginStructIndefiniteLengthMap) { + CborShapeSerializer s; + Schema root; + s.BeginStructure(root); + s.WriteMapKey("x"); + s.WriteInteger(root, 99); + s.EndStructure(); + auto payload = s.GetPayload().GetResult(); + + CborShapeDeserializer d(reinterpret_cast(payload.data()), payload.size()); + size_t count = d.BeginStruct(); + EXPECT_EQ(count, 0u); + EXPECT_FALSE(d.IsBreak()); + EXPECT_EQ(d.ReadKey(), "x"); + EXPECT_EQ(d.ReadInteger(), 99); + EXPECT_TRUE(d.IsBreak()); + d.EndStruct(); + EXPECT_FALSE(d.HasError()); +} + +// --- Timestamp handling --- + +TEST_F(CborShapeDeserializerTest, TimestampFromFloat) { + // tag(1) followed by float64 1234567890.5 + // C1 = tag(1), FB = float64, 41D26580B4A00000 = IEEE 754 1234567890.5 + const unsigned char data[] = { + 0xC1, 0xFB, 0x41, 0xD2, 0x65, 0x80, 0xB4, 0xA0, 0x00, 0x00}; + CborShapeDeserializer d(data, sizeof(data)); + auto ts = d.ReadTimestamp(); + EXPECT_FALSE(d.HasError()); + EXPECT_DOUBLE_EQ(ts.SecondsWithMSPrecision(), 1234567890.5); +} + +TEST_F(CborShapeDeserializerTest, TimestampNegativeIntSetsError) { + // tag(1) followed by negative int: C1 = tag(1), 20 = negint(0) meaning -1 + const unsigned char data[] = {0xC1, 0x20}; + CborShapeDeserializer d(data, sizeof(data)); + d.ReadTimestamp(); + EXPECT_TRUE(d.HasError()); +} + +TEST_F(CborShapeDeserializerTest, TimestampNegativeFloatSetsError) { + // tag(1) followed by float64 -1.0 + // C1 = tag(1), FB = float64, BFF0000000000000 = IEEE 754 -1.0 + const unsigned char data[] = { + 0xC1, 0xFB, 0xBF, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; + CborShapeDeserializer d(data, sizeof(data)); + d.ReadTimestamp(); + EXPECT_TRUE(d.HasError()); +} From 06bdf081bf69460459388dd836727b967a10e17e Mon Sep 17 00:00:00 2001 From: Snigdha Date: Mon, 27 Jul 2026 14:40:49 -0400 Subject: [PATCH 3/3] Optional returns to ShapeDeserializer --- .../client/schema/CborShapeDeserializer.h | 23 +- .../smithy/client/schema/ShapeDeserializer.h | 24 +- .../client/schema/CborShapeDeserializer.cpp | 104 +++--- .../schema/CborShapeDeserializerTest.cpp | 304 +++++++++++++----- 4 files changed, 284 insertions(+), 171 deletions(-) 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 index 7ad7e20f0e6..c7714db8559 100644 --- a/src/aws-cpp-sdk-core/include/smithy/client/schema/CborShapeDeserializer.h +++ b/src/aws-cpp-sdk-core/include/smithy/client/schema/CborShapeDeserializer.h @@ -12,15 +12,15 @@ class SMITHY_API CborShapeDeserializer final : public ShapeDeserializer { 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; + Aws::Crt::Optional ReadBoolean() override; + Aws::Crt::Optional ReadInteger() override; + Aws::Crt::Optional ReadLong() override; + Aws::Crt::Optional ReadFloat() override; + Aws::Crt::Optional ReadDouble() override; + Aws::Crt::Optional ReadString() override; + Aws::Crt::Optional ReadTimestamp() override; + Aws::Crt::Optional ReadBlob() override; + Aws::Crt::Optional ReadEnum() override; size_t BeginStruct() override; void EndStruct() override; @@ -31,15 +31,12 @@ class SMITHY_API CborShapeDeserializer final : public ShapeDeserializer { size_t BeginMap() override; void EndMap() override; - Aws::String ReadKey() override; + Aws::Crt::Optional ReadKey() override; bool IsBreak() override; bool IsNull() override; void ReadNull() override; void SkipValue() override; - bool HasError() const override; - int GetLastError() const override; - private: class Impl; Aws::UniquePtr m_impl; 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 index 791cd47e995..f28e66a6c3a 100644 --- a/src/aws-cpp-sdk-core/include/smithy/client/schema/ShapeDeserializer.h +++ b/src/aws-cpp-sdk-core/include/smithy/client/schema/ShapeDeserializer.h @@ -3,6 +3,7 @@ #include #include #include +#include #include #include @@ -14,15 +15,15 @@ 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 Aws::Crt::Optional ReadBoolean() = 0; + virtual Aws::Crt::Optional ReadInteger() = 0; + virtual Aws::Crt::Optional ReadLong() = 0; + virtual Aws::Crt::Optional ReadFloat() = 0; + virtual Aws::Crt::Optional ReadDouble() = 0; + virtual Aws::Crt::Optional ReadString() = 0; + virtual Aws::Crt::Optional ReadTimestamp() = 0; + virtual Aws::Crt::Optional ReadBlob() = 0; + virtual Aws::Crt::Optional ReadEnum() = 0; virtual size_t BeginStruct() = 0; virtual void EndStruct() = 0; @@ -33,14 +34,11 @@ class SMITHY_API ShapeDeserializer { virtual size_t BeginMap() = 0; virtual void EndMap() = 0; - virtual Aws::String ReadKey() = 0; + virtual Aws::Crt::Optional ReadKey() = 0; virtual bool IsBreak() = 0; virtual bool IsNull() = 0; virtual void ReadNull() = 0; virtual void SkipValue() = 0; - - virtual bool HasError() const = 0; - virtual int GetLastError() const = 0; }; } // namespace schema 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 index a19ee203b2b..dff8c39525c 100644 --- a/src/aws-cpp-sdk-core/source/smithy/client/schema/CborShapeDeserializer.cpp +++ b/src/aws-cpp-sdk-core/source/smithy/client/schema/CborShapeDeserializer.cpp @@ -11,115 +11,116 @@ using namespace Aws::Crt::Cbor; class CborShapeDeserializer::Impl { public: - Impl(const unsigned char* data, size_t length) - : m_decoder(Aws::Crt::ByteCursorFromArray(data, length)), - m_errorCode(0) {} + Impl(const unsigned char* data, size_t length) : m_decoder(Aws::Crt::ByteCursorFromArray(data, length)) {} - bool ReadBoolean() { + Aws::Crt::Optional ReadBoolean() { auto val = m_decoder.PopNextBooleanVal(); if (!val.has_value()) { - SetError(); - return false; + return {}; } return val.value(); } - int ReadInteger() { return static_cast(ReadLong()); } + Aws::Crt::Optional ReadInteger() { + auto val = ReadLong(); + if (!val.has_value()) { + return {}; + } + return static_cast(val.value()); + } - int64_t ReadLong() { + Aws::Crt::Optional ReadLong() { auto type = m_decoder.PeekType(); if (!type.has_value()) { - SetError(); - return 0; + return {}; } if (*type == CborType::UInt) { auto val = m_decoder.PopNextUnsignedIntVal(); if (!val.has_value()) { - SetError(); - return 0; + return {}; } return static_cast(val.value()); } if (*type == CborType::NegInt) { auto val = m_decoder.PopNextNegativeIntVal(); if (!val.has_value()) { - SetError(); - return 0; + return {}; } return static_cast(-(static_cast(val.value()) + 1)); } - SetError(); - return 0; + return {}; } - float ReadFloat() { return static_cast(ReadDouble()); } + Aws::Crt::Optional ReadFloat() { + auto val = ReadDouble(); + if (!val.has_value()) { + return {}; + } + return static_cast(val.value()); + } - double ReadDouble() { + Aws::Crt::Optional ReadDouble() { auto type = m_decoder.PeekType(); if (!type.has_value()) { - SetError(); - return 0.0; + return {}; } if (*type == CborType::Float) { auto val = m_decoder.PopNextFloatVal(); if (!val.has_value()) { - SetError(); - return 0.0; + return {}; } return val.value(); } - return static_cast(ReadLong()); + auto longVal = ReadLong(); + if (!longVal.has_value()) { + return {}; + } + return static_cast(longVal.value()); } - Aws::String ReadString() { + Aws::Crt::Optional ReadString() { auto val = m_decoder.PopNextTextVal(); if (!val.has_value()) { - SetError(); return {}; } return Aws::String(reinterpret_cast(val->ptr), val->len); } - DateTime ReadTimestamp() { + Aws::Crt::Optional ReadTimestamp() { auto tag = m_decoder.PopNextTagVal(); if (!tag.has_value() || tag.value() != 1) { - SetError(); return {}; } auto type = m_decoder.PeekType(); if (!type.has_value()) { - SetError(); return {}; } if (*type == CborType::Float) { auto val = m_decoder.PopNextFloatVal(); if (!val.has_value() || val.value() < 0.0) { - SetError(); return {}; } return DateTime(val.value()); } if (*type == CborType::NegInt) { - SetError(); return {}; } auto val = ReadLong(); - if (m_errorCode != 0) { + if (!val.has_value()) { return {}; } - return DateTime(static_cast(val)); + return DateTime(static_cast(val.value())); } - ByteBuffer ReadBlob() { + Aws::Crt::Optional ReadBlob() { auto val = m_decoder.PopNextBytesVal(); if (!val.has_value()) { - SetError(); return {}; } return ByteBuffer(val->ptr, val->len); } - int ReadEnum() { return ReadInteger(); } + Aws::Crt::Optional ReadEnum() { return ReadInteger(); } size_t BeginStruct() { auto type = m_decoder.PeekType(); @@ -129,7 +130,6 @@ class CborShapeDeserializer::Impl { } auto size = m_decoder.PopNextMapStart(); if (!size.has_value()) { - SetError(); return 0; } return static_cast(size.value()); @@ -145,7 +145,6 @@ class CborShapeDeserializer::Impl { } auto size = m_decoder.PopNextArrayStart(); if (!size.has_value()) { - SetError(); return 0; } return static_cast(size.value()); @@ -161,7 +160,6 @@ class CborShapeDeserializer::Impl { } auto size = m_decoder.PopNextMapStart(); if (!size.has_value()) { - SetError(); return 0; } return static_cast(size.value()); @@ -169,7 +167,7 @@ class CborShapeDeserializer::Impl { void EndMap() {} - Aws::String ReadKey() { return ReadString(); } + Aws::Crt::Optional ReadKey() { return ReadString(); } bool IsBreak() { auto type = m_decoder.PeekType(); @@ -185,39 +183,31 @@ class CborShapeDeserializer::Impl { void SkipValue() { m_decoder.ConsumeNextWholeDataItem(); } - bool HasError() const { return m_errorCode != 0; } - int GetLastError() const { return m_errorCode; } - private: - void SetError() { m_errorCode = m_decoder.LastError(); } - CborDecoder m_decoder; - int m_errorCode; }; 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(); } +Aws::Crt::Optional CborShapeDeserializer::ReadBoolean() { return m_impl->ReadBoolean(); } +Aws::Crt::Optional CborShapeDeserializer::ReadInteger() { return m_impl->ReadInteger(); } +Aws::Crt::Optional CborShapeDeserializer::ReadLong() { return m_impl->ReadLong(); } +Aws::Crt::Optional CborShapeDeserializer::ReadFloat() { return m_impl->ReadFloat(); } +Aws::Crt::Optional CborShapeDeserializer::ReadDouble() { return m_impl->ReadDouble(); } +Aws::Crt::Optional CborShapeDeserializer::ReadString() { return m_impl->ReadString(); } +Aws::Crt::Optional CborShapeDeserializer::ReadTimestamp() { return m_impl->ReadTimestamp(); } +Aws::Crt::Optional CborShapeDeserializer::ReadBlob() { return m_impl->ReadBlob(); } +Aws::Crt::Optional CborShapeDeserializer::ReadEnum() { return m_impl->ReadEnum(); } size_t CborShapeDeserializer::BeginStruct() { return 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(); } +Aws::Crt::Optional 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(); } -bool CborShapeDeserializer::HasError() const { return m_impl->HasError(); } -int CborShapeDeserializer::GetLastError() const { return m_impl->GetLastError(); } 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 index ee3f26c6fd7..a08539687ea 100644 --- a/tests/aws-cpp-sdk-core-tests/smithy/client/schema/CborShapeDeserializerTest.cpp +++ b/tests/aws-cpp-sdk-core-tests/smithy/client/schema/CborShapeDeserializerTest.cpp @@ -26,8 +26,12 @@ TEST_F(CborShapeDeserializerTest, BooleanTrue) { CborShapeDeserializer d(reinterpret_cast(payload.data()), payload.size()); d.BeginStruct(); ASSERT_FALSE(d.IsBreak()); - EXPECT_EQ(d.ReadKey(), "enabled"); - EXPECT_TRUE(d.ReadBoolean()); + auto key = d.ReadKey(); + ASSERT_TRUE(key.has_value()); + EXPECT_EQ(key.value(), "enabled"); + auto val = d.ReadBoolean(); + ASSERT_TRUE(val.has_value()); + EXPECT_TRUE(val.value()); EXPECT_TRUE(d.IsBreak()); d.EndStruct(); } @@ -43,8 +47,12 @@ TEST_F(CborShapeDeserializerTest, BooleanFalse) { CborShapeDeserializer d(reinterpret_cast(payload.data()), payload.size()); d.BeginStruct(); - EXPECT_EQ(d.ReadKey(), "ok"); - EXPECT_FALSE(d.ReadBoolean()); + auto key = d.ReadKey(); + ASSERT_TRUE(key.has_value()); + EXPECT_EQ(key.value(), "ok"); + auto val = d.ReadBoolean(); + ASSERT_TRUE(val.has_value()); + EXPECT_FALSE(val.value()); EXPECT_TRUE(d.IsBreak()); d.EndStruct(); } @@ -60,8 +68,12 @@ TEST_F(CborShapeDeserializerTest, IntegerSmall) { CborShapeDeserializer d(reinterpret_cast(payload.data()), payload.size()); d.BeginStruct(); - EXPECT_EQ(d.ReadKey(), "n"); - EXPECT_EQ(d.ReadInteger(), 7); + auto key = d.ReadKey(); + ASSERT_TRUE(key.has_value()); + EXPECT_EQ(key.value(), "n"); + auto val = d.ReadInteger(); + ASSERT_TRUE(val.has_value()); + EXPECT_EQ(val.value(), 7); EXPECT_TRUE(d.IsBreak()); d.EndStruct(); } @@ -77,8 +89,12 @@ TEST_F(CborShapeDeserializerTest, IntegerNegative) { CborShapeDeserializer d(reinterpret_cast(payload.data()), payload.size()); d.BeginStruct(); - EXPECT_EQ(d.ReadKey(), "n"); - EXPECT_EQ(d.ReadInteger(), -42); + auto key = d.ReadKey(); + ASSERT_TRUE(key.has_value()); + EXPECT_EQ(key.value(), "n"); + auto val = d.ReadInteger(); + ASSERT_TRUE(val.has_value()); + EXPECT_EQ(val.value(), -42); EXPECT_TRUE(d.IsBreak()); d.EndStruct(); } @@ -94,8 +110,12 @@ TEST_F(CborShapeDeserializerTest, LongValue) { CborShapeDeserializer d(reinterpret_cast(payload.data()), payload.size()); d.BeginStruct(); - EXPECT_EQ(d.ReadKey(), "big"); - EXPECT_EQ(d.ReadLong(), 5000000000LL); + auto key = d.ReadKey(); + ASSERT_TRUE(key.has_value()); + EXPECT_EQ(key.value(), "big"); + auto val = d.ReadLong(); + ASSERT_TRUE(val.has_value()); + EXPECT_EQ(val.value(), 5000000000LL); EXPECT_TRUE(d.IsBreak()); d.EndStruct(); } @@ -111,8 +131,12 @@ TEST_F(CborShapeDeserializerTest, DoubleValue) { CborShapeDeserializer d(reinterpret_cast(payload.data()), payload.size()); d.BeginStruct(); - EXPECT_EQ(d.ReadKey(), "d"); - EXPECT_DOUBLE_EQ(d.ReadDouble(), 3.14); + auto key = d.ReadKey(); + ASSERT_TRUE(key.has_value()); + EXPECT_EQ(key.value(), "d"); + auto val = d.ReadDouble(); + ASSERT_TRUE(val.has_value()); + EXPECT_DOUBLE_EQ(val.value(), 3.14); EXPECT_TRUE(d.IsBreak()); d.EndStruct(); } @@ -129,8 +153,12 @@ TEST_F(CborShapeDeserializerTest, DoubleWholeNumber) { CborShapeDeserializer d(reinterpret_cast(payload.data()), payload.size()); d.BeginStruct(); - EXPECT_EQ(d.ReadKey(), "d"); - EXPECT_DOUBLE_EQ(d.ReadDouble(), 5.0); + auto key = d.ReadKey(); + ASSERT_TRUE(key.has_value()); + EXPECT_EQ(key.value(), "d"); + auto val = d.ReadDouble(); + ASSERT_TRUE(val.has_value()); + EXPECT_DOUBLE_EQ(val.value(), 5.0); EXPECT_TRUE(d.IsBreak()); d.EndStruct(); } @@ -146,8 +174,12 @@ TEST_F(CborShapeDeserializerTest, FloatValue) { CborShapeDeserializer d(reinterpret_cast(payload.data()), payload.size()); d.BeginStruct(); - EXPECT_EQ(d.ReadKey(), "f"); - EXPECT_FLOAT_EQ(d.ReadFloat(), 1.5f); + auto key = d.ReadKey(); + ASSERT_TRUE(key.has_value()); + EXPECT_EQ(key.value(), "f"); + auto val = d.ReadFloat(); + ASSERT_TRUE(val.has_value()); + EXPECT_FLOAT_EQ(val.value(), 1.5f); EXPECT_TRUE(d.IsBreak()); d.EndStruct(); } @@ -163,8 +195,12 @@ TEST_F(CborShapeDeserializerTest, StringValue) { CborShapeDeserializer d(reinterpret_cast(payload.data()), payload.size()); d.BeginStruct(); - EXPECT_EQ(d.ReadKey(), "name"); - EXPECT_EQ(d.ReadString(), "hello"); + auto key = d.ReadKey(); + ASSERT_TRUE(key.has_value()); + EXPECT_EQ(key.value(), "name"); + auto val = d.ReadString(); + ASSERT_TRUE(val.has_value()); + EXPECT_EQ(val.value(), "hello"); EXPECT_TRUE(d.IsBreak()); d.EndStruct(); } @@ -173,7 +209,10 @@ 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; + blob[0] = 0xDE; + blob[1] = 0xAD; + blob[2] = 0xBE; + blob[3] = 0xEF; s.BeginStructure(root); s.WriteMapKey("data"); s.WriteBlob(root, blob); @@ -182,13 +221,16 @@ TEST_F(CborShapeDeserializerTest, BlobValue) { CborShapeDeserializer d(reinterpret_cast(payload.data()), payload.size()); d.BeginStruct(); - EXPECT_EQ(d.ReadKey(), "data"); + auto key = d.ReadKey(); + ASSERT_TRUE(key.has_value()); + EXPECT_EQ(key.value(), "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); + ASSERT_TRUE(result.has_value()); + ASSERT_EQ(result.value().GetLength(), 4u); + EXPECT_EQ(result.value()[0], 0xDE); + EXPECT_EQ(result.value()[1], 0xAD); + EXPECT_EQ(result.value()[2], 0xBE); + EXPECT_EQ(result.value()[3], 0xEF); EXPECT_TRUE(d.IsBreak()); d.EndStruct(); } @@ -205,9 +247,12 @@ TEST_F(CborShapeDeserializerTest, TimestampValue) { CborShapeDeserializer d(reinterpret_cast(payload.data()), payload.size()); d.BeginStruct(); - EXPECT_EQ(d.ReadKey(), "ts"); + auto key = d.ReadKey(); + ASSERT_TRUE(key.has_value()); + EXPECT_EQ(key.value(), "ts"); auto result = d.ReadTimestamp(); - EXPECT_EQ(result.Seconds(), 1234567890); + ASSERT_TRUE(result.has_value()); + EXPECT_EQ(result.value().Seconds(), 1234567890); EXPECT_TRUE(d.IsBreak()); d.EndStruct(); } @@ -223,7 +268,9 @@ TEST_F(CborShapeDeserializerTest, NullValue) { CborShapeDeserializer d(reinterpret_cast(payload.data()), payload.size()); d.BeginStruct(); - EXPECT_EQ(d.ReadKey(), "item"); + auto key = d.ReadKey(); + ASSERT_TRUE(key.has_value()); + EXPECT_EQ(key.value(), "item"); EXPECT_TRUE(d.IsNull()); d.ReadNull(); EXPECT_TRUE(d.IsBreak()); @@ -247,12 +294,20 @@ TEST_F(CborShapeDeserializerTest, ListOfIntegers) { CborShapeDeserializer d(reinterpret_cast(payload.data()), payload.size()); d.BeginStruct(); - EXPECT_EQ(d.ReadKey(), "nums"); + auto key = d.ReadKey(); + ASSERT_TRUE(key.has_value()); + EXPECT_EQ(key.value(), "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); + auto v1 = d.ReadInteger(); + ASSERT_TRUE(v1.has_value()); + EXPECT_EQ(v1.value(), 10); + auto v2 = d.ReadInteger(); + ASSERT_TRUE(v2.has_value()); + EXPECT_EQ(v2.value(), 20); + auto v3 = d.ReadInteger(); + ASSERT_TRUE(v3.has_value()); + EXPECT_EQ(v3.value(), 30); d.EndList(); EXPECT_TRUE(d.IsBreak()); d.EndStruct(); @@ -274,13 +329,23 @@ TEST_F(CborShapeDeserializerTest, MapOfStrings) { CborShapeDeserializer d(reinterpret_cast(payload.data()), payload.size()); d.BeginStruct(); - EXPECT_EQ(d.ReadKey(), "headers"); + auto key = d.ReadKey(); + ASSERT_TRUE(key.has_value()); + EXPECT_EQ(key.value(), "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"); + auto k1 = d.ReadKey(); + ASSERT_TRUE(k1.has_value()); + EXPECT_EQ(k1.value(), "foo"); + auto v1 = d.ReadString(); + ASSERT_TRUE(v1.has_value()); + EXPECT_EQ(v1.value(), "bar"); + auto k2 = d.ReadKey(); + ASSERT_TRUE(k2.has_value()); + EXPECT_EQ(k2.value(), "baz"); + auto v2 = d.ReadString(); + ASSERT_TRUE(v2.has_value()); + EXPECT_EQ(v2.value(), "qux"); d.EndMap(); EXPECT_TRUE(d.IsBreak()); d.EndStruct(); @@ -300,10 +365,16 @@ TEST_F(CborShapeDeserializerTest, NestedStructure) { CborShapeDeserializer d(reinterpret_cast(payload.data()), payload.size()); d.BeginStruct(); - EXPECT_EQ(d.ReadKey(), "meta"); + auto key = d.ReadKey(); + ASSERT_TRUE(key.has_value()); + EXPECT_EQ(key.value(), "meta"); d.BeginStruct(); - EXPECT_EQ(d.ReadKey(), "key"); - EXPECT_EQ(d.ReadString(), "val"); + auto innerKey = d.ReadKey(); + ASSERT_TRUE(innerKey.has_value()); + EXPECT_EQ(innerKey.value(), "key"); + auto val = d.ReadString(); + ASSERT_TRUE(val.has_value()); + EXPECT_EQ(val.value(), "val"); EXPECT_TRUE(d.IsBreak()); d.EndStruct(); EXPECT_TRUE(d.IsBreak()); @@ -327,12 +398,22 @@ TEST_F(CborShapeDeserializerTest, SkipUnknownField) { 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"); + auto k1 = d.ReadKey(); + ASSERT_TRUE(k1.has_value()); + EXPECT_EQ(k1.value(), "known"); + auto v1 = d.ReadInteger(); + ASSERT_TRUE(v1.has_value()); + EXPECT_EQ(v1.value(), 1); + auto k2 = d.ReadKey(); + ASSERT_TRUE(k2.has_value()); + EXPECT_EQ(k2.value(), "unknown"); d.SkipValue(); - EXPECT_EQ(d.ReadKey(), "also_known"); - EXPECT_EQ(d.ReadInteger(), 2); + auto k3 = d.ReadKey(); + ASSERT_TRUE(k3.has_value()); + EXPECT_EQ(k3.value(), "also_known"); + auto v3 = d.ReadInteger(); + ASSERT_TRUE(v3.has_value()); + EXPECT_EQ(v3.value(), 2); EXPECT_TRUE(d.IsBreak()); d.EndStruct(); } @@ -355,10 +436,16 @@ TEST_F(CborShapeDeserializerTest, SkipNestedUnknown) { CborShapeDeserializer d(reinterpret_cast(payload.data()), payload.size()); d.BeginStruct(); - EXPECT_EQ(d.ReadKey(), "unknown_struct"); + auto k1 = d.ReadKey(); + ASSERT_TRUE(k1.has_value()); + EXPECT_EQ(k1.value(), "unknown_struct"); d.SkipValue(); - EXPECT_EQ(d.ReadKey(), "known"); - EXPECT_EQ(d.ReadInteger(), 42); + auto k2 = d.ReadKey(); + ASSERT_TRUE(k2.has_value()); + EXPECT_EQ(k2.value(), "known"); + auto val = d.ReadInteger(); + ASSERT_TRUE(val.has_value()); + EXPECT_EQ(val.value(), 42); EXPECT_TRUE(d.IsBreak()); d.EndStruct(); } @@ -380,28 +467,38 @@ TEST_F(CborShapeDeserializerTest, MultipleScalars) { 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"); + auto k1 = d.ReadKey(); + ASSERT_TRUE(k1.has_value()); + EXPECT_EQ(k1.value(), "a"); + auto v1 = d.ReadBoolean(); + ASSERT_TRUE(v1.has_value()); + EXPECT_TRUE(v1.value()); + auto k2 = d.ReadKey(); + ASSERT_TRUE(k2.has_value()); + EXPECT_EQ(k2.value(), "b"); + auto v2 = d.ReadInteger(); + ASSERT_TRUE(v2.has_value()); + EXPECT_EQ(v2.value(), 7); + auto k3 = d.ReadKey(); + ASSERT_TRUE(k3.has_value()); + EXPECT_EQ(k3.value(), "c"); + auto v3 = d.ReadString(); + ASSERT_TRUE(v3.has_value()); + EXPECT_EQ(v3.value(), "x"); EXPECT_TRUE(d.IsBreak()); d.EndStruct(); } -// --- Error handling --- +// --- Error handling (Optional returns empty on failure) --- -TEST_F(CborShapeDeserializerTest, ErrorOnEmptyPayload) { +TEST_F(CborShapeDeserializerTest, EmptyOptionalOnEmptyPayload) { const unsigned char empty[1] = {0}; CborShapeDeserializer d(empty, 0); - EXPECT_FALSE(d.HasError()); - d.ReadBoolean(); - EXPECT_TRUE(d.HasError()); - EXPECT_NE(d.GetLastError(), 0); + auto val = d.ReadBoolean(); + EXPECT_FALSE(val.has_value()); } -TEST_F(CborShapeDeserializerTest, ErrorOnTypeMismatch) { +TEST_F(CborShapeDeserializerTest, EmptyOptionalOnTypeMismatch) { CborShapeSerializer s; Schema root; s.BeginStructure(root); @@ -412,12 +509,14 @@ TEST_F(CborShapeDeserializerTest, ErrorOnTypeMismatch) { CborShapeDeserializer d(reinterpret_cast(payload.data()), payload.size()); d.BeginStruct(); - EXPECT_EQ(d.ReadKey(), "val"); - d.ReadInteger(); - EXPECT_TRUE(d.HasError()); + auto key = d.ReadKey(); + ASSERT_TRUE(key.has_value()); + EXPECT_EQ(key.value(), "val"); + auto val = d.ReadInteger(); + EXPECT_FALSE(val.has_value()); } -TEST_F(CborShapeDeserializerTest, NoErrorOnValidPayload) { +TEST_F(CborShapeDeserializerTest, ValidOptionalOnSuccess) { CborShapeSerializer s; Schema root; s.BeginStructure(root); @@ -428,9 +527,12 @@ TEST_F(CborShapeDeserializerTest, NoErrorOnValidPayload) { CborShapeDeserializer d(reinterpret_cast(payload.data()), payload.size()); d.BeginStruct(); - EXPECT_EQ(d.ReadKey(), "n"); - EXPECT_EQ(d.ReadInteger(), 42); - EXPECT_FALSE(d.HasError()); + auto key = d.ReadKey(); + ASSERT_TRUE(key.has_value()); + EXPECT_EQ(key.value(), "n"); + auto val = d.ReadInteger(); + ASSERT_TRUE(val.has_value()); + EXPECT_EQ(val.value(), 42); } // --- BeginStruct returns size for definite-length maps --- @@ -446,12 +548,19 @@ TEST_F(CborShapeDeserializerTest, BeginStructDefiniteLengthMap) { CborShapeDeserializer d(data, sizeof(data)); size_t count = d.BeginStruct(); EXPECT_EQ(count, 2u); - EXPECT_EQ(d.ReadKey(), "a"); - EXPECT_EQ(d.ReadInteger(), 1); - EXPECT_EQ(d.ReadKey(), "b"); - EXPECT_EQ(d.ReadInteger(), 2); + auto k1 = d.ReadKey(); + ASSERT_TRUE(k1.has_value()); + EXPECT_EQ(k1.value(), "a"); + auto v1 = d.ReadInteger(); + ASSERT_TRUE(v1.has_value()); + EXPECT_EQ(v1.value(), 1); + auto k2 = d.ReadKey(); + ASSERT_TRUE(k2.has_value()); + EXPECT_EQ(k2.value(), "b"); + auto v2 = d.ReadInteger(); + ASSERT_TRUE(v2.has_value()); + EXPECT_EQ(v2.value(), 2); d.EndStruct(); - EXPECT_FALSE(d.HasError()); } TEST_F(CborShapeDeserializerTest, BeginStructIndefiniteLengthMap) { @@ -467,11 +576,14 @@ TEST_F(CborShapeDeserializerTest, BeginStructIndefiniteLengthMap) { size_t count = d.BeginStruct(); EXPECT_EQ(count, 0u); EXPECT_FALSE(d.IsBreak()); - EXPECT_EQ(d.ReadKey(), "x"); - EXPECT_EQ(d.ReadInteger(), 99); + auto key = d.ReadKey(); + ASSERT_TRUE(key.has_value()); + EXPECT_EQ(key.value(), "x"); + auto val = d.ReadInteger(); + ASSERT_TRUE(val.has_value()); + EXPECT_EQ(val.value(), 99); EXPECT_TRUE(d.IsBreak()); d.EndStruct(); - EXPECT_FALSE(d.HasError()); } // --- Timestamp handling --- @@ -479,28 +591,44 @@ TEST_F(CborShapeDeserializerTest, BeginStructIndefiniteLengthMap) { TEST_F(CborShapeDeserializerTest, TimestampFromFloat) { // tag(1) followed by float64 1234567890.5 // C1 = tag(1), FB = float64, 41D26580B4A00000 = IEEE 754 1234567890.5 - const unsigned char data[] = { - 0xC1, 0xFB, 0x41, 0xD2, 0x65, 0x80, 0xB4, 0xA0, 0x00, 0x00}; + const unsigned char data[] = {0xC1, 0xFB, 0x41, 0xD2, 0x65, 0x80, 0xB4, 0xA0, 0x00, 0x00}; CborShapeDeserializer d(data, sizeof(data)); auto ts = d.ReadTimestamp(); - EXPECT_FALSE(d.HasError()); - EXPECT_DOUBLE_EQ(ts.SecondsWithMSPrecision(), 1234567890.5); + ASSERT_TRUE(ts.has_value()); + EXPECT_DOUBLE_EQ(ts.value().SecondsWithMSPrecision(), 1234567890.5); } -TEST_F(CborShapeDeserializerTest, TimestampNegativeIntSetsError) { +TEST_F(CborShapeDeserializerTest, TimestampNegativeIntReturnsEmpty) { // tag(1) followed by negative int: C1 = tag(1), 20 = negint(0) meaning -1 const unsigned char data[] = {0xC1, 0x20}; CborShapeDeserializer d(data, sizeof(data)); - d.ReadTimestamp(); - EXPECT_TRUE(d.HasError()); + auto ts = d.ReadTimestamp(); + EXPECT_FALSE(ts.has_value()); } -TEST_F(CborShapeDeserializerTest, TimestampNegativeFloatSetsError) { +TEST_F(CborShapeDeserializerTest, TimestampNegativeFloatReturnsEmpty) { // tag(1) followed by float64 -1.0 // C1 = tag(1), FB = float64, BFF0000000000000 = IEEE 754 -1.0 - const unsigned char data[] = { - 0xC1, 0xFB, 0xBF, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; + const unsigned char data[] = {0xC1, 0xFB, 0xBF, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; + CborShapeDeserializer d(data, sizeof(data)); + auto ts = d.ReadTimestamp(); + EXPECT_FALSE(ts.has_value()); +} + +TEST_F(CborShapeDeserializerTest, TimestampInvalidTagReturnsEmpty) { + // tag(2) followed by uint 100 — tag 2 is not epoch timestamp + // C2 = tag(2), 18 64 = uint(100) + const unsigned char data[] = {0xC2, 0x18, 0x64}; + CborShapeDeserializer d(data, sizeof(data)); + auto ts = d.ReadTimestamp(); + EXPECT_FALSE(ts.has_value()); +} + +TEST_F(CborShapeDeserializerTest, ReadKeyOnNonStringReturnsEmpty) { + // Raw uint 42 — not a text string + // 18 2A = uint(42) + const unsigned char data[] = {0x18, 0x2A}; CborShapeDeserializer d(data, sizeof(data)); - d.ReadTimestamp(); - EXPECT_TRUE(d.HasError()); + auto key = d.ReadKey(); + EXPECT_FALSE(key.has_value()); }