/* Copyright 2017 - 2023 R. Thomas
 * Copyright 2017 - 2023 Quarkslab
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
#ifndef LIEF_MACHO_RELOCATION_FIXUP_H
#define LIEF_MACHO_RELOCATION_FIXUP_H
#include <ostream>
#include <memory>

#include "LIEF/visibility.h"
#include "LIEF/types.hpp"

#include "LIEF/MachO/Relocation.hpp"

namespace LIEF {
namespace MachO {

namespace details {
struct dyld_chained_ptr_arm64e_rebase;
struct dyld_chained_ptr_arm64e_auth_rebase;
struct dyld_chained_ptr_64_rebase;
struct dyld_chained_ptr_32_rebase;
}

class BinaryParser;
class Builder;

//! Class that represents a rebase relocation found in the LC_DYLD_CHAINED_FIXUPS command.
//!
//! This class extends LIEF::Relocation in which LIEF::Relocation::address is set to
//! the absolute virtual address where the relocation must take place (e.g. `0x10000d270`).
//!
//! On the other hand, RelocationFixup::target contains the value that should be
//! set at LIEF::Relocation::address if the imagebase is LIEF::Binary::imagebase (e.g. `0x1000073a8`).
//!
//! If the Mach-O loader chooses another base address (like 0x7ff100000), it must set
//! `0x10000d270` to `0x7ff1073a8`.
class LIEF_API RelocationFixup : public Relocation {

  friend class BinaryParser;
  friend class Builder;

  public:
  RelocationFixup() = delete;
  RelocationFixup(DYLD_CHAINED_PTR_FORMAT fmt, uint64_t imagebase);

  RelocationFixup& operator=(const RelocationFixup&);
  RelocationFixup(const RelocationFixup&);

  RelocationFixup& operator=(RelocationFixup&&);
  RelocationFixup(RelocationFixup&&);

  ~RelocationFixup() override;

  Relocation* clone() const override;

  //! Not relevant for this kind of relocation
  bool is_pc_relative() const override;

  //! Origin of the relocation. For this concrete object, it
  //! should be RELOCATION_ORIGINS::ORIGIN_CHAINED_FIXUPS
  RELOCATION_ORIGINS origin() const override;

  inline DYLD_CHAINED_PTR_FORMAT ptr_format() const {
    return ptr_fmt_;
  }

  //! The value that should be set at LIEF::Relocation::address
  //! if the imagebase is LIEF::Binary::imagebase. Otherwise, it should
  //! be: target() - LIEF::Binary::imagebase() + new_imagebase
  uint64_t target() const;
  void target(uint64_t target);

  //! Not relevant for this kind of relocation
  void pc_relative(bool) override;

  inline uint32_t offset() const {
    return offset_;
  }

  inline void offset(uint32_t offset) {
    offset_ = offset;
  }

  //! The address of this relocation is bound to its offset.
  uint64_t address() const override;

  //! Changing the address means changing the offset
  void address(uint64_t address) override;

  bool operator==(const RelocationFixup& rhs) const;
  bool operator!=(const RelocationFixup& rhs) const;

  void accept(Visitor& visitor) const override;

  static bool classof(const Relocation& r);

  std::ostream& print(std::ostream& os) const override;

  private:
  enum class REBASE_TYPES {
    UNKNOWN = 0,

    ARM64E_REBASE,
    ARM64E_AUTH_REBASE,
    PTR64_REBASE,
    PTR32_REBASE,
  };

  void set(const details::dyld_chained_ptr_arm64e_rebase& fixup);
  void set(const details::dyld_chained_ptr_arm64e_auth_rebase& fixup);
  void set(const details::dyld_chained_ptr_64_rebase& fixup);
  void set(const details::dyld_chained_ptr_32_rebase& fixup);

  DYLD_CHAINED_PTR_FORMAT ptr_fmt_;
  uint64_t imagebase_ = 0;
  uint32_t offset_ = 0;

  REBASE_TYPES rtypes_ = REBASE_TYPES::UNKNOWN;

  union {
    details::dyld_chained_ptr_arm64e_rebase*      arm64_rebase_ = nullptr;
    details::dyld_chained_ptr_arm64e_auth_rebase* arm64_auth_rebase_;
    details::dyld_chained_ptr_64_rebase*          p64_rebase_;
    details::dyld_chained_ptr_32_rebase*          p32_rebase_;
  };
};

}
}
#endif
