/* * Copyright (c) Meta Platforms, Inc. and affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #pragma once #include #include #include #include #include #include #include namespace apache::thrift { template class StructuredCursorReader; template class ContainerCursorReader; template class ContainerCursorIterator; template class StructuredCursorWriter; template class ContainerCursorWriter; class StringCursorWriter; class ManagedStringViewWithConversions; namespace detail { template T& maybe_emplace(T& t) { return t; } template T& maybe_emplace(std::optional& t) { return t.emplace(); } template struct ContainerTraits; template struct ContainerTraits> { using ElementType = type::native_type; using ElementTag = VTag; // This is initializer_list becuase that's what skip_n accepts. static constexpr std::initializer_list wireTypes = { op::typeTagToTType}; static void write(BinaryProtocolWriter& protocol, const ElementType& value) { op::encode(protocol, value); } }; template struct ContainerTraits> { using ElementType = type::native_type; using ElementTag = KTag; static constexpr std::initializer_list wireTypes = { op::typeTagToTType}; static void write(BinaryProtocolWriter& protocol, const ElementType& value) { op::encode(protocol, value); } }; template struct ContainerTraits> { using ElementType = std::pair, type::native_type>; using KeyTag = KTag; using ValueTag = VTag; static constexpr std::initializer_list wireTypes = { op::typeTagToTType, op::typeTagToTType}; static void write(BinaryProtocolWriter& protocol, const ElementType& key) { op::encode(protocol, key.first); op::encode(protocol, key.second); } }; template struct ContainerTraits> : ContainerTraits {}; template class BaseCursorReader { protected: ProtocolReader* protocol_; enum class State { Active, Child, // Reading a nested struct or container Done, }; State state_ = State::Active; void checkState(State expected) const { if (state_ != expected) { folly::throw_exception([&]() { switch (state_) { case State::Active: return "No child reader is active"; case State::Child: return "Child reader not passed to endRead"; case State::Done: return "Reader already finalized"; default: return "State is unknown"; } }()); } } explicit BaseCursorReader(ProtocolReader* p) : protocol_(p) {} BaseCursorReader() = default; ~BaseCursorReader() { DCHECK(state_ == State::Done) << "Reader must be passed to endRead"; } BaseCursorReader(BaseCursorReader&& other) noexcept { protocol_ = std::move(other.protocol_); state_ = other.state_; other.state_ = State::Done; } BaseCursorReader& operator=(BaseCursorReader&& other) noexcept { if (this != &other) { DCHECK(state_ == State::Done) << "Reader must be passed to endRead"; protocol_ = std::move(other.protocol_); state_ = other.state_; other.state_ = State::Done; } return *this; } public: BaseCursorReader(const BaseCursorReader&) = delete; BaseCursorReader& operator=(const BaseCursorReader&) = delete; }; class BaseCursorWriter { protected: BinaryProtocolWriter* protocol_; enum class State { Active, Child, Done, }; State state_ = State::Active; explicit BaseCursorWriter(BinaryProtocolWriter* p) : protocol_(p) {} void checkState(State expected) const { if (state_ != expected) { folly::throw_exception([&]() { switch (state_) { case State::Active: return "No child writer is active"; case State::Child: return "Child writer not passed to endWrite"; case State::Done: return "Writer already finalized"; default: return "State is unknown"; } }()); } } ~BaseCursorWriter() { DCHECK(state_ == State::Done) << "Writer must be passed to endWrite"; } BaseCursorWriter(BaseCursorWriter&& other) noexcept { protocol_ = std::move(other.protocol_); state_ = other.state_; other.state_ = State::Done; } BaseCursorWriter& operator=(BaseCursorWriter&& other) noexcept { if (this != &other) { DCHECK(state_ == State::Done) << "Writer must be passed to endWrite"; protocol_ = std::move(other.protocol_); state_ = other.state_; other.state_ = State::Done; } return *this; } public: BaseCursorWriter(const BaseCursorWriter&) = delete; BaseCursorWriter& operator=(const BaseCursorWriter&) = delete; template friend class StructuredCursorWriter; }; // std::swap isn't constexpr until C++20 so we need to reimplement :( template constexpr void constexprSwap(T& a, T& b) { T tmp = std::move(a); a = std::move(b); b = std::move(tmp); } // std::is_sorted isn't constexpr until C++20 so we need to reimplement :( template constexpr bool constexprIsSorted(const std::array& array) { for (size_t i = 1; i < N; ++i) { if (array[i] < array[i - 1]) { return false; } } return true; } // std::sort isn't constexpr until C++20 so we need to reimplement :( template constexpr void constexprQuickSort( std::array& array, ssize_t min_idx, ssize_t max_idx) { if (max_idx <= min_idx) { return; } T pivot_value = array[min_idx]; ssize_t fwd_idx = min_idx; ssize_t rev_idx = max_idx + 1; while (true) { while (array[++fwd_idx] < pivot_value && fwd_idx != max_idx) { } while (pivot_value < array[--rev_idx] && rev_idx != min_idx) { } if (fwd_idx >= rev_idx) { break; } constexprSwap(array[fwd_idx], array[rev_idx]); } constexprSwap(array[min_idx], array[rev_idx]); constexprQuickSort(array, min_idx, rev_idx - 1); constexprQuickSort(array, rev_idx + 1, max_idx); } template struct DefaultValueWriter { using T = type::native_type; struct Field { FieldId id; void (*write)(StructuredCursorWriter&) = nullptr; constexpr bool operator<(const Field& other) const { return id < other.id; } }; static constexpr std::array> fields = [] { std::array> fields; op::for_each_ordinal([&](auto ord) { using Ord = decltype(ord); using Id = op::get_field_id; using FTag = op::get_type_tag; fields[type::toPosition(Ord::value)] = { Id::value, [](StructuredCursorWriter& writer) { if constexpr (type::is_optional_or_union_field_v) { return; } const auto& val = *op::get(op::getDefault()); writer.template writeField( [&] { op::encode(*writer.protocol_, val); }, val); }}; }); constexprQuickSort(fields, 0, fields.size() - 1); assert(constexprIsSorted(fields)); return fields; }(); }; inline constexpr FieldId increment(FieldId id) { assert(folly::to_underlying(id) + 1 > folly::to_underlying(id)); return static_cast(folly::to_underlying(id) + 1); } /** Supports writing containers whose size is not known until after * serialization. */ class DelayedSizeCursorWriter : public BaseCursorWriter { protected: void* size_; constexpr static size_t kSizeLen = 4; explicit DelayedSizeCursorWriter(BinaryProtocolWriter* p) : BaseCursorWriter(p) {} void writeSize() { static_assert( std::is_same_v, "Using internals of binary protocol."); size_ = protocol_->ensure(kSizeLen); protocol_->advance(kSizeLen); } void finalize(int32_t actualSize) { checkState(State::Active); state_ = State::Done; actualSize = folly::Endian::big(actualSize); memcpy(size_, &actualSize, kSizeLen); } }; /** Converts std::string to std::string_view when Contiguous = true, and returns * the original type otherwise. */ template using lift_view_t = std::conditional_t< Contiguous && std::is_same_v, std::string_view, T>; template std::string_view readStringView(ProtocolReader& protocol) { int32_t size; protocol.readI32(size); if (size < 0) { TProtocolException::throwNegativeSize(); } folly::io::Cursor c = protocol.getCursor(); if (static_cast(size) >= c.length()) { TProtocolException::throwTruncatedData(); } protocol.skipBytes(size); return std::string_view(reinterpret_cast(c.data()), size); } template void decodeTo(ProtocolReader& protocol, T& t) { if constexpr ( std::is_same_v && type::is_a_v) { t = readStringView(protocol); } else { op::decode(protocol, t); } } template struct HasValueType { constexpr static bool value = false; }; template struct HasValueType> { constexpr static bool value = true; }; template struct IsSupportedCppType { constexpr static bool value = true; }; template struct IsSupportedCppType> { constexpr static bool value = [] { if constexpr (type::is_a_v) { return std::is_integral_v; } else if constexpr (type::is_a_v) { return std::is_same_v || std::is_same_v> || std::is_same_v || std::is_same_v || std::is_same_v; ; } else if constexpr (type::is_a_v) { return HasValueType::value; } return false; }(); }; template constexpr bool validateCppTypes() { op::for_each_ordinal([](auto ord) { using Ord = decltype(ord); static_assert( IsSupportedCppType>::value, "Unsupported cpp.Type. Consider using cpp.Adapter instead."); }); return true; } } // namespace detail } // namespace apache::thrift