// Copyright (C) 2023-2024 Internet Systems Consortium, Inc. ("ISC")
//
// 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 CHECK_EXISTS_REMOVE_H
#define CHECK_EXISTS_REMOVE_H

/// @file check_exists_remove.h This file defines the class CheckExistsRemoveTransaction.

#include <d2srv/nc_trans.h>

namespace isc {
namespace d2 {

/// @brief Thrown if the CheckExistsRemoveTransaction encounters a general error.
class CheckExistsRemoveTransactionError : public isc::Exception {
public:
    CheckExistsRemoveTransactionError(const char* file, size_t line,
                               const char* what) :
        isc::Exception(file, line, what) { }
};

/// @brief Embodies the "life-cycle" required to carry out a DDNS Remove update.
///
/// CheckExistsRemoveTransaction implements a state machine for removing a forward
/// and/or reverse DNS mappings. The logic may be paraphrased as follows:
///
/// @code
///
/// If the request includes a forward change:
///     Select a forward server
///     Send the server a request to remove client's specific forward address RR
///     (The prerequisite in the request specifies DHCPID exists instead of checking that
///      DHCID matches exactly)
///     If it succeeds or the server responds with name no longer in use
///         Send a server a request to delete any DHCID RRs for that name.
///         (The prerequisite in the request specifies that there are no A and AAAA records for that name)
///     otherwise
///         abandon the update
///
/// If the request includes a reverse change:
///     Select a reverse server
///     Send a server a request to delete reverse entry (PTR RR)
///
/// @endcode
///
/// This class derives from NameChangeTransaction from which it inherits
/// states, events, and methods common to NameChangeRequest processing.
class CheckExistsRemoveTransaction : public NameChangeTransaction {
public:

    //@{  Additional states needed for NameRemove state model.
    /// @brief State that attempts to remove specific forward address record.
    static const int REMOVING_FWD_ADDRS_ST = NCT_DERIVED_STATE_MIN + 1;

    /// @brief State that attempts to remove any other forward RRs for the DHCID
    static const int REMOVING_FWD_RRS_ST = NCT_DERIVED_STATE_MIN + 2;

    /// @brief State that attempts to remove reverse PTR records
    static const int REMOVING_REV_PTRS_ST = NCT_DERIVED_STATE_MIN + 3;
    //@}

    //@{ Additional events needed for NameRemove state model.
    /// @brief Event sent when replace attempt to fails with address not in use.
    /// @todo Currently none have been identified.
    //@}

    /// @brief Constructor
    ///
    /// Instantiates an Remove transaction that is ready to be started.
    ///
    /// @param io_service IO service to be used for IO processing
    /// @param ncr is the NameChangeRequest to fulfill
    /// @param forward_domain is the domain to use for forward DNS updates
    /// @param reverse_domain is the domain to use for reverse DNS updates
    /// @param cfg_mgr pointer to the configuration manager
    ///
    /// @throw CheckExistsRemoveTransaction error if given request is not a CHG_REMOVE,
    /// NameChangeTransaction error for base class construction errors.
    CheckExistsRemoveTransaction(asiolink::IOServicePtr& io_service,
                          dhcp_ddns::NameChangeRequestPtr& ncr,
                          DdnsDomainPtr& forward_domain,
                          DdnsDomainPtr& reverse_domain,
                          D2CfgMgrPtr& cfg_mgr);

    /// @brief Destructor
    virtual ~CheckExistsRemoveTransaction();

protected:
    /// @brief Adds events defined by CheckExistsRemoveTransaction to the event set.
    ///
    /// Invokes NameChangeTransaction's implementation and then defines the
    /// events unique to NCR Remove transaction processing.
    ///
    /// @throw StateModelError if an event definition is invalid or a duplicate.
    virtual void defineEvents();

    /// @brief Validates the contents of the set of events.
    ///
    /// Invokes NameChangeTransaction's implementation and then verifies the
    /// Remove transaction's events. This tests that the needed events are in
    /// the event dictionary.
    ///
    /// @throw StateModelError if an event value is undefined.
    virtual void verifyEvents();

    /// @brief Adds states defined by CheckExistsRemoveTransaction to the state set.
    ///
    /// Invokes NameChangeTransaction's implementation and then defines the
    /// states unique to NCR Remove transaction processing.
    ///
    /// @throw StateModelError if an state definition is invalid or a duplicate.
    virtual void defineStates();

    /// @brief Validates the contents of the set of states.
    ///
    /// Invokes NameChangeTransaction's implementation and then verifies the
    /// Remove transaction's states.  This tests that the needed states are in
    /// the state dictionary.
    ///
    /// @throw StateModelError if an event value is undefined.
    virtual void verifyStates();

    /// @brief State handler for READY_ST.
    ///
    /// Entered from:
    /// - INIT_ST with next event of START_EVT
    ///
    /// The READY_ST is the state the model transitions into when the inherited
    /// method, startTransaction() is invoked.  This handler, therefore, is the
    /// entry point into the state model execution.  Its primary task is to
    /// determine whether to start with a forward DNS change or a reverse DNS
    /// change.
    ///
    /// Transitions to:
    /// - SELECTING_FWD_SERVER_ST with next event of SERVER_SELECT_ST if request
    /// includes a forward change.
    ///
    /// - SELECTING_REV_SERVER_ST with next event of SERVER_SELECT_ST if request
    /// includes only a reverse change.
    ///
    /// @throw CheckExistsRemoveTransactionError if upon entry next event is not
    /// START_EVT.
    void readyHandler();

    /// @brief State handler for SELECTING_FWD_SERVER_ST.
    ///
    /// Entered from:
    /// - READY_ST with next event of SELECT_SERVER_EVT
    /// - REMOVING_FWD_ADDRS_ST with next event of SERVER_IO_ERROR_EVT
    ///
    /// Selects the server to be used from the forward domain for the forward
    /// DNS update.  If next event is SELECT_SERVER_EVT the handler initializes
    /// the forward domain's server selection mechanism and then attempts to
    /// select the next server. If next event is SERVER_IO_ERROR_EVT then the
    /// handler simply attempts to select the next server.
    ///
    /// Transitions to:
    /// - REMOVING_FWD_ADDRS_ST with next event of SERVER_SELECTED upon
    /// successful server selection
    ///
    /// - PROCESS_TRANS_FAILED with next event of NO_MORE_SERVERS_EVT upon
    /// failure to select a server
    ///
    /// @throw CheckExistsRemoveTransactionError if upon entry next event is not
    /// SELECT_SERVER_EVT or SERVER_IO_ERROR_EVT.
    void selectingFwdServerHandler();

    /// @brief State handler for SELECTING_REV_SERVER_ST.
    ///
    /// Entered from:
    /// - READY_ST with next event of SELECT_SERVER_EVT
    /// - REMOVING_FWD_RRS_ST with next event of SELECT_SERVER_EVT
    /// - REMOVING_REV_PTRS_ST with next event of SERVER_IO_ERROR_EVT
    ///
    /// Selects the server to be used from the reverse domain for the reverse
    /// DNS update.  If next event is SELECT_SERVER_EVT the handler initializes
    /// the reverse domain's server selection mechanism and then attempts to
    /// select the next server. If next event is SERVER_IO_ERROR_EVT then the
    /// handler simply attempts to select the next server.
    ///
    /// Transitions to:
    /// - REMOVING_REV_PTRS_ST with next event of SERVER_SELECTED upon
    /// successful server selection
    ///
    /// - PROCESS_TRANS_FAILED with next event of NO_MORE_SERVERS_EVT upon
    /// failure to select a server
    ///
    /// @throw CheckExistsRemoveTransactionError if upon entry next event is not
    /// SELECT_SERVER_EVT or SERVER_IO_ERROR_EVT.
    void selectingRevServerHandler();

    /// @brief State handler for REMOVING_FWD_ADDRS_ST.
    ///
    /// Entered from:
    /// - SELECTING_FWD_SERVER with next event of SERVER_SELECTED_EVT
    ///
    /// Attempts to remove the forward DNS entry for a given FQDN, provided
    /// a DHCID RR exists which matches the requesting DHCID.  If this is
    /// first invocation of the handler after transitioning into this state,
    /// any previous update request context is deleted.   If next event
    /// is SERVER_SELECTED_EVT, the handler builds the forward remove request,
    /// schedules an asynchronous send via sendUpdate(), and returns.  Note
    /// that sendUpdate will post NOP_EVT as next event.
    ///
    /// Posting the NOP_EVT will cause runModel() to suspend execution of
    /// the state model thus affecting a "wait" for the update IO to complete.
    /// Update completion occurs via the DNSClient callback operator() method
    /// inherited from NameChangeTransaction.  When invoked this callback will
    /// post a next event of IO_COMPLETED_EVT and then invoke runModel which
    /// resumes execution of the state model.
    ///
    /// When the handler is invoked with a next event of IO_COMPLETED_EVT,
    /// the DNS update status is checked and acted upon accordingly:
    ///
    /// Transitions to:
    /// - REMOVING_FWD_RRS_ST with next event of UPDATE_OK_EVT upon successful
    /// removal or RCODE of indication FQDN is no longer in use (NXDOMAIN).
    ///
    /// - PROCESS_TRANS_FAILED_ST with next event of UPDATE_FAILED_EVT if the
    /// DNS server rejected the update for any reason or the IO completed
    /// with an unrecognized status.
    ///
    /// - RE-ENTER this state with next event of SERVER_SELECTED_EVT if
    /// there was an IO error communicating with the server and the number of
    /// per server retries has not been exhausted.
    ///
    /// - SELECTING_FWD_SERVER_ST with next event of SERVER_IO_ERROR_EVT if
    /// there was an IO error communicating with the server and the number of
    /// per server retries has been exhausted.
    ///
    /// @throw CheckExistsRemoveTransactionError if upon entry next event is not
    /// SERVER_SELECTED_EVT or IO_COMPLETE_EVT
    void removingFwdAddrsHandler();

    /// @brief State handler for REMOVING_FWD_RRS_ST.
    ///
    /// Entered from:
    /// - REMOVING_FWD_ADDRS_ST with next event of UPDATE_OK_EVT
    ///
    /// Attempts to delete any remaining RRs associated with the given FQDN
    /// such as the DHCID RR.  If this is first invocation of the handler after
    /// transitioning into this state, any previous update request context is
    /// deleted and the handler builds the forward remove request. It then
    /// schedules an asynchronous send via sendUpdate(),
    /// and returns.  Note that sendUpdate will post NOP_EVT as the next event.
    ///
    /// Posting the NOP_EVT will cause runModel() to suspend execution of
    /// the state model thus affecting a "wait" for the update IO to complete.
    /// Update completion occurs via the DNSClient callback operator() method
    /// inherited from NameChangeTransaction.  When invoked this callback will
    /// post a next event of IO_COMPLETED_EVT and then invoke runModel which
    /// resumes execution of the state model.
    ///
    /// When the handler is invoked with a next event of IO_COMPLETED_EVT,
    /// the DNS update status is checked and acted upon accordingly:
    ///
    /// Transitions to:
    /// - SELECTING_REV_SERVER_ST with a next event of SELECT_SERVER_EVT upon
    /// successful completion and the request includes a reverse DNS update.
    ///
    /// - PROCESS_TRANS_OK_ST with next event of UPDATE_OK_EVT upon successful
    /// completion and the request does not include a reverse DNS update.
    ///
    /// - PROCESS_TRANS_FAILED_ST with a next event of UPDATE_FAILED_EVT if the
    /// DNS server rejected the update for any other reason or the IO completed
    /// with an unrecognized status.
    ///
    /// - RE-ENTER this state with a next event of SERVER_SELECTED_EVT if
    /// there was an IO error communicating with the server and the number of
    /// per server retries has not been exhausted.
    ///
    /// - PROCESS_TRANS_FAILED_ST with a next event of SERVER_IO_ERROR_EVT if
    /// there we have reached maximum number of retries without success on the
    /// current server.
    ///
    /// @note If we exhaust the IO retries for the current server due to IO
    /// failures, we will abort the remaining updates.  The rational is that
    /// we are only in this state, if the remove of the forward address RR
    /// succeeded (removingFwdAddrsHandler) on the current server so we should
    /// not attempt another removal on a different server.  This is perhaps a
    /// point for discussion. @todo Should we go ahead with the reverse remove?
    ///
    /// @throw CheckExistsRemoveTransactionError if upon entry next event is not:
    /// UPDATE_OK_EVT or IO_COMPLETE_EVT
    void removingFwdRRsHandler();

    /// @brief State handler for REMOVING_REV_PTRS_ST.
    ///
    /// Entered from:
    /// - SELECTING_REV_SERVER_ST with a next event of SERVER_SELECTED_EVT
    ///
    /// Attempts to delete a reverse DNS entry for a given FQDN. If this is
    /// first invocation of the handler after transitioning into this state,
    /// any previous update request context is deleted.  If next event is
    /// SERVER_SELECTED_EVT, the handler builds the reverse remove request,
    /// schedules an asynchronous send via sendUpdate(), and then returns.
    /// Note that sendUpdate will post NOP_EVT as next event.
    ///
    /// Posting the NOP_EVT will cause runModel() to suspend execution of
    /// the state model thus affecting a "wait" for the update IO to complete.
    /// Update completion occurs via the DNSClient callback operator() method
    /// inherited from NameChangeTransaction.  When invoked this callback will
    /// post a next event of IO_COMPLETED_EVT and then invoke runModel which
    /// resumes execution of the state model.
    ///
    /// When the handler is invoked with a next event of IO_COMPLETED_EVT,
    /// the DNS update status is checked and acted upon accordingly:
    ///
    /// Transitions to:
    /// - PROCESS_TRANS_OK_ST with a next event of UPDATE_OK_EVT upon
    /// successful completion.
    ///
    /// - PROCESS_TRANS_FAILED_ST with a next event of UPDATE_FAILED_EVT If the
    /// DNS server rejected the update for any reason or the IO completed
    /// with an unrecognized status.
    ///
    /// - RE-ENTER this state with a next event of SERVER_SELECTED_EVT if
    /// there was an IO error communicating with the server and the number of
    /// per server retries has not been exhausted.
    ///
    /// - SELECTING_REV_SERVER_ST with next event of SERVER_IO_ERROR_EVT if
    /// there was an IO error communicating with the server and the number of
    /// per server retries has been exhausted.
    ///
    /// @throw CheckExistsRemoveTransactionError if upon entry next event is not:
    /// SERVER_SELECTED_EVT or IO_COMPLETED_EVT
    void removingRevPtrsHandler();

    /// @brief State handler for PROCESS_TRANS_OK_ST.
    ///
    /// Entered from:
    /// - REMOVING_FWD_RRS_ST with a next event of UPDATE_OK_EVT
    /// - REMOVING_REV_PTRS_ST with a next event of UPDATE_OK_EVT
    ///
    /// Sets the transaction action status to indicate success and ends
    /// model execution.
    ///
    /// Transitions to:
    /// - END_ST with a next event of END_EVT.
    ///
    /// @throw CheckExistsRemoveTransactionError if upon entry next event is not:
    /// UPDATE_OK_EVT
    void processRemoveOkHandler();

    /// @brief State handler for PROCESS_TRANS_FAILED_ST.
    ///
    /// Entered from:
    /// - SELECTING_FWD_SERVER_ST with a next event of NO_MORE_SERVERS
    /// - REMOVING_FWD_ADDRS_ST with a next event of UPDATE_FAILED_EVT
    /// - REMOVING_FWD_RRS_ST with a next event of UPDATE_FAILED_EVT
    /// - REMOVING_FWD_RRS_ST with a next event of SERVER_IO_ERROR_EVT
    /// - SELECTING_REV_SERVER_ST with a next event of NO_MORE_SERVERS
    /// - REMOVING_REV_PTRS_ST with a next event of UPDATE_FAILED_EVT
    ///
    /// Sets the transaction status to indicate failure and ends
    /// model execution.
    ///
    /// Transitions to:
    /// - END_ST with a next event of FAIL_EVT.
    ///
    /// @throw CheckExistsRemoveTransactionError if upon entry next event is not:
    /// UPDATE_FAILED_EVT
    void processRemoveFailedHandler();

    /// @brief Builds a DNS request to remove a forward DNS address for a FQDN.
    ///
    /// Constructs a DNS update request, based upon the NCR, for removing a
    /// forward DNS address mapping. Once constructed, the request is stored as
    /// the transaction's DNS update request.
    ///
    /// Prerequisite RRsets:
    /// 1. An assertion that a DHCID RR exists for that FQDN.
    ///
    /// Updates RRsets:
    /// 1. A delete of the FQDN/IP RR (type A for IPv4, AAAA for IPv6)
    ///
    /// @throw This method does not throw but underlying methods may.
    void buildRemoveFwdAddressRequest();

    /// @brief Builds a DNS request to remove all forward DNS RRs for a FQDN.
    ///
    /// Constructs a DNS update request, based upon the NCR, for removing any
    /// remaining DHCID RR, once all A or AAAA entries for the FQDN
    /// have been removed. Once constructed, the request is stored as the
    /// transaction's DNS update request.
    ///
    /// Prerequisite RRsets:
    /// 1. An assertion that no A RRs for the FQDN exist
    /// 2. An assertion that no AAAA RRs for the FQDN exist
    ///
    /// Updates RRsets:
    /// 1. A delete of any DHCID RR for the FQDN
    ///
    /// @throw This method does not throw but underlying methods may.
    void buildRemoveFwdRRsRequest();

    /// @brief Builds a DNS request to remove a reverse DNS entry for a FQDN
    ///
    /// Constructs a DNS update request, based upon the NCR, for removing a
    /// reverse DNS mapping.  Once constructed, the request is stored as
    /// the transaction's DNS update request.
    ///
    /// The request content is adherent to RFC 4703 section 5.5, paragraph 2:
    ///
    /// Prerequisite RRsets:
    /// 1. An assertion that a PTR record matching the client's FQDN exists.
    ///
    /// Updates RRsets:
    /// 1. A delete of all RRs for the FQDN
    ///
    /// @throw This method does not throw but underlying methods may.
    void buildRemoveRevPtrsRequest();
};

/// @brief Defines a pointer to a CheckExistsRemoveTransaction.
typedef boost::shared_ptr<CheckExistsRemoveTransaction> CheckExistsRemoveTransactionPtr;


} // namespace isc::d2
} // namespace isc
#endif
