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

#ifndef COMPONENTS_VIZ_COMMON_SURFACES_LOCAL_SURFACE_ID_H_
#define COMPONENTS_VIZ_COMMON_SURFACES_LOCAL_SURFACE_ID_H_

#include <inttypes.h>

#include <compare>
#include <iosfwd>
#include <limits>
#include <string>

#include "base/unguessable_token.h"
#include "components/viz/common/viz_common_export.h"
#include "mojo/public/cpp/bindings/struct_traits.h"

namespace perfetto {
template <typename MessageType>
class TracedProto;
namespace protos::pbzero {
class LocalSurfaceId;
}
}  // namespace perfetto

namespace viz {
namespace mojom {
class LocalSurfaceIdDataView;
}

class ParentLocalSurfaceIdAllocator;
class ChildLocalSurfaceIdAllocator;

constexpr uint32_t kInvalidParentSequenceNumber = 0;
constexpr uint32_t kInvalidChildSequenceNumber = 0;
constexpr uint32_t kInitialParentSequenceNumber = 1;
constexpr uint32_t kInitialChildSequenceNumber = 1;
constexpr uint32_t kMaxParentSequenceNumber =
    std::numeric_limits<uint32_t>::max();
constexpr uint32_t kMaxChildSequenceNumber =
    std::numeric_limits<uint32_t>::max();

// This struct is the part of SurfaceId that can be modified by the client.
// LocalSurfaceId uniquely identifies a surface among the surfaces created by a
// particular client. A SurfaceId, which is FrameSinkId+LocalSurfaceId, uniquely
// identifies a surface globally across all clients.
//
// LocalSurfaceId consists of:
//
// - parent_sequence_number: This part is incremented by the embedder of the
//   client.
//
// - child_sequence_number: This part is incremented by the client itself.
//
// - embed_token: An UnguessableToken generated by the embedder. The purpose of
//   this value is to make SurfaceIds unguessable, because FrameSinkIds and
//   LocalSurfaceIds are otherwise predictable and clients might exploit this
//   fact to embed surfaces they're not allowed to. This value is generated once
//   by ParentLocalSurfaceIdAllocator and remains constant during the lifetime
//   of the embedding, even if a new LocalSurfaceId is generated for the
//   embedded client because of some change in its state (e.g. size,
//   device scale factor, etc.), or for other reasons. If a client is
//   re-parented, then the new parent allocates a new LocalSurfaceId, with a new
//   embed token, and communicates that to the embedded client.
//
// The embedder uses ParentLocalSurfaceIdAllocator to generate LocalSurfaceIds
// for the embedee. If Surface Synchronization is on, the embedee uses
// ChildLocalSurfaceIdAllocator to generate LocalSurfaceIds for itself. If
// Surface Synchronization is off, the embedee also uses
// ParentLocalSurfaceIdAllocator, as the parent doesn't generate LocalSurfaceIds
// for the child.
class VIZ_COMMON_EXPORT LocalSurfaceId {
 public:
  constexpr LocalSurfaceId()
      : parent_sequence_number_(kInvalidParentSequenceNumber),
        child_sequence_number_(kInvalidChildSequenceNumber) {}

  constexpr LocalSurfaceId(const LocalSurfaceId& other) = default;
  constexpr LocalSurfaceId& operator=(const LocalSurfaceId& other) = default;

  constexpr LocalSurfaceId(uint32_t parent_sequence_number,
                           const base::UnguessableToken& embed_token)
      : parent_sequence_number_(parent_sequence_number),
        child_sequence_number_(kInitialChildSequenceNumber),
        embed_token_(embed_token) {}

  constexpr LocalSurfaceId(uint32_t parent_sequence_number,
                           uint32_t child_sequence_number,
                           const base::UnguessableToken& embed_token)
      : parent_sequence_number_(parent_sequence_number),
        child_sequence_number_(child_sequence_number),
        embed_token_(embed_token) {}

  static constexpr LocalSurfaceId MaxSequenceId() {
    return LocalSurfaceId(kMaxParentSequenceNumber, kMaxChildSequenceNumber,
                          base::UnguessableToken());
  }

  constexpr bool is_valid() const {
    return parent_sequence_number_ != kInvalidParentSequenceNumber &&
           child_sequence_number_ != kInvalidChildSequenceNumber &&
           !embed_token_.is_empty();
  }

  constexpr uint32_t parent_sequence_number() const {
    return parent_sequence_number_;
  }

  constexpr uint32_t child_sequence_number() const {
    return child_sequence_number_;
  }

  constexpr const base::UnguessableToken& embed_token() const {
    return embed_token_;
  }

  // The |embed_trace_id| is used as the id for trace events associated with
  // embedding this LocalSurfaceId.
  uint64_t embed_trace_id() const { return persistent_hash() << 1; }

  // The |submission_trace_id| is used as the id for trace events associated
  // with submission of a CompositorFrame to a surface with this LocalSurfaceId.
  uint64_t submission_trace_id() const { return (persistent_hash() << 1) | 1; }

  friend std::strong_ordering operator<=>(const LocalSurfaceId&,
                                          const LocalSurfaceId&) = default;

  // This implementation is fast and appropriate for a hash table lookup.
  // However the hash differs per process, and is inappropriate for tracing.
  size_t hash() const;

  // This implementation is slow and not appropriate for a hash table lookup.
  // However the hash is consistent across processes and can be used for
  // tracing.
  size_t persistent_hash() const;

  std::string ToString() const;

  // Returns whether this LocalSurfaceId was generated after |other|. In the
  // case where both `this` and `other` have advanced separate sequences, then
  // this will return false.
  bool IsNewerThan(const LocalSurfaceId& other) const;

  // Returns whether this LocalSurfaceId was generated after `other`. Same as
  // IsNewerThan but ignores whether the embedding token is different.
  bool IsNewerThanIgnoringEmbedToken(const LocalSurfaceId& other) const;

  // Returns whether this LocalSurfaceId was generated after |other|. In the
  // case where both `this` and `other` have advanced separate sequences, then
  // this will return false. In the case where `embed_token_` has changed, this
  // will return true.
  bool IsNewerThanOrEmbeddingChanged(const LocalSurfaceId& other) const;

  // Returns whether this LocalSurfaceId was generated after |other| or equal to
  // it.
  bool IsSameOrNewerThan(const LocalSurfaceId& other) const;

  // Returns the smallest valid LocalSurfaceId with the same embed token as this
  // LocalSurfaceID.
  LocalSurfaceId ToSmallestId() const;

  using TraceProto = perfetto::protos::pbzero::LocalSurfaceId;
  void WriteIntoTrace(perfetto::TracedProto<TraceProto> proto) const;

 private:
  friend struct mojo::StructTraits<mojom::LocalSurfaceIdDataView,
                                   LocalSurfaceId>;
  friend class ParentLocalSurfaceIdAllocator;
  friend class ChildLocalSurfaceIdAllocator;


  uint32_t parent_sequence_number_;
  uint32_t child_sequence_number_;
  base::UnguessableToken embed_token_;
};

VIZ_COMMON_EXPORT std::ostream& operator<<(
    std::ostream& out,
    const LocalSurfaceId& local_surface_id);

}  // namespace viz

#endif  // COMPONENTS_VIZ_COMMON_SURFACES_LOCAL_SURFACE_ID_H_
