aboutsummaryrefslogtreecommitdiff
path: root/src/zencore/compactbinarybuilder.cpp
diff options
context:
space:
mode:
authorStefan Boberg <[email protected]>2023-05-02 10:01:47 +0200
committerGitHub <[email protected]>2023-05-02 10:01:47 +0200
commit075d17f8ada47e990fe94606c3d21df409223465 (patch)
treee50549b766a2f3c354798a54ff73404217b4c9af /src/zencore/compactbinarybuilder.cpp
parentfix: bundle shouldn't append content zip to zen (diff)
downloadzen-075d17f8ada47e990fe94606c3d21df409223465.tar.xz
zen-075d17f8ada47e990fe94606c3d21df409223465.zip
moved source directories into `/src` (#264)
* moved source directories into `/src` * updated bundle.lua for new `src` path * moved some docs, icon * removed old test trees
Diffstat (limited to 'src/zencore/compactbinarybuilder.cpp')
-rw-r--r--src/zencore/compactbinarybuilder.cpp1545
1 files changed, 1545 insertions, 0 deletions
diff --git a/src/zencore/compactbinarybuilder.cpp b/src/zencore/compactbinarybuilder.cpp
new file mode 100644
index 000000000..d4ccd434d
--- /dev/null
+++ b/src/zencore/compactbinarybuilder.cpp
@@ -0,0 +1,1545 @@
+// Copyright Epic Games, Inc. All Rights Reserved.
+
+#include "zencore/compactbinarybuilder.h"
+
+#include <zencore/compactbinarypackage.h>
+#include <zencore/compactbinaryvalidation.h>
+#include <zencore/endian.h>
+#include <zencore/stream.h>
+#include <zencore/string.h>
+#include <zencore/testing.h>
+
+#define _USE_MATH_DEFINES
+#include <math.h>
+
+namespace zen {
+
+template<typename T>
+uint64_t
+AddUninitialized(std::vector<T>& Vector, uint64_t Count)
+{
+ const uint64_t Offset = Vector.size();
+ Vector.resize(Offset + Count);
+ return Offset;
+}
+
+template<typename T>
+uint64_t
+Append(std::vector<T>& Vector, const T* Data, uint64_t Count)
+{
+ const uint64_t Offset = Vector.size();
+ Vector.resize(Offset + Count);
+
+ memcpy(Vector.data() + Offset, Data, sizeof(T) * Count);
+
+ return Offset;
+}
+
+//////////////////////////////////////////////////////////////////////////
+
+enum class CbWriter::StateFlags : uint8_t
+{
+ None = 0,
+ /** Whether a name has been written for the current field. */
+ Name = 1 << 0,
+ /** Whether this state is in the process of writing a field. */
+ Field = 1 << 1,
+ /** Whether this state is for array fields. */
+ Array = 1 << 2,
+ /** Whether this state is for object fields. */
+ Object = 1 << 3,
+};
+
+ENUM_CLASS_FLAGS(CbWriter::StateFlags);
+
+/** Whether the field type can be used in a uniform array or uniform object. */
+static constexpr bool
+IsUniformType(const CbFieldType Type)
+{
+ if (CbFieldTypeOps::HasFieldName(Type))
+ {
+ return true;
+ }
+
+ switch (Type)
+ {
+ case CbFieldType::None:
+ case CbFieldType::Null:
+ case CbFieldType::BoolFalse:
+ case CbFieldType::BoolTrue:
+ return false;
+ default:
+ return true;
+ }
+}
+
+/** Append the payload from the compact binary value to the array and return its type. */
+static inline CbFieldType
+AppendCompactBinary(const CbFieldView& Value, std::vector<uint8_t>& OutData)
+{
+ struct FCopy : public CbFieldView
+ {
+ using CbFieldView::GetPayloadView;
+ using CbFieldView::GetType;
+ };
+ const FCopy& ValueCopy = static_cast<const FCopy&>(Value);
+ const MemoryView SourceView = ValueCopy.GetPayloadView();
+ const uint64_t TargetOffset = OutData.size();
+ OutData.resize(TargetOffset + SourceView.GetSize());
+ memcpy(OutData.data() + TargetOffset, SourceView.GetData(), SourceView.GetSize());
+ return CbFieldTypeOps::GetType(ValueCopy.GetType());
+}
+
+CbWriter::CbWriter()
+{
+ States.emplace_back();
+}
+
+CbWriter::CbWriter(const int64_t InitialSize) : CbWriter()
+{
+ Data.reserve(InitialSize);
+}
+
+CbWriter::~CbWriter()
+{
+}
+
+void
+CbWriter::Reset()
+{
+ Data.resize(0);
+ States.resize(0);
+ States.emplace_back();
+}
+
+CbFieldIterator
+CbWriter::Save()
+{
+ const uint64_t Size = GetSaveSize();
+ UniqueBuffer Buffer = UniqueBuffer::Alloc(Size);
+ const CbFieldViewIterator Output = Save(Buffer);
+
+ SharedBuffer SharedBuf(std::move(Buffer));
+ SharedBuf.MakeImmutable();
+
+ return CbFieldIterator::MakeRangeView(Output, SharedBuf);
+}
+
+CbFieldViewIterator
+CbWriter::Save(const MutableMemoryView Buffer)
+{
+ ZEN_ASSERT(States.size() == 1 && States.back().Flags == StateFlags::None);
+ // TEXT("It is invalid to save while there are incomplete write operations."));
+ ZEN_ASSERT(Data.size() > 0); // TEXT("It is invalid to save when nothing has been written."));
+ ZEN_ASSERT(Buffer.GetSize() == Data.size());
+ // TEXT("Buffer is %" UINT64_FMT " bytes but %" INT64_FMT " is required."),
+ // Buffer.GetSize(),
+ // Data.Num());
+ memcpy(Buffer.GetData(), Data.data(), Data.size());
+ return CbFieldViewIterator::MakeRange(Buffer);
+}
+
+void
+CbWriter::Save(BinaryWriter& Writer)
+{
+ ZEN_ASSERT(States.size() == 1 && States.back().Flags == StateFlags::None);
+ // TEXT("It is invalid to save while there are incomplete write operations."));
+ ZEN_ASSERT(Data.size() > 0); // TEXT("It is invalid to save when nothing has been written."));
+ Writer.Write(Data.data(), Data.size());
+}
+
+uint64_t
+CbWriter::GetSaveSize() const
+{
+ return Data.size();
+}
+
+void
+CbWriter::BeginField()
+{
+ WriterState& State = States.back();
+ if ((State.Flags & StateFlags::Field) == StateFlags::None)
+ {
+ State.Flags |= StateFlags::Field;
+ State.Offset = Data.size();
+ Data.push_back(0);
+ }
+ else
+ {
+ ZEN_ASSERT((State.Flags & StateFlags::Name) == StateFlags::Name);
+ // TEXT("A new field cannot be written until the previous field '%.*hs' is finished."),
+ // GetActiveName().Len(),
+ // GetActiveName().GetData());
+ }
+}
+
+void
+CbWriter::EndField(CbFieldType Type)
+{
+ WriterState& State = States.back();
+
+ if ((State.Flags & StateFlags::Name) == StateFlags::Name)
+ {
+ Type |= CbFieldType::HasFieldName;
+ }
+ else
+ {
+ ZEN_ASSERT((State.Flags & StateFlags::Object) == StateFlags::None);
+ // TEXT("It is invalid to write an object field without a unique non-empty name."));
+ }
+
+ if (State.Count == 0)
+ {
+ State.UniformType = Type;
+ }
+ else if (State.UniformType != Type)
+ {
+ State.UniformType = CbFieldType::None;
+ }
+
+ State.Flags &= ~(StateFlags::Name | StateFlags::Field);
+ ++State.Count;
+ Data[State.Offset] = uint8_t(Type);
+}
+
+ZEN_NOINLINE
+CbWriter&
+CbWriter::SetName(const std::string_view Name)
+{
+ WriterState& State = States.back();
+ ZEN_ASSERT((State.Flags & StateFlags::Array) != StateFlags::Array);
+ // TEXT("It is invalid to write a name for an array field. Name '%.*hs'"),
+ // Name.Len(),
+ // Name.GetData());
+ ZEN_ASSERT(!Name.empty());
+ // TEXT("%s"),
+ //(State.Flags & EStateFlags::Object) == EStateFlags::Object
+ // ? TEXT("It is invalid to write an empty name for an object field. Specify a unique non-empty name.")
+ // : TEXT("It is invalid to write an empty name for a top-level field. Specify a name or avoid this call."));
+ ZEN_ASSERT((State.Flags & (StateFlags::Name | StateFlags::Field)) == StateFlags::None);
+ // TEXT("A new field '%.*hs' cannot be written until the previous field '%.*hs' is finished."),
+ // Name.Len(),
+ // Name.GetData(),
+ // GetActiveName().Len(),
+ // GetActiveName().GetData());
+
+ BeginField();
+ State.Flags |= StateFlags::Name;
+ const uint32_t NameLenByteCount = MeasureVarUInt(uint32_t(Name.size()));
+ const int64_t NameLenOffset = Data.size();
+ Data.resize(NameLenOffset + NameLenByteCount);
+
+ WriteVarUInt(uint64_t(Name.size()), Data.data() + NameLenOffset);
+
+ const uint8_t* NamePtr = reinterpret_cast<const uint8_t*>(Name.data());
+ Data.insert(Data.end(), NamePtr, NamePtr + Name.size());
+ return *this;
+}
+
+void
+CbWriter::SetNameOrAddString(const std::string_view NameOrValue)
+{
+ // A name is only written if it would begin a new field inside of an object.
+ if ((States.back().Flags & (StateFlags::Name | StateFlags::Field | StateFlags::Object)) == StateFlags::Object)
+ {
+ SetName(NameOrValue);
+ }
+ else
+ {
+ AddString(NameOrValue);
+ }
+}
+
+std::string_view
+CbWriter::GetActiveName() const
+{
+ const WriterState& State = States.back();
+ if ((State.Flags & StateFlags::Name) == StateFlags::Name)
+ {
+ const uint8_t* const EncodedName = Data.data() + State.Offset + sizeof(CbFieldType);
+ uint32_t NameLenByteCount;
+ const uint64_t NameLen = ReadVarUInt(EncodedName, NameLenByteCount);
+ const size_t ClampedNameLen = std::clamp<uint64_t>(NameLen, 0, ~uint64_t(0));
+ return std::string_view(reinterpret_cast<const char*>(EncodedName + NameLenByteCount), ClampedNameLen);
+ }
+ return std::string_view();
+}
+
+void
+CbWriter::MakeFieldsUniform(const int64_t FieldBeginOffset, const int64_t FieldEndOffset)
+{
+ MutableMemoryView SourceView(Data.data() + FieldBeginOffset, uint64_t(FieldEndOffset - FieldBeginOffset));
+ MutableMemoryView TargetView = SourceView;
+ TargetView.RightChopInline(sizeof(CbFieldType));
+
+ while (!SourceView.IsEmpty())
+ {
+ const uint64_t FieldSize = MeasureCompactBinary(SourceView) - sizeof(CbFieldType);
+ SourceView.RightChopInline(sizeof(CbFieldType));
+ if (TargetView.GetData() != SourceView.GetData())
+ {
+ memmove(TargetView.GetData(), SourceView.GetData(), FieldSize);
+ }
+ SourceView.RightChopInline(FieldSize);
+ TargetView.RightChopInline(FieldSize);
+ }
+
+ if (!TargetView.IsEmpty())
+ {
+ const auto EraseBegin = Data.begin() + (FieldEndOffset - TargetView.GetSize());
+ const auto EraseEnd = EraseBegin + TargetView.GetSize();
+
+ Data.erase(EraseBegin, EraseEnd);
+ }
+}
+
+void
+CbWriter::AddField(const CbFieldView& Value)
+{
+ ZEN_ASSERT(Value.HasValue()); // , TEXT("It is invalid to write a field with no value."));
+ BeginField();
+ EndField(AppendCompactBinary(Value, Data));
+}
+
+void
+CbWriter::AddField(const CbField& Value)
+{
+ AddField(CbFieldView(Value));
+}
+
+void
+CbWriter::BeginObject()
+{
+ BeginField();
+ States.push_back(WriterState());
+ States.back().Flags |= StateFlags::Object;
+}
+
+void
+CbWriter::EndObject()
+{
+ ZEN_ASSERT(States.size() > 1 && (States.back().Flags & StateFlags::Object) == StateFlags::Object);
+
+ // TEXT("It is invalid to end an object when an object is not at the top of the stack."));
+ ZEN_ASSERT((States.back().Flags & StateFlags::Field) == StateFlags::None);
+ // TEXT("It is invalid to end an object until the previous field is finished."));
+
+ const bool bUniform = IsUniformType(States.back().UniformType);
+ const uint64_t Count = States.back().Count;
+ States.pop_back();
+
+ // Calculate the offset of the payload.
+ const WriterState& State = States.back();
+ int64_t PayloadOffset = State.Offset + 1;
+ if ((State.Flags & StateFlags::Name) == StateFlags::Name)
+ {
+ uint32_t NameLenByteCount;
+ const uint64_t NameLen = ReadVarUInt(Data.data() + PayloadOffset, NameLenByteCount);
+ PayloadOffset += NameLen + NameLenByteCount;
+ }
+
+ // Remove redundant field types for uniform objects.
+ if (bUniform && Count > 1)
+ {
+ MakeFieldsUniform(PayloadOffset, Data.size());
+ }
+
+ // Insert the object size.
+ const uint64_t Size = uint64_t(Data.size() - PayloadOffset);
+ const uint32_t SizeByteCount = MeasureVarUInt(Size);
+ Data.insert(Data.begin() + PayloadOffset, SizeByteCount, 0);
+ WriteVarUInt(Size, Data.data() + PayloadOffset);
+
+ EndField(bUniform ? CbFieldType::UniformObject : CbFieldType::Object);
+}
+
+void
+CbWriter::AddObject(const CbObjectView& Value)
+{
+ BeginField();
+ EndField(AppendCompactBinary(Value.AsFieldView(), Data));
+}
+
+void
+CbWriter::AddObject(const CbObject& Value)
+{
+ AddObject(CbObjectView(Value));
+}
+
+ZEN_NOINLINE
+void
+CbWriter::BeginArray()
+{
+ BeginField();
+ States.push_back(WriterState());
+ States.back().Flags |= StateFlags::Array;
+}
+
+void
+CbWriter::EndArray()
+{
+ ZEN_ASSERT(States.size() > 1 && (States.back().Flags & StateFlags::Array) == StateFlags::Array);
+ // TEXT("Invalid attempt to end an array when an array is not at the top of the stack."));
+ ZEN_ASSERT((States.back().Flags & StateFlags::Field) == StateFlags::None);
+ // TEXT("It is invalid to end an array until the previous field is finished."));
+ const bool bUniform = IsUniformType(States.back().UniformType);
+ const uint64_t Count = States.back().Count;
+ States.pop_back();
+
+ // Calculate the offset of the payload.
+ const WriterState& State = States.back();
+ int64_t PayloadOffset = State.Offset + 1;
+ if ((State.Flags & StateFlags::Name) == StateFlags::Name)
+ {
+ uint32_t NameLenByteCount;
+ const uint64_t NameLen = ReadVarUInt(Data.data() + PayloadOffset, NameLenByteCount);
+ PayloadOffset += NameLen + NameLenByteCount;
+ }
+
+ // Remove redundant field types for uniform arrays.
+ if (bUniform && Count > 1)
+ {
+ MakeFieldsUniform(PayloadOffset, Data.size());
+ }
+
+ // Insert the array size and field count.
+ const uint32_t CountByteCount = MeasureVarUInt(Count);
+ const uint64_t Size = uint64_t(Data.size() - PayloadOffset) + CountByteCount;
+ const uint32_t SizeByteCount = MeasureVarUInt(Size);
+ Data.insert(Data.begin() + PayloadOffset, SizeByteCount + CountByteCount, 0);
+ WriteVarUInt(Size, Data.data() + PayloadOffset);
+ WriteVarUInt(Count, Data.data() + PayloadOffset + SizeByteCount);
+
+ EndField(bUniform ? CbFieldType::UniformArray : CbFieldType::Array);
+}
+
+void
+CbWriter::AddArray(const CbArrayView& Value)
+{
+ BeginField();
+ EndField(AppendCompactBinary(Value.AsFieldView(), Data));
+}
+
+void
+CbWriter::AddArray(const CbArray& Value)
+{
+ AddArray(CbArrayView(Value));
+}
+
+void
+CbWriter::AddNull()
+{
+ BeginField();
+ EndField(CbFieldType::Null);
+}
+
+void
+CbWriter::AddBinary(const void* const Value, const uint64_t Size)
+{
+ const size_t SizeByteCount = MeasureVarUInt(Size);
+ Data.reserve(Data.size() + 1 + SizeByteCount + Size);
+ BeginField();
+ const size_t SizeOffset = Data.size();
+ Data.resize(Data.size() + SizeByteCount);
+ WriteVarUInt(Size, Data.data() + SizeOffset);
+ Data.insert(Data.end(), static_cast<const uint8_t*>(Value), static_cast<const uint8_t*>(Value) + Size);
+ EndField(CbFieldType::Binary);
+}
+
+void
+CbWriter::AddBinary(IoBuffer Buffer)
+{
+ AddBinary(Buffer.Data(), Buffer.Size());
+}
+
+void
+CbWriter::AddBinary(SharedBuffer Buffer)
+{
+ AddBinary(Buffer.GetData(), Buffer.GetSize());
+}
+
+void
+CbWriter::AddBinary(const CompositeBuffer& Buffer)
+{
+ AddBinary(Buffer.Flatten());
+}
+
+void
+CbWriter::AddString(const std::string_view Value)
+{
+ BeginField();
+ const uint64_t Size = uint64_t(Value.size());
+ const uint32_t SizeByteCount = MeasureVarUInt(Size);
+ const int64_t Offset = Data.size();
+
+ Data.resize(Offset + SizeByteCount + Size);
+
+ uint8_t* StringData = Data.data() + Offset;
+ WriteVarUInt(Size, StringData);
+ StringData += SizeByteCount;
+ if (Size > 0)
+ {
+ memcpy(StringData, Value.data(), Value.size() * sizeof(char));
+ }
+ EndField(CbFieldType::String);
+}
+
+void
+CbWriter::AddString(const std::wstring_view Value)
+{
+ BeginField();
+ ExtendableStringBuilder<128> Utf8;
+ WideToUtf8(Value, Utf8);
+
+ const uint32_t Size = uint32_t(Utf8.Size());
+ const uint32_t SizeByteCount = MeasureVarUInt(Size);
+ const int64_t Offset = Data.size();
+ Data.resize(Offset + SizeByteCount + Size);
+ uint8_t* StringData = Data.data() + Offset;
+ WriteVarUInt(Size, StringData);
+ StringData += SizeByteCount;
+ if (Size > 0)
+ {
+ memcpy(reinterpret_cast<char*>(StringData), Utf8.Data(), Utf8.Size());
+ }
+ EndField(CbFieldType::String);
+}
+
+ZEN_NOINLINE
+void
+CbWriter::AddInteger(const int32_t Value)
+{
+ if (Value >= 0)
+ {
+ return AddInteger(uint32_t(Value));
+ }
+ BeginField();
+ const uint32_t Magnitude = ~uint32_t(Value);
+ const uint32_t MagnitudeByteCount = MeasureVarUInt(Magnitude);
+ const int64_t Offset = Data.size();
+ Data.resize(Offset + MagnitudeByteCount);
+ WriteVarUInt(Magnitude, Data.data() + Offset);
+ EndField(CbFieldType::IntegerNegative);
+}
+
+void
+CbWriter::AddInteger(const int64_t Value)
+{
+ if (Value >= 0)
+ {
+ return AddInteger(uint64_t(Value));
+ }
+ BeginField();
+ const uint64_t Magnitude = ~uint64_t(Value);
+ const uint32_t MagnitudeByteCount = MeasureVarUInt(Magnitude);
+ const uint64_t Offset = AddUninitialized(Data, MagnitudeByteCount);
+ WriteVarUInt(Magnitude, Data.data() + Offset);
+ EndField(CbFieldType::IntegerNegative);
+}
+
+ZEN_NOINLINE
+void
+CbWriter::AddInteger(const uint32_t Value)
+{
+ BeginField();
+ const uint32_t ValueByteCount = MeasureVarUInt(Value);
+ const uint64_t Offset = AddUninitialized(Data, ValueByteCount);
+ WriteVarUInt(Value, Data.data() + Offset);
+ EndField(CbFieldType::IntegerPositive);
+}
+
+ZEN_NOINLINE
+void
+CbWriter::AddInteger(const uint64_t Value)
+{
+ BeginField();
+ const uint32_t ValueByteCount = MeasureVarUInt(Value);
+ const uint64_t Offset = AddUninitialized(Data, ValueByteCount);
+ WriteVarUInt(Value, Data.data() + Offset);
+ EndField(CbFieldType::IntegerPositive);
+}
+
+ZEN_NOINLINE
+void
+CbWriter::AddFloat(const float Value)
+{
+ BeginField();
+ const uint32_t RawValue = FromNetworkOrder(reinterpret_cast<const uint32_t&>(Value));
+ Append(Data, reinterpret_cast<const uint8_t*>(&RawValue), sizeof(uint32_t));
+ EndField(CbFieldType::Float32);
+}
+
+ZEN_NOINLINE
+void
+CbWriter::AddFloat(const double Value)
+{
+ const float Value32 = float(Value);
+ if (Value == double(Value32))
+ {
+ return AddFloat(Value32);
+ }
+ BeginField();
+ const uint64_t RawValue = FromNetworkOrder(reinterpret_cast<const uint64_t&>(Value));
+ Append(Data, reinterpret_cast<const uint8_t*>(&RawValue), sizeof(uint64_t));
+ EndField(CbFieldType::Float64);
+}
+
+ZEN_NOINLINE
+void
+CbWriter::AddBool(const bool bValue)
+{
+ BeginField();
+ EndField(bValue ? CbFieldType::BoolTrue : CbFieldType::BoolFalse);
+}
+
+ZEN_NOINLINE
+void
+CbWriter::AddObjectAttachment(const IoHash& Value)
+{
+ BeginField();
+ Append(Data, Value.Hash, sizeof Value.Hash);
+ EndField(CbFieldType::ObjectAttachment);
+}
+
+ZEN_NOINLINE
+void
+CbWriter::AddBinaryAttachment(const IoHash& Value)
+{
+ BeginField();
+ Append(Data, Value.Hash, sizeof Value.Hash);
+ EndField(CbFieldType::BinaryAttachment);
+}
+
+ZEN_NOINLINE
+void
+CbWriter::AddAttachment(const CbAttachment& Attachment)
+{
+ BeginField();
+ const IoHash& Value = Attachment.GetHash();
+ Append(Data, Value.Hash, sizeof Value.Hash);
+ EndField(CbFieldType::BinaryAttachment);
+}
+
+ZEN_NOINLINE
+void
+CbWriter::AddHash(const IoHash& Value)
+{
+ BeginField();
+ Append(Data, Value.Hash, sizeof Value.Hash);
+ EndField(CbFieldType::Hash);
+}
+
+void
+CbWriter::AddUuid(const Guid& Value)
+{
+ const auto AppendSwappedBytes = [this](uint32_t In) {
+ In = FromNetworkOrder(In);
+ Append(Data, reinterpret_cast<const uint8_t*>(&In), sizeof In);
+ };
+ BeginField();
+ AppendSwappedBytes(Value.A);
+ AppendSwappedBytes(Value.B);
+ AppendSwappedBytes(Value.C);
+ AppendSwappedBytes(Value.D);
+ EndField(CbFieldType::Uuid);
+}
+
+void
+CbWriter::AddObjectId(const Oid& Value)
+{
+ BeginField();
+ Append(Data, reinterpret_cast<const uint8_t*>(&Value.OidBits), sizeof Value.OidBits);
+ EndField(CbFieldType::ObjectId);
+}
+
+void
+CbWriter::AddDateTimeTicks(const int64_t Ticks)
+{
+ BeginField();
+ const uint64_t RawValue = FromNetworkOrder(uint64_t(Ticks));
+ Append(Data, reinterpret_cast<const uint8_t*>(&RawValue), sizeof(uint64_t));
+ EndField(CbFieldType::DateTime);
+}
+
+void
+CbWriter::AddDateTime(const DateTime Value)
+{
+ AddDateTimeTicks(Value.GetTicks());
+}
+
+void
+CbWriter::AddTimeSpanTicks(const int64_t Ticks)
+{
+ BeginField();
+ const uint64_t RawValue = FromNetworkOrder(uint64_t(Ticks));
+ Append(Data, reinterpret_cast<const uint8_t*>(&RawValue), sizeof(uint64_t));
+ EndField(CbFieldType::TimeSpan);
+}
+
+void
+CbWriter::AddTimeSpan(const TimeSpan Value)
+{
+ AddTimeSpanTicks(Value.GetTicks());
+}
+
+///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+CbWriter&
+operator<<(CbWriter& Writer, const DateTime Value)
+{
+ Writer.AddDateTime(Value);
+ return Writer;
+}
+
+CbWriter&
+operator<<(CbWriter& Writer, const TimeSpan Value)
+{
+ Writer.AddTimeSpan(Value);
+ return Writer;
+}
+
+///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+#if ZEN_WITH_TESTS
+void
+usonbuilder_forcelink()
+{
+}
+
+// doctest::String
+// toString(const DateTime&)
+// {
+// // TODO:implement
+// return "";
+// }
+
+// doctest::String
+// toString(const TimeSpan&)
+// {
+// // TODO:implement
+// return "";
+// }
+
+TEST_CASE("usonbuilder.object")
+{
+ using namespace std::literals;
+
+ FixedCbWriter<256> Writer;
+
+ SUBCASE("EmptyObject")
+ {
+ Writer.BeginObject();
+ Writer.EndObject();
+ CbField Field = Writer.Save();
+
+ CHECK(ValidateCompactBinary(Field.GetBuffer(), CbValidateMode::All) == CbValidateError::None);
+ CHECK(Field.IsObject() == true);
+ CHECK(Field.AsObjectView().CreateViewIterator().HasValue() == false);
+ }
+
+ SUBCASE("NamedEmptyObject")
+ {
+ Writer.SetName("Object"sv);
+ Writer.BeginObject();
+ Writer.EndObject();
+ CbField Field = Writer.Save();
+
+ CHECK(ValidateCompactBinary(Field.GetBuffer(), CbValidateMode::All) == CbValidateError::None);
+ CHECK(Field.IsObject() == true);
+ CHECK(Field.AsObjectView().CreateViewIterator().HasValue() == false);
+ }
+
+ SUBCASE("BasicObject")
+ {
+ Writer.BeginObject();
+ Writer.SetName("Integer"sv).AddInteger(0);
+ Writer.SetName("Float"sv).AddFloat(0.0f);
+ Writer.EndObject();
+ CbField Field = Writer.Save();
+
+ CHECK(ValidateCompactBinary(Field.GetBuffer(), CbValidateMode::All) == CbValidateError::None);
+ CHECK(Field.IsObject() == true);
+
+ CbObjectView Object = Field.AsObjectView();
+ CHECK(Object["Integer"sv].IsInteger() == true);
+ CHECK(Object["Float"sv].IsFloat() == true);
+ }
+
+ SUBCASE("UniformObject")
+ {
+ Writer.BeginObject();
+ Writer.SetName("Field1"sv).AddInteger(0);
+ Writer.SetName("Field2"sv).AddInteger(1);
+ Writer.EndObject();
+ CbField Field = Writer.Save();
+
+ CHECK(ValidateCompactBinary(Field.GetBuffer(), CbValidateMode::All) == CbValidateError::None);
+ CHECK(Field.IsObject() == true);
+
+ CbObjectView Object = Field.AsObjectView();
+ CHECK(Object["Field1"sv].IsInteger() == true);
+ CHECK(Object["Field2"sv].IsInteger() == true);
+ }
+}
+
+TEST_CASE("usonbuilder.array")
+{
+ using namespace std::literals;
+
+ FixedCbWriter<256> Writer;
+
+ SUBCASE("EmptyArray")
+ {
+ Writer.BeginArray();
+ Writer.EndArray();
+ CbField Field = Writer.Save();
+
+ CHECK(ValidateCompactBinary(Field.GetBuffer(), CbValidateMode::All) == CbValidateError::None);
+ CHECK(Field.IsArray() == true);
+ CHECK(Field.AsArrayView().Num() == 0);
+ }
+
+ SUBCASE("NamedEmptyArray")
+ {
+ Writer.SetName("Array"sv);
+ Writer.BeginArray();
+ Writer.EndArray();
+ CbField Field = Writer.Save();
+
+ CHECK(ValidateCompactBinary(Field.GetBuffer(), CbValidateMode::All) == CbValidateError::None);
+ CHECK(Field.IsArray() == true);
+ CHECK(Field.AsArrayView().Num() == 0);
+ }
+
+ SUBCASE("BasicArray")
+ {
+ Writer.BeginArray();
+ Writer.AddInteger(0);
+ Writer.AddFloat(0.0f);
+ Writer.EndArray();
+ CbField Field = Writer.Save();
+
+ CHECK(ValidateCompactBinary(Field.GetBuffer(), CbValidateMode::All) == CbValidateError::None);
+ CHECK(Field.IsArray() == true);
+ CbFieldViewIterator Iterator = Field.AsArrayView().CreateViewIterator();
+ CHECK(Iterator.IsInteger() == true);
+ ++Iterator;
+ CHECK(Iterator.IsFloat() == true);
+ ++Iterator;
+ CHECK(Iterator.HasValue() == false);
+ }
+
+ SUBCASE("UniformArray")
+ {
+ Writer.BeginArray();
+ Writer.AddInteger(0);
+ Writer.AddInteger(1);
+ Writer.EndArray();
+
+ CbField Field = Writer.Save();
+
+ CHECK(ValidateCompactBinary(Field.GetBuffer(), CbValidateMode::All) == CbValidateError::None);
+ CHECK(Field.IsArray() == true);
+ CbFieldViewIterator Iterator = Field.AsArrayView().CreateViewIterator();
+ CHECK(Iterator.IsInteger() == true);
+ ++Iterator;
+ CHECK(Iterator.IsInteger() == true);
+ ++Iterator;
+ CHECK(Iterator.HasValue() == false);
+ }
+}
+
+TEST_CASE("usonbuilder.null")
+{
+ using namespace std::literals;
+
+ FixedCbWriter<256> Writer;
+
+ SUBCASE("Null")
+ {
+ Writer.AddNull();
+ CbField Field = Writer.Save();
+ CHECK(ValidateCompactBinary(Field.GetBuffer(), CbValidateMode::All) == CbValidateError::None);
+ CHECK(Field.HasName() == false);
+ CHECK(Field.IsNull() == true);
+ }
+
+ SUBCASE("NullWithName")
+ {
+ Writer.SetName("Null"sv);
+ Writer.AddNull();
+ CbField Field = Writer.Save();
+ CHECK(ValidateCompactBinary(Field.GetBuffer(), CbValidateMode::All) == CbValidateError::None);
+ CHECK(Field.HasName() == true);
+ CHECK(Field.GetName().compare("Null"sv) == 0);
+ CHECK(Field.IsNull() == true);
+ }
+
+ SUBCASE("Null Array/Object Uniformity")
+ {
+ Writer.BeginArray();
+ Writer.AddNull();
+ Writer.AddNull();
+ Writer.AddNull();
+ Writer.EndArray();
+
+ Writer.BeginObject();
+ Writer.SetName("N1"sv).AddNull();
+ Writer.SetName("N2"sv).AddNull();
+ Writer.SetName("N3"sv).AddNull();
+ Writer.EndObject();
+
+ CbFieldIterator Fields = Writer.Save();
+
+ CHECK(ValidateCompactBinary(Fields.GetBuffer(), CbValidateMode::All) == CbValidateError::None);
+ }
+
+ SUBCASE("Null with Save(Buffer)")
+ {
+ constexpr int NullCount = 3;
+ for (int Index = 0; Index < NullCount; ++Index)
+ {
+ Writer.AddNull();
+ }
+ uint8_t Buffer[NullCount]{};
+ MutableMemoryView BufferView(Buffer, sizeof Buffer);
+ CbFieldViewIterator Fields = Writer.Save(BufferView);
+
+ CHECK(ValidateCompactBinaryRange(BufferView, CbValidateMode::All) == CbValidateError::None);
+
+ for (int Index = 0; Index < NullCount; ++Index)
+ {
+ CHECK(Fields.IsNull() == true);
+ ++Fields;
+ }
+ CHECK(Fields.HasValue() == false);
+ }
+}
+
+TEST_CASE("usonbuilder.binary")
+{
+ using namespace std::literals;
+
+ FixedCbWriter<256> Writer;
+}
+
+TEST_CASE("usonbuilder.string")
+{
+ using namespace std::literals;
+
+ FixedCbWriter<256> Writer;
+
+ SUBCASE("Empty Strings")
+ {
+ Writer.AddString(std::string_view());
+ Writer.AddString(std::wstring_view());
+
+ CbFieldIterator Fields = Writer.Save();
+
+ CHECK(ValidateCompactBinary(Fields.GetBuffer(), CbValidateMode::All) == CbValidateError::None);
+
+ for (CbFieldView Field : Fields)
+ {
+ CHECK(Field.HasName() == false);
+ CHECK(Field.IsString() == true);
+ CHECK(Field.AsString().empty() == true);
+ }
+ }
+
+ SUBCASE("Test Basic Strings")
+ {
+ Writer.SetName("String"sv).AddString("Value"sv);
+ Writer.SetName("String"sv).AddString(L"Value"sv);
+
+ CbFieldIterator Fields = Writer.Save();
+
+ CHECK(ValidateCompactBinary(Fields.GetBuffer(), CbValidateMode::All) == CbValidateError::None);
+
+ for (CbFieldView Field : Fields)
+ {
+ CHECK(Field.GetName().compare("String"sv) == 0);
+ CHECK(Field.HasName() == true);
+ CHECK(Field.IsString() == true);
+ CHECK(Field.AsString().compare("Value"sv) == 0);
+ }
+ }
+
+ SUBCASE("Long Strings")
+ {
+ constexpr int DotCount = 256;
+ StringBuilder<DotCount + 1> Dots;
+ for (int Index = 0; Index < DotCount; ++Index)
+ {
+ Dots.Append('.');
+ }
+ Writer.AddString(Dots);
+ Writer.AddString(std::wstring().append(256, L'.'));
+ CbFieldIterator Fields = Writer.Save();
+
+ CHECK(ValidateCompactBinary(Fields.GetBuffer(), CbValidateMode::All) == CbValidateError::None);
+
+ for (CbFieldView Field : Fields)
+ {
+ CHECK((Field.AsString() == std::string_view(Dots)));
+ }
+ }
+
+ SUBCASE("Non-ASCII String")
+ {
+# if ZEN_SIZEOF_WCHAR_T == 2
+ wchar_t Value[2] = {0xd83d, 0xde00};
+# else
+ wchar_t Value[1] = {0x1f600};
+# endif
+
+ Writer.AddString("\xf0\x9f\x98\x80"sv);
+ Writer.AddString(std::wstring_view(Value, ZEN_ARRAY_COUNT(Value)));
+ CbFieldIterator Fields = Writer.Save();
+
+ CHECK(ValidateCompactBinary(Fields.GetBuffer(), CbValidateMode::All) == CbValidateError::None);
+
+ for (CbFieldView Field : Fields)
+ {
+ CHECK((Field.AsString() == "\xf0\x9f\x98\x80"sv));
+ }
+ }
+}
+
+TEST_CASE("usonbuilder.integer")
+{
+ using namespace std::literals;
+
+ FixedCbWriter<256> Writer;
+
+ auto TestInt32 = [&Writer](int32_t Value) {
+ Writer.Reset();
+ Writer.AddInteger(Value);
+ CbField Field = Writer.Save();
+
+ CHECK(ValidateCompactBinary(Field.GetBuffer(), CbValidateMode::All) == CbValidateError::None);
+
+ CHECK(Field.AsInt32() == Value);
+ CHECK(Field.HasError() == false);
+ };
+
+ auto TestUInt32 = [&Writer](uint32_t Value) {
+ Writer.Reset();
+ Writer.AddInteger(Value);
+ CbField Field = Writer.Save();
+
+ CHECK(ValidateCompactBinary(Field.GetBuffer(), CbValidateMode::All) == CbValidateError::None);
+
+ CHECK(Field.AsUInt32() == Value);
+ CHECK(Field.HasError() == false);
+ };
+
+ auto TestInt64 = [&Writer](int64_t Value) {
+ Writer.Reset();
+ Writer.AddInteger(Value);
+ CbField Field = Writer.Save();
+
+ CHECK(ValidateCompactBinary(Field.GetBuffer(), CbValidateMode::All) == CbValidateError::None);
+
+ CHECK(Field.AsInt64() == Value);
+ CHECK(Field.HasError() == false);
+ };
+
+ auto TestUInt64 = [&Writer](uint64_t Value) {
+ Writer.Reset();
+ Writer.AddInteger(Value);
+ CbField Field = Writer.Save();
+
+ CHECK(ValidateCompactBinary(Field.GetBuffer(), CbValidateMode::All) == CbValidateError::None);
+
+ CHECK(Field.AsUInt64() == Value);
+ CHECK(Field.HasError() == false);
+ };
+
+ TestUInt32(uint32_t(0x00));
+ TestUInt32(uint32_t(0x7f));
+ TestUInt32(uint32_t(0x80));
+ TestUInt32(uint32_t(0xff));
+ TestUInt32(uint32_t(0x0100));
+ TestUInt32(uint32_t(0x7fff));
+ TestUInt32(uint32_t(0x8000));
+ TestUInt32(uint32_t(0xffff));
+ TestUInt32(uint32_t(0x0001'0000));
+ TestUInt32(uint32_t(0x7fff'ffff));
+ TestUInt32(uint32_t(0x8000'0000));
+ TestUInt32(uint32_t(0xffff'ffff));
+
+ TestUInt64(uint64_t(0x0000'0001'0000'0000));
+ TestUInt64(uint64_t(0x7fff'ffff'ffff'ffff));
+ TestUInt64(uint64_t(0x8000'0000'0000'0000));
+ TestUInt64(uint64_t(0xffff'ffff'ffff'ffff));
+
+ TestInt32(int32_t(0x01));
+ TestInt32(int32_t(0x80));
+ TestInt32(int32_t(0x81));
+ TestInt32(int32_t(0x8000));
+ TestInt32(int32_t(0x8001));
+ TestInt32(int32_t(0x7fff'ffff));
+ TestInt32(int32_t(0x8000'0000));
+ TestInt32(int32_t(0x8000'0001));
+
+ TestInt64(int64_t(0x0000'0001'0000'0000));
+ TestInt64(int64_t(0x8000'0000'0000'0000));
+ TestInt64(int64_t(0x7fff'ffff'ffff'ffff));
+ TestInt64(int64_t(0x8000'0000'0000'0001));
+ TestInt64(int64_t(0xffff'ffff'ffff'ffff));
+}
+
+TEST_CASE("usonbuilder.float")
+{
+ using namespace std::literals;
+
+ FixedCbWriter<256> Writer;
+
+ SUBCASE("Float32")
+ {
+ constexpr float Values[] = {
+ 0.0f,
+ 1.0f,
+ -1.0f,
+ 3.14159265358979323846f, // PI
+ 3.402823466e+38f, // FLT_MAX
+ 1.175494351e-38f // FLT_MIN
+ };
+
+ for (float Value : Values)
+ {
+ Writer.AddFloat(Value);
+ }
+ CbFieldIterator Fields = Writer.Save();
+
+ CHECK(ValidateCompactBinary(Fields.GetBuffer(), CbValidateMode::All) == CbValidateError::None);
+
+ const float* CheckValue = Values;
+ for (CbFieldView Field : Fields)
+ {
+ CHECK(Field.AsFloat() == *CheckValue++);
+ CHECK(Field.HasError() == false);
+ }
+ }
+
+ SUBCASE("Float64")
+ {
+ constexpr double Values[] = {
+ 0.0f,
+ 1.0f,
+ -1.0f,
+ 3.14159265358979323846, // PI
+ 1.9999998807907104,
+ 1.9999999403953552,
+ 3.4028234663852886e38,
+ 6.8056469327705771e38,
+ 2.2250738585072014e-308, // DBL_MIN
+ 1.7976931348623158e+308 // DBL_MAX
+ };
+
+ for (double Value : Values)
+ {
+ Writer.AddFloat(Value);
+ }
+
+ CbFieldIterator Fields = Writer.Save();
+
+ CHECK(ValidateCompactBinary(Fields.GetBuffer(), CbValidateMode::All) == CbValidateError::None);
+
+ const double* CheckValue = Values;
+ for (CbFieldView Field : Fields)
+ {
+ CHECK(Field.AsDouble() == *CheckValue++);
+ CHECK(Field.HasError() == false);
+ }
+ }
+}
+
+TEST_CASE("usonbuilder.bool")
+{
+ using namespace std::literals;
+
+ FixedCbWriter<256> Writer;
+
+ SUBCASE("Bool")
+ {
+ Writer.AddBool(true);
+ Writer.AddBool(false);
+
+ CbFieldIterator Fields = Writer.Save();
+
+ CHECK(ValidateCompactBinary(Fields.GetBuffer(), CbValidateMode::All) == CbValidateError::None);
+
+ CHECK(Fields.AsBool() == true);
+ CHECK(Fields.HasError() == false);
+ ++Fields;
+ CHECK(Fields.AsBool() == false);
+ CHECK(Fields.HasError() == false);
+ ++Fields;
+ CHECK(Fields.HasValue() == false);
+ }
+
+ SUBCASE("Bool Array/Object Uniformity")
+ {
+ Writer.BeginArray();
+ Writer.AddBool(false);
+ Writer.AddBool(false);
+ Writer.AddBool(false);
+ Writer.EndArray();
+
+ Writer.BeginObject();
+ Writer.SetName("B1"sv).AddBool(false);
+ Writer.SetName("B2"sv).AddBool(false);
+ Writer.SetName("B3"sv).AddBool(false);
+ Writer.EndObject();
+
+ CbFieldIterator Fields = Writer.Save();
+
+ CHECK(ValidateCompactBinary(Fields.GetBuffer(), CbValidateMode::All) == CbValidateError::None);
+ }
+}
+
+TEST_CASE("usonbuilder.usonattachment")
+{
+ using namespace std::literals;
+
+ FixedCbWriter<256> Writer;
+}
+
+TEST_CASE("usonbuilder.binaryattachment")
+{
+ using namespace std::literals;
+
+ FixedCbWriter<256> Writer;
+}
+
+TEST_CASE("usonbuilder.hash")
+{
+ using namespace std::literals;
+
+ FixedCbWriter<256> Writer;
+}
+
+TEST_CASE("usonbuilder.uuid")
+{
+ using namespace std::literals;
+
+ FixedCbWriter<256> Writer;
+}
+
+TEST_CASE("usonbuilder.datetime")
+{
+ using namespace std::literals;
+
+ FixedCbWriter<256> Writer;
+
+ const DateTime Values[] = {DateTime(0), DateTime(2020, 5, 13, 15, 10)};
+ for (DateTime Value : Values)
+ {
+ Writer.AddDateTime(Value);
+ }
+
+ CbFieldIterator Fields = Writer.Save();
+
+ CHECK(ValidateCompactBinary(Fields.GetBuffer(), CbValidateMode::All) == CbValidateError::None);
+
+ const DateTime* CheckValue = Values;
+ for (CbFieldView Field : Fields)
+ {
+ CHECK(Field.AsDateTime() == *CheckValue++);
+ CHECK(Field.HasError() == false);
+ }
+}
+
+TEST_CASE("usonbuilder.timespan")
+{
+ using namespace std::literals;
+
+ FixedCbWriter<256> Writer;
+
+ const TimeSpan Values[] = {TimeSpan(0), TimeSpan(1, 2, 4, 8)};
+ for (TimeSpan Value : Values)
+ {
+ Writer.AddTimeSpan(Value);
+ }
+
+ CbFieldIterator Fields = Writer.Save();
+
+ CHECK(ValidateCompactBinary(Fields.GetBuffer(), CbValidateMode::All) == CbValidateError::None);
+
+ const TimeSpan* CheckValue = Values;
+ for (CbFieldView Field : Fields)
+ {
+ CHECK(Field.AsTimeSpan() == *CheckValue++);
+ CHECK(Field.HasError() == false);
+ }
+}
+
+TEST_CASE("usonbuilder.complex")
+{
+ using namespace std::literals;
+
+ FixedCbWriter<256> Writer;
+
+ SUBCASE("complex")
+ {
+ CbObject Object;
+
+ {
+ Writer.BeginObject();
+
+ const uint8_t LocalField[] = {uint8_t(CbFieldType::IntegerPositive | CbFieldType::HasFieldName), 1, 'I', 42};
+ Writer.AddField("FieldCopy"sv, CbFieldView(LocalField));
+ Writer.AddField("FieldRefCopy"sv, CbField(SharedBuffer::Clone(MakeMemoryView(LocalField))));
+
+ const uint8_t LocalObject[] = {uint8_t(CbFieldType::Object | CbFieldType::HasFieldName),
+ 1,
+ 'O',
+ 7,
+ uint8_t(CbFieldType::IntegerPositive | CbFieldType::HasFieldName),
+ 1,
+ 'I',
+ 42,
+ uint8_t(CbFieldType::Null | CbFieldType::HasFieldName),
+ 1,
+ 'N'};
+ Writer.AddObject("ObjectCopy"sv, CbObjectView(LocalObject));
+ Writer.AddObject("ObjectRefCopy"sv, CbObject(SharedBuffer::Clone(MakeMemoryView(LocalObject))));
+
+ const uint8_t LocalArray[] = {uint8_t(CbFieldType::UniformArray | CbFieldType::HasFieldName),
+ 1,
+ 'A',
+ 4,
+ 2,
+ uint8_t(CbFieldType::IntegerPositive),
+ 42,
+ 21};
+ Writer.AddArray("ArrayCopy"sv, CbArrayView(LocalArray));
+ Writer.AddArray("ArrayRefCopy"sv, CbArray(SharedBuffer::Clone(MakeMemoryView(LocalArray))));
+
+ Writer.AddNull("Null"sv);
+
+ Writer.BeginObject("Binary"sv);
+ {
+ Writer.AddBinary("Empty"sv, MemoryView());
+ Writer.AddBinary("Value"sv, MakeMemoryView("BinaryValue"));
+ Writer.AddBinary("LargeValue"sv, MakeMemoryView(std::wstring().append(256, L'.')));
+ Writer.AddBinary("LargeRefValue"sv, SharedBuffer::Clone(MakeMemoryView(std::wstring().append(256, L'!'))));
+ }
+ Writer.EndObject();
+
+ Writer.BeginObject("Strings"sv);
+ {
+ Writer.AddString("AnsiString"sv, "AnsiValue"sv);
+ Writer.AddString("WideString"sv, std::wstring().append(256, L'.'));
+ Writer.AddString("EmptyAnsiString"sv, std::string_view());
+ Writer.AddString("EmptyWideString"sv, std::wstring_view());
+ Writer.AddString("AnsiStringLiteral", "AnsiValue");
+ Writer.AddString("WideStringLiteral", L"AnsiValue");
+ }
+ Writer.EndObject();
+
+ Writer.BeginArray("Integers"sv);
+ {
+ Writer.AddInteger(int32_t(-1));
+ Writer.AddInteger(int64_t(-1));
+ Writer.AddInteger(uint32_t(1));
+ Writer.AddInteger(uint64_t(1));
+ Writer.AddInteger(std::numeric_limits<int32_t>::min());
+ Writer.AddInteger(std::numeric_limits<int32_t>::max());
+ Writer.AddInteger(std::numeric_limits<uint32_t>::max());
+ Writer.AddInteger(std::numeric_limits<int64_t>::min());
+ Writer.AddInteger(std::numeric_limits<int64_t>::max());
+ Writer.AddInteger(std::numeric_limits<uint64_t>::max());
+ }
+ Writer.EndArray();
+
+ Writer.BeginArray("UniformIntegers"sv);
+ {
+ Writer.AddInteger(0);
+ Writer.AddInteger(std::numeric_limits<int32_t>::max());
+ Writer.AddInteger(std::numeric_limits<uint32_t>::max());
+ Writer.AddInteger(std::numeric_limits<int64_t>::max());
+ Writer.AddInteger(std::numeric_limits<uint64_t>::max());
+ }
+ Writer.EndArray();
+
+ Writer.AddFloat("Float32"sv, 1.0f);
+ Writer.AddFloat("Float64as32"sv, 2.0);
+ Writer.AddFloat("Float64"sv, 3.0e100);
+
+ Writer.AddBool("False"sv, false);
+ Writer.AddBool("True"sv, true);
+
+ Writer.AddObjectAttachment("ObjectAttachment"sv, IoHash());
+ Writer.AddBinaryAttachment("BinaryAttachment"sv, IoHash());
+ Writer.AddAttachment("Attachment"sv, CbAttachment());
+
+ Writer.AddHash("Hash"sv, IoHash());
+ Writer.AddUuid("Uuid"sv, Guid());
+
+ Writer.AddDateTimeTicks("DateTimeZero"sv, 0);
+ Writer.AddDateTime("DateTime2020"sv, DateTime(2020, 5, 13, 15, 10));
+
+ Writer.AddTimeSpanTicks("TimeSpanZero"sv, 0);
+ Writer.AddTimeSpan("TimeSpan"sv, TimeSpan(1, 2, 4, 8));
+
+ Writer.BeginObject("NestedObjects"sv);
+ {
+ Writer.BeginObject("Empty"sv);
+ Writer.EndObject();
+
+ Writer.BeginObject("Null"sv);
+ Writer.AddNull("Null"sv);
+ Writer.EndObject();
+ }
+ Writer.EndObject();
+
+ Writer.BeginArray("NestedArrays"sv);
+ {
+ Writer.BeginArray();
+ Writer.EndArray();
+
+ Writer.BeginArray();
+ Writer.AddNull();
+ Writer.AddNull();
+ Writer.AddNull();
+ Writer.EndArray();
+
+ Writer.BeginArray();
+ Writer.AddBool(false);
+ Writer.AddBool(false);
+ Writer.AddBool(false);
+ Writer.EndArray();
+
+ Writer.BeginArray();
+ Writer.AddBool(true);
+ Writer.AddBool(true);
+ Writer.AddBool(true);
+ Writer.EndArray();
+ }
+ Writer.EndArray();
+
+ Writer.BeginArray("ArrayOfObjects"sv);
+ {
+ Writer.BeginObject();
+ Writer.EndObject();
+
+ Writer.BeginObject();
+ Writer.AddNull("Null"sv);
+ Writer.EndObject();
+ }
+ Writer.EndArray();
+
+ Writer.BeginArray("LargeArray"sv);
+ for (int Index = 0; Index < 256; ++Index)
+ {
+ Writer.AddInteger(Index - 128);
+ }
+ Writer.EndArray();
+
+ Writer.BeginArray("LargeUniformArray"sv);
+ for (int Index = 0; Index < 256; ++Index)
+ {
+ Writer.AddInteger(Index);
+ }
+ Writer.EndArray();
+
+ Writer.BeginArray("NestedUniformArray"sv);
+ for (int Index = 0; Index < 16; ++Index)
+ {
+ Writer.BeginArray();
+ for (int Value = 0; Value < 4; ++Value)
+ {
+ Writer.AddInteger(Value);
+ }
+ Writer.EndArray();
+ }
+ Writer.EndArray();
+
+ Writer.EndObject();
+ Object = Writer.Save().AsObject();
+ }
+ CHECK(ValidateCompactBinary(Object.GetBuffer(), CbValidateMode::All) == CbValidateError::None);
+ }
+}
+
+TEST_CASE("usonbuilder.stream")
+{
+ using namespace std::literals;
+
+ FixedCbWriter<256> Writer;
+
+ SUBCASE("basic")
+ {
+ CbObject Object;
+ {
+ Writer.BeginObject();
+
+ const uint8_t LocalField[] = {uint8_t(CbFieldType::IntegerPositive | CbFieldType::HasFieldName), 1, 'I', 42};
+ Writer << "FieldCopy"sv << CbFieldView(LocalField);
+
+ const uint8_t LocalObject[] = {uint8_t(CbFieldType::Object | CbFieldType::HasFieldName),
+ 1,
+ 'O',
+ 7,
+ uint8_t(CbFieldType::IntegerPositive | CbFieldType::HasFieldName),
+ 1,
+ 'I',
+ 42,
+ uint8_t(CbFieldType::Null | CbFieldType::HasFieldName),
+ 1,
+ 'N'};
+ Writer << "ObjectCopy"sv << CbObjectView(LocalObject);
+
+ const uint8_t LocalArray[] = {uint8_t(CbFieldType::UniformArray | CbFieldType::HasFieldName),
+ 1,
+ 'A',
+ 4,
+ 2,
+ uint8_t(CbFieldType::IntegerPositive),
+ 42,
+ 21};
+ Writer << "ArrayCopy"sv << CbArrayView(LocalArray);
+
+ Writer << "Null"sv << nullptr;
+
+ Writer << "Strings"sv;
+ Writer.BeginObject();
+ Writer << "AnsiString"sv
+ << "AnsiValue"sv
+ << "AnsiStringLiteral"sv
+ << "AnsiValue"
+ << "WideString"sv << L"WideValue"sv << "WideStringLiteral"sv << L"WideValue";
+ Writer.EndObject();
+
+ Writer << "Integers"sv;
+ Writer.BeginArray();
+ Writer << int32_t(-1) << int64_t(-1) << uint32_t(1) << uint64_t(1);
+ Writer.EndArray();
+
+ Writer << "Float32"sv << 1.0f;
+ Writer << "Float64"sv << 2.0;
+
+ Writer << "False"sv << false << "True"sv << true;
+
+ Writer << "Attachment"sv << CbAttachment();
+
+ Writer << "Hash"sv << IoHash();
+ Writer << "Uuid"sv << Guid();
+
+ Writer << "DateTime"sv << DateTime(2020, 5, 13, 15, 10);
+ Writer << "TimeSpan"sv << TimeSpan(1, 2, 4, 8);
+
+ Writer << "LiteralName" << nullptr;
+
+ Writer.EndObject();
+ Object = Writer.Save().AsObject();
+ }
+
+ CHECK(ValidateCompactBinary(Object.GetBuffer(), CbValidateMode::All) == CbValidateError::None);
+ }
+}
+#endif
+
+} // namespace zen