// Copyright 2014 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#ifndef MOJO_PUBLIC_CPP_BINDINGS_LIB_VALIDATION_ERRORS_H_
#define MOJO_PUBLIC_CPP_BINDINGS_LIB_VALIDATION_ERRORS_H_

#include <stddef.h>

#include "base/component_export.h"
#include "base/dcheck_is_on.h"
#include "base/functional/callback_forward.h"
#include "mojo/public/cpp/bindings/lib/send_validation_type.h"

namespace mojo {

class Message;

namespace internal {

class ValidationContext;

struct ArrayIndexError {
  const char* message;
  size_t size;
  size_t index;
};

struct ArrayExpectedSizeError {
  const char* message;
  size_t size;
  size_t expected_size;
};

enum ValidationError {
  // There is no validation error.
  VALIDATION_ERROR_NONE,
  // An object (struct or array) is not 8-byte aligned.
  VALIDATION_ERROR_MISALIGNED_OBJECT,
  // An object is not contained inside the message data, or it overlaps other
  // objects.
  VALIDATION_ERROR_ILLEGAL_MEMORY_RANGE,
  // A struct header doesn't make sense, for example:
  // - |num_bytes| is smaller than the size of the struct header.
  // - |num_bytes| and |version| don't match.
  // TODO(yzshen): Consider splitting it into two different error codes. Because
  // the former indicates someone is misbehaving badly whereas the latter could
  // be due to an inappropriately-modified .mojom file.
  VALIDATION_ERROR_UNEXPECTED_STRUCT_HEADER,
  // An array header doesn't make sense, for example:
  // - |num_bytes| is smaller than the size of the header plus the size required
  // to store |num_elements| elements.
  // - For fixed-size arrays, |num_elements| is different than the specified
  // size.
  VALIDATION_ERROR_UNEXPECTED_ARRAY_HEADER,
  // An encoded handle is illegal.
  VALIDATION_ERROR_ILLEGAL_HANDLE,
  // A non-nullable handle field is set to invalid handle.
  VALIDATION_ERROR_UNEXPECTED_INVALID_HANDLE,
  // An encoded pointer is illegal.
  VALIDATION_ERROR_ILLEGAL_POINTER,
  // A non-nullable pointer field is set to null.
  VALIDATION_ERROR_UNEXPECTED_NULL_POINTER,
  // An interface ID is illegal.
  VALIDATION_ERROR_ILLEGAL_INTERFACE_ID,
  // A non-nullable interface ID field is set to invalid.
  VALIDATION_ERROR_UNEXPECTED_INVALID_INTERFACE_ID,
  // |flags| in the message header is invalid. The flags are either
  // inconsistent with one another, inconsistent with other parts of the
  // message, or unexpected for the message receiver.  For example the
  // receiver is expecting a request message but the flags indicate that
  // the message is a response message.
  VALIDATION_ERROR_MESSAGE_HEADER_INVALID_FLAGS,
  // |flags| in the message header indicates that a request ID is required but
  // there isn't one.
  VALIDATION_ERROR_MESSAGE_HEADER_MISSING_REQUEST_ID,
  // The |name| field in a message header contains an unexpected value.
  VALIDATION_ERROR_MESSAGE_HEADER_UNKNOWN_METHOD,
  // Two parallel arrays which are supposed to represent a map have different
  // lengths.
  VALIDATION_ERROR_DIFFERENT_SIZED_ARRAYS_IN_MAP,
  // Attempted to deserialize a tagged union with an unknown tag.
  VALIDATION_ERROR_UNKNOWN_UNION_TAG,
  // A value of a non-extensible enum type is unknown.
  VALIDATION_ERROR_UNKNOWN_ENUM_VALUE,
  // Message deserialization failure, for example due to rejection by custom
  // validation logic.
  VALIDATION_ERROR_DESERIALIZATION_FAILED,
  // The message contains a too deeply nested value, for example a recursively
  // defined field which runtime value is too large.
  VALIDATION_ERROR_MAX_RECURSION_DEPTH,
};

COMPONENT_EXPORT(MOJO_CPP_BINDINGS_BASE)
const char* ValidationErrorToString(ValidationError error);

COMPONENT_EXPORT(MOJO_CPP_BINDINGS_BASE)
void ReportValidationError(ValidationContext* context,
                           ValidationError error,
                           const char* details = nullptr);

COMPONENT_EXPORT(MOJO_CPP_BINDINGS_BASE)
void ReportValidationError(ValidationContext* context,
                           ValidationError error,
                           const ArrayIndexError& details);

COMPONENT_EXPORT(MOJO_CPP_BINDINGS_BASE)
void ReportValidationError(ValidationContext* context,
                           ValidationError error,
                           const ArrayExpectedSizeError& details);

COMPONENT_EXPORT(MOJO_CPP_BINDINGS_BASE)
void ReportValidationErrorForMessage(mojo::Message* message,
                                     ValidationError error,
                                     const char* interface_name,
                                     unsigned int method_ordinal,
                                     bool is_response);

COMPONENT_EXPORT(MOJO_CPP_BINDINGS_BASE)
bool GetIsValidationErrorLoggingSuppressedForTesting();

COMPONENT_EXPORT(MOJO_CPP_BINDINGS_BASE)
void SetIsValidationErrorLoggingSuppressedForTesting(bool suppress_logging);

COMPONENT_EXPORT(MOJO_CPP_BINDINGS_BASE)
void SetSerializationWarningCallbackForTesting(
    base::RepeatingCallback<void(ValidationError, SendValidation)>* callback);

COMPONENT_EXPORT(MOJO_CPP_BINDINGS_BASE)
void SetValidationErrorCallbackForTesting(
    base::RepeatingCallback<void(ValidationError)>* callback);

COMPONENT_EXPORT(MOJO_CPP_BINDINGS_BASE)
void HandleSerializationError(ValidationError error, const char* details);

COMPONENT_EXPORT(MOJO_CPP_BINDINGS_BASE)
void HandleSerializationError(ValidationError error,
                              const ArrayIndexError& details);

COMPONENT_EXPORT(MOJO_CPP_BINDINGS_BASE)
void HandleSerializationError(ValidationError error,
                              const ArrayExpectedSizeError& details);

COMPONENT_EXPORT(MOJO_CPP_BINDINGS_BASE)
void HandleSerializationWarning(ValidationError error, const char* details);

COMPONENT_EXPORT(MOJO_CPP_BINDINGS_BASE)
void HandleSerializationWarning(ValidationError error,
                                const ArrayIndexError& details);

COMPONENT_EXPORT(MOJO_CPP_BINDINGS_BASE)
void HandleSerializationWarning(ValidationError error,
                                const ArrayExpectedSizeError& details);

// In debug build, logs a serialization warning if |condition| evaluates to
// true:
//   - if there is a SerializationWarningObserverForTesting object alive,
//     records |error| in it;
//   - otherwise, logs a fatal-level message.
// |error| is the validation error that will be triggered by the receiver
// of the serialization result.
//
// In non-debug build, does nothing (not even compiling |condition|).
#define MOJO_INTERNAL_DLOG_SERIALIZATION_WARNING(condition, error, details) \
  if (DCHECK_IS_ON() && !(condition)) {                                     \
    HandleSerializationWarning(error, details);                             \
  }

// If |condition| evaluates to true:
//   - if there is a SerializationWarningObserverForTesting object alive,
//     records |error| in it;
//   - if there is no SerializationWarningObserverForTesting, then it will CHECK
// |error| is the validation error that will be triggered by the receiver
// of the serialization result.
//
// By checking the condition first, we delay evaluating the details.
#define MOJO_INTERNAL_CHECK_SERIALIZATION_ERROR(condition, error, details) \
  if (!(condition)) {                                                      \
    HandleSerializationError(error, details);                              \
  }

#define RUNTIME_MOJO_INTERNAL_CHECK_SERIALIZATION(send_validation, condition, \
                                                  error, details)             \
  if (send_validation == mojo::internal::SendValidation::kFatal) {            \
    MOJO_INTERNAL_CHECK_SERIALIZATION_ERROR(condition, error, details);       \
  } else if (send_validation == mojo::internal::SendValidation::kWarning) {   \
    MOJO_INTERNAL_DLOG_SERIALIZATION_WARNING(condition, error, details);      \
  }

#define MOJO_INTERNAL_CHECK_SERIALIZATION(send_validation, condition, error, \
                                          details)                           \
  if constexpr (send_validation == mojo::internal::SendValidation::kFatal) { \
    MOJO_INTERNAL_CHECK_SERIALIZATION_ERROR(condition, error, details);      \
  } else if constexpr (send_validation ==                                    \
                       mojo::internal::SendValidation::kWarning) {           \
    MOJO_INTERNAL_DLOG_SERIALIZATION_WARNING(condition, error, details);     \
  }

}  // namespace internal
}  // namespace mojo

#endif  // MOJO_PUBLIC_CPP_BINDINGS_LIB_VALIDATION_ERRORS_H_
