// Copyright Epic Games, Inc. All Rights Reserved. #include "zencore/compactbinary.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #if ZEN_PLATFORM_WINDOWS # include #else # include #endif namespace zen { const int DaysToMonth[] = {0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365}; double GetJulianDay(uint64_t Ticks) { return (double)(1721425.5 + Ticks / TimeSpan::TicksPerDay); } bool IsLeapYear(int Year) { if ((Year % 4) == 0) { return (((Year % 100) != 0) || ((Year % 400) == 0)); } return false; } static constexpr uint64_t GetPlatformToDateTimeBiasInSeconds() { #if ZEN_PLATFORM_WINDOWS const uint64_t PlatformEpochYear = 1601; #else const uint64_t PlatformEpochYear = 1970; #endif const uint64_t DateTimeEpochYear = 1; return uint64_t(double(PlatformEpochYear - DateTimeEpochYear) * 365.2425) * 86400; } uint64_t DateTime::NowTicks() { static constexpr uint64_t EpochBias = GetPlatformToDateTimeBiasInSeconds(); #if ZEN_PLATFORM_WINDOWS FILETIME SysTime; GetSystemTimePreciseAsFileTime(&SysTime); return (EpochBias * TimeSpan::TicksPerSecond) + ((uint64_t(SysTime.dwHighDateTime) << 32) | SysTime.dwLowDateTime); #else int64_t SecondsSinceUnixEpoch = time(nullptr); return (EpochBias + SecondsSinceUnixEpoch) * TimeSpan::TicksPerSecond; #endif } DateTime DateTime::Now() { return DateTime{NowTicks()}; } void DateTime::Set(int Year, int Month, int Day, int Hour, int Minute, int Second, int MilliSecond) { int TotalDays = 0; if ((Month > 2) && IsLeapYear(Year)) { ++TotalDays; } --Year; // the current year is not a full year yet --Month; // the current month is not a full month yet TotalDays += Year * 365; TotalDays += Year / 4; // leap year day every four years... TotalDays -= Year / 100; // ...except every 100 years... TotalDays += Year / 400; // ...but also every 400 years TotalDays += DaysToMonth[Month]; // days in this year up to last month TotalDays += Day - 1; // days in this month minus today Ticks = TotalDays * TimeSpan::TicksPerDay + Hour * TimeSpan::TicksPerHour + Minute * TimeSpan::TicksPerMinute + Second * TimeSpan::TicksPerSecond + MilliSecond * TimeSpan::TicksPerMillisecond; } int DateTime::GetYear() const { int Year, Month, Day; GetDate(Year, Month, Day); return Year; } int DateTime::GetMonth() const { int Year, Month, Day; GetDate(Year, Month, Day); return Month; } int DateTime::GetDay() const { int Year, Month, Day; GetDate(Year, Month, Day); return Day; } int DateTime::GetHour() const { return (int)((Ticks / TimeSpan::TicksPerHour) % 24); } int DateTime::GetHour12() const { int Hour = GetHour(); if (Hour < 1) { return 12; } if (Hour > 12) { return (Hour - 12); } return Hour; } int DateTime::GetMinute() const { return (int)((Ticks / TimeSpan::TicksPerMinute) % 60); } int DateTime::GetSecond() const { return (int)((Ticks / TimeSpan::TicksPerSecond) % 60); } int DateTime::GetMillisecond() const { return (int)((Ticks / TimeSpan::TicksPerMillisecond) % 1000); } void DateTime::GetDate(int& Year, int& Month, int& Day) const { // Based on FORTRAN code in: // Fliegel, H. F. and van Flandern, T. C., // Communications of the ACM, Vol. 11, No. 10 (October 1968). int i, j, k, l, n; l = int(GetJulianDay(Ticks) + 0.5) + 68569; n = 4 * l / 146097; l = l - (146097 * n + 3) / 4; i = 4000 * (l + 1) / 1461001; l = l - 1461 * i / 4 + 31; j = 80 * l / 2447; k = l - 2447 * j / 80; l = j / 11; j = j + 2 - 12 * l; i = 100 * (n - 49) + i + l; Year = i; Month = j; Day = k; } std::string DateTime::ToString(const char* Format) const { ExtendableStringBuilder<32> Result; int Year, Month, Day; GetDate(Year, Month, Day); if (Format != nullptr) { while (*Format != '\0') { if ((*Format == '%') && (*(++Format) != '\0')) { switch (*Format) { // case 'a': Result.Append(IsMorning() ? TEXT("am") : TEXT("pm")); break; // case 'A': Result.Append(IsMorning() ? TEXT("AM") : TEXT("PM")); break; case 'd': Result.Append(fmt::format("{:02}", Day)); break; // case 'D': Result.Appendf(TEXT("%03i"), GetDayOfYear()); break; case 'm': Result.Append(fmt::format("{:02}", Month)); break; case 'y': Result.Append(fmt::format("{:02}", Year % 100)); break; case 'Y': Result.Append(fmt::format("{:04}", Year)); break; case 'h': Result.Append(fmt::format("{:02}", GetHour12())); break; case 'H': Result.Append(fmt::format("{:02}", GetHour())); break; case 'M': Result.Append(fmt::format("{:02}", GetMinute())); break; case 'S': Result.Append(fmt::format("{:02}", GetSecond())); break; case 's': Result.Append(fmt::format("{:03}", GetMillisecond())); break; default: Result.Append(*Format); } } else { Result.Append(*Format); } // move to the next one Format++; } } return Result.ToString(); } std::string DateTime::ToIso8601() const { return ToString("%Y-%m-%dT%H:%M:%S.%sZ"); } void TimeSpan::Set(int Days, int Hours, int Minutes, int Seconds, int FractionNano) { int64_t TotalTicks = 0; TotalTicks += Days * TicksPerDay; TotalTicks += Hours * TicksPerHour; TotalTicks += Minutes * TicksPerMinute; TotalTicks += Seconds * TicksPerSecond; TotalTicks += FractionNano / NanosecondsPerTick; Ticks = TotalTicks; } std::string TimeSpan::ToString(const char* Format) const { StringBuilder<128> Result; Result.Append((int64_t(Ticks) < 0) ? '-' : '+'); while (*Format != '\0') { if ((*Format == '%') && (*++Format != '\0')) { switch (*Format) { case 'd': Result.Append(fmt::format("{}", GetDays())); break; case 'D': Result.Append(fmt::format("{:08}", GetDays())); break; case 'h': Result.Append(fmt::format("{:02}", GetHours())); break; case 'm': Result.Append(fmt::format("{:02}", GetMinutes())); break; case 's': Result.Append(fmt::format("{:02}", GetSeconds())); break; case 'f': Result.Append(fmt::format("{:03}", GetFractionMilli())); break; case 'u': Result.Append(fmt::format("{:06}", GetFractionMicro())); break; case 't': Result.Append(fmt::format("{:07}", GetFractionTicks())); break; case 'n': Result.Append(fmt::format("{:09}", GetFractionNano())); break; default: Result.Append(*Format); } } else { Result.Append(*Format); } ++Format; } return Result.ToString(); } std::string TimeSpan::ToString() const { if (GetDays() == 0) { return ToString("%h:%m:%s.%f"); } return ToString("%d.%h:%m:%s.%f"); } StringBuilderBase& Guid::ToString(StringBuilderBase& Sb) const { char Buf[128]; snprintf(Buf, sizeof Buf, "%08x-%04x-%04x-%04x-%04x%08x", A, B >> 16, B & 0xFFFF, C >> 16, C & 0xFFFF, D); Sb << Buf; return Sb; } Guid Guid::FromString(std::string_view InString) { if (InString.size() != 36) { } bool Ok = true; uint32_t V0 = 0, V5 = 0; uint16_t V1 = 0, V2 = 0, V3 = 0, V4 = 0; Ok = Ok && ParseHexNumber(InString.substr(0, 8), /* out */ V0); Ok = Ok && ParseHexNumber(InString.substr(9, 4), /* out */ V1); Ok = Ok && ParseHexNumber(InString.substr(14, 4), /* out */ V2); Ok = Ok && ParseHexNumber(InString.substr(19, 4), /* out */ V3); Ok = Ok && ParseHexNumber(InString.substr(24, 4), /* out */ V4); Ok = Ok && ParseHexNumber(InString.substr(28, 8), /* out */ V5); Guid Value; Value.A = V0; Value.B = V1 << 16 | V2; Value.C = V3 << 16 | V4; Value.D = V5; return Value; } ////////////////////////////////////////////////////////////////////////// namespace CompactBinaryPrivate { static constexpr const uint8_t GEmptyObjectPayload[] = {uint8_t(CbFieldType::Object), 0x00}; static constexpr const uint8_t GEmptyArrayPayload[] = {uint8_t(CbFieldType::Array), 0x01, 0x00}; } // namespace CompactBinaryPrivate ////////////////////////////////////////////////////////////////////////// CbFieldView::CbFieldView(const void* DataPointer, CbFieldType FieldType) { const uint8_t* Bytes = static_cast(DataPointer); const CbFieldType LocalType = CbFieldTypeOps::HasFieldType(FieldType) ? (CbFieldType(*Bytes++) | CbFieldType::HasFieldType) : FieldType; uint32_t NameLenByteCount = 0; const uint64_t NameLen64 = CbFieldTypeOps::HasFieldName(LocalType) ? ReadVarUInt(Bytes, NameLenByteCount) : 0; Bytes += NameLen64 + NameLenByteCount; Type = LocalType; NameLen = uint32_t(std::clamp(NameLen64, 0, ~uint32_t(0))); Payload = Bytes; } void CbFieldView::IterateAttachments(std::function Visitor) const { switch (CbFieldTypeOps::GetType(Type)) { case CbFieldType::Object: case CbFieldType::UniformObject: return CbObjectView::FromFieldView(*this).IterateAttachments(Visitor); case CbFieldType::Array: case CbFieldType::UniformArray: return CbArrayView::FromFieldView(*this).IterateAttachments(Visitor); case CbFieldType::ObjectAttachment: case CbFieldType::BinaryAttachment: return Visitor(*this); default: return; } } CbObjectView CbFieldView::AsObjectView() { if (CbFieldTypeOps::IsObject(Type)) { Error = CbFieldError::None; return CbObjectView::FromFieldView(*this); } else { Error = CbFieldError::TypeError; return CbObjectView(); } } CbArrayView CbFieldView::AsArrayView() { if (CbFieldTypeOps::IsArray(Type)) { Error = CbFieldError::None; return CbArrayView::FromFieldView(*this); } else { Error = CbFieldError::TypeError; return CbArrayView(); } } MemoryView CbFieldView::AsBinaryView(const MemoryView Default) { if (CbValue Accessor = GetValue(); CbFieldTypeOps::IsBinary(Accessor.GetType())) { Error = CbFieldError::None; return Accessor.AsBinary(); } else { Error = CbFieldError::TypeError; return Default; } } std::string_view CbFieldView::AsString(const std::string_view Default) { if (CbValue Accessor = GetValue(); CbFieldTypeOps::IsString(Accessor.GetType())) { Error = CbFieldError::None; return Accessor.AsString(); } else { Error = CbFieldError::TypeError; return Default; } } std::u8string_view CbFieldView::AsU8String(const std::u8string_view Default) { if (CbValue Accessor = GetValue(); CbFieldTypeOps::IsString(Accessor.GetType())) { Error = CbFieldError::None; return Accessor.AsU8String(); } else { Error = CbFieldError::TypeError; return Default; } } uint64_t CbFieldView::AsInteger(const uint64_t Default, const CompactBinaryPrivate::IntegerParams Params) { if (CbValue Accessor = GetValue(); CbFieldTypeOps::IsInteger(Accessor.GetType())) { return Accessor.AsInteger(Params, &Error, Default); } else { Error = CbFieldError::TypeError; return Default; } } float CbFieldView::AsFloat(const float Default) { switch (CbValue Accessor = GetValue(); Accessor.GetType()) { case CbFieldType::IntegerPositive: case CbFieldType::IntegerNegative: { const uint64_t IsNegative = uint8_t(Accessor.GetType()) & 1; constexpr uint64_t OutOfRangeMask = ~((uint64_t(1) << /*FLT_MANT_DIG*/ 24) - 1); uint32_t MagnitudeByteCount; const int64_t Magnitude = ReadVarUInt(Accessor.GetData(), MagnitudeByteCount) + IsNegative; const uint64_t IsInRange = !(Magnitude & OutOfRangeMask); Error = IsInRange ? CbFieldError::None : CbFieldError::RangeError; return IsInRange ? float(IsNegative ? -Magnitude : Magnitude) : Default; } case CbFieldType::Float32: { Error = CbFieldError::None; return Accessor.AsFloat32(); } case CbFieldType::Float64: Error = CbFieldError::RangeError; return Default; default: Error = CbFieldError::TypeError; return Default; } } double CbFieldView::AsDouble(const double Default) { switch (CbValue Accessor = GetValue(); Accessor.GetType()) { case CbFieldType::IntegerPositive: case CbFieldType::IntegerNegative: { const uint64_t IsNegative = uint8_t(Accessor.GetType()) & 1; constexpr uint64_t OutOfRangeMask = ~((uint64_t(1) << /*DBL_MANT_DIG*/ 53) - 1); uint32_t MagnitudeByteCount; const int64_t Magnitude = ReadVarUInt(Accessor.GetData(), MagnitudeByteCount) + IsNegative; const uint64_t IsInRange = !(Magnitude & OutOfRangeMask); Error = IsInRange ? CbFieldError::None : CbFieldError::RangeError; return IsInRange ? double(IsNegative ? -Magnitude : Magnitude) : Default; } case CbFieldType::Float32: { Error = CbFieldError::None; return Accessor.AsFloat32(); } case CbFieldType::Float64: { Error = CbFieldError::None; return Accessor.AsFloat64(); } default: Error = CbFieldError::TypeError; return Default; } } bool CbFieldView::AsBool(const bool bDefault) { CbValue Accessor = GetValue(); const bool IsBool = CbFieldTypeOps::IsBool(Accessor.GetType()); Error = IsBool ? CbFieldError::None : CbFieldError::TypeError; return (uint8_t(IsBool) & Accessor.AsBool()) | ((!IsBool) & bDefault); } IoHash CbFieldView::AsObjectAttachment(const IoHash& Default) { if (CbValue Accessor = GetValue(); CbFieldTypeOps::IsObjectAttachment(Accessor.GetType())) { Error = CbFieldError::None; return Accessor.AsObjectAttachment(); } else { Error = CbFieldError::TypeError; return Default; } } IoHash CbFieldView::AsBinaryAttachment(const IoHash& Default) { if (CbValue Accessor = GetValue(); CbFieldTypeOps::IsBinaryAttachment(Accessor.GetType())) { Error = CbFieldError::None; return Accessor.AsBinaryAttachment(); } else { Error = CbFieldError::TypeError; return Default; } } IoHash CbFieldView::AsAttachment(const IoHash& Default) { if (CbValue Accessor = GetValue(); CbFieldTypeOps::IsAttachment(Accessor.GetType())) { Error = CbFieldError::None; return Accessor.AsAttachment(); } else { Error = CbFieldError::TypeError; return Default; } } IoHash CbFieldView::AsHash(const IoHash& Default) { if (CbValue Accessor = GetValue(); CbFieldTypeOps::IsHash(Accessor.GetType())) { Error = CbFieldError::None; return Accessor.AsHash(); } else { Error = CbFieldError::TypeError; return Default; } } Guid CbFieldView::AsUuid() { return AsUuid(Guid()); } Guid CbFieldView::AsUuid(const Guid& Default) { if (CbValue Accessor = GetValue(); CbFieldTypeOps::IsUuid(Accessor.GetType())) { Error = CbFieldError::None; return Accessor.AsUuid(); } else { Error = CbFieldError::TypeError; return Default; } } Oid CbFieldView::AsObjectId() { return AsObjectId(Oid()); } Oid CbFieldView::AsObjectId(const Oid& Default) { if (CbValue Accessor = GetValue(); CbFieldTypeOps::IsObjectId(Accessor.GetType())) { Error = CbFieldError::None; return Accessor.AsObjectId(); } else { Error = CbFieldError::TypeError; return Default; } } CbCustomById CbFieldView::AsCustomById(CbCustomById Default) { if (CbValue Accessor = GetValue(); CbFieldTypeOps::IsCustomById(Accessor.GetType())) { Error = CbFieldError::None; return Accessor.AsCustomById(); } else { Error = CbFieldError::TypeError; return Default; } } CbCustomByName CbFieldView::AsCustomByName(CbCustomByName Default) { if (CbValue Accessor = GetValue(); CbFieldTypeOps::IsCustomByName(Accessor.GetType())) { Error = CbFieldError::None; return Accessor.AsCustomByName(); } else { Error = CbFieldError::TypeError; return Default; } } int64_t CbFieldView::AsDateTimeTicks(const int64_t Default) { if (CbValue Accessor = GetValue(); CbFieldTypeOps::IsDateTime(Accessor.GetType())) { Error = CbFieldError::None; return Accessor.AsDateTimeTicks(); } else { Error = CbFieldError::TypeError; return Default; } } DateTime CbFieldView::AsDateTime() { return DateTime(AsDateTimeTicks(0)); } DateTime CbFieldView::AsDateTime(DateTime Default) { return DateTime(AsDateTimeTicks(Default.GetTicks())); } int64_t CbFieldView::AsTimeSpanTicks(const int64_t Default) { if (CbValue Accessor = GetValue(); CbFieldTypeOps::IsTimeSpan(Accessor.GetType())) { Error = CbFieldError::None; return Accessor.AsTimeSpanTicks(); } else { Error = CbFieldError::TypeError; return Default; } } TimeSpan CbFieldView::AsTimeSpan() { return TimeSpan(AsTimeSpanTicks(0)); } TimeSpan CbFieldView::AsTimeSpan(TimeSpan Default) { return TimeSpan(AsTimeSpanTicks(Default.GetTicks())); } uint64_t CbFieldView::GetSize() const { return sizeof(CbFieldType) + GetViewNoType().GetSize(); } uint64_t CbFieldView::GetPayloadSize() const { switch (CbFieldTypeOps::GetType(Type)) { case CbFieldType::None: case CbFieldType::Null: return 0; case CbFieldType::Object: case CbFieldType::UniformObject: case CbFieldType::Array: case CbFieldType::UniformArray: case CbFieldType::Binary: case CbFieldType::String: { uint32_t PayloadSizeByteCount; const uint64_t PayloadSize = ReadVarUInt(Payload, PayloadSizeByteCount); return PayloadSize + PayloadSizeByteCount; } case CbFieldType::IntegerPositive: case CbFieldType::IntegerNegative: return MeasureVarUInt(Payload); case CbFieldType::Float32: return 4; case CbFieldType::Float64: return 8; case CbFieldType::BoolFalse: case CbFieldType::BoolTrue: return 0; case CbFieldType::ObjectAttachment: case CbFieldType::BinaryAttachment: case CbFieldType::Hash: return 20; case CbFieldType::Uuid: return 16; case CbFieldType::ObjectId: return 12; case CbFieldType::DateTime: case CbFieldType::TimeSpan: return 8; default: return 0; } } IoHash CbFieldView::GetHash() const { IoHashStream HashStream; GetHash(HashStream); return HashStream.GetHash(); } void CbFieldView::GetHash(IoHashStream& Hash) const { const CbFieldType SerializedType = CbFieldTypeOps::GetSerializedType(Type); Hash.Append(&SerializedType, sizeof(SerializedType)); auto View = GetViewNoType(); Hash.Append(View.GetData(), View.GetSize()); } bool CbFieldView::Equals(const CbFieldView& Other) const { return CbFieldTypeOps::GetSerializedType(Type) == CbFieldTypeOps::GetSerializedType(Other.Type) && GetViewNoType().EqualBytes(Other.GetViewNoType()); } void CbFieldView::CopyTo(MutableMemoryView Buffer) const { const MemoryView Source = GetViewNoType(); ZEN_ASSERT_FORMAT(Buffer.GetSize() == sizeof(CbFieldType) + Source.GetSize(), "A buffer of {} bytes was provided when {} bytes are required", Buffer.GetSize(), sizeof(CbFieldType) + Source.GetSize()); *static_cast(Buffer.GetData()) = CbFieldTypeOps::GetSerializedType(Type); Buffer.RightChopInline(sizeof(CbFieldType)); memcpy(Buffer.GetData(), Source.GetData(), Source.GetSize()); } void CbFieldView::CopyTo(BinaryWriter& Ar) const { const MemoryView SourceView = GetViewNoType(); CbFieldType SerializedType = CbFieldTypeOps::GetSerializedType(Type); const MemoryView TypeView(reinterpret_cast(&SerializedType), sizeof(SerializedType)); Ar.Write({TypeView, SourceView}); } MemoryView CbFieldView::GetView() const { const uint32_t TypeSize = CbFieldTypeOps::HasFieldType(Type) ? sizeof(CbFieldType) : 0; const uint32_t NameSize = CbFieldTypeOps::HasFieldName(Type) ? NameLen + MeasureVarUInt(NameLen) : 0; const uint64_t PayloadSize = GetPayloadSize(); return MemoryView(static_cast(Payload) - TypeSize - NameSize, TypeSize + NameSize + PayloadSize); } MemoryView CbFieldView::GetViewNoType() const { const uint32_t NameSize = CbFieldTypeOps::HasFieldName(Type) ? NameLen + MeasureVarUInt(NameLen) : 0; const uint64_t PayloadSize = GetPayloadSize(); return MemoryView(static_cast(Payload) - NameSize, NameSize + PayloadSize); } /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// CbArrayView::CbArrayView() : CbFieldView(CompactBinaryPrivate::GEmptyArrayPayload) { } uint64_t CbArrayView::Num() const { const uint8_t* PayloadBytes = static_cast(GetPayload()); PayloadBytes += MeasureVarUInt(PayloadBytes); uint32_t NumByteCount; return ReadVarUInt(PayloadBytes, NumByteCount); } CbFieldViewIterator CbArrayView::CreateViewIterator() const { const uint8_t* PayloadBytes = static_cast(GetPayload()); uint32_t PayloadSizeByteCount; const uint64_t PayloadSize = ReadVarUInt(PayloadBytes, PayloadSizeByteCount); PayloadBytes += PayloadSizeByteCount; const uint64_t NumByteCount = MeasureVarUInt(PayloadBytes); if (PayloadSize > NumByteCount) { const void* const PayloadEnd = PayloadBytes + PayloadSize; PayloadBytes += NumByteCount; const CbFieldType UniformType = CbFieldTypeOps::GetType(GetType()) == CbFieldType::UniformArray ? CbFieldType(*PayloadBytes++) : CbFieldType::HasFieldType; return CbFieldViewIterator::MakeRange(MemoryView(PayloadBytes, PayloadEnd), UniformType); } return CbFieldViewIterator(); } void CbArrayView::VisitFields(ICbVisitor&) { } uint64_t CbArrayView::GetSize() const { return sizeof(CbFieldType) + GetPayloadSize(); } IoHash CbArrayView::GetHash() const { IoHashStream Hash; GetHash(Hash); return Hash.GetHash(); } void CbArrayView::GetHash(IoHashStream& HashStream) const { const CbFieldType SerializedType = CbFieldTypeOps::GetType(GetType()); HashStream.Append(&SerializedType, sizeof(SerializedType)); auto _ = GetPayloadView(); HashStream.Append(_.GetData(), _.GetSize()); } bool CbArrayView::Equals(const CbArrayView& Other) const { return CbFieldTypeOps::GetType(GetType()) == CbFieldTypeOps::GetType(Other.GetType()) && GetPayloadView().EqualBytes(Other.GetPayloadView()); } void CbArrayView::CopyTo(MutableMemoryView Buffer) const { const MemoryView Source = GetPayloadView(); ZEN_ASSERT_FORMAT(Buffer.GetSize() == sizeof(CbFieldType) + Source.GetSize(), "Buffer is {} bytes but {} is required.", Buffer.GetSize(), sizeof(CbFieldType) + Source.GetSize()); *static_cast(Buffer.GetData()) = CbFieldTypeOps::GetType(GetType()); Buffer.RightChopInline(sizeof(CbFieldType)); memcpy(Buffer.GetData(), Source.GetData(), Source.GetSize()); } void CbArrayView::CopyTo(BinaryWriter& Ar) const { const MemoryView SourceView = GetPayloadView(); CbFieldType SerializedType = CbFieldTypeOps::GetSerializedType(GetType()); const MemoryView TypeView(reinterpret_cast(&SerializedType), sizeof(SerializedType)); Ar.Write({TypeView, SourceView}); } /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// CbObjectView::CbObjectView() : CbFieldView(CompactBinaryPrivate::GEmptyObjectPayload) { } CbFieldViewIterator CbObjectView::CreateViewIterator() const { const uint8_t* PayloadBytes = static_cast(GetPayload()); uint32_t PayloadSizeByteCount; const uint64_t PayloadSize = ReadVarUInt(PayloadBytes, PayloadSizeByteCount); PayloadBytes += PayloadSizeByteCount; if (PayloadSize) { const void* const PayloadEnd = PayloadBytes + PayloadSize; const CbFieldType UniformType = CbFieldTypeOps::GetType(GetType()) == CbFieldType::UniformObject ? CbFieldType(*PayloadBytes++) : CbFieldType::HasFieldType; return CbFieldViewIterator::MakeRange(MemoryView(PayloadBytes, PayloadEnd), UniformType); } return CbFieldViewIterator(); } void CbObjectView::VisitFields(ICbVisitor&) { } CbFieldView CbObjectView::FindView(const std::string_view Name) const { for (const CbFieldView& Field : *this) { if (Name == Field.GetName()) { return Field; } } return CbFieldView(); } CbFieldView CbObjectView::FindViewIgnoreCase(const std::string_view Name) const { for (const CbFieldView& Field : *this) { if (Name == Field.GetName()) { return Field; } } return CbFieldView(); } CbObjectView::operator bool() const { return GetSize() > sizeof(CompactBinaryPrivate::GEmptyObjectPayload); } uint64_t CbObjectView::GetSize() const { return sizeof(CbFieldType) + GetPayloadSize(); } IoHash CbObjectView::GetHash() const { IoHashStream Hash; GetHash(Hash); return Hash.GetHash(); } void CbObjectView::GetHash(IoHashStream& HashStream) const { const CbFieldType SerializedType = CbFieldTypeOps::GetType(GetType()); HashStream.Append(&SerializedType, sizeof(SerializedType)); HashStream.Append(GetPayloadView()); } bool CbObjectView::Equals(const CbObjectView& Other) const { return CbFieldTypeOps::GetType(GetType()) == CbFieldTypeOps::GetType(Other.GetType()) && GetPayloadView().EqualBytes(Other.GetPayloadView()); } void CbObjectView::CopyTo(MutableMemoryView Buffer) const { const MemoryView Source = GetPayloadView(); ZEN_ASSERT_FORMAT(Buffer.GetSize() == (sizeof(CbFieldType) + Source.GetSize()), "Buffer is {} bytes but {} is required.", Buffer.GetSize(), sizeof(CbFieldType) + Source.GetSize()); *static_cast(Buffer.GetData()) = CbFieldTypeOps::GetType(GetType()); Buffer.RightChopInline(sizeof(CbFieldType)); memcpy(Buffer.GetData(), Source.GetData(), Source.GetSize()); } void CbObjectView::CopyTo(BinaryWriter& Ar) const { const MemoryView SourceView = GetPayloadView(); CbFieldType SerializedType = CbFieldTypeOps::GetSerializedType(GetType()); const MemoryView TypeView(reinterpret_cast(&SerializedType), sizeof(SerializedType)); Ar.Write({TypeView, SourceView}); } ////////////////////////////////////////////////////////////////////////// template uint64_t TCbFieldIterator::GetRangeSize() const { MemoryView View; if (TryGetSerializedRangeView(View)) { return View.GetSize(); } else { uint64_t Size = 0; for (CbFieldViewIterator It(*this); It; ++It) { Size += It.GetSize(); } return Size; } } template IoHash TCbFieldIterator::GetRangeHash() const { IoHashStream Hash; GetRangeHash(Hash); return IoHash(Hash.GetHash()); } template void TCbFieldIterator::GetRangeHash(IoHashStream& Hash) const { MemoryView View; if (TryGetSerializedRangeView(View)) { Hash.Append(View.GetData(), View.GetSize()); } else { for (CbFieldViewIterator It(*this); It; ++It) { It.GetHash(Hash); } } } template void TCbFieldIterator::CopyRangeTo(MutableMemoryView InBuffer) const { MemoryView Source; if (TryGetSerializedRangeView(Source)) { ZEN_ASSERT_FORMAT(InBuffer.GetSize() == Source.GetSize(), "Buffer is {} bytes but {} is required.", InBuffer.GetSize(), Source.GetSize()); memcpy(InBuffer.GetData(), Source.GetData(), Source.GetSize()); } else { for (CbFieldViewIterator It(*this); It; ++It) { const uint64_t Size = It.GetSize(); It.CopyTo(InBuffer.Left(Size)); InBuffer.RightChopInline(Size); } } } template class TCbFieldIterator; template class TCbFieldIterator; template void TCbFieldIterator::IterateRangeAttachments(std::function Visitor) const { if (CbFieldTypeOps::HasFieldType(FieldType::GetType())) { // Always iterate over non-uniform ranges because we do not know if they contain an attachment. for (CbFieldViewIterator It(*this); It; ++It) { if (CbFieldTypeOps::MayContainAttachments(It.GetType())) { It.IterateAttachments(Visitor); } } } else { // Only iterate over uniform ranges if the uniform type may contain an attachment. if (CbFieldTypeOps::MayContainAttachments(FieldType::GetType())) { for (CbFieldViewIterator It(*this); It; ++It) { It.IterateAttachments(Visitor); } } } } /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// CbFieldIterator CbFieldIterator::CloneRange(const CbFieldViewIterator& It) { MemoryView View; if (It.TryGetSerializedRangeView(View)) { return MakeRange(SharedBuffer::Clone(View)); } else { UniqueBuffer Buffer = UniqueBuffer::Alloc(It.GetRangeSize()); It.CopyRangeTo(MutableMemoryView(Buffer.GetData(), Buffer.GetSize())); return MakeRange(SharedBuffer(std::move(Buffer))); } } SharedBuffer CbFieldIterator::GetRangeBuffer() const { const MemoryView RangeView = GetRangeView(); const SharedBuffer& OuterBuffer = GetOuterBuffer(); return OuterBuffer.GetView() == RangeView ? OuterBuffer : SharedBuffer::MakeView(RangeView, OuterBuffer); } /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// uint64_t MeasureCompactBinary(MemoryView View, CbFieldType Type) { uint64_t Size; return TryMeasureCompactBinary(View, Type, Size, Type) ? Size : 0; } /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// bool TryMeasureCompactBinary(MemoryView View, CbFieldType& OutType, uint64_t& OutSize, CbFieldType Type) { uint64_t Size = 0; if (CbFieldTypeOps::HasFieldType(Type)) { if (View.GetSize() == 0) { OutType = CbFieldType::None; OutSize = 1; return false; } Type = *static_cast(View.GetData()); View.RightChopInline(1); Size += 1; } bool bDynamicSize = false; uint64_t FixedSize = 0; switch (CbFieldTypeOps::GetType(Type)) { case CbFieldType::Null: break; case CbFieldType::Object: case CbFieldType::UniformObject: case CbFieldType::Array: case CbFieldType::UniformArray: case CbFieldType::Binary: case CbFieldType::String: case CbFieldType::IntegerPositive: case CbFieldType::IntegerNegative: bDynamicSize = true; break; case CbFieldType::Float32: FixedSize = 4; break; case CbFieldType::Float64: FixedSize = 8; break; case CbFieldType::BoolFalse: case CbFieldType::BoolTrue: break; case CbFieldType::ObjectAttachment: case CbFieldType::BinaryAttachment: case CbFieldType::Hash: FixedSize = 20; break; case CbFieldType::Uuid: FixedSize = 16; break; case CbFieldType::ObjectId: FixedSize = 12; break; case CbFieldType::DateTime: case CbFieldType::TimeSpan: FixedSize = 8; break; case CbFieldType::None: default: OutType = CbFieldType::None; OutSize = 0; return false; } OutType = Type; if (CbFieldTypeOps::HasFieldName(Type)) { if (View.GetSize() == 0) { OutSize = Size + 1; return false; } uint32_t NameLenByteCount = MeasureVarUInt(View.GetData()); if (View.GetSize() < NameLenByteCount) { OutSize = Size + NameLenByteCount; return false; } const uint64_t NameLen = ReadMeasuredVarUInt(View.GetData(), NameLenByteCount); const uint64_t NameSize = NameLen + NameLenByteCount; if (bDynamicSize && View.GetSize() < NameSize) { OutSize = Size + NameSize; return false; } View.RightChopInline(NameSize); Size += NameSize; } switch (CbFieldTypeOps::GetType(Type)) { case CbFieldType::Object: case CbFieldType::UniformObject: case CbFieldType::Array: case CbFieldType::UniformArray: case CbFieldType::Binary: case CbFieldType::String: if (View.GetSize() == 0) { OutSize = Size + 1; return false; } else { uint32_t PayloadSizeByteCount = MeasureVarUInt(View.GetData()); if (View.GetSize() < PayloadSizeByteCount) { OutSize = Size + PayloadSizeByteCount; return false; } const uint64_t PayloadSize = ReadMeasuredVarUInt(View.GetData(), PayloadSizeByteCount); OutSize = Size + PayloadSize + PayloadSizeByteCount; } return true; case CbFieldType::IntegerPositive: case CbFieldType::IntegerNegative: if (View.GetSize() == 0) { OutSize = Size + 1; return false; } OutSize = Size + MeasureVarUInt(View.GetData()); return true; default: OutSize = Size + FixedSize; return true; } } ////////////////////////////////////////////////////////////////////////// CbField LoadCompactBinary(BinaryReader& Ar, BufferAllocator Allocator) { std::vector HeaderBytes; CbFieldType FieldType; uint64_t FieldSize = 1; for (const int64_t StartPos = Ar.CurrentOffset(); FieldSize > 0;) { // Read in small increments until the total field size is known, to avoid reading too far. const int32_t ReadSize = int32_t(FieldSize - HeaderBytes.size()); if (Ar.CurrentOffset() + ReadSize > Ar.GetSize()) { break; } const size_t ReadOffset = HeaderBytes.size(); HeaderBytes.resize(ReadOffset + ReadSize); Ar.Read(HeaderBytes.data() + ReadOffset, ReadSize); if (TryMeasureCompactBinary(MakeMemoryView(HeaderBytes), FieldType, FieldSize)) { if (FieldSize <= uint64_t(Ar.Size() - StartPos)) { UniqueBuffer Buffer = Allocator(FieldSize); ZEN_ASSERT(Buffer.GetSize() == FieldSize); MutableMemoryView View = Buffer.GetMutableView(); memcpy(View.GetData(), HeaderBytes.data(), HeaderBytes.size()); View.RightChopInline(HeaderBytes.size()); if (!View.IsEmpty()) { // Read the remainder of the field. Ar.Read(View.GetData(), View.GetSize()); } if (ValidateCompactBinary(Buffer, CbValidateMode::Default) == CbValidateError::None) { return CbField(SharedBuffer(std::move(Buffer))); } } break; } } return CbField(); } CbObject LoadCompactBinaryObject(IoBuffer&& Payload) { if (Payload.GetSize() == 0) { return CbObject(); } return CbObject{SharedBuffer(std::move(Payload))}; } CbObject LoadCompactBinaryObject(const IoBuffer& Payload) { if (Payload.GetSize() == 0) { return CbObject(); } return CbObject{SharedBuffer(Payload)}; } CbObject LoadCompactBinaryObject(CompressedBuffer&& Payload) { CompositeBuffer Decompressed = std::move(Payload).DecompressToComposite(); if (Decompressed.GetSize() == 0) { return CbObject(); } return CbObject{std::move(Decompressed).Flatten()}; } CbObject LoadCompactBinaryObject(const CompressedBuffer& Payload) { CompositeBuffer Decompressed = Payload.DecompressToComposite(); if (Decompressed.GetSize() == 0) { return CbObject(); } return CbObject{std::move(Decompressed).Flatten()}; } ////////////////////////////////////////////////////////////////////////// void SaveCompactBinary(BinaryWriter& Ar, const CbFieldView& Field) { Field.CopyTo(Ar); } void SaveCompactBinary(BinaryWriter& Ar, const CbArrayView& Array) { Array.CopyTo(Ar); } void SaveCompactBinary(BinaryWriter& Ar, const CbObjectView& Object) { Object.CopyTo(Ar); } ////////////////////////////////////////////////////////////////////////// std::vector ReadCompactBinaryStream(MemoryView Data) { std::vector Result; const uint8_t* Buffer = reinterpret_cast(Data.GetData()); uint64_t Offset = 0; const uint64_t Size = Data.GetSize(); while (Offset < Size) { if (ValidateCompactBinary(MemoryView(Buffer + Offset, Size - Offset), CbValidateMode::Default) != CbValidateError::None) { break; } CbFieldView Field(Buffer + Offset); Offset += Field.GetSize(); Result.emplace_back(std::move(Field)); } return Result; } ////////////////////////////////////////////////////////////////////////// #if ZEN_WITH_TESTS void uson_forcelink() { } TEST_CASE("guid") { using namespace std::literals; const Guid A = Guid::FromString("03000c43-d267-36fd-9164-a7555824822b"sv); StringBuilder<40> GuidStr; A.ToString(GuidStr); CHECK(std::string_view(GuidStr) == "03000c43-d267-36fd-9164-a7555824822b"sv); const Guid B = Guid::FromString(GuidStr); CHECK(A == B); } TEST_CASE("uson") { using namespace std::literals; SUBCASE("CbField") { constexpr CbFieldView DefaultField; static_assert(!DefaultField.HasName(), "Error in HasName()"); static_assert(!DefaultField.HasValue(), "Error in HasValue()"); static_assert(!DefaultField.HasError(), "Error in HasError()"); static_assert(DefaultField.GetError() == CbFieldError::None, "Error in GetError()"); CHECK(DefaultField.GetSize() == 1); CHECK(DefaultField.GetName().size() == 0); CHECK(DefaultField.HasName() == false); CHECK(DefaultField.HasValue() == false); CHECK(DefaultField.HasError() == false); CHECK(DefaultField.GetError() == CbFieldError::None); const uint8_t Type = (uint8_t)CbFieldType::None; CHECK(DefaultField.GetHash() == IoHash::HashBuffer(&Type, sizeof Type)); CHECK(DefaultField.GetView() == MemoryView{}); MemoryView SerializedView; CHECK(DefaultField.TryGetSerializedView(SerializedView) == false); } SUBCASE("CbField(None)") { CbFieldView NoneField(nullptr, CbFieldType::None); CHECK(NoneField.GetSize() == 1); CHECK(NoneField.GetName().size() == 0); CHECK(NoneField.HasName() == false); CHECK(NoneField.HasValue() == false); CHECK(NoneField.HasError() == false); CHECK(NoneField.GetError() == CbFieldError::None); CHECK(NoneField.GetHash() == CbFieldView().GetHash()); CHECK(NoneField.GetView() == MemoryView()); MemoryView SerializedView; CHECK(NoneField.TryGetSerializedView(SerializedView) == false); } SUBCASE("CbField(None|Type|Name)") { constexpr CbFieldType FieldType = CbFieldType::None | CbFieldType::HasFieldName; const char NoneBytes[] = {char(FieldType), 4, 'N', 'a', 'm', 'e'}; CbFieldView NoneField(NoneBytes); CHECK(NoneField.GetSize() == sizeof(NoneBytes)); CHECK(NoneField.GetName().compare("Name"sv) == 0); CHECK(NoneField.HasName() == true); CHECK(NoneField.HasValue() == false); CHECK(NoneField.GetHash() == IoHash::HashBuffer(NoneBytes, sizeof NoneBytes)); CHECK(NoneField.GetView() == MemoryView(NoneBytes, sizeof NoneBytes)); MemoryView SerializedView; CHECK(NoneField.TryGetSerializedView(SerializedView) == true); CHECK(SerializedView == MemoryView(NoneBytes, sizeof NoneBytes)); uint8_t CopyBytes[sizeof(NoneBytes)]; NoneField.CopyTo(MutableMemoryView(CopyBytes, sizeof CopyBytes)); CHECK(MemoryView(NoneBytes, sizeof NoneBytes).EqualBytes(MemoryView(CopyBytes, sizeof CopyBytes))); } SUBCASE("CbField(None|Type)") { constexpr CbFieldType FieldType = CbFieldType::None; const char NoneBytes[] = {char(FieldType)}; CbFieldView NoneField(NoneBytes); CHECK(NoneField.GetSize() == sizeof NoneBytes); CHECK(NoneField.GetName().size() == 0); CHECK(NoneField.HasName() == false); CHECK(NoneField.HasValue() == false); CHECK(NoneField.GetHash() == CbFieldView().GetHash()); CHECK(NoneField.GetView() == MemoryView(NoneBytes, sizeof NoneBytes)); MemoryView SerializedView; CHECK(NoneField.TryGetSerializedView(SerializedView) == true); CHECK(SerializedView == MemoryView(NoneBytes, sizeof NoneBytes)); } SUBCASE("CbField(None|Name)") { constexpr CbFieldType FieldType = CbFieldType::None | CbFieldType::HasFieldName; const char NoneBytes[] = {char(FieldType), 4, 'N', 'a', 'm', 'e'}; CbFieldView NoneField(NoneBytes + 1, FieldType); CHECK(NoneField.GetSize() == uint64_t(sizeof NoneBytes)); CHECK(NoneField.GetName().compare("Name") == 0); CHECK(NoneField.HasName() == true); CHECK(NoneField.HasValue() == false); CHECK(NoneField.GetHash() == IoHash::HashBuffer(NoneBytes, sizeof NoneBytes)); CHECK(NoneField.GetView() == MemoryView(NoneBytes + 1, sizeof NoneBytes - 1)); MemoryView SerializedView; CHECK(NoneField.TryGetSerializedView(SerializedView) == false); uint8_t CopyBytes[sizeof(NoneBytes)]; NoneField.CopyTo(MutableMemoryView(CopyBytes, sizeof CopyBytes)); CHECK(MemoryView(NoneBytes, sizeof NoneBytes).EqualBytes(MemoryView(CopyBytes, sizeof CopyBytes))); } SUBCASE("CbField(None|EmptyName)") { constexpr CbFieldType FieldType = CbFieldType::None | CbFieldType::HasFieldName; const uint8_t NoneBytes[] = {uint8_t(FieldType), 0}; CbFieldView NoneField(NoneBytes + 1, FieldType); CHECK(NoneField.GetSize() == sizeof NoneBytes); CHECK(NoneField.GetName().empty() == true); CHECK(NoneField.HasName() == true); CHECK(NoneField.HasValue() == false); CHECK(NoneField.GetHash() == IoHash::HashBuffer(NoneBytes, sizeof NoneBytes)); CHECK(NoneField.GetView() == MemoryView(NoneBytes + 1, sizeof NoneBytes - 1)); MemoryView SerializedView; CHECK(NoneField.TryGetSerializedView(SerializedView) == false); } static_assert(!std::is_constructible::value, "Invalid constructor for CbField"); static_assert(!std::is_assignable::value, "Invalid assignment for CbField"); static_assert(!std::is_convertible::value, "Invalid conversion to CbObject"); static_assert(!std::is_assignable::value, "Invalid assignment for CbObject"); static_assert(std::is_constructible::value, "Missing constructor for CbField"); static_assert(std::is_constructible::value, "Missing constructor for CbField"); static_assert(std::is_constructible::value, "Missing constructor for CbField"); } TEST_CASE("uson.null") { using namespace std::literals; SUBCASE("CbField(Null)") { CbFieldView NullField(nullptr, CbFieldType::Null); CHECK(NullField.GetSize() == 1); CHECK(NullField.IsNull() == true); CHECK(NullField.HasValue() == true); CHECK(NullField.HasError() == false); CHECK(NullField.GetError() == CbFieldError::None); const uint8_t Null[]{uint8_t(CbFieldType::Null)}; CHECK(NullField.GetHash() == IoHash::HashBuffer(Null, sizeof Null)); } SUBCASE("CbField(None)") { CbFieldView Field; CHECK(Field.IsNull() == false); } } TEST_CASE("uson.datetime") { using namespace std::literals; { DateTime D1600(1601, 1, 1); CHECK_EQ(D1600.GetYear(), 1601); CHECK_EQ(D1600.GetMonth(), 1); CHECK_EQ(D1600.GetDay(), 1); CHECK_EQ(D1600.GetHour(), 0); CHECK_EQ(D1600.GetMinute(), 0); CHECK_EQ(D1600.GetSecond(), 0); CHECK_EQ(D1600.ToIso8601(), "1601-01-01T00:00:00.000Z"sv); } { DateTime D72(1972, 2, 23, 17, 30, 10); CHECK_EQ(D72.GetYear(), 1972); CHECK_EQ(D72.GetMonth(), 2); CHECK_EQ(D72.GetDay(), 23); CHECK_EQ(D72.GetHour(), 17); CHECK_EQ(D72.GetMinute(), 30); CHECK_EQ(D72.GetSecond(), 10); } } ////////////////////////////////////////////////////////////////////////// TEST_SUITE_BEGIN("core.datetime"); TEST_CASE("core.datetime.compare") { DateTime T1(2000, 12, 13); DateTime T2(2000, 12, 14); CHECK(T1 < T2); CHECK(T2 > T1); CHECK(T1 == T1); CHECK(T1 != T2); CHECK(T1 >= T1); CHECK(T2 >= T1); CHECK(T1 <= T1); CHECK(T1 <= T2); } TEST_CASE("core.datetime.add") { DateTime T1(2000, 12, 13); DateTime T2(2000, 12, 14); TimeSpan dT = T2 - T1; TimeSpan dT1 = T1 - T1; CHECK(T1 + dT == T2); CHECK(dT + T1 == T2); CHECK(dT + T1 - T2 == dT1); } TEST_SUITE_END(); TEST_SUITE_BEGIN("core.timespan"); TEST_CASE("core.timespan.compare") { TimeSpan T1(1000); TimeSpan T2(1001); CHECK(T1 < T2); CHECK(T2 > T1); CHECK(T1 == T1); CHECK(T1 != T2); CHECK(T1 >= T1); CHECK(T2 >= T1); CHECK(T1 <= T1); CHECK(T1 <= T2); } TEST_SUITE_END(); ////////////////////////////////////////////////////////////////////////// #endif } // namespace zen