// 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 BASE_MEMORY_WRITABLE_SHARED_MEMORY_REGION_H_
#define BASE_MEMORY_WRITABLE_SHARED_MEMORY_REGION_H_

#include "base/base_export.h"
#include "base/check.h"
#include "base/memory/platform_shared_memory_region.h"
#include "base/memory/read_only_shared_memory_region.h"
#include "base/memory/shared_memory_mapping.h"
#include "base/memory/unsafe_shared_memory_region.h"
#include "build/build_config.h"

#include <stdint.h>

namespace base {

// Scoped move-only handle to a region of platform shared memory. The instance
// owns the platform handle it wraps. Mappings created by this region are
// writable. These mappings remain valid even after the region handle is moved
// or destroyed.
//
// This region can be locked to read-only access by converting it to a
// ReadOnlySharedMemoryRegion. However, unlike ReadOnlySharedMemoryRegion and
// UnsafeSharedMemoryRegion, ownership of this region (while writable) is unique
// and may only be transferred, not duplicated.
//
// Unlike ReadOnlySharedMemoryRegion and UnsafeSharedMemoryRegion,
// WritableSharedMemoryRegion doesn't provide GetPlatformHandle() method to
// ensure that the region is never duplicated while writable.
class BASE_EXPORT WritableSharedMemoryRegion {
 public:
  using MappingType = WritableSharedMemoryMapping;
  // Creates a new WritableSharedMemoryRegion instance of a given
  // size that can be used for mapping writable shared memory into the virtual
  // address space.
  static WritableSharedMemoryRegion Create(size_t size);
  using CreateFunction = decltype(Create);

  // Returns a WritableSharedMemoryRegion built from a platform handle that was
  // taken from another WritableSharedMemoryRegion instance. Returns an invalid
  // region iff the |handle| is invalid. CHECK-fails if the |handle| isn't
  // writable.
  // This should be used only by the code passing handles across process
  // boundaries.
  static WritableSharedMemoryRegion Deserialize(
      subtle::PlatformSharedMemoryRegion handle);

  // Extracts a platform handle from the region. Ownership is transferred to the
  // returned region object.
  // This should be used only for sending the handle from the current
  // process to another.
  static subtle::PlatformSharedMemoryRegion TakeHandleForSerialization(
      WritableSharedMemoryRegion region);

  // Makes the region read-only. No new writable mappings of the region can be
  // created after this call. Returns an invalid region on failure.
  static ReadOnlySharedMemoryRegion ConvertToReadOnly(
      WritableSharedMemoryRegion region);

  // Makes the region unsafe. The region cannot be converted to read-only after
  // this call. Returns an invalid region on failure.
  static UnsafeSharedMemoryRegion ConvertToUnsafe(
      WritableSharedMemoryRegion region);

  // Default constructor initializes an invalid instance.
  WritableSharedMemoryRegion();

  // Move operations are allowed.
  WritableSharedMemoryRegion(WritableSharedMemoryRegion&&);
  WritableSharedMemoryRegion& operator=(WritableSharedMemoryRegion&&);

  WritableSharedMemoryRegion(const WritableSharedMemoryRegion&) = delete;
  WritableSharedMemoryRegion& operator=(const WritableSharedMemoryRegion&) =
      delete;

  // Destructor closes shared memory region if valid.
  // All created mappings will remain valid.
  ~WritableSharedMemoryRegion();

  // Maps the shared memory region into the caller's address space with write
  // access. The mapped address is guaranteed to have an alignment of
  // at least |subtle::PlatformSharedMemoryRegion::kMapMinimumAlignment|.
  // Returns a valid WritableSharedMemoryMapping instance on success, invalid
  // otherwise. A custom |SharedMemoryMapper| for mapping (and later unmapping)
  // the region can be provided using the optional |mapper| parameter.
  WritableSharedMemoryMapping Map(SharedMemoryMapper* mapper = nullptr) const;

  // Similar to `Map()`, but maps only `size` bytes of the shared memory block
  // at byte `offset`. Returns an invalid mapping if requested bytes are out of
  // the region limits.
  //
  // `offset` does not need to be aligned; if `offset` is not a multiple of
  // `subtle::PlatformSharedMemoryRegion::kMapMinimumAlignment`, then the
  // returned mapping will not respect alignment either. Internally, `offset`
  // and `size` are still first adjusted to respect alignment when mapping in
  // the shared memory region, but the returned mapping will be "unadjusted" to
  // match the exact `offset` and `size` requested.
  WritableSharedMemoryMapping MapAt(uint64_t offset,
                                    size_t size,
                                    SharedMemoryMapper* mapper = nullptr) const;

  // Whether underlying platform handles are valid.
  bool IsValid() const;

  // Returns the maximum mapping size that can be created from this region.
  size_t GetSize() const {
    DCHECK(IsValid());
    return handle_.GetSize();
  }

  // Returns 128-bit GUID of the region.
  const UnguessableToken& GetGUID() const {
    DCHECK(IsValid());
    return handle_.GetGUID();
  }

#if BUILDFLAG(IS_WIN)
  // On Windows it is necessary in rare cases to take a writable handle from a
  // region that will be converted to read-only. On this platform it is a safe
  // operation, as the handle returned from this method will remain writable
  // after the region is converted to read-only. However, it breaks chromium's
  // WritableSharedMemoryRegion semantics and so should be use with care.
  HANDLE UnsafeGetPlatformHandle() const { return handle_.GetPlatformHandle(); }
#endif

 private:
  friend class SharedMemoryHooks;

  explicit WritableSharedMemoryRegion(
      subtle::PlatformSharedMemoryRegion handle);

  static void set_create_hook(CreateFunction* hook) { create_hook_ = hook; }

  static CreateFunction* create_hook_;

  subtle::PlatformSharedMemoryRegion handle_;
};

}  // namespace base

#endif  // BASE_MEMORY_WRITABLE_SHARED_MEMORY_REGION_H_
