/*
 *
 *  Copyright (C) 2009-2025, OFFIS e.V.
 *  All rights reserved.  See COPYRIGHT file for details.
 *
 *  This software and supporting documentation were developed by
 *
 *    OFFIS e.V.
 *    R&D Division Health
 *    Escherweg 2
 *    D-26121 Oldenburg, Germany
 *
 *
 *  Module:  dcmnet
 *
 *  Author:  Michael Onken
 *
 *  Purpose: General SCP class that can be used to implement derived SCP
 *           applications.
 *
 */

#ifndef SCP_H
#define SCP_H

#include "dcmtk/config/osconfig.h" /* make sure OS specific configuration is included first */

#include "dcmtk/dcmdata/dctk.h" /* Covers most common dcmdata classes */
#include "dcmtk/dcmnet/assoc.h"
#include "dcmtk/dcmnet/dimse.h"  /* DIMSE network layer */
#include "dcmtk/dcmnet/diutil.h" /* for DCMNET_WARN() */
#include "dcmtk/dcmnet/scpcfg.h"
#include "dcmtk/oflog/oflog.h"

// include this file in doxygen documentation

/** @file scp.h
 *  @brief general Service Class Provider (SCP) class
 */

/** Structure representing single process in multi-process mode
 */
struct DCMTK_DCMNET_EXPORT DcmProcessSlotType
{
    /// Name of peer
    DIC_NODENAME peerName;
    /// Calling AE title
    DIC_AE callingAETitle;
    /// Called AE title
    DIC_AE calledAETitle;
    /// Process ID
    int processId;
    /// Start time
    time_t startTime;
    /// Indicator if process has storage ability
    OFBool hasStorageAbility;
};

/** Action codes that can be given to DcmSCP to control behavior during SCP's operation.
 *  Different hooks permit jumping into different phases of SCP operation.
 */
enum DcmSCPActionType
{
    /// No action defined
    DCMSCP_ACTION_UNDEFINED,
    /// Tell SCP to refuse association
    DCMSCP_ACTION_REFUSE_ASSOCIATION
};

/** Codes denoting a reason for refusing an association
 */
enum DcmRefuseReasonType
{
    /// Too many associations (SCP cannot handle a further association)
    DCMSCP_TOO_MANY_ASSOCIATIONS,
    /// Forking a new SCP process failed
    DCMSCP_CANNOT_FORK,
    /// Refusing association because of bad application context name
    DCMSCP_BAD_APPLICATION_CONTEXT_NAME,
    /// Refusing association because of disallowed connecting host
    DCMSCP_CALLING_HOST_NOT_ALLOWED,
    /// Refusing association because of unaccepted called AE title
    DCMSCP_CALLED_AE_TITLE_NOT_RECOGNIZED,
    /// Refusing association because of unaccepted calling AE title
    DCMSCP_CALLING_AE_TITLE_NOT_RECOGNIZED,
    /// Refusing association because SCP was forced to do so
    DCMSCP_FORCED,
    /// Refusing association because of missing Implementation Class UID
    DCMSCP_NO_IMPLEMENTATION_CLASS_UID,
    /// Refusing association because of no acceptable Presentation Contexts
    DCMSCP_NO_PRESENTATION_CONTEXTS,
    /// Refusing association because of internal error
    DCMSCP_INTERNAL_ERROR
};

/** Structure representing a single Presentation Context. Fields "reserved" and "result"
 *  not included from DUL_PRESENTATIONCONTEXT, which served as the blueprint for this
 *  structure.
 */
struct DCMTK_DCMNET_EXPORT DcmPresentationContextInfo
{
    DcmPresentationContextInfo()
        : presentationContextID(0)
        , abstractSyntax()
        , proposedSCRole(DUL_SC_ROLE_NONE)
        , acceptedSCRole(DUL_SC_ROLE_NONE)
        , acceptedTransferSyntax()
    {
    }

    /// Presentation Context ID as proposed by SCU
    Uint8 presentationContextID;
    /// Abstract Syntax name (UID) as proposed by SCU
    OFString abstractSyntax;
    /// SCP role as proposed from SCU
    DUL_SC_ROLE proposedSCRole;
    /// Role accepted by SCP for this Presentation Context
    DUL_SC_ROLE acceptedSCRole;
    /// Transfer Syntax accepted for this Presentation Context (UID)
    OFString acceptedTransferSyntax;
    // Fields "reserved" and "result" not included from DUL_PRESENTATIONCONTEXT
};

/** Base class for implementing a DICOM Service Class Provider (SCP). Derived classes can
 *  add the presentation contexts they want to support, set further parameters (port, peer
 *  host name, etc. as desired) and then call DcmSCP's listen() method to start the server.
 *  For incoming associations and DIMSE messages, a derived class can define the behavior
 *  of the server.
 *  The DcmSCP base class does not support any presentation contexts per default.
 *  In particular the Verification SOP class which every SCP must support,
 *  is not added automatically in order to give the user full control over the
 *  supported list of presentation contexts. However, if this class should negotiate
 *  Verification, call setEnableVerification(). In that case DcmSCP will also
 *  respond to related C-ECHO requests. Note that this cannot be reverted.
  */
class DCMTK_DCMNET_EXPORT DcmSCP
{

public:
    /** Constructor. Initializes internal member variables.
     */
    DcmSCP();

    /** Virtual destructor, frees internal memory.
     */
    virtual ~DcmSCP();

    /** Starts providing the implemented services to SCUs.
     *  After calling this method the SCP is listening for connection requests.
     *  @return The result. Per default, the method only returns in case of fatal errors.
     *          However, there are ways to stop listening in a controlled way:
     *          <ul>
     *          <li>In non-blocking mode, use stopAfterConnectionTimeout() in order
     *          shut down after the TCP timeout set with setConnectionTimeout() has
     *          occurred. In that case, the method returns with NET_EC_StopAfterConnectionTimeout.</li>
     *          <li>In non-blocking and blocking mode, stopAfterCurrentAssociation() can
     *          be used to return after an association has been handled and ended.
     *          In that case, NET_EC_StopAfterAssociation is returned.</li>
     *          </ul>
     *         Other error codes include
     *          <ul>
     *          <li>NET_EC_InvalidSCPAssociationProfile: Returned if the SCP's presentation
     *          context information is invalid (e.g. no presentation contexts have
     *          been added).
     *          </li>
     *          <li>NET_EC_InsufficientPortPrivileges: Returned if the SCP is not
     *          allowed to open the specified TCP port for listening. The reason
     *          may be that you try to open a port number below 1024 on a Unix-like
     *          system as non-root user.
     *          <li>EC_setuidFailed: Returned (on Unix-like systems) if the DcmSCP
     *          was not able to drop root privileges.
     *          </ul>
     */
    virtual OFCondition listen();

    /** Alternative interface to providing the implemented services to SCUs.
     *  This method opens the TCP port for incoming connections, drops root privileges
     *  if necessary and then returns. The caller can then perform short other operations
     *  and finally call acceptAssociations(). If any SCU tries to connect between
     *  this method and the call to acceptAssociations(), they are placed on the
     *  TCP listen backlog and will be handled by acceptAssociations() unless they
     *  time out or the backlog overflows (the size of the backlog is defined by the
     *  PRV_LISTENBACKLOG macro).
     *  @return EC_Normal if successful, an error code otherwise.
     */
    virtual OFCondition openListenPort();

    /** Alternative interface to providing the implemented services to SCUs.
     *  This method should be called after a call to openListenPort().
     *  @return The result. Per default, the method only returns in case of fatal errors.
     *    See documentation of listen() method on how to stop listening in a controlled way.
     */
    virtual OFCondition acceptAssociations();

    /* ************************************************************* */
    /*             Set methods for configuring SCP behavior          */
    /* ************************************************************* */

    /** Enables negotiation of the Verification SOP Class. It adds the Verification
     *  SOP Class to the list of supported abstract syntaxes for the given profile.
     *  All uncompressed transfer syntaxes are supported. If Verification SOP
     *  class is added here, DcmSCP will respond to related C-ECHO requests. Note
     *  that this cannot be reverted.
     *  The default behavior of DcmSCP is not to support any SOP Class at all.
     *  @param profile [in] The profile Verification SOP Class should
     *                 be added to. The default is to add it to the
     *                 DcmSCP's internal standard profile called
     *                 "DEFAULT".
     *  @return EC_Normal if Verification SOP Class could be added,
     *          error otherwise.
     */
    OFCondition setEnableVerification(const OFString& profile = "DEFAULT");

    /** Add abstract syntax to presentation contexts the SCP is able to negotiate with SCUs.
     *  @param abstractSyntax [in] The UID of the abstract syntax (e.g.\ SOP class) to add
     *  @param xferSyntaxes   [in] List of transfer syntaxes (UIDs) that should be supported
     *                             for the given abstract syntax name
     *  @param requestorRole  [in] The role to be negotiated. This denotes the role of the
     *                        the association requestor that this instance should accept, i.e. if
     *                        set to ASC_SC_ROLE_SCP it means that the association requestor
     *                        is allowed to negotiate the SCP role, thus, that this DcmSCP instance
     *                        will be playing the SCU role for this abstract syntax. The default
     *                        role (ASC_SC_ROLE_DEFAULT) implicates that this DcmSCP instance
     *                        will be allowed to play the SCP role only, i.e. it will acknowledge
     *                        such an explicit SCU role request, but also it will accept a proposal
     *                        for the abstract syntax with no explicit role being proposed
     *                        at all (since per default the requestor is SCU and the acceptor SCP).
     *  @param profile        [in] The profile the abstract syntax should be added to. The
     *                             default is to add it to the DcmSCP's internal standard
     *                             profile called "DEFAULT".
     *  @return EC_Normal if adding was successful, an error code otherwise
     */
    virtual OFCondition addPresentationContext(const OFString& abstractSyntax,
                                               const OFList<OFString>& xferSyntaxes,
                                               const T_ASC_SC_ROLE requestorRole = ASC_SC_ROLE_DEFAULT,
                                               const OFString& profile           = "DEFAULT");

    /** Set SCP's TCP/IP listening port
     *  @param port [in] The port number to listen on. Note that usually on Unix-like systems
     *                   only root user is permitted to open ports below 1024. If port number 0
     *                   is provided, the OS will assign a free port once openListenPort()
     *                   is being called. The port number actually used is then available via
     *                   getPort().
     */
    void setPort(const Uint16 port);

    /** Set AE title of the server
     *  @param aetitle [in] The AE title of the server. By default, all SCU association requests
     *                      calling another AE title will be rejected. This behavior can be
     *                      changed by using the setRespondWithCalledAETitle() method.
     */
    void setAETitle(const OFString& aetitle);

    /** Set SCP to use the called AE title from the SCU request for the response, i.e.\ the SCP
     *  will always respond with setting it's own name to the one the SCU used for calling.
     *  Overrides any AE title eventually set with setAETitle().
     *  @param useCalled [in] If OFTrue, the SCP will use the called AE title from the request
     *                        for responding. DcmSCP's default is OFFalse.
     */
    void setRespondWithCalledAETitle(const OFBool useCalled);

    /** Loads association configuration file
     *  @param assocFile [in] The filename of the association configuration to be loaded. The
     *                        association configuration file must be valid for an SCP.
     *  @return EC_Normal if loading was successful, error otherwise
     */
    virtual OFCondition loadAssociationCfgFile(const OFString& assocFile);

    /** If an association profile should be selected, either by loading an association
     *  configuration file or using the addPresentationContext() function, one of those can
     *  be selected and checked for validity using this method.
     *  @param profileName [in] The name of the association profile which must be configured
     *                          before being selected here
     *  @return EC_Normal if selecting/checking was successful, an error code otherwise
     */
    virtual OFCondition setAndCheckAssociationProfile(const OFString& profileName);

    /** Force every association request to be refused by SCP, no matter what the SCU is
     *  offering
     *  @param doRefuse [in] If OFTrue, every association is being refused. DcmSCP's default
     *                       is not to refuse every association.
     */
    void forceAssociationRefuse(const OFBool doRefuse);

    /** Set maximum PDU size the SCP is able to receive. This size is sent in association
     *  response message to SCU.
     *  @param maxRecPDU [in] The maximum PDU size to use in bytes
     */
    void setMaxReceivePDULength(const Uint32 maxRecPDU);

    /** Set whether waiting for a TCP/IP connection should be blocking or non-blocking.
     *  In non-blocking mode, the networking routines will wait for specified connection
     *  timeout, see setConnectionTimeout() function. In blocking mode, no timeout is set
     *  but the operating system's network routines will be used to read from the socket
     *  for incoming data. In the worst case, this may be a long time until that call
     *  returns. The default of DcmSCP is blocking mode.
     *  @param blockingMode [in] Either DUL_BLOCK for blocking mode or DUL_NOBLOCK
     *                           for non-blocking mode
     */
    void setConnectionBlockingMode(const DUL_BLOCKOPTIONS blockingMode);

    /** Set whether DIMSE messaging should be blocking or non-blocking. In non-blocking mode,
     *  the networking routines will wait for DIMSE messages for the specified DIMSE timeout
     *  time, see setDIMSETimeout() function. In blocking mode, no timeout is set but the
     *  operating system's network routines will be used to read from the socket for new data.
     *  In the worst case, this may be a long time until that call returns. The default of
     *  DcmSCP is blocking mode.
     *  @param blockingMode [in] Either DIMSE_BLOCKING for blocking mode or DIMSE_NONBLOCKING
     *                           for non-blocking mode
     */
    void setDIMSEBlockingMode(const T_DIMSE_BlockingMode blockingMode);

    /** Set the timeout to be waited for incoming DIMSE message packets. This is only relevant
     *  for DIMSE blocking mode messaging (see also setDIMSEBlockingMode()).
     *  @param dimseTimeout [in] DIMSE receive timeout in seconds
     */
    void setDIMSETimeout(const Uint32 dimseTimeout);

    /** Set the timeout used during ACSE messaging protocol.
     *  @param acseTimeout [in] ACSE timeout in seconds.
     */
    void setACSETimeout(const Uint32 acseTimeout);

    /** Set the timeout that should be waited for connection requests.
     *  Only relevant in non-blocking mode (which is not the default).
     *  @param timeout [in] TCP/IP connection timeout in seconds.
     */
    void setConnectionTimeout(const Uint32 timeout);

    /** Set whether to show presentation contexts in verbose or debug mode
     *  @param mode [in] Show presentation contexts in verbose mode if OFTrue. By default, the
     *                   presentation contexts are shown in debug mode.
     */
    void setVerbosePCMode(const OFBool mode);

    /** Enables or disables looking up the host name from a connecting system.
     *  Note that this sets a GLOBAL flag in DCMTK, i.e. the behavior changes
     *  for all servers. This should be changed in the future.
     *  @param mode [in] OFTrue, if host name lookup should be enabled, OFFalse for disabling it.
     */
    void setHostLookupEnabled(const OFBool mode);

    /** Set the mode that specifies whether the progress of sending and receiving DIMSE messages
     *  is notified by calling notifySENDProgress() and notifyRECEIVEProgress(), respectively.
     *  The progress notification is enabled by default.
     *  @param mode [in] Disable progress notification if OFFalse
     */
    void setProgressNotificationMode(const OFBool mode);

    /** Option to always accept a default role as association acceptor.
     *  If OFFalse (default) the acceptor will reject a presentation context proposed
     *  with Default role (no role selection at all) when it is configured for role
     *  SCP only. If this option is set to OFTrue then such presentation contexts will
     *  be accepted in Default role (i.e. acceptor does not return role selection for
     *  this presentation context at all). Overall, if set to OFTrue, there are no
     *  requestor proposals possible that lead to a complete rejection of a presentation
     *  context. See also role documentation in dul.h.
     *  @param  enabled If OFTrue, do not reject Default role proposals when configured
     *          for SCP role. OFFalse (default behaviour): Reject such proposals.
     */
    void setAlwaysAcceptDefaultRole(const OFBool enabled);

    /* Get methods for SCP settings */

    /** Returns TCP/IP port number SCP listens for new connection requests
     *  @return The port number
     */
    Uint16 getPort() const;

    /** Returns SCP's own AE title. Only used if the SCP is not configured to respond with the
     *  called AE title the SCU uses for association negotiation, see setRespondWithCalledAETitle().
     *  @return The configured AE title
     */
    const OFString& getAETitle() const;

    /** Returns whether SCP uses the called AE title from SCU requests to respond to connection
     *  requests instead of a configured AE title
     *  @return OFTrue, if the SCU's calling AE title is utilized, OFFalse otherwise
     */
    OFBool getRespondWithCalledAETitle() const;

    /** Returns whether SCP should refuse any association request no matter what the SCU proposes
     *  @return OFTrue, if SCP is configured to refuse every association
     */
    OFBool getRefuseAssociation() const;

    /** Returns maximum PDU length configured to be received by SCP
     *  @return Maximum PDU length in bytes
     */
    Uint32 getMaxReceivePDULength() const;

    /** Returns whether receiving of TCP/IP connection requests is done in blocking or
     *  unblocking mode
     *  @return DUL_BLOCK if in blocking mode, otherwise DUL_NOBLOCK
     */
    DUL_BLOCKOPTIONS getConnectionBlockingMode() const;

    /** Returns whether receiving of DIMSE messages is done in blocking or unblocking mode
     *  @return DIMSE_BLOCKING if in blocking mode, otherwise DIMSE_NONBLOCKING
     */
    T_DIMSE_BlockingMode getDIMSEBlockingMode() const;

    /** Returns DIMSE timeout (only applicable in blocking mode)
     *  @return DIMSE timeout in seconds
     */
    Uint32 getDIMSETimeout() const;

    /** Returns ACSE timeout
     *  @return ACSE timeout in seconds
     */
    Uint32 getACSETimeout() const;

    /** Returns connection timeout
     *  @return TCP/IP connection timeout in seconds
     */
    Uint32 getConnectionTimeout() const;

    /** Returns the verbose presentation context mode configured specifying whether details on
     *  the presentation contexts (negotiated during association setup) should be shown in
     *  verbose or debug mode. The latter is the default.
     *  @return The verbose presentation context mode configured
     */
    OFBool getVerbosePCMode() const;

    /** Returns whether a connecting system's host name is looked up.
     *  @return OFTrue, if host name lookup is enabled, OFFalse otherwise
     */
    OFBool getHostLookupEnabled() const;

    /** Returns the mode that specifies whether the progress of sending and receiving DIMSE
     *  messages is notified by calling notifySENDProgress() and notifyRECEIVEProgress(),
     *  respectively. The progress notification is enabled by default.
     *  @return The current progress notification mode, enabled if OFTrue
     */
    OFBool getProgressNotificationMode() const;

    /** Get access to the configuration of the SCP. Note that the functionality
     *  on the configuration object is shadowed by other API functions of DcmSCP.
     *  The existing functions are provided in order to not break users of this
     *  "older" API where no configuration object existed.
     *  @return a reference to the DcmSCPConfig object used by this DcmSCP object.
     */
    virtual DcmSCPConfig& getConfig();

    /** Set the DcmSCPConfig object to use for configuring this DcmSCP object.
     *  A deep copy is performed.
     *  @param  config The configuration to use.
     *  @return EC_Normal if configuration can be used. The configuration can
     *          only be changed if the SCP is not yet connected, otherwise
     *          NET_EC_AlreadyConnected is returned.
     *
     */
    virtual OFCondition setConfig(const DcmSCPConfig& config);

    /* ************************************************************* */
    /*  Methods for receiving runtime (i.e. connection time) infos   */
    /* ************************************************************* */

    /** Returns whether SCP is currently connected. If in multi-process mode, the "father"
     *  process should always return false here, because connection is always handled by child
     *  process.
     *  @return OFTrue, if SCP is currently connected to calling SCU
     */
    OFBool isConnected() const;

    /** Returns number of associations currently running. Only applicable in Unix-like
     *  operating systems. Can only be greater than one when running in multi-process mode.
     *  @return Number of currently running associations
     */
    Uint16 numAssociations() const;

    /** Returns AE title the SCU used as called AE title in association request
     *  @return AE title the SCP was called with. Empty string if SCP is currently not
     *          connected.
     */
    OFString getCalledAETitle() const;

    /** Returns AE title (calling AE title) the SCU used for association request
     *  @return Calling AE title of SCU. Empty string if SCP is currently not connected.
     */
    OFString getPeerAETitle() const;

    /** Returns IP address of connected SCU
     *  @return IP address of connected SCU. Empty string if SCP is currently not connected.
     */
    OFString getPeerIP() const;

    /** Returns maximum PDU size the communication peer (i.e.\ the SCU) is able to receive
     *  @return Maximum PDU size the SCU is able to receive. Returns zero if SCP is currently
     *          not connected.
     */
    Uint32 getPeerMaxPDULength() const;

    /// DcmThreadSCP needs access to configuration (m_cfg), at least
    friend class DcmThreadSCP;

protected:
    /* ********************************************* */
    /*  Functions available to derived classes only  */
    /* ********************************************* */

    /** This call returns the presentation context belonging to the given
     *  presentation context ID.
     *  @param presID         [in]  The presentation context ID to look for
     *  @param abstractSyntax [out] The abstract syntax (UID) for that ID.
     *                              Empty, if such a presentation context does not exist.
     *  @param transferSyntax [out] The transfer syntax (UID) for that ID.
     *                              Empty, if such a presentation context does not exist.
     */
    void findPresentationContext(const T_ASC_PresentationContextID presID,
                                 OFString& abstractSyntax,
                                 OFString& transferSyntax);

    /** Aborts the current association by sending an A-ABORT request to the SCU.
     *  This method allows derived classes to abort an association in case of severe errors.
     *  @return status, EC_Normal if successful, an error code otherwise
     */
    virtual OFCondition abortAssociation();

    /* *********************************************************************** */
    /*  Functions particularly interesting for overwriting in derived classes  */
    /* *********************************************************************** */

    /** Handle incoming command set and react accordingly, e.g.\ sending response via
     *  DIMSE_sendXXXResponse(). The standard handler only knows how to handle
     *  a C-ECHO request message (by calling handleEchoRequest()) if it is sent on a
     *  presentation context configured for the Verification SOP Class.
     *  This function is most likely to be implemented by a derived class
     *  implementing a specific SCP behavior.
     *  @param incomingMsg The DIMSE message received
     *  @param presInfo Additional information on the Presentation Context used
     *  @return EC_Normal if the message could be handled, error if not. Especially
     *          DIMSE_BADCOMMANDTYPE should be returned if there is no handler for
     *          this particular type of DIMSE message. E.g. the default handler in
     *          DcmSCP only handles C-ECHO requests and, therefore, returns
     *          DIMSE_BADCOMMANDTYPE otherwise.
     */
    virtual OFCondition handleIncomingCommand(T_DIMSE_Message* incomingMsg, const DcmPresentationContextInfo& presInfo);

    /** Overwrite this function to be notified about an incoming association request.
     *  The standard handler only outputs some information to the logger.
     *  @param params The association parameters that were received.
     *  @param desiredAction The desired action how to handle this association request.
     */
    virtual void notifyAssociationRequest(const T_ASC_Parameters& params, DcmSCPActionType& desiredAction);

    /** Overwrite this function if called AE title should undergo custom checking.
    *   If OFTrue is returned, the AE title is accepted and processing is continued.
     *  In case of OFFalse, the SCP will refuse the incoming association with
     *  error "Called Application Entity Title Not Recognized".
     *
     *  The standard handler always returns true, if setRespondWithCalledAETitle()
     *  has been enabled, i.e. DcmSCP will respond with the AE Title the remote AE
     *  used for DcmSCP as the "Called AE Title". This is DcmSCP's default behaviour
     *  and in particular good for testing.
     *
     *  If setRespondWithCalledAETitle() is set to false, DcmSCP will check whether
     *  the Called AE Title used by the remote AE matches the one configured for
     *  DcmSCP. The default value is "DCMTK_SCP" and can be changed calling setAETitle().
     *  @param calledAE The called AE title the SCU used that should be checked
     *  @return OFTrue, if AE title is accepted, OFFalse otherwise
     */
    virtual OFBool checkCalledAETitleAccepted(const OFString& calledAE);

    /** Overwrite this function if calling AE title should undergo checking. If
     *  OFTrue is returned, the AE title is accepted and processing is continued.
     *  In case of OFFalse, the SCP will refuse the incoming association with
     *  error "Calling Application Entity Title Not Recognized".
     *  The standard handler always returns OFTrue.
     *  @param callingAE The calling AE title the SCU used that should be checked
     *  @return OFTrue, if AE title is accepted, OFFalse otherwise
     */
    virtual OFBool checkCallingAETitleAccepted(const OFString& callingAE);

    /** Overwrite this function if calling IP / host name should undergo checking.
     *  If OFTrue is returned, the host is accepted and processing is continued.
     *  In case of OFFalse, the SCP will refuse the incoming association with
     *  an error. The standard handler always returns OFTrue.
     *  @param hostOrIP The IP of the client to check.
     *  @return OFTrue, if IP/host is accepted, OFFalse otherwise
     */
    virtual OFBool checkCallingHostAccepted(const OFString& hostOrIP);

    /** Overwrite this function to be notified about an incoming association request.
     *  The standard handler only outputs some information to the logger.
     */
    virtual void notifyAssociationAcknowledge();

    /** Overwrite this function to be notified about an incoming association release request.
     *  The standard handler only outputs some information to the logger.
     */
    virtual void notifyReleaseRequest();

    /** Overwrite this function to be notified about an incoming association abort request.
     *  The standard handler only outputs some information to the logger.
     */
    virtual void notifyAbortRequest();

    /** Overwrite this function to be notified when an association is terminated.
     *  The standard handler only outputs some information to the logger.
     */
    virtual void notifyAssociationTermination();

    /** Overwrite this function to be notified about a connection timeout in
     *  non-blocking mode (see setConnectionBlockingMode() and setConnectionTimeout()
     *  methods). In blocking mode, this method has no effect since it's never called.
     *  The standard handler only outputs some information to the TRACE logger.
     */
    virtual void notifyConnectionTimeout();

    /** Overwrite this function to be notified when a DIMSE error occurs.
     *  The standard handler only outputs error information to the logger.
     *  @param cond [in] The DIMSE error occurred.
     */
    virtual void notifyDIMSEError(const OFCondition& cond);

    /** This function is called while sending DIMSE messages, i.e.\ on each PDV of a dataset.
     *  The default implementation just prints a TRACE message on the number of bytes sent so
     *  far. By overwriting this method, the progress of the send process can be shown to the
     *  user in a more appropriate way. The progress notification can also be disabled
     *  completely by calling setProgressNotificationMode().
     *  @param byteCount [in] Number of bytes sent so far
     */
    virtual void notifySENDProgress(const unsigned long byteCount);

    /** This function is called while receiving DIMSE messages, i.e.\ on each PDV of a dataset.
     *  The default implementation just prints a TRACE message on the number of bytes received
     *  so far. By overwriting this method, the progress of the receive process can be shown to
     *  the user in a more appropriate way. The progress notification can also be disabled
     *  completely by calling setProgressNotificationMode().
     *  @param byteCount [in] Number of bytes received so far
     */
    virtual void notifyRECEIVEProgress(const unsigned long byteCount);

    /** This method can be used to return from the listen() loop in a controlled way.
     *  In order to use it, it must be overwritten in a derived class. As long as no
     *  severe error occurs and this method returns OFFalse, the listen() method will wait
     *  for incoming associations in an infinite loop.
     *  @return The standard handler always returns OFFalse
     */
    virtual OFBool stopAfterCurrentAssociation();

    /** This method can be used to return from the listen() loop in a controlled way.
     *  In order to use it, it must be overwritten in a derived class. As long as no
     *  severe error occurs and this method returns OFFalse, the listen() method will wait
     *  for incoming associations in an infinite loop. If this method returns OFTrue, the
     *  SCP will return from the listen() loop after a connection timeout occurs (see
     *  setConnectionTimeout() method). In blocking mode (see setConnectionBlockingMode()
     *  method), this method has no effect (it's never called) since the underlying
     *  routines will wait forever for an incoming TCP connection.
     *  @return The standard handler always returns OFFalse
     */
    virtual OFBool stopAfterConnectionTimeout();

    // -- C-ECHO --

    /** Standard handler for Verification Service Class (DICOM Echo). Returns echo response
     *  (i.e. whether C-ECHO could be responded to with status success).
     *  @param reqMessage [in] The C-ECHO request message that was received
     *  @param presID     [in] The presentation context to be used. By default, the
     *                         presentation context of the request is used.
     *  @return status, EC_Normal if successful, an error code otherwise
     */
    virtual OFCondition handleECHORequest(T_DIMSE_C_EchoRQ& reqMessage, const T_ASC_PresentationContextID presID);

    // --- C-STORE --

    /** Receive C-STORE request on the currently opened association, store the
     *  accompanying dataset in memory and send a corresponding response. Calls
     *  checkSTORERequest() in order to determine the DIMSE status code to be used for
     *  the C-STORE response.
     *  @note This handler receives the dataset belonging the C-STORE request completely
     *    in memory. If very large datasets are expected, another handler should be
     *    implemented that calls the receiveSTORERequest() method with a filename.
     *  @param reqMessage [in]    The C-STORE request message that was received
     *  @param presID     [in]    The presentation context to be used. By default, the
     *                            presentation context of the request is used.
     *  @param reqDataset [inout] Pointer to data structure where the received dataset
     *                            should be stored. If NULL, a new dataset is created,
     *                            which has to be deleted by the caller.
     *  @return status, EC_Normal if successful, an error code otherwise
     */
    virtual OFCondition handleSTORERequest(T_DIMSE_C_StoreRQ& reqMessage,
                                           const T_ASC_PresentationContextID presID,
                                           DcmDataset*& reqDataset);

    /** Receive C-STORE request (and store accompanying dataset in memory).
     *  For very large datasets, the other receiveSTORERequest() method should be used
     *  because it stores the received dataset directly to file.
     *  @param reqMessage [in]    The C-STORE request message that was received
     *  @param presID     [in]    The presentation context to be used. By default, the
     *                            presentation context of the request is used.
     *  @param reqDataset [inout] Pointer to data structure where the received dataset
     *                            should be stored. If NULL, a new dataset is created,
     *                            which has to be deleted by the caller.
     *  @return status, EC_Normal if successful, an error code otherwise
     */
    virtual OFCondition receiveSTORERequest(T_DIMSE_C_StoreRQ& reqMessage,
                                            const T_ASC_PresentationContextID presID,
                                            DcmDataset*& reqDataset);

    /** Receive C-STORE request (and store accompanying dataset directly to file).
     *  The dataset is stored exactly as received, i.e. without any conversions.
     *  @param reqMessage [in] The C-STORE request message that was received
     *  @param presID     [in] The presentation context to be used. By default, the
     *                         presentation context of the request is used.
     *  @param filename   [in] The filename used to store the received dataset
     *  @return status, EC_Normal if successful, an error code otherwise
     */
    virtual OFCondition receiveSTORERequest(T_DIMSE_C_StoreRQ& reqMessage,
                                            const T_ASC_PresentationContextID presID,
                                            const OFString& filename);

    /** Respond to the C-STORE request (with details from the request message)
     *  @param presID        [in] The presentation context ID to respond to
     *  @param reqMessage    [in] The C-STORE request that should be responded to
     *  @param rspStatusCode [in] The response status code. 0 means success,
     *                            others can found in the DICOM standard.
     *  @return EC_Normal, if responding was successful, an error code otherwise
     */
    virtual OFCondition sendSTOREResponse(const T_ASC_PresentationContextID presID,
                                          const T_DIMSE_C_StoreRQ& reqMessage,
                                          const Uint16 rspStatusCode);

    /** Respond to the C-STORE request (with given details)
     *  @param presID         [in] The presentation context ID to respond to
     *  @param messageID      [in] The message ID being responded to
     *  @param sopClassUID    [in] The affected SOP class UID
     *  @param sopInstanceUID [in] The affected SOP instance UID
     *  @param rspStatusCode  [in] The response status code. 0 means success,
     *                             others can found in the DICOM standard.
     *  @param statusDetail   [in] The status detail of the response (if desired).
     *  @return EC_Normal, if responding was successful, an error code otherwise
     */
    virtual OFCondition sendSTOREResponse(const T_ASC_PresentationContextID presID,
                                          const Uint16 messageID,
                                          const OFString& sopClassUID,
                                          const OFString& sopInstanceUID,
                                          const Uint16 rspStatusCode,
                                          DcmDataset* statusDetail = NULL);

    /** Check given C-STORE request and dataset for validity. This method is called by
     *  handleSTORERequest() before sending the response in order to determine the DIMSE
     *  status code to be used for the response message.
     *  @param reqMessage [in] The C-STORE request message data structure
     *  @param reqDataset [in] The C-STORE request dataset received. Might be NULL.
     *  @return DIMSE status code to be used for the C-STORE response.
     *          Always returns STATUS_Success (0). Derived classes should, therefore,
     *          overwrite this method and return a more appropriate value based on the
     *          result of the checks performed.
     */
    virtual Uint16 checkSTORERequest(T_DIMSE_C_StoreRQ& reqMessage, DcmDataset* reqDataset);

    // -- C-FIND --

    /** Receive C-FIND request
     *  @param reqMessage [in]  The C-FIND request message that was received
     *  @param presID     [in]  The presentation context to be used. By default, the
     *                          presentation context of the request is used.
     *  @param reqDataset [out] Pointer to the dataset received
     *  @return status, EC_Normal if successful, an error code otherwise
     */
    virtual OFCondition
    receiveFINDRequest(T_DIMSE_C_FindRQ& reqMessage, const T_ASC_PresentationContextID presID, DcmDataset*& reqDataset);

    /** Handle C-FIND request. This function is deprecated and will be removed in
     *  the future. For now it calls receiveFINDRequest() which should be used
     *  instead.
     *  @param reqMessage [in]  The C-FIND request message that was received
     *  @param presID     [in]  The presentation context to be used. By default, the
     *                          presentation context of the request is used.
     *  @param reqDataset [out] Pointer to the dataset received
     *  @return status, EC_Normal if successful, an error code otherwise
     */
    virtual OFCondition
    handleFINDRequest(T_DIMSE_C_FindRQ& reqMessage, const T_ASC_PresentationContextID presID, DcmDataset*& reqDataset)
    {
        DCMNET_WARN("handleFINDRequest() is deprecated, use receiveFINDRequest() instead");
        return receiveFINDRequest(reqMessage, presID, reqDataset);
    }

    /** Respond to the C-FIND request
     *  @param presID        [in] The presentation context ID to respond to
     *  @param messageID     [in] The message ID being responded to
     *  @param sopClassUID   [in] The affected SOP class UID
     *  @param rspDataset    [in] The response dataset
     *  @param rspStatusCode [in] The response status code. 0 means success,
     *                            others can found in the DICOM standard.
     *  @param statusDetail  [in] Any status (must fit response code), if desired
     *  @return EC_Normal, if responding was successful, an error code otherwise
     */
    virtual OFCondition sendFINDResponse(const T_ASC_PresentationContextID presID,
                                         const Uint16 messageID,
                                         const OFString& sopClassUID,
                                         DcmDataset* rspDataset,
                                         const Uint16 rspStatusCode,
                                         DcmDataset* statusDetail = NULL);

    /** Check for C-CANCEL. This is needed for example for a Query/Retrieve
     *  server that is in the middle of returning C-FIND responses to a
     *  client and has to perform a regular check whether the client sent a
     *  C-CANCEL in order to stop receiving C-FIND responses.
     *  @param presID    [in] The presentation context ID where C-CANCEL is
     *                        expected.
     *  @param messageID [in] The "message ID responded to" that the client
     *                        is expected to use (usually this is the message
     *                        ID used in the original FIND/GET/MOVE request).
     *  @return EC_Normal, if C-CANCEL was received. DIMSE_NODATAAVAILABLE if no
     *          DIMSE message (or anything) was received from the client.
     *          DIMSEC_UNEXPECTEDREQUEST if command is received but it is not
     *          a C-CANCEL message, or the message ID used by client is wrong
     *          (message ID must be the one from the original FIND/MOVE/GET
     *          request).
     *          DIMSEC_INVALIDPRESENTATIONCONTEXTID if the wrong presentation
     *          context (ID) was used for sending. Other low level errors
     *          (e.g. DIMSEC_UNEXPECTEDPDVTYPE) could be returned, too.
     */
    virtual OFCondition checkForCANCEL(T_ASC_PresentationContextID presID, const Uint16 messageID);

    // --- C-MOVE --

    /** Receive C-MOVE request on the currently active association.
     *  @param reqMessage [in]  The C-MOVE request message that was received
     *  @param presID     [in]  The presentation context to be used. By default, the
     *                          presentation context of the request is used.
     *  @param reqDataset [out] Pointer to the dataset received
     *  @param moveDest   [out] The move destination where to send the instances
     *  @return status, EC_Normal if successful, an error code otherwise
     */
    virtual OFCondition receiveMOVERequest(T_DIMSE_C_MoveRQ& reqMessage,
                                           const T_ASC_PresentationContextID presID,
                                           DcmDataset*& reqDataset,
                                           OFString& moveDest);

    /** Receive C-MOVE request on the currently active association. This function
     *  is deprecated and will be removed in the future. For now it calls
     *  receiveMOVERequest() which should be used instead.
     *  @param reqMessage [in]  The C-MOVE request message that was received
     *  @param presID     [in]  The presentation context to be used. By default, the
     *                          presentation context of the request is used.
     *  @param reqDataset [out] Pointer to the dataset received
     *  @param moveDest   [out] The move destination where to send the instances
     *  @return status, EC_Normal if successful, an error code otherwise
     */
    virtual OFCondition handleMOVERequest(T_DIMSE_C_MoveRQ& reqMessage,
                                          const T_ASC_PresentationContextID presID,
                                          DcmDataset*& reqDataset,
                                          OFString& moveDest)
    {
        DCMNET_WARN("handleMOVERequest() is deprecated, use receiveMOVERequest() instead");
        return receiveMOVERequest(reqMessage, presID, reqDataset, moveDest);
    }

    /** Respond to the C-MOVE request
     *  @param presID        [in] The presentation context ID to respond to
     *  @param messageID     [in] The message ID being responded to
     *  @param sopClassUID   [in] The affected SOP class UID
     *  @param rspDataset    [in] The response dataset
     *  @param rspStatusCode [in] The status code of the response. 0 means success,
     *                            others can found in the DICOM standard.
     *  @param statusDetail  [in] The status detail of the response (if desired).
     *  @param numRemain     [in] Number of remaining sub-operations.
     *                            Required for Pending status codes, often optional otherwise.
     *                            Sent if one of the num parameters is not 0.
     *  @param numComplete   [in] Number of completed sub-operations.
     *                            Required for Pending status codes, often optional otherwise.
     *                            Sent if one of the num parameters is not 0.
     *  @param numFail       [in] Number of failed sub-operations.
     *                            Required for Pending status codes, often optional otherwise.
     *                            Sent if one of the num parameters is not 0.
     *  @param numWarn       [in] Number of warning sub-operations.
     *                            Required for Pending status codes, often optional otherwise.
     *                            Sent if one of the num parameters is not 0.
     *  @return EC_Normal, if responding was successful, an error code otherwise
     */
    virtual OFCondition sendMOVEResponse(const T_ASC_PresentationContextID presID,
                                         const Uint16 messageID,
                                         const OFString& sopClassUID,
                                         DcmDataset* rspDataset,
                                         const Uint16 rspStatusCode,
                                         DcmDataset* statusDetail = NULL,
                                         const Uint16 numRemain   = 0,
                                         const Uint16 numComplete = 0,
                                         const Uint16 numFail     = 0,
                                         const Uint16 numWarn     = 0);

    // -- N-ACTION --

    /** Receive N-ACTION request on the currently opened association.
     *  @param reqMessage   [in]  The N-ACTION request message that was received
     *  @param presID       [in]  The presentation context to be used. By default, the
     *                            presentation context of the request is used.
     *  @param reqDataset   [out] Pointer to the dataset received
     *  @param actionTypeID [out] Action Type ID from the command set received
     *  @return status, EC_Normal if successful, an error code otherwise
     */
    virtual OFCondition receiveACTIONRequest(T_DIMSE_N_ActionRQ& reqMessage,
                                             const T_ASC_PresentationContextID presID,
                                             DcmDataset*& reqDataset,
                                             Uint16& actionTypeID);

    /** Receive N-ACTION request on the currently opened association. This
     *  function is deprecated and will be removed in the future. For now it calls
     *  receiveACTIONRequest() which should be used instead.
     *  @param reqMessage   [in]  The N-ACTION request message that was received
     *  @param presID       [in]  The presentation context to be used. By default, the
     *                            presentation context of the request is used.
     *  @param reqDataset   [out] Pointer to the dataset received
     *  @param actionTypeID [out] Action Type ID from the command set received
     *  @return status, EC_Normal if successful, an error code otherwise
     */
    virtual OFCondition handleACTIONRequest(T_DIMSE_N_ActionRQ& reqMessage,
                                            const T_ASC_PresentationContextID presID,
                                            DcmDataset*& reqDataset,
                                            Uint16& actionTypeID)
    {
        DCMNET_WARN("handleACTIONRequest() is deprecated, use receiveACTIONRequest() instead");
        return receiveACTIONRequest(reqMessage, presID, reqDataset, actionTypeID);
    }

    /** Respond to the N-ACTION request
     *  @param presID         [in] The presentation context ID to respond to
     *  @param messageID      [in] The message ID being responded to
     *  @param sopClassUID    [in] The affected SOP class UID
     *  @param sopInstanceUID [in] The affected SOP instance UID
     *  @param rspStatusCode  [in] The response status code. 0 means success,
     *                             others can found in the DICOM standard.
     *  @return EC_Normal, if responding was successful, an error code otherwise
     */
    virtual OFCondition sendACTIONResponse(const T_ASC_PresentationContextID presID,
                                           const Uint16 messageID,
                                           const OFString& sopClassUID,
                                           const OFString& sopInstanceUID,
                                           const Uint16 rspStatusCode);

    // -- N-EVENT-REPORT --

    /** Receive N-EVENT-REPORT request on the currently opened association and send a
     *  corresponding response. Calls checkEVENTREPORTRequest() in order to determine the
     *  DIMSE status code to be used for the N-EVENT-REPORT response.
     *  @param reqMessage  [in]  The N-EVENT-REPORT request message that was received
     *  @param presID      [in]  The presentation context to be used. By default, the
     *                           presentation context of the request is used.
     *  @param reqDataset  [out] Pointer to the dataset received
     *  @param eventTypeID [out] Event Type ID from the command set received
     *  @return status, EC_Normal if successful, an error code otherwise
     */
    virtual OFCondition handleEVENTREPORTRequest(T_DIMSE_N_EventReportRQ& reqMessage,
                                                 const T_ASC_PresentationContextID presID,
                                                 DcmDataset*& reqDataset,
                                                 Uint16& eventTypeID);

    /** Send N-EVENT-REPORT request on the current association and receive a corresponding
     *  response.
     *  @param presID         [in]  The ID of the presentation context to be used for sending
     *                              the request message. Should not be 0.
     *  @param sopInstanceUID [in]  The requested SOP Instance UID
     *  @param messageID      [in]  The request message ID
     *  @param eventTypeID    [in]  The event type ID to be used
     *  @param reqDataset     [in]  The request dataset to be sent
     *  @param rspStatusCode  [out] The response status code received. 0 means success,
     *                              others can be found in the DICOM standard.
     *  @return EC_Normal if request could be issued and response was received successfully,
     *          an error code otherwise
     */
    virtual OFCondition sendEVENTREPORTRequest(const T_ASC_PresentationContextID presID,
                                               const OFString& sopInstanceUID,
                                               const Uint16 messageID,
                                               const Uint16 eventTypeID,
                                               DcmDataset* reqDataset,
                                               Uint16& rspStatusCode);

    /** Check given N-EVENT-REPORT request and dataset for validity. This method is called by
     *  handleEVENTREPORTRequest() before sending the response in order to determine the
     *  DIMSE status code to be used for the response message.
     *  @param reqMessage [in] The N-EVENT-REPORT request message data structure
     *  @param reqDataset [in] The N-EVENT-REPORT request dataset received. Might be NULL.
     *  @return DIMSE status code to be used for the N-EVENT-REPORT response.
     *          Always returns STATUS_Success (0). Derived classes should, therefore,
     *          overwrite this method and return a more appropriate value based on the
     *          result of the checks performed.
     */
    virtual Uint16 checkEVENTREPORTRequest(T_DIMSE_N_EventReportRQ& reqMessage, DcmDataset* reqDataset);

    /* ********************************************************************* */
    /*  Further functions and member variables                               */
    /* ********************************************************************* */

    /** Helper function to return presentation context information by given
     *  presentation context ID.
     *  @param head The presentation context list
     *  @param presentationContextID The presentation context ID
     *  @return The presentation context information
     */
    static DUL_PRESENTATIONCONTEXT* findPresentationContextID(LST_HEAD* head,
                                                              T_ASC_PresentationContextID presentationContextID);

    /** Helper function to return presentation context information by given
     *  presentation context ID.
     *  @param assoc The association to search
     *  @param presID The presentation context ID
     *  @param presInfo The result presentation context information, if found
     *  @return OFTrue if presentation context with ID could be found, OFFalse
     *          otherwise
     */
    static OFBool getPresentationContextInfo(const T_ASC_Association* assoc,
                                             const Uint8 presID,
                                             DcmPresentationContextInfo& presInfo);

    /** This function takes care of receiving, negotiating and accepting/refusing an
     *  association request. Additionally, if negotiation was successful, it handles any
     *  incoming DIMSE commands by calling handleAssociation(). An error is only returned, if
     *  something goes wrong. Therefore, refusing an association because of wrong application
     *  context name or no common presentation contexts with the SCU does NOT lead to an error.
     *  @param network [in] Contains network parameters
     *  @return EC_Normal, if everything went fine, DUL_NOASSOCIATIONREQUEST if a timeout
     *          occurs in non-blocking mode, DIMSE_ILLEGALASSOCIATION or ASC_NULLKEY if
     *          severe internal errors occurred (should not happen)
     */
    virtual OFCondition waitForAssociationRQ(T_ASC_Network* network);

    /** Actually process association request.
     *  @return EC_Normal if association could be processed, ASC_NULLKEY otherwise
     *          (only if internal association structure is invalid, should never happen)
     */
    virtual OFCondition processAssociationRQ();

    /** This function checks all presentation contexts proposed by the SCU whether they are
     *  supported or not. It is not an error if no common presentation context could be
     *  identified with the SCU; only issues like problems in memory management etc. are
     *  reported as an error. This function does not send a response message to the SCU. This
     *  is done in other functions.
     *  @return EC_Normal if negotiation was successfully done, an error code otherwise
     */
    virtual OFCondition negotiateAssociation();

    /** This function takes care of refusing an association request
     *  @param reason [in] The reason why the association request will be refused and that
     *                     will be reported to the SCU.
     */
    virtual void refuseAssociation(const DcmRefuseReasonType reason);

    /** This function takes care of handling the other DICOM application's request. After
     *  having accomplished all necessary steps, the association will be dropped and destroyed.
     */
    virtual void handleAssociation();

    /** Send a DIMSE command and possibly also a dataset from a data object via network to
     *  another DICOM application
     *  @param presID          [in]  Presentation context ID to be used for message
     *  @param message         [in]  Structure that represents a certain DIMSE command which
     *                               shall be sent
     *  @param dataObject      [in]  The instance data which shall be sent to the other DICOM
     *                               application; NULL, if there is none
     *  @param statusDetail    [in]  The status detail of the response (if desired).
     *  @param commandSet      [out] If this parameter is not NULL it will return a copy of the
     *                               DIMSE command which is sent to the other DICOM application
     *  @return Returns EC_Normal if sending request was successful, an error code otherwise
     */
    OFCondition sendDIMSEMessage(const T_ASC_PresentationContextID presID,
                                 T_DIMSE_Message* message,
                                 DcmDataset* dataObject,
                                 DcmDataset* statusDetail = NULL,
                                 DcmDataset** commandSet  = NULL);

    /** Receive DIMSE command (excluding dataset!) over the currently open association
     *  @param presID       [out] Contains in the end the ID of the presentation context
     *                            which was specified in the DIMSE command received
     *  @param message      [out] The message received
     *  @param statusDetail [out] If a non-NULL value is passed this variable will in the end
     *                            contain detailed information with regard to the status
     *                            information which is captured in the status element
     *                            (0000,0900). Note that the value for element (0000,0900) is
     *                            not contained in this return value but in internal message.
     *                            For details on the structure of this object, see DICOM
     *                            standard part 7, annex C).
     *  @param commandSet   [out] If this parameter is not NULL, it will return a copy of the
     *                            DIMSE command which was received from the other DICOM
     *                            application. The caller is responsible to de-allocate the
     *                            returned object!
     *  @param timeout      [in]  If this parameter is not 0, it specifies the timeout (in
     *                            seconds) to be used for receiving the DIMSE command.
     *                            Otherwise, the default timeout value is used (see
     *                            setDIMSETimeout()).
     *  @return EC_Normal if command could be received successfully, an error code otherwise
     */
    OFCondition receiveDIMSECommand(T_ASC_PresentationContextID* presID,
                                    T_DIMSE_Message* message,
                                    DcmDataset** statusDetail,
                                    DcmDataset** commandSet = NULL,
                                    const Uint32 timeout    = 0);

    /** Receive one dataset (of instance data) via network from another DICOM application
     *  @param presID     [out]   Contains in the end the ID of the presentation context
     *                            which was used in the PDVs that were received on the
     *                            network. If the PDVs show different presentation context
     *                            IDs, this function will return an error.
     *  @param dataObject [inout] Contains in the end the information that was received
     *                            over the network. If this parameter points to NULL, a new
     *                            dataset will be created by the underlying routines, which
     *                            has to be deleted by the caller.
     *  @return EC_Normal if dataset could be received successfully, an error code otherwise
     */
    OFCondition receiveDIMSEDataset(T_ASC_PresentationContextID* presID, DcmDataset** dataObject);

    /** Receive one C-STORE request dataset via network from another DICOM application and
     *  store it directly to file (i.e.\ exactly as received without any conversions)
     *  @param presID      [inout] Initially, the presentation context the C-STORE request was
     *                             received on. Contains in the end the ID of the presentation
     *                             context which was used in the PDVs that were received on the
     *                             network. If the PDVs show different presentation context
     *                             IDs, this function will return an error.
     *  @param reqMessage  [in]    The C-STORE request message that was received
     *  @param filename    [in]    Name of the file that is created to store the received dataset
     *  @return EC_Normal if dataset could be received successfully, an error code otherwise
     */
    OFCondition receiveSTORERequestDataset(T_ASC_PresentationContextID* presID,
                                           T_DIMSE_C_StoreRQ& reqMessage,
                                           const OFString& filename);

    /** Add given element to existing status detail object or create new one.
     *  @param statusDetail  The status detail to add the element to. Status detail
     *           is information additional to the DIMSE status code which can be
     *           provided for some DIMSE messages. If NULL is provided,
     *           a new status detail object is created and returned. All status
     *           detail attributes need to have the VR AT or LO which is also
     *           checked by the underlying routine.
     *  @param elem The element to be copied into the status detail.
     *  @return OFTrue if status detail was successfully added,
     *          OFFalse otherwise.
     */
    static OFBool addStatusDetail(DcmDataset** statusDetail, const DcmElement* elem);

    /* Callback functions (static) */

    /** Callback function used for sending DIMSE messages.
     *  @param callbackContext [in] The desired user callback data
     *  @param byteCount       [in] Progress bytes count
     */
    static void callbackSENDProgress(void* callbackContext, unsigned long byteCount);

    /** Callback function used for receiving DIMSE messages.
     *  @param callbackContext [in] The desired user callback data
     *  @param byteCount       [in] Progress bytes count
     */
    static void callbackRECEIVEProgress(void* callbackContext, unsigned long byteCount);

private:
    /// Network instance run by this SCP
    T_ASC_Network* m_network;

    /// Current association run by this SCP
    T_ASC_Association* m_assoc;

    /// SCP configuration. The configuration is a shared object since in some scenarios one
    /// might like to share a single configuration instance with multiple SCPs without copying
    /// it, e.g. in the context of the DcmSCPPool class.
    DcmSharedSCPConfig m_cfg;

    /** Drops association and clears internal structures to free memory
     */
    void dropAndDestroyAssociation();

    /** Private undefined copy constructor. Shall never be called.
     *  @param src Source object
     */
    DcmSCP(const DcmSCP& src);

    /** Private undefined assignment operator. Shall never be called.
     *  @param src Source object
     *  @return Reference to this
     */
    DcmSCP& operator=(const DcmSCP& src);
};

#endif // SCP_H
