/* * 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. */ #ifndef CPP2_PROTOCOL_PROTOCOL_H_ #define CPP2_PROTOCOL_PROTOCOL_H_ 1 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include FOLLY_GFLAGS_DECLARE_int32(thrift_protocol_max_depth); /** * Protocol Readers and Writers are ducktyped in cpp2. * This means they have no base class or virtual methods, * and you must explicitly choose the correct class to use. * * This results in a 2x performance increase over a traditional * object oriented hierarchy. If you need a base class, you can * use the VirtualReader / VitualWriter in VirtualProtocol.h */ namespace apache { namespace thrift { namespace detail { class ProtocolBase { public: // height is maximum permitted remaining levels of nesting, applying to map, // set, list, and struct types void setHeight(size_t height) { height_ = height + 1; } size_t getHeight() const { return height_ - 1; } ProtocolBase(); protected: void descend() { if (!--height_) { protocol::TProtocolException::throwExceededDepthLimit(); } } void ascend() { ++height_; } private: size_t height_; }; } // namespace detail /** * Certain serialization / deserialization operations allow sharing * external (user-owned) buffers. This means that the external buffers must * remain allocated (and unchanged) until the serialization / deserialization * is complete. * * This is often counter-intuitive; for example, deserializing from a string * wouldn't work with a temporary string if sharing is allowed. So sharing * external buffers must be requested explicitly. * * Note that we always share memory that is under IOBuf's control (that is, * IOBuf chains for which isManaged() is true). To prevent that, call unshare() * on the IOBuf chain as appropriate. */ enum ExternalBufferSharing { COPY_EXTERNAL_BUFFER, SHARE_EXTERNAL_BUFFER, }; using apache::thrift::protocol::TProtocolException; using apache::thrift::protocol::TType; typedef apache::thrift::protocol::PROTOCOL_TYPES ProtocolType; /* * Enumerated definition of the message types that the Thrift protocol * supports. */ enum class MessageType { T_CALL = 1, T_REPLY = 2, T_EXCEPTION = 3, T_ONEWAY = 4, }; namespace detail { struct SkipNoopString { void append(const char*, size_t) {} void clear() {} void reserve(size_t) {} }; // Checks if bool hold a valid value (true or false) and throws exception // otherwise. Without the check we may produce undeserializable data. inline bool validate_bool(uint8_t value) { static constexpr char kUndefinedBehaviorMsg[] = "Undefined behavior detected! Thrift code has detected an invalid bool " "value that almost certainly indicates ongoing memory corruption in this " "process. This issue is most likely not related to Thrift, but is " "detected here in a best-effort attempt at early detection of undefined " "behavior. The process is terminated to prevent further corruption and " "raise awareness of the ongoing issue."; #if defined(__x86_64__) && defined(__GNUC__) && \ (!defined(__clang_major__) || __clang_major__ >= 9) // An optimized version that avoid extra load/store. asm volatile goto( #if !defined(__clang_major__) || __clang_major__ >= 14 // Provide AT&T and Intel syntax when included in an -masm=intel context. // See also https://reviews.llvm.org/D113707 "cmp{b $1, %b0| %b0, $1}\n" #else "cmpb $1, %b0\n" #endif "ja %l1" : // no outputs. : "r"(value) : "cc" : invalid); return value; invalid: LOG(FATAL) << kUndefinedBehaviorMsg; return false; #else // Store in a volatile variable to prevent the compiler from optimizing the // check away. volatile uint8_t volatileByte = value; uint8_t byte = volatileByte; if (!(byte == 0 || byte == 1)) { LOG(FATAL) << kUndefinedBehaviorMsg; } return byte != 0; #endif } } // namespace detail /* forward declaration */ template void skip_n( Protocol_& prot, uint32_t n, std::initializer_list types, int depth = 0); /** * Helper template for implementing Protocol::skip(). * * Templatized to avoid having to make virtual function calls. Protocols with * their own opinions about skipping implementation can specialize, although * currently none do. * * depth (int): Current depth of the nested fields. * * It first checks if the current depth exceeds a predefined maximum depth * (FLAGS_thrift_protocol_max_depth). If the depth is exceeded, it throws a * `TProtocolException` to prevent stack overflow. */ template void skip(Protocol_& prot, WireType arg_type, int depth = 0) { if (depth >= FLAGS_thrift_protocol_max_depth) { protocol::TProtocolException::throwExceededDepthLimit(); } switch (arg_type) { case TType::T_BOOL: { bool boolv; prot.readBool(boolv); return; } // case TType::T_I08: // same numeric value as T_BYTE case TType::T_BYTE: { int8_t bytev; prot.readByte(bytev); return; } case TType::T_I16: { int16_t i16; prot.readI16(i16); return; } case TType::T_I32: { int32_t i32; prot.readI32(i32); return; } case TType::T_U64: case TType::T_I64: { int64_t i64; prot.readI64(i64); return; } case TType::T_DOUBLE: { double dub; prot.readDouble(dub); return; } case TType::T_FLOAT: { float flt; prot.readFloat(flt); return; } // case TType::T_UTF7: // same numeric value as T_STRING case TType::T_UTF8: case TType::T_UTF16: case TType::T_STRING: { apache::thrift::detail::SkipNoopString str; prot.readBinary(str); return; } case TType::T_STRUCT: { std::string name; int16_t fid; TType ftype; prot.readStructBegin(name); while (true) { prot.readFieldBegin(name, ftype, fid); if (ftype == TType::T_STOP) { break; } apache::thrift::skip(prot, ftype, depth + 1); prot.readFieldEnd(); } prot.readStructEnd(); return; } case TType::T_MAP: { TType keyType; TType valType; uint32_t size; prot.readMapBegin(keyType, valType, size); skip_n(prot, size, {keyType, valType}, depth + 1); prot.readMapEnd(); return; } case TType::T_SET: { TType elemType; uint32_t size; prot.readSetBegin(elemType, size); skip_n(prot, size, {elemType}, depth + 1); prot.readSetEnd(); return; } case TType::T_LIST: { TType elemType; uint32_t size; prot.readListBegin(elemType, size); skip_n(prot, size, {elemType}, depth + 1); prot.readListEnd(); return; } case TType::T_STOP: case TType::T_VOID: case TType::T_STREAM: // Unimplemented, fallback to default default: { TProtocolException::throwInvalidSkipType(arg_type); } } } /** * Check if the remaining part of buffers contain least necessary amount of * bytes to encode N elements of given type. * * Note: this is a lightweight lower bound check, it doesn't necessary mean * that we would actually succeed at reading N items. */ template inline bool canReadNElements( Protocol_& prot, uint32_t n, std::initializer_list< typename apache::thrift::detail::ProtocolReaderWireTypeInfo< Protocol_>::WireType> types) { return prot.getCursor().canAdvance(n * types.size()); } /* * Skip n tuples - used for skipping lists, sets, maps. * * As with skip(), protocols can specialize. * * depth (int): Current depth of the nested fields. * * It first checks if the current depth exceeds a predefined maximum depth * (FLAGS_thrift_protocol_max_depth). If the depth is exceeded, it throws a * `TProtocolException` to prevent stack overflow. */ template void skip_n( Protocol_& prot, uint32_t n, std::initializer_list types, int depth) { if (depth >= FLAGS_thrift_protocol_max_depth) { protocol::TProtocolException::throwExceededDepthLimit(); } size_t sum = 0; bool allFixedSizes = true; for (auto type : types) { auto size = prot.fixedSizeInContainer(type); sum += size; allFixedSizes = allFixedSizes && size; } if (allFixedSizes) { prot.skipBytes(sum * n); return; } for (uint32_t i = 0; i < n; i++) { for (auto type : types) { apache::thrift::skip(prot, type, depth + 1); } } } template struct StringTraits { static StrType fromStringLiteral(const char* str) { return StrType(str); } static bool isEmpty(const StrType& str) { return str.empty(); } static bool isEqual(const StrType& lhs, const StrType& rhs) { return lhs == rhs; } static bool isLess(const StrType& lhs, const StrType& rhs) { return lhs < rhs; } }; template <> struct StringTraits { // Use with string literals only! static folly::IOBuf fromStringLiteral(const char* str) { return folly::IOBuf::wrapBufferAsValue(str, strlen(str)); } static bool isEmpty(const folly::IOBuf& str) { return str.empty(); } static bool isEqual(const folly::IOBuf& lhs, const folly::IOBuf& rhs) { return folly::IOBufEqualTo{}(lhs, rhs); } static bool isLess(const folly::IOBuf& lhs, const folly::IOBuf& rhs) { return folly::IOBufLess{}(lhs, rhs); } }; template <> struct StringTraits> { // Use with string literals only! static std::unique_ptr fromStringLiteral(const char* str) { return ( str[0] != '\0' ? folly::IOBuf::wrapBuffer(str, strlen(str)) : nullptr); } static bool isEmpty(const std::unique_ptr& str) { return !str || str->empty(); } static bool isEqual( const std::unique_ptr& lhs, const std::unique_ptr& rhs) { return folly::IOBufEqualTo{}(lhs, rhs); } static bool isLess( const std::unique_ptr& lhs, const std::unique_ptr& rhs) { return folly::IOBufLess{}(lhs, rhs); } }; } // namespace thrift } // namespace apache #endif