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

#ifndef TESTING_LIBFUZZER_PROTO_SKIA_IMAGE_FILTER_PROTO_CONVERTER_H_
#define TESTING_LIBFUZZER_PROTO_SKIA_IMAGE_FILTER_PROTO_CONVERTER_H_

#include <random>
#include <set>
#include <string>
#include <string_view>
#include <tuple>
#include <vector>

#include "base/containers/fixed_flat_map.h"
#include "base/containers/fixed_flat_set.h"
#include "testing/libfuzzer/proto/skia_image_filter.pb.h"
#include "third_party/skia/include/core/SkPoint.h"

using google::protobuf::FieldDescriptor;
using google::protobuf::Message;
using google::protobuf::Reflection;


namespace skia_image_filter_proto_converter {

// Takes an Input proto as input and converts it to a string that will usually
// be deserialized as a skia image filter.
class Converter {
 public:
  Converter();
  Converter(const Converter&);
  ~Converter();

  // Provides the public interface for this class's functionality by converting
  // Input to a string representing a serialized image filter.
  std::string Convert(const Input&);

 private:
  // These constexprs are copied from skia.
  static constexpr uint8_t kICC_Flag = 1 << 1;
  static constexpr size_t kICCTagTableEntrySize = 12;
  static constexpr uint32_t kMatrix_Flag = 1 << 0;
  static constexpr uint8_t k0_Version = 0;
  static constexpr uint8_t kTransferFn_Flag = 1 << 3;
  static constexpr size_t kLut8InputSize = 48;
  static constexpr size_t kOneChannelGammasSize = 256;
  static constexpr size_t kMaxLut16GammaEntries = 4096;
  static constexpr uint8_t kLut8Precision = 1;
  static const uint32_t kPictEofTag;
  static const uint32_t kProfileLookupTable[];
  static const uint32_t kInputColorSpaceLookupTable[];
  static const uint32_t kPCSLookupTable[];
  static const uint32_t kTagLookupTable[];
  static const char kPictureMagicString[];
  static const char kSkPictReaderTag[];
  static const uint8_t kCountNibBits[];

  // The size of kColorTableBuffer.
  static const int kColorTableBufferLength;

  // Used to bound flattenable_depth_.
  static const int kFlattenableDepthLimit;

  // Used to bound numeric fields.
  static const int kNumBound;

  // Used by ColorTableToArray to store a ColorTable Message as an array.
  static uint8_t kColorTableBuffer[];

  // There will be a 1/kMutateEnumDenominator chance that WriteEnum
  // writes an invalid enum value instead of the one given to us by LPM.
  // This must be greater than 1.
  static const uint8_t kMutateEnumDenominator;

  // Mapping of field names to types.
  // Does not include SkSumPathEffect, SkComposePathEffect or SkRegion since
  // they don't use the VISIT FLATTENABLE macros.
  static constexpr base::fixed_flat_map<std::string_view, std::string_view, 54u>
      kFieldToFlattenableName =
          base::MakeFixedFlatMap<std::string_view, std::string_view>({
              {"path_1d_path_effect", "SkPath1DPathEffect"},
              {"path_2d_path_effect", "SkPath2DPathEffect"},
              {"alpha_threshold_filter_impl", "SkAlphaThresholdFilterImpl"},
              {"arithmetic_image_filter", "SkArithmeticImageFilter"},
              {"blur_image_filter_impl", "SkBlurImageFilterImpl"},
              {"blur_mask_filter_impl", "SkBlurMaskFilterImpl"},
              {"color_4_shader", "SkColor4Shader"},
              {"color_filter_image_filter", "SkColorFilterImageFilter"},
              {"color_filter_shader", "SkColorFilterShader"},
              {"color_matrix_filter_row_major_255",
               "SkColorMatrixFilterRowMajor255"},
              {"color_shader", "SkColorShader"},
              {"compose_color_filter", "SkComposeColorFilter"},
              {"compose_image_filter", "SkComposeImageFilter"},
              {"compose_shader", "SkComposeShader"},
              {"corner_path_effect", "SkCornerPathEffect"},
              {"dash_impl", "SkDashImpl"},
              {"diffuse_lighting_image_filter", "SkDiffuseLightingImageFilter"},
              {"dilate_image_filter", "SkDilateImageFilter"},
              {"discrete_path_effect", "SkDiscretePathEffect"},
              {"displacement_map_effect", "SkDisplacementMapEffect"},
              {"drop_shadow_image_filter", "SkDropShadowImageFilter"},
              {"emboss_mask_filter", "SkEmbossMaskFilter"},
              {"empty_shader", "SkEmptyShader"},
              {"image_shader", "SkImageShader"},
              {"image_source", "SkImageSource"},
              {"line_2d_path_effect", "SkLine2DPathEffect"},
              {"linear_gradient", "SkLinearGradient"},
              {"local_matrix_image_filter", "SkLocalMatrixImageFilter"},
              {"local_matrix_shader", "SkLocalMatrixShader"},
              {"luma_color_filter", "SkLumaColorFilter"},
              {"magnifier_image_filter", "SkMagnifierImageFilter"},
              {"matrix_convolution_image_filter",
               "SkMatrixConvolutionImageFilter"},
              {"matrix_image_filter", "SkMatrixImageFilter"},
              {"merge_image_filter", "SkMergeImageFilter"},
              {"mode_color_filter", "SkModeColorFilter"},
              {"offset_image_filter", "SkOffsetImageFilter"},
              {"overdraw_color_filter", "SkOverdrawColorFilter"},
              {"paint_image_filter", "SkPaintImageFilter"},
              {"picture_image_filter", "SkPictureImageFilter"},
              {"picture_shader", "SkPictureShader"},
              {"radial_gradient", "SkRadialGradient"},
              {"specular_lighting_image_filter",
               "SkSpecularLightingImageFilter"},
              {"sweep_gradient", "SkSweepGradient"},
              {"tile_image_filter", "SkTileImageFilter"},
              {"two_point_conical_gradient", "SkTwoPointConicalGradient"},
              {"xfermode_image_filter", "SkXfermodeImageFilter"},
              {"xfermode_image_filter__base", "SkXfermodeImageFilter_Base"},
              {"srgb_gamma_color_filter", "SkSRGBGammaColorFilter"},
              {"high_contrast__filter", "SkHighContrast_Filter"},
              {"table__color_filter", "SkTable_ColorFilter"},
              {"to_srgb_color_filter", "SkToSRGBColorFilter"},
              {"layer_draw_looper", "SkLayerDrawLooper"},
              {"perlin_noise_shader_impl", "SkPerlinNoiseShaderImpl"},
              {"erode_image_filter", "SkErodeImageFilter"},
          });

  // Used by IsBlacklisted to determine which skia flattenable should not be
  // serialized.
  static constexpr base::fixed_flat_set<std::string_view, 3u>
      kMisbehavedFlattenableBlacklist =
          base::MakeFixedFlatSet<std::string_view>({
              "matrix_image_filter",   // Causes OOMs.
              "discrete_path_effect",  // Causes timeouts.
              "path_1d_path_effect",   // Causes timeouts.
          });

  // Probably the most important attribute, a char vector that contains
  // serialized skia flattenable written by the Visit functions. The contents of
  // output_ is returned by Convert().
  std::vector<char> output_;

  // Stores the size of output_ when a skia flattenable is being written (since
  // they need to store their size).
  std::vector<size_t> start_sizes_;

  // Keep a record of whether we used kStrokeAndFill_Style or kStroke_Style
  // already since using them multiple times increases the risk of OOMs and
  // timeouts.
  bool stroke_style_used_;

  // Used to keep track of how nested are the skia flattenables we are writing.
  // We use this to limit nesting to avoid OOMs and timeouts.
  int flattenable_depth_;

  // Nesting PairPathEffects is particularly likely to cause OOMs and timeouts
  // so limit this even more than other skia flattenables.
  int pair_path_effect_depth_;

  // Don't allow ComposeColorFilters to contain themselves or else LPM will go
  // crazy and nest them to the point that it causes OOMs and timeouts (these
  // filters are more likely than other skia flattenables to cause these
  // problems).
  bool in_compose_color_filter_;

  // Used to generate random numbers (for replacing NaNs, and mutating enum
  // values).
  std::mt19937 rand_gen_;

  // A distribution from 2-kMutateEnumDenominator that will be used
  // to generate a random value that we will use to decide if an enum value
  // should be written as-is or if it should be mutated.
  // The reason why there is a 2/kMutateEnumDenominator chance rather than a
  // 1/kMutateEnumDenominator chance is because we treat making an enum value
  // too small as a separate case from making it too big.
  std::uniform_int_distribution<> enum_mutator_chance_distribution_;

  // Prevents WriteEnum from writing an invalid enum value instead of the one it
  // was given.
  bool dont_mutate_enum_;

  // BoundNum and BoundFloat will only return positive numbers when this is
  // true.
  bool bound_positive_;

// In production we don't need attributes used by ICC code since it is not
// built for production code.
#ifdef DEVELOPMENT
  uint32_t icc_base_;
  int tag_offset_;
#endif  // DEVELOPMENT

  // Reset any state used by the flattenable so that is can be used to convert
  // another proto input.
  void Reset();

  void Visit(const PictureImageFilter&);
  void Visit(const Picture&);
  void Visit(const PictureTagChild&);

  // The generic Visit function. The compiler will allow this to be called on
  // any proto message, though this won't result in a correct conversion.
  // However some very simple messages have contents that can pretty much be
  // written as they are defined by the fields of msg (eg "DistantLight"). This
  // method is intended for those messages and will properly convert
  // those. Note that this method is viral in that any fields on msg that
  // contain other messages will only be written using this method and
  // WriteFields. For example, "DistantLight" has a field "direction" that
  // contains a "Point3" message. It is OK to call this on "DistantLight"
  // messages because it is OK to call this on "Point3". In essence, it
  // is only correct to call this method on msg if it is correct to call this
  // method on any fields (or fields of fields etc.) of msg that are also
  // Messages. See the file comment on skia_image_filter.proto for an
  // explanation of how this and WriteFields are used for autovisit.
  void Visit(const Message& msg);

  void Visit(const PictureData&);
  void Visit(const RecordingData&);
  void Visit(const LightParent&);
  void Visit(const ImageFilterChild&);
  void Visit(const ImageFilterParent&, const int num_inputs_required);
  void Visit(const MatrixImageFilter&);
  void Visit(const Matrix&, bool is_local = false);
  void Visit(const SpecularLightingImageFilter&);
  void Visit(const PaintImageFilter&);
  void Visit(const Paint&);
  void Visit(const PaintEffects&);
  void Visit(const PathEffectChild&);
  void Visit(const LooperChild&);
  void Visit(const LayerDrawLooper&);
  void Visit(const LayerInfo&);
  void Visit(const ColorFilterChild&);
  void Visit(const ComposeColorFilter&);
  void Visit(const OverdrawColorFilter&);
  void Visit(const ToSRGBColorFilter&);
  void Visit(const ColorFilterMatrix&);
  void Visit(const ColorMatrixFilterRowMajor255&);
  void Visit(const MergeImageFilter&);
  void Visit(const XfermodeImageFilter&);
  void Visit(const DiffuseLightingImageFilter&);
  void Visit(const XfermodeImageFilter_Base&);
  void Visit(const TileImageFilter&);
  void Visit(const OffsetImageFilter&);
  void Visit(const ErodeImageFilter&);
  void Visit(const DilateImageFilter&);
  void Visit(const DiscretePathEffect&);
  void Visit(const MatrixConvolutionImageFilter&);
  void Visit(const MagnifierImageFilter&);
  void Visit(const LocalMatrixImageFilter&);
  void Visit(const ImageSource&);
  void Visit(const Path&);
  void Visit(const PathRef&);
  void Visit(const DropShadowImageFilter&);
  void Visit(const DisplacementMapEffect&);
  void Visit(const ComposeImageFilter&);
  void Visit(const ColorFilterImageFilter&);
  void Visit(const BlurImageFilterImpl&);
  void Visit(const AlphaThresholdFilterImpl&);
  void Visit(const Region&);
  void Visit(const Path1DPathEffect&);

  // Writes the correct PairPathEffect skia flattenable (SkSumPathEffect or
  // SkComposePathEffect) depending on pair.type(). Note that it writes the
  // entire skia flattenable (including name and size) unlike most Visit
  // functions and thus should not be be preceded by a call to
  // PreVisitFlattenable, nor should it be followed by a call to
  // PostVisitFlattenable.
  void Visit(const PairPathEffect&);

  void Visit(const ShaderChild&);
  void Visit(const Color4Shader&);
  void Visit(const GradientDescriptor&);
  void Visit(const GradientParent&);
  void Visit(const TwoPointConicalGradient&);
  void Visit(const LinearGradient&);
  void Visit(const SweepGradient&);
  void Visit(const RadialGradient&);
  void Visit(const PictureShader&);
  void Visit(const LocalMatrixShader&);
  void Visit(const ComposeShader&);
  void Visit(const ColorFilterShader&);
  void Visit(const ImageShader&);
  void Visit(const Color4f&);
  void Visit(const Image&);
  void Visit(const ImageData&);
  void Visit(const ColorSpaceChild&);
  void Visit(const ColorSpace_XYZ&);
  void Visit(const ColorSpaceNamed&);
  void Visit(const TransferFn&);
  void Visit(const MaskFilterChild&);
  void Visit(const Table_ColorFilter&);
  void Visit(const EmbossMaskFilter&);
  void Visit(const EmbossMaskFilterLight&);
  void Visit(const DashImpl&);
  void Visit(const Path2DPathEffect&);
  void Visit(const ArithmeticImageFilter&);
  void Visit(const LightChild&);
  void Visit(const CropRectangle&);
  void Visit(const Rectangle&);
  void Visit(const PictureInfo&);
  void Visit(const BlurMaskFilter&);
  void Visit(const HighContrast_Filter&);
  void Visit(const ReaderPictureTag&);
  void Visit(const Vertices&);
  void Visit(const TextBlob&);
  template <class T>
  void Visit(const google::protobuf::RepeatedPtrField<T>& repeated_field);
  void VisitPictureTag(const PathPictureTag& path_picture_tag, uint32_t tag);
  void VisitPictureTag(const PaintPictureTag& paint_picture_tag, uint32_t tag);
  template <class T>
  void VisitPictureTag(const T& picture_tag_child, uint32_t tag);

  // Returns false if there is too much nesting (determined by
  // kFlattenableDepthLimit and flattenable_depth_).  Writes name and reserves a
  // space to write the size of the flattenable. Also increments
  // flattenable_depth_.
  bool PreVisitFlattenable(std::string_view name);

  // Writes the size of the flattenable to the reserved space, ensures that
  // output_ is four byte aligned and then decrements flattenable_depth_.
  void PostVisitFlattenable();

  std::tuple<int32_t, int32_t, int32_t, int32_t> WriteNonEmptyIRect(
      const IRect& irect);

  void WriteColorSpaceVersion();
  // Write a string in the proper serialized format, padding if necessary.
  void WriteString(std::string_view str);

  // Get the size of a skia flattenable that was just written and insert it at
  // the proper location. Every call to this method should have a corresponding
  // call to RecordSize.
  void WriteBytesWritten();

  // Reserves space to write the size of what we are serializing and records
  // info so that WriteBytesWritten can determine the size. Every call to this
  // method should have a corresponding call to WriteBytesWritten that it is
  // followed by.
  void RecordSize();

  // Write size to position in output_.
  void InsertSize(const size_t size, const uint32_t position);

  // Pops off the end of start_sizes_.
  size_t PopStartSize();

  // Pad the write_size bytes that were written with zeroes so that the
  // write_size + number of padding bytes is divisible by four.
  void Pad(const size_t write_size);

  // Write size elements of RepeatedField of uint32_ts repeated_field as an
  // array.
  void WriteArray(
      const google::protobuf::RepeatedField<uint32_t>& repeated_field,
      const size_t size);

  // Write size bytes of arr as an array and pad if necessary.
  void WriteArray(const char* arr, const size_t size);

  void WriteBool(const bool bool_val);

  // Write the fields of msg starting with the field whose number is start and
  // ending with the field whose number is end. If end is 0 then all fields
  // until the last one will be written. start defaults to 1 and end defaults to
  // 0. Note that not all possible (eg repeated bools) fields are supported,
  // consult the code to determine if the field is supported (an error will be
  // thrown if a field is unsupported). Note that WriteFields is viral in that
  // if msg contains a field containing another Message, let's say msg2, then
  // WriteFields(msg2) will be called (assuming that fields is in the range of
  // msgs we are writing). If there is a method defined Visit(const Message2&
  // msg2), it will not be called because there is no simple way to determine
  // the type of msg2 using protobuf's reflection API. WriteFields will bound
  // any numeric fields before writing them to avoid OOMs and timeouts. See the
  // file comment on skia_image_filter.proto for an explanation of how this and
  // the generic Visit function are used for autovisit. Note that this may write
  // invalid enum values instead of the ones provided if dont_mutate_enum_ is
  // true. Note that this method may not work if the max field of msg has number
  // that is different than the number of fields. For example, if msg does not
  // have a field with field number 1, but has fields with field numbers 2 and
  // 3, calling WriteFields(msg, 2) will not write field 3.
  void WriteFields(const Message& msg,
                   const unsigned start = 1,
                   const unsigned end = 0);

  // Given the name of a proto field, field_name returns the name of the
  // flattenable skia flattenable object it represents.
  std::string_view FieldToFlattenableName(std::string_view field_name) const;

  void CheckAlignment() const;
  // Append our proto Message proto_point to sk_points as an SkPoint.
  void AppendAsSkPoint(std::vector<SkPoint>& sk_points,
                       const Point& proto_point) const;

  template <typename T>
  void WriteUInt8(T num);
  void WriteUInt16(uint16_t num);
  void WriteNum(const char (&num_arr)[4]);
  // Write num as a number. Assumes num is four bytes or less.
  template <class T>
  void WriteNum(T num);

  // Write the enum value described by field descriptor and stored on message,
  // or write an invalid enum value with some probability if dont_mutate_enums_
  // is false.
  void WriteEnum(const Message& msg,
                 const Reflection* reflection,
                 const FieldDescriptor* field_descriptor);

  // Bound a num using num_bound so that it won't cause OOMs or timeouts.
  template <typename T>
  T BoundNum(T num, int upper_bound) const;

  // kNumBound cant be a default parameter to BoundNum(T num, int upper_bound)
  // so this function exists instead.
  template <typename T>
  T BoundNum(T num);

  // kNumBound cant be a default parameter to BoundFloat(T num, int upper_bound)
  // so this function exists instead.
  float BoundFloat(float num);

  // Bound a float num using kNumBound so that it won't cause OOMs or
  // timeouts.
  float BoundFloat(float num, const float num_bound);

  // Convert input_num to a uint8_t.
  template <typename T>
  uint8_t ToUInt8(const T input_num) const;

  float GetRandomFloat(std::mt19937* gen_ptr);
  float GetRandomFloat(float seed, float min, float max);

  // Write a sane value of field_value, which should be the value of a field of
  // a matrix.
  void WriteMatrixField(float field_value);

  // Saturating wrapper for stdlib.h's abs, which has undefined behavior when
  // given INT_MIN. This returns abs(INT_MIN+1) if val is INT_MIN.
  int Abs(const int val) const;

  // Writes the representation of a rectangle returned by GetValidRectangle.
  template <typename T>
  void WriteRectangle(std::tuple<T, T, T, T> rectangle);

  // Bound the points making up a rectangle so that the returned tuple is a
  // valid rectangle.
  std::tuple<float, float, float, float> GetValidRectangle(float left,
                                                           float top,
                                                           float right,
                                                           float bottom);

  std::tuple<int32_t, int32_t, int32_t, int32_t> GetValidIRect(int32_t left,
                                                               int32_t top,
                                                               int32_t right,
                                                               int32_t bottom);

  bool IsFinite(float num) const;
  bool IsBlacklisted(const std::string& field_name) const;

  // Converts color_table from our proto Message format to a 256-byte array.
  // Note that this function modifies kColorTableBuffer.
  const uint8_t* ColorTableToArray(const ColorTable& color_table);

#ifdef DEVELOPMENT
  // ICC related functions
  void Visit(const ICC&);
  void Visit(const ICCColorSpace&);
  void Visit(const ICCXYZ&);
  void Visit(const ICCGray&);
  void Visit(const ICCA2B0&);
  void Visit(const ICCA2B0AToB&);
  void Visit(const ICCA2B0Lut8&);
  void Visit(const ICCA2B0Lut16&);
  uint8_t GetClutGridPoints(const ICCA2B0Lut8&);
  uint32_t GetCurrentICCOffset();
  uint32_t GetLut8Size(const ICCA2B0Lut8&);
  uint32_t GetLut16Size(const ICCA2B0Lut16&);
  void WriteLut8(const ICCA2B0Lut8&);
  void WriteA2B0TagCommon();
  void WriteTagSize(const char (&tag)[4], const size_t size);
  void WriteBigEndian(uint32_t num);
  void WriteTagHeader(const uint32_t tag, const uint32_t len);

  // Write num_fields zeroes to fill the space used by ignored or reserved
  // fields in an ICC ColorSpace.
  void WriteIgnoredFields(const int num_fields);

  // Bound illuminant using num to avoid OOMs and timeouts.
  int32_t BoundIlluminant(float illuminant, const float num) const;

  // ImageInfo related functions
  void Visit(const ImageInfo&, const int32_t width, const int32_t height);
  std::tuple<int32_t, int32_t, int32_t>
  GetNumPixelBytes(const ImageInfo& image_info, int32_t width, int32_t height);

  size_t ComputeMinByteSize(int32_t width,
                            int32_t height,
                            ImageInfo::AlphaType alpha_type) const;
#endif  // DEVELOPMENT
};
}  // namespace skia_image_filter_proto_converter
#endif  // TESTING_LIBFUZZER_PROTO_SKIA_IMAGE_FILTER_PROTO_CONVERTER_H_
