/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

#ifndef __nsRFPService_h__
#define __nsRFPService_h__

#include <cstdint>
#include "ErrorList.h"
#include "PLDHashTable.h"
#include "mozilla/BasicEvents.h"
#include "mozilla/gfx/Types.h"
#include "nsHashtablesFwd.h"
#include "nsICookieJarSettings.h"
#include "nsIObserver.h"
#include "nsISupports.h"
#include "nsStringFwd.h"

// Defines regarding spoofed values of Navigator object. These spoofed values
// are returned when 'privacy.resistFingerprinting' is true.
// We decided to give different spoofed values according to the platform. The
// reason is that it is easy to detect the real platform. So there is no benefit
// for hiding the platform: it only brings breakages, like keyboard shortcuts
// won't work in macOS if we spoof it as a Windows platform.
#ifdef XP_WIN
#  define SPOOFED_UA_OS "Windows NT 10.0; Win64; x64"
#  define SPOOFED_APPVERSION "5.0 (Windows)"
#  define SPOOFED_OSCPU "Windows NT 10.0; Win64; x64"
#  define SPOOFED_PLATFORM "Win32"
#elif defined(XP_MACOSX)
#  define SPOOFED_UA_OS "Macintosh; Intel Mac OS X 10.15"
#  define SPOOFED_APPVERSION "5.0 (Macintosh)"
#  define SPOOFED_OSCPU "Intel Mac OS X 10.15"
#  define SPOOFED_PLATFORM "MacIntel"
#elif defined(MOZ_WIDGET_ANDROID)
#  define SPOOFED_UA_OS "Android 10; Mobile"
#  define SPOOFED_APPVERSION "5.0 (Android 10)"
#  define SPOOFED_OSCPU "Linux aarch64"
#  define SPOOFED_PLATFORM "Linux aarch64"
#else
// For Linux and other platforms, like BSDs, SunOS and etc, we will use Linux
// platform.
#  define SPOOFED_UA_OS "X11; Linux x86_64"
#  define SPOOFED_APPVERSION "5.0 (X11)"
#  define SPOOFED_OSCPU "Linux x86_64"
#  define SPOOFED_PLATFORM "Linux x86_64"
#endif

#define SPOOFED_APPNAME "Netscape"
#define LEGACY_BUILD_ID "20181001000000"
#define LEGACY_UA_GECKO_TRAIL "20100101"

#define SPOOFED_POINTER_INTERFACE MouseEvent_Binding::MOZ_SOURCE_MOUSE

// For the HTTP User-Agent header, we use a simpler set of spoofed values
// that do not reveal the specific desktop platform.
#if defined(MOZ_WIDGET_ANDROID)
#  define SPOOFED_HTTP_UA_OS "Android 10; Mobile"
#else
#  define SPOOFED_HTTP_UA_OS "Windows NT 10.0"
#endif

struct JSContext;

class nsIChannel;

namespace mozilla {
class WidgetKeyboardEvent;
namespace dom {
class Document;
}

enum KeyboardLang { EN = 0x01 };

#define RFP_KEYBOARD_LANG_STRING_EN "en"

typedef uint8_t KeyboardLangs;

enum KeyboardRegion { US = 0x01 };

#define RFP_KEYBOARD_REGION_STRING_US "US"

typedef uint8_t KeyboardRegions;

// This struct has the information about how to spoof the keyboardEvent.code,
// keyboardEvent.keycode and modifier states.
struct SpoofingKeyboardCode {
  CodeNameIndex mCode;
  uint8_t mKeyCode;
  Modifiers mModifierStates;
};

struct SpoofingKeyboardInfo {
  nsString mKey;
  KeyNameIndex mKeyIdx;
  SpoofingKeyboardCode mSpoofingCode;
};

class KeyboardHashKey : public PLDHashEntryHdr {
 public:
  typedef const KeyboardHashKey& KeyType;
  typedef const KeyboardHashKey* KeyTypePointer;

  KeyboardHashKey(const KeyboardLangs aLang, const KeyboardRegions aRegion,
                  const KeyNameIndexType aKeyIdx, const nsAString& aKey);

  explicit KeyboardHashKey(KeyTypePointer aOther);

  KeyboardHashKey(KeyboardHashKey&& aOther) noexcept;

  ~KeyboardHashKey();

  bool KeyEquals(KeyTypePointer aOther) const;

  static KeyTypePointer KeyToPointer(KeyType aKey);

  static PLDHashNumber HashKey(KeyTypePointer aKey);

  enum { ALLOW_MEMMOVE = true };

  KeyboardLangs mLang;
  KeyboardRegions mRegion;
  KeyNameIndexType mKeyIdx;
  nsString mKey;
};

// ============================================================================

// Reduce Timer Precision (RTP) Caller Type
enum class RTPCallerType : uint8_t {
  Normal = 0,
  SystemPrincipal = (1 << 0),
  ResistFingerprinting = (1 << 1),
  CrossOriginIsolated = (1 << 2)
};

enum TimerPrecisionType {
  DangerouslyNone = 1,
  UnconditionalAKAHighRes = 2,
  Normal = 3,
  RFP = 4,
};

// ============================================================================

// NOLINTNEXTLINE(bugprone-macro-parentheses)
#define ITEM_VALUE(name, val) name = val,

enum class RFPTarget : uint64_t {
#include "RFPTargets.inc"
};

#undef ITEM_VALUE

// ============================================================================

class nsRFPService final : public nsIObserver {
 public:
  NS_DECL_ISUPPORTS
  NS_DECL_NSIOBSERVER

  static nsRFPService* GetOrCreate();

  static bool IsRFPEnabledFor(RFPTarget aTarget);

  // --------------------------------------------------------------------------
  static double TimerResolution(RTPCallerType aRTPCallerType);

  enum TimeScale { Seconds = 1, MilliSeconds = 1000, MicroSeconds = 1000000 };

  // The following Reduce methods can be called off main thread.
  static double ReduceTimePrecisionAsUSecs(double aTime, int64_t aContextMixin,
                                           RTPCallerType aRTPCallerType);
  static double ReduceTimePrecisionAsMSecs(double aTime, int64_t aContextMixin,
                                           RTPCallerType aRTPCallerType);
  static double ReduceTimePrecisionAsMSecsRFPOnly(double aTime,
                                                  int64_t aContextMixin,
                                                  RTPCallerType aRTPCallerType);
  static double ReduceTimePrecisionAsSecs(double aTime, int64_t aContextMixin,
                                          RTPCallerType aRTPCallerType);
  static double ReduceTimePrecisionAsSecsRFPOnly(double aTime,
                                                 int64_t aContextMixin,
                                                 RTPCallerType aRTPCallerType);

  // Used by the JS Engine, as it doesn't know about the TimerPrecisionType enum
  static double ReduceTimePrecisionAsUSecsWrapper(double aTime, JSContext* aCx);

  // Public only for testing purposes
  static double ReduceTimePrecisionImpl(double aTime, TimeScale aTimeScale,
                                        double aResolutionUSec,
                                        int64_t aContextMixin,
                                        TimerPrecisionType aType);
  static nsresult RandomMidpoint(long long aClampedTimeUSec,
                                 long long aResolutionUSec,
                                 int64_t aContextMixin, long long* aMidpointOut,
                                 uint8_t* aSecretSeed = nullptr);

  // --------------------------------------------------------------------------

  // This method calculates the video resolution (i.e. height x width) based
  // on the video quality (480p, 720p, etc).
  static uint32_t CalculateTargetVideoResolution(uint32_t aVideoQuality);

  // Methods for getting spoofed media statistics and the return value will
  // depend on the video resolution.
  static uint32_t GetSpoofedTotalFrames(double aTime);
  static uint32_t GetSpoofedDroppedFrames(double aTime, uint32_t aWidth,
                                          uint32_t aHeight);
  static uint32_t GetSpoofedPresentedFrames(double aTime, uint32_t aWidth,
                                            uint32_t aHeight);

  // --------------------------------------------------------------------------

  // This method generates the spoofed value of User Agent.
  static void GetSpoofedUserAgent(nsACString& userAgent, bool isForHTTPHeader);

  // --------------------------------------------------------------------------

  /**
   * This method for getting spoofed modifier states for the given keyboard
   * event.
   *
   * @param aDoc           [in]  the owner's document for getting content
   *                             language.
   * @param aKeyboardEvent [in]  the keyboard event that needs to be spoofed.
   * @param aModifier      [in]  the modifier that needs to be spoofed.
   * @param aOut           [out] the spoofed state for the given modifier.
   * @return               true if there is a spoofed state for the modifier.
   */
  static bool GetSpoofedModifierStates(
      const mozilla::dom::Document* aDoc,
      const WidgetKeyboardEvent* aKeyboardEvent, const Modifiers aModifier,
      bool& aOut);

  /**
   * This method for getting spoofed code for the given keyboard event.
   *
   * @param aDoc           [in]  the owner's document for getting content
   *                             language.
   * @param aKeyboardEvent [in]  the keyboard event that needs to be spoofed.
   * @param aOut           [out] the spoofed code.
   * @return               true if there is a spoofed code in the fake keyboard
   *                       layout.
   */
  static bool GetSpoofedCode(const dom::Document* aDoc,
                             const WidgetKeyboardEvent* aKeyboardEvent,
                             nsAString& aOut);

  /**
   * This method for getting spoofed keyCode for the given keyboard event.
   *
   * @param aDoc           [in]  the owner's document for getting content
   *                             language.
   * @param aKeyboardEvent [in]  the keyboard event that needs to be spoofed.
   * @param aOut           [out] the spoofed keyCode.
   * @return               true if there is a spoofed keyCode in the fake
   *                       keyboard layout.
   */
  static bool GetSpoofedKeyCode(const mozilla::dom::Document* aDoc,
                                const WidgetKeyboardEvent* aKeyboardEvent,
                                uint32_t& aOut);

  // --------------------------------------------------------------------------

  // The method to generate the key for randomization. It can return nothing if
  // the session key is not available due to the randomization is disabled.
  static Maybe<nsTArray<uint8_t>> GenerateKey(nsIChannel* aChannel);

  // The method to add random noises to the image data based on the random key
  // of the given cookieJarSettings.
  static nsresult RandomizePixels(nsICookieJarSettings* aCookieJarSettings,
                                  uint8_t* aData, uint32_t aSize,
                                  mozilla::gfx::SurfaceFormat aSurfaceFormat);

  // --------------------------------------------------------------------------

 private:
  nsresult Init();

  nsRFPService() = default;

  ~nsRFPService() = default;

  void UpdateFPPOverrideList();
  void StartShutdown();

  void PrefChanged(const char* aPref);
  static void PrefChanged(const char* aPref, void* aSelf);

  static Maybe<RFPTarget> TextToRFPTarget(const nsAString& aText);

  // --------------------------------------------------------------------------

  static void MaybeCreateSpoofingKeyCodes(const KeyboardLangs aLang,
                                          const KeyboardRegions aRegion);
  static void MaybeCreateSpoofingKeyCodesForEnUS();

  static void GetKeyboardLangAndRegion(const nsAString& aLanguage,
                                       KeyboardLangs& aLocale,
                                       KeyboardRegions& aRegion);
  static bool GetSpoofedKeyCodeInfo(const mozilla::dom::Document* aDoc,
                                    const WidgetKeyboardEvent* aKeyboardEvent,
                                    SpoofingKeyboardCode& aOut);

  static nsTHashMap<KeyboardHashKey, const SpoofingKeyboardCode*>*
      sSpoofingKeyboardCodes;

  // --------------------------------------------------------------------------

  static TimerPrecisionType GetTimerPrecisionType(RTPCallerType aRTPCallerType);

  static TimerPrecisionType GetTimerPrecisionTypeRFPOnly(
      RTPCallerType aRTPCallerType);

  static void TypeToText(TimerPrecisionType aType, nsACString& aText);

  // --------------------------------------------------------------------------

  // A helper function to generate canvas key from the given image data and
  // randomization key.
  static nsresult GenerateCanvasKeyFromImageData(
      nsICookieJarSettings* aCookieJarSettings, uint8_t* aImageData,
      uint32_t aSize, nsTArray<uint8_t>& aCanvasKey);

  // Generate the session key if it hasn't been generated.
  nsresult EnsureSessionKey(bool aIsPrivate);
  void ClearSessionKey(bool aIsPrivate);

  // The keys that represent the browsing session. The lifetime of the key ties
  // to the browsing session. For normal windows, the key is generated when
  // loading the first http channel after the browser startup and persists until
  // the browser shuts down. For private windows, the key is generated when
  // opening a http channel on a private window and reset after all private
  // windows close, i.e. private browsing session ends.
  //
  // The key will be used to generate the randomization noise used to fiddle the
  // browser fingerprints. Note that this key lives and can only be accessed in
  // the parent process.
  Maybe<nsID> mBrowsingSessionKey;
  Maybe<nsID> mPrivateBrowsingSessionKey;
};

}  // namespace mozilla

#endif /* __nsRFPService_h__ */
