text: // Copyright 2017 The Fuchsia Authors. All rights reserved.
  // Use of this source code is governed by a BSD-style license that can be
  // found in the LICENSE file.
  
  #include <lib/fidl/coding.h>
  #include <lib/fidl/internal.h>
  #include <lib/fidl/visitor.h>
  #include <lib/fidl/walker.h>
  #include <lib/stdcompat/variant.h>
  #include <stdalign.h>
  #include <zircon/assert.h>
  #include <zircon/compiler.h>
  
  #include <cstdint>
  #include <cstdlib>
  #include <cstring>
  
  #ifdef __Fuchsia__
  #include <zircon/syscalls.h>
  #endif
  
  // TODO(kulakowski) Design zx_status_t error values.
  
  namespace {
  
  template <typename Byte>
  struct DecodingPosition {
    Byte* addr;
    DecodingPosition operator+(uint32_t size) const { return DecodingPosition{addr + size}; }
    DecodingPosition& operator+=(uint32_t size) {
      addr += size;
      return *this;
    }
    template <typename T, typename U = std::conditional_t<std::is_const<Byte>::value, const T, T>>
    constexpr U* Get() const {
      return reinterpret_cast<U*>(addr);
    }
  };
  
  struct EnvelopeCheckpoint {
    uint32_t num_bytes;
    uint32_t num_handles;
  };
  
  constexpr zx_rights_t subtract_rights(zx_rights_t minuend, zx_rights_t subtrahend) {
    return minuend & ~subtrahend;
  }
  static_assert(subtract_rights(0b011, 0b101) == 0b010, "ensure rights subtraction works correctly");
  
  enum class Mode { Decode, Validate };
  
  template <Mode mode, typename T, typename U>
  void AssignInDecode(T* ptr, U value) {
    static_assert(mode == Mode::Decode, "only assign if decode");
    *ptr = value;
  }
  
  template <Mode mode, typename T, typename U>
  void AssignInDecode(const T* ptr, U value) {
    static_assert(mode == Mode::Validate, "don't assign if validate");
    // nothing in validate mode
  }
  
  void ConvertEnvelopeToDecodedRepresentation(const void* bytes_base_ptr,
                                              fidl_envelope_t envelope_copy,
                                              const fidl_envelope_t* envelope_ptr) {
    // No conversion is needed for v1.
  }
  void ConvertEnvelopeToDecodedRepresentation(const void* bytes_base_ptr,
                                              fidl_envelope_t envelope_copy,
                                              fidl_envelope_t* envelope_ptr) {
    // No conversion is needed for v1.
  }
  void ConvertEnvelopeToDecodedRepresentation(const void* bytes_base_ptr,
                                              fidl_envelope_v2_t envelope_copy,
                                              const fidl_envelope_v2_t* envelope_ptr) {
    // No conversion is needed for v2 validate.
  }
  void ConvertEnvelopeToDecodedRepresentation(const void* bytes_base_ptr,
                                              fidl_envelope_v2_t envelope_copy,
                                              fidl_envelope_v2_t* envelope_ptr) {
    if ((envelope_copy.flags & FIDL_ENVELOPE_FLAGS_INLINING_MASK) != 0) {
      fidl_envelope_v2_unknown_data_t unknown_data_envelope = {
          .num_handles = envelope_copy.num_handles,
          .flags = envelope_copy.flags,
      };
      memcpy(unknown_data_envelope.inline_value, envelope_ptr->inline_value,
             sizeof(envelope_ptr->inline_value));
      memcpy(envelope_ptr, &unknown_data_envelope, sizeof(unknown_data_envelope));
      return;
    }
  
    uintptr_t data_ptr = *reinterpret_cast<uintptr_t*>(envelope_ptr);
    uintptr_t base_ptr = reinterpret_cast<uintptr_t>(bytes_base_ptr);
    uintptr_t offset = data_ptr - base_ptr;
    ZX_ASSERT(offset <= std::numeric_limits<uint16_t>::max());
    ZX_ASSERT(envelope_copy.num_bytes <= std::numeric_limits<uint16_t>::max());
    fidl_envelope_v2_unknown_data_t unknown_data_envelope = {
        .out_of_line =
            {
                .num_bytes = static_cast<uint16_t>(envelope_copy.num_bytes),
                .offset = static_cast<uint16_t>(offset),
            },
        .num_handles = envelope_copy.num_handles,
        .flags = envelope_copy.flags,
    };
    memcpy(envelope_ptr, &unknown_data_envelope, sizeof(unknown_data_envelope));
  }
  
  template <FidlWireFormatVersion WireFormatVersion, typename Byte>
  using BaseVisitor =
      fidl::Visitor<WireFormatVersion,
                    std::conditional_t<std::is_const<Byte>::value, fidl::NonMutatingVisitorTrait,
                                       fidl::MutatingVisitorTrait>,
                    DecodingPosition<Byte>, EnvelopeCheckpoint>;
  
  template <Mode mode, FidlWireFormatVersion WireFormatVersion, typename Byte>
  class FidlDecoder final : public BaseVisitor<WireFormatVersion, Byte> {
   public:
    FidlDecoder(Byte* bytes, uint32_t num_bytes, const zx_handle_t* handles, uint32_t num_handles,
                uint32_t next_out_of_line, const char** out_error_msg, bool hlcpp_mode)
        : bytes_(bytes),
          num_bytes_(num_bytes),
          num_handles_(num_handles),
          next_out_of_line_(next_out_of_line),
          out_error_msg_(out_error_msg),
          hlcpp_mode_(hlcpp_mode) {
      if (likely(handles != nullptr)) {
        handles_ = handles;
      }
    }
  
    FidlDecoder(Byte* bytes, uint32_t num_bytes, const zx_handle_info_t* handle_infos,
                uint32_t num_handle_infos, uint32_t next_out_of_line, const char** out_error_msg,
                bool hlcpp_mode)
        : bytes_(bytes),
          num_bytes_(num_bytes),
          num_handles_(num_handle_infos),
          next_out_of_line_(next_out_of_line),
          out_error_msg_(out_error_msg),
          hlcpp_mode_(hlcpp_mode) {
      if (likely(handle_infos != nullptr)) {
        handles_ = handle_infos;
      }
    }
  
    using Position = typename BaseVisitor<WireFormatVersion, Byte>::Position;
    using Status = typename BaseVisitor<WireFormatVersion, Byte>::Status;
    using PointeeType = typename BaseVisitor<WireFormatVersion, Byte>::PointeeType;
    using ObjectPointerPointer = typename BaseVisitor<WireFormatVersion, Byte>::ObjectPointerPointer;
    using HandlePointer = typename BaseVisitor<WireFormatVersion, Byte>::HandlePointer;
    using CountPointer = typename BaseVisitor<WireFormatVersion, Byte>::CountPointer;
    using EnvelopeType = typename BaseVisitor<WireFormatVersion, Byte>::EnvelopeType;
    using EnvelopePointer = typename BaseVisitor<WireFormatVersion, Byte>::EnvelopePointer;
  
    static constexpr bool kOnlyWalkResources = false;
    static constexpr bool kContinueAfterConstraintViolation = false;
    static constexpr bool kValidateEnvelopeInlineBit = true;
  
    Status VisitAbsentPointerInNonNullableCollection(ObjectPointerPointer object_ptr_ptr) {
      SetError("absent pointer disallowed in non-nullable collection");
      return Status::kConstraintViolationError;
    }
  
    Status VisitPointer(Position ptr_position, PointeeType pointee_type,
                        ObjectPointerPointer object_ptr_ptr, uint32_t inline_size,
                        FidlMemcpyCompatibility pointee_memcpy_compatibility,
                        Position* out_position) {
      if (unlikely(WireFormatVersion == FIDL_WIRE_FORMAT_VERSION_V1 &&
                   reinterpret_cast<uintptr_t>(*object_ptr_ptr) != FIDL_ALLOC_PRESENT)) {
        SetError("invalid presence marker");
        return Status::kMemoryError;
      }
      uint32_t new_offset;
      if (unlikely(!FidlAddOutOfLine(next_out_of_line_, inline_size, &new_offset))) {
        SetError("overflow updating out-of-line offset");
        return Status::kMemoryError;
      }
      if (unlikely(new_offset > num_bytes_)) {
        SetError("message tried to access more than provided number of bytes");
        return Status::kMemoryError;
      }
      {
        if (inline_size % FIDL_ALIGNMENT != 0) {
          // Validate the last 8-byte block.
          const uint64_t* block_end = reinterpret_cast<const uint64_t*>(&bytes_[new_offset]) - 1;
          uint64_t padding_len = new_offset - next_out_of_line_ - inline_size;
          uint64_t padding_mask = ~0ull << (64 - 8 * padding_len);
          auto status = ValidatePadding(block_end, padding_mask);
          if (status != Status::kSuccess) {
            return status;
          }
        }
      }
      if (unlikely(pointee_type == PointeeType::kString)) {
        auto status = fidl_validate_string(reinterpret_cast<const char*>(&bytes_[next_out_of_line_]),
                                           inline_size);
        if (status != ZX_OK) {
          SetError("encountered invalid UTF8 string");
          return Status::kConstraintViolationError;
        }
      }
      *out_position = Position{bytes_ + next_out_of_line_};
      AssignInDecode<mode>(
          object_ptr_ptr,
          reinterpret_cast<std::remove_pointer_t<ObjectPointerPointer>>(&bytes_[next_out_of_line_]));
  
      next_out_of_line_ = new_offset;
      return Status::kSuccess;
    }
  
    Status VisitHandleInfo(Position handle_position, HandlePointer handle,
                           zx_rights_t required_handle_rights,
                           zx_obj_type_t required_handle_subtype) {
  // Disable this function for clang static analyzer because:
  //   This function would never be called in Validate mode, however, the function is compiled with
  //   Validate mode in compile time even though mode would never be Validate in run time.
  //   The clang static analyzer could not associate the runtime mode with compile time 'mode', so it
  //   would construct a code path with compile time mode as Validate and runtime mode as Decode,
  //   causing 'AssignInDecode' function to be called with Validate mode and causing
  //   'received_handle' to be leaked.
  #ifndef __clang_analyzer__
      assert(mode == Mode::Decode);
      assert(has_handle_infos());
      zx_handle_info_t received_handle_info = handle_infos()[handle_idx_];
      zx_handle_t received_handle = received_handle_info.handle;
      if (unlikely(received_handle == ZX_HANDLE_INVALID)) {
        SetError("invalid handle detected in handle table");
        return Status::kConstraintViolationError;
      }
  
      const char* error = nullptr;
      zx_status_t status = FidlEnsureHandleRights(
          &received_handle, received_handle_info.type, received_handle_info.rights,
          required_handle_subtype, required_handle_rights, &error);
      if (unlikely(status != ZX_OK)) {
        SetError(error);
        return Status::kConstraintViolationError;
      }
  
      AssignInDecode<mode>(handle, received_handle);
      handle_idx_++;
      return Status::kSuccess;
  #endif  //  #ifndef __clang_analyzer__
    }
  
    Status VisitHandle(Position handle_position, HandlePointer handle,
                       zx_rights_t required_handle_rights, zx_obj_type_t required_handle_subtype) {
      if (unlikely(*handle != FIDL_HANDLE_PRESENT)) {
        SetError("message tried to decode a garbage handle");
        return Status::kConstraintViolationError;
      }
      if (unlikely(handle_idx_ == num_handles_)) {
        SetError("message decoded too many handles");
        return Status::kConstraintViolationError;
      }
  
      if (mode == Mode::Validate) {
        handle_idx_++;
        return Status::kSuccess;
      }
  
      if (has_handles()) {
        if (unlikely(handles()[handle_idx_] == ZX_HANDLE_INVALID)) {
          SetError("invalid handle detected in handle table");
          return Status::kConstraintViolationError;
        }
        AssignInDecode<mode>(handle, handles()[handle_idx_]);
        handle_idx_++;
        return Status::kSuccess;
      } else if (likely(has_handle_infos())) {
        return VisitHandleInfo(handle_position, handle, required_handle_rights,
                               required_handle_subtype);
      } else {
        SetError("decoder noticed a handle is present but the handle table is empty");
        AssignInDecode<mode>(handle, ZX_HANDLE_INVALID);
        return Status::kConstraintViolationError;
      }
    }
  
    Status VisitVectorOrStringCount(CountPointer ptr) { return Status::kSuccess; }
  
    template <typename MaskType>
    Status VisitInternalPadding(Position padding_position, MaskType mask) {
      return ValidatePadding(padding_position.template Get<const MaskType>(), mask);
    }
  
    EnvelopeCheckpoint EnterEnvelope() {
      return {
          .num_bytes = next_out_of_line_,
          .num_handles = handle_idx_,
      };
    }
  
    Status LeaveEnvelope(EnvelopeType in_envelope, EnvelopePointer out_envelope,
                         EnvelopeCheckpoint prev_checkpoint) {
      // Now that the envelope has been consumed, check the correctness of the envelope header.
      uint32_t num_bytes = next_out_of_line_ - prev_checkpoint.num_bytes;
      uint32_t num_handles = handle_idx_ - prev_checkpoint.num_handles;
      if (unlikely(in_envelope.num_bytes != num_bytes)) {
        SetError("Envelope num_bytes was mis-sized");
        return Status::kConstraintViolationError;
      }
      if (unlikely(in_envelope.num_handles != num_handles)) {
        SetError("Envelope num_handles was mis-sized");
        return Status::kConstraintViolationError;
      }
      return Status::kSuccess;
    }
  
    Status LeaveInlinedEnvelope(EnvelopeType in_envelope, EnvelopePointer out_envelope,
                                EnvelopeCheckpoint prev_checkpoint) {
      // Now that the envelope has been consumed, check the correctness of the envelope header.
      uint32_t num_handles = handle_idx_ - prev_checkpoint.num_handles;
      if (unlikely(in_envelope.num_handles != num_handles)) {
        SetError("Envelope num_handles was mis-sized");
        return Status::kConstraintViolationError;
      }
      return Status::kSuccess;
    }
  
    Status VisitUnknownEnvelope(EnvelopeType envelope_copy, EnvelopePointer envelope_ptr,
                                FidlIsResource is_resource) {
      if (mode == Mode::Validate) {
        handle_idx_ += envelope_copy.num_handles;
        return Status::kSuccess;
      }
  
      if (hlcpp_mode_) {
        ConvertEnvelopeToDecodedRepresentation(bytes_, envelope_copy, envelope_ptr);
      }
  
      // If we do not have the coding table for this payload,
      // treat it as unknown and close its contained handles
      if (unlikely(envelope_copy.num_handles > 0)) {
        uint32_t total_unknown_handles;
        if (add_overflow(unknown_handle_idx_, envelope_copy.num_handles, &total_unknown_handles)) {
          SetError("number of unknown handles overflows");
          return Status::kConstraintViolationError;
        }
        if (total_unknown_handles > ZX_CHANNEL_MAX_MSG_HANDLES) {
          SetError("number of unknown handles exceeds unknown handle array size");
          return Status::kConstraintViolationError;
        }
        uint32_t end_incoming_handle;
        if (add_overflow(handle_idx_, envelope_copy.num_handles, &end_incoming_handle)) {
          SetError("number of incoming handles overflows");
          return Status::kConstraintViolationError;
        }
        if (end_incoming_handle > num_handles_) {
          SetError("number of incoming handles exceeds incoming handle array size");
          return Status::kConstraintViolationError;
        }
        // If hlcpp_mode_ is true, leave the unknown handles intact
        // for something else to process (e.g. HLCPP Decode)
        if (hlcpp_mode_ && is_resource == kFidlIsResource_Resource) {
          handle_idx_ += envelope_copy.num_handles;
          return Status::kSuccess;
        }
        // Receiving unknown handles for a resource type is only an error if
        // hlcpp_mode_ is true, i.e. the walker itself is not
        // automatically closing all unknown handles (making it impossible for
        // the domain object to store the unknown handles).
        if (unlikely(hlcpp_mode_ && is_resource == kFidlIsResource_NotResource)) {
          SetError("received unknown handles for a non-resource type");
          return Status::kConstraintViolationError;
        }
        if (has_handles()) {
          memcpy(&unknown_handles_[unknown_handle_idx_], &handles()[handle_idx_],
                 envelope_copy.num_handles * sizeof(zx_handle_t));
          handle_idx_ = end_incoming_handle;
          unknown_handle_idx_ = total_unknown_handles;
        } else if (has_handle_infos()) {
          for (; handle_idx_ < end_incoming_handle; handle_idx_++, unknown_handle_idx_++) {
            unknown_handles_[unknown_handle_idx_] = handle_infos()[handle_idx_].handle;
          }
        }
      }
      return Status::kSuccess;
    }
  
    void OnError(const char* error) { SetError(error); }
  
    zx_status_t status() const { return status_; }
  
    bool DidConsumeAllBytes() const { return next_out_of_line_ == num_bytes_; }
  
    bool DidConsumeAllHandles() const { return handle_idx_ == num_handles_; }
  
    uint32_t unknown_handle_idx() const { return unknown_handle_idx_; }
  
    const zx_handle_t* unknown_handles() const { return unknown_handles_; }
  
   private:
    void SetError(const char* error) {
      if (status_ != ZX_OK) {
        return;
      }
      status_ = ZX_ERR_INVALID_ARGS;
      if (!out_error_msg_) {
        return;
      }
      *out_error_msg_ = error;
    }
  
    template <typename MaskType>
    Status ValidatePadding(const MaskType* padding_ptr, MaskType mask) {
      if ((*padding_ptr & mask) != 0) {
        SetError("non-zero padding bytes detected");
        return Status::kConstraintViolationError;
      }
      return Status::kSuccess;
    }
  
    bool has_handles() const { return cpp17::holds_alternative<const zx_handle_t*>(handles_); }
    bool has_handle_infos() const {
      return cpp17::holds_alternative<const zx_handle_info_t*>(handles_);
    }
    const zx_handle_t* handles() const { return cpp17::get<const zx_handle_t*>(handles_); }
    const zx_handle_info_t* handle_infos() const {
      return cpp17::get<const zx_handle_info_t*>(handles_);
    }
  
    // Message state passed in to the constructor.
    Byte* const bytes_;
    const uint32_t num_bytes_;
    cpp17::variant<cpp17::monostate, const zx_handle_t*, const zx_handle_info_t*> handles_;
    const uint32_t num_handles_;
    uint32_t next_out_of_line_;
    const char** const out_error_msg_;
    // HLCPP first uses FidlDecoder to do an in-place decode, then extracts data
    // out into domain objects. Since HLCPP stores unknown handles
    // (and LLCPP does not), this field allows HLCPP to use the decoder while
    // keeping unknown handles in flexible resource unions intact.
    bool hlcpp_mode_;
  
    // Decoder state
    zx_status_t status_ = ZX_OK;
    uint32_t handle_idx_ = 0;
    uint32_t unknown_handle_idx_ = 0;
    zx_handle_t unknown_handles_[ZX_CHANNEL_MAX_MSG_HANDLES];
  };
  
  template <Mode mode, FidlWireFormatVersion WireFormatVersion>
  zx_status_t fidl_decode_impl(const fidl_type_t* type, void* bytes, uint32_t num_bytes,
                               const zx_handle_info_t* handles, uint32_t num_handles,
                               const char** out_error_msg, bool hlcpp_mode) {
    auto drop_all_handles = [&]() { FidlHandleInfoCloseMany(handles, num_handles); };
    auto set_error = [&out_error_msg](const char* msg) {
      if (out_error_msg)
        *out_error_msg = msg;
    };
    if (unlikely(type == nullptr)) {
      set_error("fidl type cannot be null");
      return ZX_ERR_INVALID_ARGS;
    }
    if (unlikely(handles == nullptr && num_handles != 0)) {
      set_error("Cannot provide non-zero handle count and null handle pointer");
      return ZX_ERR_INVALID_ARGS;
    }
    if (unlikely(bytes == nullptr)) {
      set_error("Cannot decode null bytes");
      drop_all_handles();
      return ZX_ERR_INVALID_ARGS;
    }
    if (unlikely(!FidlIsAligned(reinterpret_cast<uint8_t*>(bytes)))) {
      set_error("Bytes must be aligned to FIDL_ALIGNMENT");
      drop_all_handles();
      return ZX_ERR_INVALID_ARGS;
    }
  
    zx_status_t status;
    uint32_t primary_size;
    uint32_t next_out_of_line;
    if (unlikely((status = fidl::PrimaryObjectSize<WireFormatVersion>(
                      type, num_bytes, &primary_size, &next_out_of_line, out_error_msg)) != ZX_OK)) {
      drop_all_handles();
      return status;
    }
  
    uint8_t* b = reinterpret_cast<uint8_t*>(bytes);
    for (size_t i = primary_size; i < size_t(next_out_of_line); i++) {
      if (b[i] != 0) {
        set_error("non-zero padding bytes detected");
        drop_all_handles();
        return ZX_ERR_INVALID_ARGS;
      }
    }
  
    FidlDecoder<mode, WireFormatVersion, uint8_t> decoder(
        b, num_bytes, handles, num_handles, next_out_of_line, out_error_msg, hlcpp_mode);
    fidl::Walk<WireFormatVersion>(decoder, type, DecodingPosition<uint8_t>{b});
  
    if (unlikely(decoder.status() != ZX_OK)) {
      drop_all_handles();
      return decoder.status();
    }
    if (unlikely(!decoder.DidConsumeAllBytes())) {
      set_error("message did not decode all provided bytes");
      drop_all_handles();
      return ZX_ERR_INVALID_ARGS;
    }
    if (unlikely(!decoder.DidConsumeAllHandles())) {
      set_error("message did not decode all provided handles");
      drop_all_handles();
      return ZX_ERR_INVALID_ARGS;
    }
  
    (void)FidlHandleCloseMany(decoder.unknown_handles(), decoder.unknown_handle_idx());
    return ZX_OK;
  }
  
  }  // namespace
  
  zx_status_t internal__fidl_decode_etc_hlcpp__v2__may_break(const fidl_type_t* type, void* bytes,
                                                             uint32_t num_bytes,
                                                             const zx_handle_info_t* handle_infos,
                                                             uint32_t num_handle_infos,
                                                             const char** out_error_msg) {
    return fidl_decode_impl<Mode::Decode, FIDL_WIRE_FORMAT_VERSION_V2>(
        type, bytes, num_bytes, handle_infos, num_handle_infos, out_error_msg, true);
  }
  
  zx_status_t fidl_decode_etc(const fidl_type_t* type, void* bytes, uint32_t num_bytes,
                              const zx_handle_info_t* handle_infos, uint32_t num_handle_infos,
                              const char** out_error_msg) {
    return fidl_decode_impl<Mode::Decode, FIDL_WIRE_FORMAT_VERSION_V1>(
        type, bytes, num_bytes, handle_infos, num_handle_infos, out_error_msg, false);
  }
  
  zx_status_t internal_fidl_decode_etc__v2__may_break(const fidl_type_t* type, void* bytes,
                                                      uint32_t num_bytes,
                                                      const zx_handle_info_t* handle_infos,
                                                      uint32_t num_handle_infos,
                                                      const char** out_error_msg) {
    return fidl_decode_impl<Mode::Decode, FIDL_WIRE_FORMAT_VERSION_V2>(
        type, bytes, num_bytes, handle_infos, num_handle_infos, out_error_msg, false);
  }
  
  zx_status_t fidl_decode_msg(const fidl_type_t* type, fidl_incoming_msg_t* msg,
                              const char** out_error_msg) {
    return fidl_decode_etc(type, msg->bytes, msg->num_bytes, msg->handles, msg->num_handles,
                           out_error_msg);
  }
  
  template <FidlWireFormatVersion WireFormatVersion>
  zx_status_t fidl_validate_impl(const fidl_type_t* type, const void* bytes, uint32_t num_bytes,
                                 uint32_t num_handles, const char** out_error_msg) {
    auto set_error = [&out_error_msg](const char* msg) {
      if (out_error_msg)
        *out_error_msg = msg;
    };
    if (unlikely(type == nullptr)) {
      set_error("fidl type cannot be null");
      return ZX_ERR_INVALID_ARGS;
    }
    if (bytes == nullptr) {
      set_error("Cannot validate null bytes");
      return ZX_ERR_INVALID_ARGS;
    }
    if (unlikely(!FidlIsAligned(reinterpret_cast<const uint8_t*>(bytes)))) {
      set_error("Bytes must be aligned to FIDL_ALIGNMENT");
      return ZX_ERR_INVALID_ARGS;
    }
  
    zx_status_t status;
    uint32_t primary_size;
    uint32_t next_out_of_line;
    if (unlikely((status = fidl::PrimaryObjectSize<WireFormatVersion>(
                      type, num_bytes, &primary_size, &next_out_of_line, out_error_msg)) != ZX_OK)) {
      return status;
    }
  
    const uint8_t* b = reinterpret_cast<const uint8_t*>(bytes);
    for (size_t i = primary_size; i < size_t(next_out_of_line); i++) {
      if (b[i] != 0) {
        set_error("non-zero padding bytes detected");
        return ZX_ERR_INVALID_ARGS;
      }
    }
  
    FidlDecoder<Mode::Validate, WireFormatVersion, const uint8_t> validator(
        b, num_bytes, (zx_handle_t*)(nullptr), num_handles, next_out_of_line, out_error_msg, false);
    fidl::Walk<WireFormatVersion>(validator, type, DecodingPosition<const uint8_t>{b});
  
    if (validator.status() == ZX_OK) {
      if (!validator.DidConsumeAllBytes()) {
        set_error("message did not consume all provided bytes");
        return ZX_ERR_INVALID_ARGS;
      }
      if (!validator.DidConsumeAllHandles()) {
        set_error("message did not reference all provided handles");
        return ZX_ERR_INVALID_ARGS;
      }
    }
  
    return validator.status();
  }
  
  zx_status_t internal__fidl_validate__v1__may_break(const fidl_type_t* type, const void* bytes,
                                                     uint32_t num_bytes, uint32_t num_handles,
                                                     const char** out_error_msg) {
    return fidl_validate_impl<FIDL_WIRE_FORMAT_VERSION_V1>(type, bytes, num_bytes, num_handles,
                                                           out_error_msg);
  }
  
  zx_status_t internal__fidl_validate__v2__may_break(const fidl_type_t* type, const void* bytes,
                                                     uint32_t num_bytes, uint32_t num_handles,
                                                     const char** out_error_msg) {
    return fidl_validate_impl<FIDL_WIRE_FORMAT_VERSION_V2>(type, bytes, num_bytes, num_handles,
                                                           out_error_msg);
  }
  