/*
 *
 *  Copyright (C) 2009-2024, 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: Base class for Service Class Providers (SCPs)
 *
 */

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

#include "dcmtk/dcmdata/dcostrmf.h" /* for class DcmOutputFileStream */
#include "dcmtk/dcmnet/assoc.h"
#include "dcmtk/dcmnet/scp.h"
#include "dcmtk/dcmtls/tlslayer.h"

// ----------------------------------------------------------------------------

DcmSCP::DcmSCP()
: m_network(NULL)
, m_assoc(NULL)
, m_cfg()
{
    OFStandard::initializeNetwork();
}

// ----------------------------------------------------------------------------

DcmSCP::~DcmSCP()
{
    // If there is an open association, drop it and free memory (just to be sure...)
    if (m_assoc)
    {
        dropAndDestroyAssociation();
    }

    // clean up network structure if initialized
    if (m_network)
    {
        ASC_dropNetwork(&m_network);
    }

    OFStandard::shutdownNetwork();
}

// ----------------------------------------------------------------------------

DcmSCPConfig& DcmSCP::getConfig()
{
    return *m_cfg;
}

// ----------------------------------------------------------------------------

OFCondition DcmSCP::setConfig(const DcmSCPConfig& config)
{
    if (isConnected())
    {
        return NET_EC_AlreadyConnected;
    }
    m_cfg = DcmSharedSCPConfig(config);
    return EC_Normal;
}

// ----------------------------------------------------------------------------

OFCondition DcmSCP::openListenPort()
{

    // clean up network structure if already initialized
    if (m_network)
    {
      ASC_dropNetwork(&m_network);
      m_network = NULL;
    }

    // make sure not to let dcmdata remove trailing blank padding or perform other
    // manipulations. We want to see the real data.
    dcmEnableAutomaticInputDataCorrection.set(OFFalse);

    OFCondition cond = EC_Normal;
    // Make sure data dictionary is loaded.
    if (!dcmDataDict.isDictionaryLoaded())
        DCMNET_WARN("No data dictionary loaded, check environment variable: " << DCM_DICT_ENVIRONMENT_VARIABLE);

#ifndef DISABLE_PORT_PERMISSION_CHECK
#ifdef HAVE_GETEUID
    // If port is privileged we must be as well.
    if ( ( (m_cfg->getPort() < 1024) && m_cfg->getPort() !=0) && geteuid() != 0)
    {
        DCMNET_ERROR("No privileges to open this network port (" << m_cfg->getPort() << ")");
        return NET_EC_InsufficientPortPrivileges;
    }
#endif
#endif

    // Check whether current association profile is valid
    OFString tmp;
    OFCondition result = m_cfg->checkAssociationProfile(m_cfg->getActiveAssociationProfile(), tmp);
    if (result.bad())
        return result;

    // Initialize network, i.e. create an instance of T_ASC_Network*.
    const int port = OFstatic_cast(int, m_cfg->getPort());
    cond = ASC_initializeNetwork(NET_ACCEPTOR, port, m_cfg->getACSETimeout(), &m_network);
    if (cond.bad())
    {
        m_network = NULL;
        return cond;
    }

    // Update config with assigned port if client requested it
    if (port == 0)
    {
      m_cfg->setPort(OFstatic_cast(Uint16, m_network->acceptorPort));
    }

    if (m_cfg->transportLayerEnabled())
    {
      cond = ASC_setTransportLayer(m_network, m_cfg->getTransportLayer(), OFFalse /* Do not take over ownership */);
      if (cond.bad())
      {
          DCMNET_ERROR("DcmSCP: Error setting secure transport layer: " << cond.text());
          return cond;
      }
    }

    // drop root privileges now and revert to the calling user id (if we are running as setuid root)
    cond = OFStandard::dropPrivileges();
    if (cond.bad())
    {
        DCMNET_ERROR("setuid() failed, maximum number of processes/threads for uid already running.");
        return cond;
    }

    return cond;
}

// ----------------------------------------------------------------------------

OFCondition DcmSCP::acceptAssociations()
{
    if (m_network == NULL)
    {
       DCMNET_ERROR("network port not initialized, call DcmSCP::openListenPort() first.");
       return EC_IllegalCall;
    }

    OFCondition cond = EC_Normal;

    // At this point, the entire initialization process has been completed
    // successfully. Now, we want to start handling all incoming requests. Since
    // this activity is supposed to represent a server process, we do not want to
    // terminate this activity (unless indicated by the stopAfterCurrentAssociation()
    // or stopAfterConnectionTimeout() methods).
    while (cond.good())
    {
        // Wait for an association and handle the requests of
        // the calling applications correspondingly.
        cond = waitForAssociationRQ(m_network);

        // Check whether we have a timeout
        if (cond == DUL_NOASSOCIATIONREQUEST)
        {
            // If a stop is requested, stop
            if (stopAfterConnectionTimeout())
            {
                cond = NET_EC_StopAfterConnectionTimeout;
                break;
            }
            else
            {
                // stay in loop
                cond = EC_Normal;
            }
        }
        // Stop if SCP is told to stop after association was handled
        else if (stopAfterCurrentAssociation())
        {
            cond = NET_EC_StopAfterAssociation;
            break;
        }
    }

    // Drop the network, i.e. free memory of T_ASC_Network* structure. This call
    // is the counterpart of ASC_initializeNetwork(...) which was called above.
    ASC_dropNetwork(&m_network);
    m_network = NULL;

    // return ok
    return cond;
}

// ----------------------------------------------------------------------------

OFCondition DcmSCP::listen()
{
  OFCondition cond = openListenPort();
  if (cond.good()) cond = acceptAssociations();
  return cond;
}

// ----------------------------------------------------------------------------

void DcmSCP::findPresentationContext(const T_ASC_PresentationContextID presID,
                                     OFString& abstractSyntax,
                                     OFString& transferSyntax)
{
    transferSyntax.clear();
    abstractSyntax.clear();
    if (m_assoc == NULL)
        return;

    DUL_PRESENTATIONCONTEXT* pc;
    LST_HEAD** l;

    /* we look for a presentation context matching
     * both abstract and transfer syntax
     */
    l  = &m_assoc->params->DULparams.acceptedPresentationContext;
    pc = (DUL_PRESENTATIONCONTEXT*)LST_Head(l);
    (void)LST_Position(l, (LST_NODE*)pc);
    while (pc)
    {
        if (presID == pc->presentationContextID)
        {
            if (pc->result == ASC_P_ACCEPTANCE)
            {
                // found a match
                transferSyntax = pc->acceptedTransferSyntax;
                abstractSyntax = pc->abstractSyntax;
            }
            break;
        }
        pc = (DUL_PRESENTATIONCONTEXT*)LST_Next(l);
    }
}

DUL_PRESENTATIONCONTEXT* DcmSCP::findPresentationContextID(LST_HEAD* head,
                                                           T_ASC_PresentationContextID presentationContextID)
{
    DUL_PRESENTATIONCONTEXT* pc;
    LST_HEAD** l;
    OFBool found = OFFalse;

    if (head == NULL)
        return NULL;

    l = &head;
    if (*l == NULL)
        return NULL;

    pc = (DUL_PRESENTATIONCONTEXT*)LST_Head(l);
    (void)LST_Position(l, (LST_NODE*)pc);

    while (pc && !found)
    {
        if (pc->presentationContextID == presentationContextID)
        {
            found = OFTrue;
        }
        else
        {
            pc = (DUL_PRESENTATIONCONTEXT*)LST_Next(l);
        }
    }
    return pc;
}

// ----------------------------------------------------------------------------

void DcmSCP::refuseAssociation(const DcmRefuseReasonType reason)
{
    if (m_assoc == NULL)
    {
        DCMNET_WARN("DcmSCP::refuseAssociation() called but actually no association running, ignoring");
        return;
    }

    T_ASC_RejectParameters rej;

    // dump some information if required
    switch (reason)
    {
        case DCMSCP_TOO_MANY_ASSOCIATIONS:
            DCMNET_INFO("Refusing Association (too many associations)");
            break;
        case DCMSCP_CANNOT_FORK:
            DCMNET_INFO("Refusing Association (cannot fork)");
            break;
        case DCMSCP_BAD_APPLICATION_CONTEXT_NAME:
            DCMNET_INFO("Refusing Association (bad application context)");
            break;
        case DCMSCP_CALLING_HOST_NOT_ALLOWED:
            DCMNET_INFO("Refusing Association (connecting host not allowed)");
            break;
        case DCMSCP_CALLED_AE_TITLE_NOT_RECOGNIZED:
            DCMNET_INFO("Refusing Association (called AE title not recognized)");
            break;
        case DCMSCP_CALLING_AE_TITLE_NOT_RECOGNIZED:
            DCMNET_INFO("Refusing Association (calling AE title not recognized)");
            break;
        case DCMSCP_FORCED:
            DCMNET_INFO("Refusing Association (forced via command line)");
            break;
        case DCMSCP_NO_IMPLEMENTATION_CLASS_UID:
            DCMNET_INFO("Refusing Association (no implementation class UID provided)");
            break;
        case DCMSCP_NO_PRESENTATION_CONTEXTS:
            DCMNET_INFO("Refusing Association (no acceptable presentation contexts)");
            break;
        case DCMSCP_INTERNAL_ERROR:
            DCMNET_INFO("Refusing Association (internal error)");
            break;
        default:
            DCMNET_INFO("Refusing Association (unknown reason)");
            break;
    }

    // Set some values in the reject message depending on the reason
    switch (reason)
    {
        case DCMSCP_TOO_MANY_ASSOCIATIONS:
            rej.result = ASC_RESULT_REJECTEDTRANSIENT;
            rej.source = ASC_SOURCE_SERVICEPROVIDER_PRESENTATION_RELATED;
            rej.reason = ASC_REASON_SP_PRES_LOCALLIMITEXCEEDED;
            break;
        case DCMSCP_CANNOT_FORK:
            rej.result = ASC_RESULT_REJECTEDPERMANENT;
            rej.source = ASC_SOURCE_SERVICEPROVIDER_PRESENTATION_RELATED;
            rej.reason = ASC_REASON_SP_PRES_TEMPORARYCONGESTION;
            break;
        case DCMSCP_BAD_APPLICATION_CONTEXT_NAME:
            rej.result = ASC_RESULT_REJECTEDTRANSIENT;
            rej.source = ASC_SOURCE_SERVICEUSER;
            rej.reason = ASC_REASON_SU_APPCONTEXTNAMENOTSUPPORTED;
            break;
        case DCMSCP_CALLED_AE_TITLE_NOT_RECOGNIZED:
            rej.result = ASC_RESULT_REJECTEDPERMANENT;
            rej.source = ASC_SOURCE_SERVICEUSER;
            rej.reason = ASC_REASON_SU_CALLEDAETITLENOTRECOGNIZED;
            break;
        case DCMSCP_CALLING_AE_TITLE_NOT_RECOGNIZED:
            rej.result = ASC_RESULT_REJECTEDPERMANENT;
            rej.source = ASC_SOURCE_SERVICEUSER;
            rej.reason = ASC_REASON_SU_CALLINGAETITLENOTRECOGNIZED;
            break;
        case DCMSCP_FORCED:
        case DCMSCP_NO_IMPLEMENTATION_CLASS_UID:
        case DCMSCP_NO_PRESENTATION_CONTEXTS:
        case DCMSCP_CALLING_HOST_NOT_ALLOWED:
        case DCMSCP_INTERNAL_ERROR:
        default:
            rej.result = ASC_RESULT_REJECTEDPERMANENT;
            rej.source = ASC_SOURCE_SERVICEUSER;
            rej.reason = ASC_REASON_SU_NOREASON;
            break;
    }

    // Reject the association request.
    ASC_rejectAssociation(m_assoc, &rej);
}

// ----------------------------------------------------------------------------

OFCondition DcmSCP::waitForAssociationRQ(T_ASC_Network* network)
{
    if (network == NULL)
        return ASC_NULLKEY;
    if (m_assoc != NULL)
        return DIMSE_ILLEGALASSOCIATION;

    Uint32 timeout = m_cfg->getConnectionTimeout();
    OFBool useSecureLayer = m_cfg->transportLayerEnabled();

    // Listen to a socket for timeout seconds and wait for an association request
    OFCondition cond = ASC_receiveAssociation(network,
                                              &m_assoc,
                                              m_cfg->getMaxReceivePDULength(),
                                              NULL,
                                              NULL,
                                              useSecureLayer,
                                              m_cfg->getConnectionBlockingMode(),
                                              OFstatic_cast(int, timeout));

    // In case of a timeout in non-blocking mode, call notifier (and return
    // to main event loop later)
    if (cond == DUL_NOASSOCIATIONREQUEST)
    {
        notifyConnectionTimeout();
    }
    else
    {
        // If association could be received, handle it
        if (cond.good())
        {
            cond = processAssociationRQ();
            // There was an association which has ended now:
            // Call notifier and output separator line.
            notifyAssociationTermination();
            DCMNET_DEBUG("+++++++++++++++++++++++++++++");
        }
        // Else, if we could not receive an association request since there was
        // some error, just ignore it (and continue in main event loop later)
        else
        {
            DCMNET_ERROR("Could not receive association request: " << cond.text());
            cond = EC_Normal;
        }
    }

    // We are done with this association, free it and set to NULL.
    // ASC_receiveAssociation will always create a related structure, even
    // if no association was received at all.
    dropAndDestroyAssociation();
    return cond;
}

OFCondition DcmSCP::processAssociationRQ()
{
    DcmSCPActionType desiredAction = DCMSCP_ACTION_UNDEFINED;
    if ((m_assoc == NULL) || (m_assoc->params == NULL))
        return ASC_NULLKEY;

    // call notifier function
    notifyAssociationRequest(*m_assoc->params, desiredAction);
    if (desiredAction != DCMSCP_ACTION_UNDEFINED)
    {
        if (desiredAction == DCMSCP_ACTION_REFUSE_ASSOCIATION)
        {
            refuseAssociation(DCMSCP_INTERNAL_ERROR);
            return EC_Normal;
        }
        else
            desiredAction = DCMSCP_ACTION_UNDEFINED; // reset for later use
    }

    // Now we have to figure out if we might have to refuse the association request.
    // This is the case if at least one of five conditions is met:

    // Condition 1: if option "--refuse" is set we want to refuse the association request.
    if (m_cfg->getRefuseAssociation())
    {
        refuseAssociation(DCMSCP_FORCED);
        return EC_Normal;
    }

    // Condition 2: determine the application context name. If an error occurred or if the
    // application context name is not supported we want to refuse the association request.
    char buf[BUFSIZ];
    OFCondition cond = ASC_getApplicationContextName(m_assoc->params, buf, sizeof(buf));
    if (cond.bad() || strcmp(buf, DICOM_STDAPPLICATIONCONTEXT) != 0)
    {
        refuseAssociation(DCMSCP_BAD_APPLICATION_CONTEXT_NAME);
        return EC_Normal;
    }

    // Condition 3: if the calling host is not supported, we want to refuse
    // the association request
    if (!checkCallingHostAccepted(m_assoc->params->DULparams.calledPresentationAddress))
    {
        refuseAssociation(DCMSCP_CALLING_HOST_NOT_ALLOWED);
        return EC_Normal;
    }

    // Condition 4: if the calling or called application entity title is not supported
    // we want to refuse the association request
    if (!checkCalledAETitleAccepted(m_assoc->params->DULparams.calledAPTitle))
    {
        refuseAssociation(DCMSCP_CALLED_AE_TITLE_NOT_RECOGNIZED);
        return EC_Normal;
    }

    if (!checkCallingAETitleAccepted(m_assoc->params->DULparams.callingAPTitle))
    {
        refuseAssociation(DCMSCP_CALLING_AE_TITLE_NOT_RECOGNIZED);
        return EC_Normal;
    }

    /* set our application entity title */
    if (m_cfg->getRespondWithCalledAETitle())
        ASC_setAPTitles(m_assoc->params, NULL, NULL, m_assoc->params->DULparams.calledAPTitle);
    else
        ASC_setAPTitles(m_assoc->params, NULL, NULL, m_cfg->getAETitle().c_str());

    /* If we get to this point the association shall be negotiated.
       Thus, for every presentation context it is checked whether
       it can be accepted. However, this is only a "dry" run, i.e.
       there is not yet sent a response message to the SCU
     */
    cond = negotiateAssociation();
    if (cond.bad())
    {
        return EC_Normal;
    }

    // Reject association if no presentation context was negotiated
    if (ASC_countAcceptedPresentationContexts(m_assoc->params) == 0)
    {
        // Dump some debug information
        OFString tempStr;
        DCMNET_INFO("No Acceptable Presentation Contexts");
        if (m_cfg->getVerbosePCMode())
            DCMNET_INFO(ASC_dumpParameters(tempStr, m_assoc->params, ASC_ASSOC_RJ));
        else
            DCMNET_DEBUG(ASC_dumpParameters(tempStr, m_assoc->params, ASC_ASSOC_RJ));
        refuseAssociation(DCMSCP_NO_PRESENTATION_CONTEXTS);
        return EC_Normal;
    }

    // If the negotiation was successful, accept the association request
    cond = ASC_acknowledgeAssociation(m_assoc);
    if (cond.bad())
    {
        return EC_Normal;
    }
    notifyAssociationAcknowledge();

    // Dump some debug information
    OFString tempStr;
    DCMNET_INFO("Association Acknowledged (Max Send PDV: " << OFstatic_cast(Uint32, m_assoc->sendPDVLength) << ")");
    if (m_cfg->getVerbosePCMode())
        DCMNET_INFO(ASC_dumpParameters(tempStr, m_assoc->params, ASC_ASSOC_AC));
    else
        DCMNET_DEBUG(ASC_dumpParameters(tempStr, m_assoc->params, ASC_ASSOC_AC));

    // Go ahead and handle the association (i.e. handle the caller's requests) in this process
    handleAssociation();

    return EC_Normal;
}

// ----------------------------------------------------------------------------

OFCondition DcmSCP::negotiateAssociation()
{
    // Check whether there is something to negotiate...
    if (m_assoc == NULL)
        return DIMSE_ILLEGALASSOCIATION;

    // Set presentation contexts as defined in association configuration
    OFCondition result = m_cfg->evaluateIncomingAssociation(*m_assoc);
    if (result.bad())
    {
        OFString tempStr;
        DCMNET_ERROR(DimseCondition::dump(tempStr, result));
    }
    return result;
}

// ----------------------------------------------------------------------------

OFCondition DcmSCP::abortAssociation()
{
    OFCondition cond = DIMSE_ILLEGALASSOCIATION;
    // Check whether there is an active association
    if (isConnected())
    {
        // Abort current association
        DCMNET_INFO("Aborting Association (initiated by SCP)");
        cond = ASC_abortAssociation(m_assoc);
        // Notify user in case of error
        if (cond.bad())
        {
            OFString tempStr;
            DCMNET_ERROR("Association Abort Failed: " << DimseCondition::dump(tempStr, cond));
        }
        // Note: association is dropped and memory freed somewhere else
    }
    else
        DCMNET_WARN("DcmSCP::abortAssociation() called but SCP actually has no association running, ignoring");
    return cond;
}

// ----------------------------------------------------------------------------

void DcmSCP::handleAssociation()
{
    if (m_assoc == NULL)
    {
        DCMNET_WARN("DcmSCP::handleAssociation() called but SCP actually has no association running, ignoring");
        return;
    }

    // Receive a DIMSE command and perform all the necessary actions. (Note that ReceiveAndHandleCommands()
    // will always return a value 'cond' for which 'cond.bad()' will be true. This value indicates that either
    // some kind of error occurred, or that the peer aborted the association (DUL_PEERABORTEDASSOCIATION),
    // or that the peer requested the release of the association (DUL_PEERREQUESTEDRELEASE).) (Also note
    // that ReceiveAndHandleCommands() will never return EC_Normal.)
    OFCondition cond = EC_Normal;
    T_DIMSE_Message message;
    T_ASC_PresentationContextID presID;

    // start a loop to be able to receive more than one DIMSE command
    while (cond.good())
    {
        // receive a DIMSE command over the network
        cond = DIMSE_receiveCommand(
            m_assoc, m_cfg->getDIMSEBlockingMode(), m_cfg->getDIMSETimeout(), &presID, &message, NULL);

        // check if peer did release or abort, or if we have a valid message
        if (cond.good())
        {
            DcmPresentationContextInfo presInfo;
            getPresentationContextInfo(m_assoc, presID, presInfo);
            cond = handleIncomingCommand(&message, presInfo);
        }
    }
    // Clean up on association termination.
    if (cond == DUL_PEERREQUESTEDRELEASE)
    {
        notifyReleaseRequest();
        ASC_acknowledgeRelease(m_assoc);
    }
    else if (cond == DUL_PEERABORTEDASSOCIATION)
    {
        notifyAbortRequest();
    }
    else
    {
        notifyDIMSEError(cond);
        ASC_abortAssociation(m_assoc);
    }
}

// ----------------------------------------------------------------------------

OFCondition DcmSCP::handleIncomingCommand(T_DIMSE_Message* incomingMsg, const DcmPresentationContextInfo& presInfo)
{
    OFCondition cond;
    // Handle C-ECHO for Verification SOP Class
    if ((incomingMsg->CommandField == DIMSE_C_ECHO_RQ) && (presInfo.abstractSyntax == UID_VerificationSOPClass))
    {
        // Process C-ECHO request
        cond = handleECHORequest(incomingMsg->msg.CEchoRQ, presInfo.presentationContextID);
    }
    else
    {
        // We cannot handle this kind of message. Note that the condition will be returned
        // and that the caller is responsible to end the association if desired.
        OFString tempStr;
        DCMNET_ERROR("Cannot handle this kind of DIMSE command (0x"
                     << STD_NAMESPACE hex << STD_NAMESPACE setfill('0') << STD_NAMESPACE setw(4)
                     << OFstatic_cast(unsigned int, incomingMsg->CommandField) << ")");
        DCMNET_DEBUG(DIMSE_dumpMessage(tempStr, *incomingMsg, DIMSE_INCOMING));
        cond = DIMSE_BADCOMMANDTYPE;
    }

    // return result
    return cond;
}

// ----------------------------------------------------------------------------

// -- C-ECHO --

OFCondition DcmSCP::handleECHORequest(T_DIMSE_C_EchoRQ& reqMessage, const T_ASC_PresentationContextID presID)
{
    OFCondition cond;
    OFString tempStr;

    // Dump debug information
    if (DCM_dcmnetLogger.isEnabledFor(OFLogger::DEBUG_LOG_LEVEL))
    {
        DCMNET_INFO("Received C-ECHO Request");
        DCMNET_DEBUG(DIMSE_dumpMessage(tempStr, reqMessage, DIMSE_INCOMING, NULL, presID));
        DCMNET_INFO("Sending C-ECHO Response");
    }
    else
    {
        DCMNET_INFO("Received C-ECHO Request (MsgID " << reqMessage.MessageID << ")");
        DCMNET_INFO("Sending C-ECHO Response (" << DU_cechoStatusString(STATUS_Success) << ")");
    }

    // Send response message
    cond = DIMSE_sendEchoResponse(m_assoc, presID, &reqMessage, STATUS_Success, NULL);
    if (cond.bad())
        DCMNET_ERROR("Cannot send C-ECHO Response: " << DimseCondition::dump(tempStr, cond));
    else
        DCMNET_DEBUG("C-ECHO Response successfully sent");

    return cond;
}

// ----------------------------------------------------------------------------

// -- C-STORE --

OFCondition DcmSCP::handleSTORERequest(T_DIMSE_C_StoreRQ& reqMessage,
                                       const T_ASC_PresentationContextID presID,
                                       DcmDataset*& reqDataset)
{
    // First, receive the C-STORE request
    OFCondition cond = receiveSTORERequest(reqMessage, presID, reqDataset);

    if (cond.good())
    {
        // Then, check the request message and dataset and return an DIMSE status code
        const Uint16 rspStatusCode = checkSTORERequest(reqMessage, reqDataset);
        // ... that is sent back with the C-STORE response message
        cond = sendSTOREResponse(presID, reqMessage, rspStatusCode);
    }

    return cond;
}

OFCondition DcmSCP::receiveSTORERequest(T_DIMSE_C_StoreRQ& reqMessage,
                                        const T_ASC_PresentationContextID presID,
                                        DcmDataset*& reqDataset)
{
    // Do some basic validity checks
    if (m_assoc == NULL)
        return DIMSE_ILLEGALASSOCIATION;

    OFCondition cond;
    OFString tempStr;
    T_ASC_PresentationContextID presIDdset;
    // Remember the passed dataset pointer
    DcmDataset* dataset = reqDataset;

    // Dump debug information
    if (DCM_dcmnetLogger.isEnabledFor(OFLogger::DEBUG_LOG_LEVEL))
        DCMNET_INFO("Received C-STORE Request");
    else
        DCMNET_INFO("Received C-STORE Request (MsgID " << reqMessage.MessageID << ")");

    // Check if dataset is announced correctly
    if (reqMessage.DataSetType == DIMSE_DATASET_NULL)
    {
        DCMNET_DEBUG(DIMSE_dumpMessage(tempStr, reqMessage, DIMSE_INCOMING, NULL, presID));
        DCMNET_ERROR("Received C-STORE request but no dataset announced, aborting");
        return DIMSE_BADMESSAGE;
    }

    // Receive dataset (in memory)
    cond = receiveDIMSEDataset(&presIDdset, &dataset);
    if (cond.bad())
    {
        DCMNET_DEBUG(DIMSE_dumpMessage(tempStr, reqMessage, DIMSE_INCOMING, NULL, presID));
        DCMNET_ERROR("Unable to receive C-STORE dataset on presentation context "
                     << OFstatic_cast(unsigned int, presID));
        return cond;
    }

    // Output request message only if trace level is enabled
    if (DCM_dcmnetLogger.isEnabledFor(OFLogger::TRACE_LOG_LEVEL))
        DCMNET_DEBUG(DIMSE_dumpMessage(tempStr, reqMessage, DIMSE_INCOMING, dataset, presID));
    else
        DCMNET_DEBUG(DIMSE_dumpMessage(tempStr, reqMessage, DIMSE_INCOMING, NULL, presID));

    // Compare presentation context ID of command and data set
    if (presIDdset != presID)
    {
        DCMNET_ERROR("Presentation Context ID of command (" << OFstatic_cast(unsigned int, presID) << ") and data set ("
                                                            << OFstatic_cast(unsigned int, presIDdset) << ") differs");
        if (dataset != reqDataset)
        {
            // Free memory allocated by receiveDIMSEDataset()
            delete dataset;
        }
        return makeDcmnetCondition(DIMSEC_INVALIDPRESENTATIONCONTEXTID,
                                   OF_error,
                                   "DIMSE: Presentation Contexts of Command and Data Set differ");
    }

    // Set return value
    reqDataset = dataset;

    return cond;
}

OFCondition DcmSCP::receiveSTORERequest(T_DIMSE_C_StoreRQ& reqMessage,
                                        const T_ASC_PresentationContextID presID,
                                        const OFString& filename)
{
    // Do some basic validity checks
    if (m_assoc == NULL)
        return DIMSE_ILLEGALASSOCIATION;

    OFCondition cond;
    OFString tempStr;
    // Use presentation context ID of the command set as a default
    T_ASC_PresentationContextID presIDdset = presID;

    // Dump debug information
    if (DCM_dcmnetLogger.isEnabledFor(OFLogger::DEBUG_LOG_LEVEL))
        DCMNET_INFO("Received C-STORE Request");
    else
        DCMNET_INFO("Received C-STORE Request (MsgID " << reqMessage.MessageID << ")");

    // Check if dataset is announced correctly
    if (reqMessage.DataSetType == DIMSE_DATASET_NULL)
    {
        DCMNET_DEBUG(DIMSE_dumpMessage(tempStr, reqMessage, DIMSE_INCOMING, NULL, presID));
        DCMNET_ERROR("Received C-STORE request but no dataset announced, aborting");
        return DIMSE_BADMESSAGE;
    }

    // Receive dataset (directly to file)
    cond = receiveSTORERequestDataset(&presIDdset, reqMessage, filename);
    if (cond.bad())
    {
        DCMNET_DEBUG(DIMSE_dumpMessage(tempStr, reqMessage, DIMSE_INCOMING, NULL, presID));
        DCMNET_ERROR("Unable to receive C-STORE dataset on presentation context "
                     << OFstatic_cast(unsigned int, presID));
        return cond;
    }

    // Output request message only if trace level is enabled
    DCMNET_DEBUG(DIMSE_dumpMessage(tempStr, reqMessage, DIMSE_INCOMING, NULL, presID));

    // Compare presentation context ID of command and data set
    if (presIDdset != presID)
    {
        DCMNET_ERROR("Presentation Context ID of command (" << OFstatic_cast(unsigned int, presID) << ") and data set ("
                                                            << OFstatic_cast(unsigned int, presIDdset) << ") differs");
        return makeDcmnetCondition(DIMSEC_INVALIDPRESENTATIONCONTEXTID,
                                   OF_error,
                                   "DIMSE: Presentation Contexts of Command and Data Set differ");
    }

    return cond;
}

OFCondition DcmSCP::sendSTOREResponse(const T_ASC_PresentationContextID presID,
                                      const T_DIMSE_C_StoreRQ& reqMessage,
                                      const Uint16 rspStatusCode)
{
    // Call the method doing the real work
    return sendSTOREResponse(presID,
                             reqMessage.MessageID,
                             reqMessage.AffectedSOPClassUID,
                             reqMessage.AffectedSOPInstanceUID,
                             rspStatusCode,
                             NULL /* statusDetail */);
}

OFCondition DcmSCP::sendSTOREResponse(const T_ASC_PresentationContextID presID,
                                      const Uint16 messageID,
                                      const OFString& sopClassUID,
                                      const OFString& sopInstanceUID,
                                      const Uint16 rspStatusCode,
                                      DcmDataset* statusDetail)
{
    OFCondition cond;
    OFString tempStr;

    // Send back response
    T_DIMSE_Message response;
    // Make sure everything is zeroed (especially options)
    memset((char*)&response, 0, sizeof(response));
    T_DIMSE_C_StoreRSP& storeRsp       = response.msg.CStoreRSP;
    response.CommandField              = DIMSE_C_STORE_RSP;
    storeRsp.MessageIDBeingRespondedTo = messageID;
    storeRsp.DimseStatus               = rspStatusCode;
    storeRsp.DataSetType               = DIMSE_DATASET_NULL;
    // Always send the optional fields "Affected SOP Class UID" and "Affected SOP Instance UID"
    storeRsp.opts = O_STORE_AFFECTEDSOPCLASSUID | O_STORE_AFFECTEDSOPINSTANCEUID;
    OFStandard::strlcpy(storeRsp.AffectedSOPClassUID, sopClassUID.c_str(), sizeof(storeRsp.AffectedSOPClassUID));
    OFStandard::strlcpy(
        storeRsp.AffectedSOPInstanceUID, sopInstanceUID.c_str(), sizeof(storeRsp.AffectedSOPInstanceUID));

    if (DCM_dcmnetLogger.isEnabledFor(OFLogger::DEBUG_LOG_LEVEL))
    {
        DCMNET_INFO("Sending C-STORE Response");
        DCMNET_DEBUG(DIMSE_dumpMessage(tempStr, response, DIMSE_OUTGOING, NULL, presID));
    }
    else
    {
        DCMNET_INFO("Sending C-STORE Response (" << DU_cstoreStatusString(rspStatusCode) << ")");
    }

    // Send response message
    cond = sendDIMSEMessage(presID, &response, NULL /* dataObject */, statusDetail);
    if (cond.bad())
    {
        DCMNET_ERROR("Failed sending C-STORE response: " << DimseCondition::dump(tempStr, cond));
    }

    return cond;
}

Uint16 DcmSCP::checkSTORERequest(T_DIMSE_C_StoreRQ& /*reqMessage*/, DcmDataset* /*reqDataset*/)
{
    // we default to success
    return STATUS_Success;
}

// ----------------------------------------------------------------------------

// -- C-FIND --

OFCondition DcmSCP::receiveFINDRequest(T_DIMSE_C_FindRQ& reqMessage,
                                       const T_ASC_PresentationContextID presID,
                                       DcmDataset*& reqDataset)
{
    // Do some basic validity checks
    if (m_assoc == NULL)
        return DIMSE_ILLEGALASSOCIATION;

    OFCondition cond;
    OFString tempStr;
    T_ASC_PresentationContextID presIDdset;
    DcmDataset* dataset = NULL;

    // Dump debug information
    if (DCM_dcmnetLogger.isEnabledFor(OFLogger::DEBUG_LOG_LEVEL))
        DCMNET_INFO("Received C-FIND Request");
    else
        DCMNET_INFO("Received C-FIND Request (MsgID " << reqMessage.MessageID << ")");

    // Check if dataset is announced correctly
    if (reqMessage.DataSetType == DIMSE_DATASET_NULL)
    {
        DCMNET_DEBUG(DIMSE_dumpMessage(tempStr, reqMessage, DIMSE_INCOMING, NULL, presID));
        DCMNET_ERROR("Received C-FIND request but no dataset announced, aborting");
        return DIMSE_BADMESSAGE;
    }

    // Receive dataset
    cond = receiveDIMSEDataset(&presIDdset, &dataset);
    if (cond.bad())
    {
        DCMNET_DEBUG(DIMSE_dumpMessage(tempStr, reqMessage, DIMSE_INCOMING, NULL, presID));
        DCMNET_ERROR("Unable to receive C-FIND dataset on presentation context "
                     << OFstatic_cast(unsigned int, presID));
        return DIMSE_BADDATA;
    }

    // Output request message only if trace level is enabled
    if (DCM_dcmnetLogger.isEnabledFor(OFLogger::TRACE_LOG_LEVEL))
        DCMNET_DEBUG(DIMSE_dumpMessage(tempStr, reqMessage, DIMSE_INCOMING, dataset, presID));
    else
        DCMNET_DEBUG(DIMSE_dumpMessage(tempStr, reqMessage, DIMSE_INCOMING, NULL, presID));

    // Compare presentation context ID of command and data set
    if (presIDdset != presID)
    {
        DCMNET_ERROR("Presentation Context ID of command (" << OFstatic_cast(unsigned int, presID) << ") and data set ("
                                                            << OFstatic_cast(unsigned int, presIDdset) << ") differs");
        delete dataset;
        return makeDcmnetCondition(DIMSEC_INVALIDPRESENTATIONCONTEXTID,
                                   OF_error,
                                   "DIMSE: Presentation Contexts of Command and Data Set differ");
    }

    // Set return value
    reqDataset = dataset;

    return cond;
}

OFCondition DcmSCP::sendFINDResponse(const T_ASC_PresentationContextID presID,
                                     const Uint16 messageID,
                                     const OFString& sopClassUID,
                                     DcmDataset* rspDataset,
                                     const Uint16 rspStatusCode,
                                     DcmDataset* statusDetail)
{
    OFCondition cond;
    OFString tempStr;

    // Send back response
    T_DIMSE_Message response;
    // Make sure everything is zeroed (especially options)
    memset((char*)&response, 0, sizeof(response));
    T_DIMSE_C_FindRSP& findRsp        = response.msg.CFindRSP;
    response.CommandField             = DIMSE_C_FIND_RSP;
    findRsp.MessageIDBeingRespondedTo = messageID;
    findRsp.DimseStatus               = rspStatusCode;
    // Always send (the optional) field "Affected SOP Class UID"
    findRsp.opts = O_FIND_AFFECTEDSOPCLASSUID;
    OFStandard::strlcpy(findRsp.AffectedSOPClassUID, sopClassUID.c_str(), sizeof(findRsp.AffectedSOPClassUID));

    if (rspDataset)
        findRsp.DataSetType = DIMSE_DATASET_PRESENT;
    else
        findRsp.DataSetType = DIMSE_DATASET_NULL;

    if (DCM_dcmnetLogger.isEnabledFor(OFLogger::DEBUG_LOG_LEVEL))
    {
        DCMNET_INFO("Sending C-FIND Response");
        DCMNET_DEBUG(DIMSE_dumpMessage(tempStr, response, DIMSE_OUTGOING, rspDataset, presID));
    }
    else
    {
        DCMNET_INFO("Sending C-FIND Response (" << DU_cfindStatusString(rspStatusCode) << ")");
    }

    // Send response message with dataset
    cond = sendDIMSEMessage(presID, &response, rspDataset /* dataObject */, statusDetail);
    if (cond.bad())
    {
        DCMNET_ERROR("Failed sending C-FIND response: " << DimseCondition::dump(tempStr, cond));
        return cond;
    }
    return cond;
}

OFCondition DcmSCP::checkForCANCEL(T_ASC_PresentationContextID presID, const Uint16 messageID)
{
    return DIMSE_checkForCancelRQ(m_assoc, presID, messageID);
}

// ----------------------------------------------------------------------------

// -- C-MOVE --

OFCondition DcmSCP::receiveMOVERequest(T_DIMSE_C_MoveRQ& reqMessage,
                                       const T_ASC_PresentationContextID presID,
                                       DcmDataset*& reqDataset,
                                       OFString& moveDest)
{
    // Do some basic validity checks
    if (m_assoc == NULL)
        return DIMSE_ILLEGALASSOCIATION;

    OFCondition cond;
    OFString tempStr;
    T_ASC_PresentationContextID presIDdset;
    DcmDataset* dataset = NULL;

    // Dump debug information
    if (DCM_dcmnetLogger.isEnabledFor(OFLogger::DEBUG_LOG_LEVEL))
        DCMNET_INFO("Received C-MOVE Request");
    else
        DCMNET_INFO("Received C-MOVE Request (MsgID " << reqMessage.MessageID << ")");

    // Check if dataset is announced correctly
    if (reqMessage.DataSetType == DIMSE_DATASET_NULL)
    {
        DCMNET_DEBUG(DIMSE_dumpMessage(tempStr, reqMessage, DIMSE_INCOMING, NULL, presID));
        DCMNET_ERROR("Received C-MOVE request but no dataset announced, aborting");
        return DIMSE_BADMESSAGE;
    }

    // Receive dataset
    cond = receiveDIMSEDataset(&presIDdset, &dataset);
    if (cond.bad())
    {
        DCMNET_DEBUG(DIMSE_dumpMessage(tempStr, reqMessage, DIMSE_INCOMING, NULL, presID));
        DCMNET_ERROR("Unable to receive C-MOVE dataset on presentation context "
                     << OFstatic_cast(unsigned int, presID));
        return DIMSE_BADDATA;
    }

    // Output request message only if trace level is enabled
    if (DCM_dcmnetLogger.isEnabledFor(OFLogger::TRACE_LOG_LEVEL))
        DCMNET_DEBUG(DIMSE_dumpMessage(tempStr, reqMessage, DIMSE_INCOMING, dataset, presID));
    else
        DCMNET_DEBUG(DIMSE_dumpMessage(tempStr, reqMessage, DIMSE_INCOMING, NULL, presID));

    // Compare presentation context ID of command and data set
    if (presIDdset != presID)
    {
        DCMNET_ERROR("Presentation Context ID of command (" << OFstatic_cast(unsigned int, presID) << ") and data set ("
                                                            << OFstatic_cast(unsigned int, presIDdset) << ") differs");
        delete dataset;
        return makeDcmnetCondition(DIMSEC_INVALIDPRESENTATIONCONTEXTID,
                                   OF_error,
                                   "DIMSE: Presentation Contexts of Command and Data Set differ");
    }

    // Set return values
    reqDataset = dataset;
    moveDest   = reqMessage.MoveDestination;

    return cond;
}

OFCondition DcmSCP::sendMOVEResponse(const T_ASC_PresentationContextID presID,
                                     const Uint16 messageID,
                                     const OFString& sopClassUID,
                                     DcmDataset* rspDataset,
                                     const Uint16 rspStatusCode,
                                     DcmDataset* statusDetail,
                                     const Uint16 numRemain,
                                     const Uint16 numComplete,
                                     const Uint16 numFail,
                                     const Uint16 numWarn)
{
    OFCondition cond;
    OFString tempStr;

    // Send back response
    T_DIMSE_Message response;
    // Make sure everything is zeroed (especially options)
    memset((char*)&response, 0, sizeof(response));
    T_DIMSE_C_MoveRSP& moveRsp        = response.msg.CMoveRSP;
    response.CommandField             = DIMSE_C_MOVE_RSP;
    moveRsp.MessageIDBeingRespondedTo = messageID;
    moveRsp.DimseStatus               = rspStatusCode;
    // Always send the optional field "Affected SOP Class UID"
    moveRsp.opts = O_MOVE_AFFECTEDSOPCLASSUID;
    OFStandard::strlcpy(moveRsp.AffectedSOPClassUID, sopClassUID.c_str(), sizeof(moveRsp.AffectedSOPClassUID));
    // Only send the other optional fields if needed
    if ((numRemain != 0) || (numComplete != 0) || (numFail != 0) || (numWarn != 0))
    {
        moveRsp.NumberOfRemainingSubOperations = numRemain;
        moveRsp.NumberOfCompletedSubOperations = numComplete;
        moveRsp.NumberOfFailedSubOperations    = numFail;
        moveRsp.NumberOfWarningSubOperations   = numWarn;
        moveRsp.opts |= O_MOVE_NUMBEROFREMAININGSUBOPERATIONS | O_MOVE_NUMBEROFCOMPLETEDSUBOPERATIONS
            | O_MOVE_NUMBEROFFAILEDSUBOPERATIONS | O_MOVE_NUMBEROFWARNINGSUBOPERATIONS;
    }

    if (rspDataset)
        moveRsp.DataSetType = DIMSE_DATASET_PRESENT;
    else
        moveRsp.DataSetType = DIMSE_DATASET_NULL;

    if (DCM_dcmnetLogger.isEnabledFor(OFLogger::DEBUG_LOG_LEVEL))
    {
        DCMNET_INFO("Sending C-MOVE Response");
        DCMNET_DEBUG(DIMSE_dumpMessage(tempStr, response, DIMSE_OUTGOING, rspDataset, presID));
    }
    else
    {
        DCMNET_INFO("Sending C-MOVE Response (" << DU_cmoveStatusString(rspStatusCode) << ")");
    }

    // Send response message with dataset
    cond = sendDIMSEMessage(presID, &response, rspDataset /* dataObject */, statusDetail);
    if (cond.bad())
    {
        DCMNET_ERROR("Failed sending C-MOVE response: " << DimseCondition::dump(tempStr, cond));
    }

    return cond;
}

// ----------------------------------------------------------------------------

// -- N-ACTION --

OFCondition DcmSCP::receiveACTIONRequest(T_DIMSE_N_ActionRQ& reqMessage,
                                         const T_ASC_PresentationContextID presID,
                                         DcmDataset*& reqDataset,
                                         Uint16& actionTypeID)
{
    // Do some basic validity checks
    if (m_assoc == NULL)
        return DIMSE_ILLEGALASSOCIATION;

    OFCondition cond;
    OFString tempStr;
    T_ASC_PresentationContextID presIDdset;
    DcmDataset* dataset = NULL;

    // Dump debug information
    if (DCM_dcmnetLogger.isEnabledFor(OFLogger::DEBUG_LOG_LEVEL))
        DCMNET_INFO("Received N-ACTION Request");
    else
        DCMNET_INFO("Received N-ACTION Request (MsgID " << reqMessage.MessageID << ")");

    // Check if dataset is announced correctly
    if (reqMessage.DataSetType == DIMSE_DATASET_NULL)
    {
        DCMNET_DEBUG(DIMSE_dumpMessage(tempStr, reqMessage, DIMSE_INCOMING, NULL, presID));
        DCMNET_ERROR("Received N-ACTION request but no dataset announced, aborting");
        return DIMSE_BADMESSAGE;
    }

    // Receive dataset
    cond = receiveDIMSEDataset(&presIDdset, &dataset);
    if (cond.bad())
    {
        DCMNET_DEBUG(DIMSE_dumpMessage(tempStr, reqMessage, DIMSE_INCOMING, NULL, presID));
        DCMNET_ERROR("Unable to receive N-ACTION dataset on presentation context "
                     << OFstatic_cast(unsigned int, presID));
        return DIMSE_BADDATA;
    }

    // Output request message only if trace level is enabled
    if (DCM_dcmnetLogger.isEnabledFor(OFLogger::TRACE_LOG_LEVEL))
        DCMNET_DEBUG(DIMSE_dumpMessage(tempStr, reqMessage, DIMSE_INCOMING, dataset, presID));
    else
        DCMNET_DEBUG(DIMSE_dumpMessage(tempStr, reqMessage, DIMSE_INCOMING, NULL, presID));

    // Compare presentation context ID of command and data set
    if (presIDdset != presID)
    {
        DCMNET_ERROR("Presentation Context ID of command (" << OFstatic_cast(unsigned int, presID) << ") and data set ("
                                                            << OFstatic_cast(unsigned int, presIDdset) << ") differs");
        delete dataset;
        return makeDcmnetCondition(DIMSEC_INVALIDPRESENTATIONCONTEXTID,
                                   OF_error,
                                   "DIMSE: Presentation Contexts of Command and Data Set differ");
    }

    // Set return values
    reqDataset   = dataset;
    actionTypeID = reqMessage.ActionTypeID;

    return cond;
}

OFCondition DcmSCP::sendACTIONResponse(const T_ASC_PresentationContextID presID,
                                       const Uint16 messageID,
                                       const OFString& sopClassUID,
                                       const OFString& sopInstanceUID,
                                       const Uint16 rspStatusCode)
{
    OFCondition cond;
    OFString tempStr;

    // Send back response
    T_DIMSE_Message response;
    // Make sure everything is zeroed (especially options)
    memset((char*)&response, 0, sizeof(response));
    T_DIMSE_N_ActionRSP& actionRsp      = response.msg.NActionRSP;
    response.CommandField               = DIMSE_N_ACTION_RSP;
    actionRsp.MessageIDBeingRespondedTo = messageID;
    actionRsp.DimseStatus               = rspStatusCode;
    actionRsp.DataSetType               = DIMSE_DATASET_NULL;
    // Always send the optional fields "Affected SOP Class UID" and "Affected SOP Instance UID"
    actionRsp.opts = O_NACTION_AFFECTEDSOPCLASSUID | O_NACTION_AFFECTEDSOPINSTANCEUID;
    OFStandard::strlcpy(actionRsp.AffectedSOPClassUID, sopClassUID.c_str(), sizeof(actionRsp.AffectedSOPClassUID));
    OFStandard::strlcpy(
        actionRsp.AffectedSOPInstanceUID, sopInstanceUID.c_str(), sizeof(actionRsp.AffectedSOPInstanceUID));
    // Do not send any other optional fields, e.g. "Action Type ID"

    if (DCM_dcmnetLogger.isEnabledFor(OFLogger::DEBUG_LOG_LEVEL))
    {
        DCMNET_INFO("Sending N-ACTION Response");
        DCMNET_DEBUG(DIMSE_dumpMessage(tempStr, response, DIMSE_OUTGOING, NULL, presID));
    }
    else
    {
        DCMNET_INFO("Sending N-ACTION Response (" << DU_nactionStatusString(rspStatusCode) << ")");
    }

    // Send response message
    cond = sendDIMSEMessage(presID, &response, NULL /* dataObject */);
    if (cond.bad())
    {
        DCMNET_ERROR("Failed sending N-ACTION response: " << DimseCondition::dump(tempStr, cond));
    }

    return cond;
}

// ----------------------------------------------------------------------------

// -- N-EVENT-REPORT --

OFCondition DcmSCP::handleEVENTREPORTRequest(T_DIMSE_N_EventReportRQ& reqMessage,
                                             const T_ASC_PresentationContextID presID,
                                             DcmDataset*& reqDataset,
                                             Uint16& eventTypeID)
{
    // Do some basic validity checks
    if (m_assoc == NULL)
        return DIMSE_ILLEGALASSOCIATION;

    OFCondition cond;
    OFString tempStr;
    T_ASC_PresentationContextID presIDdset;
    DcmDataset* dataset = NULL;
    // DcmDataset *statusDetail = NULL; // TODO: do we need this and if so, how do we get it?
    Uint16 rspStatusCode = 0;

    // Dump debug information
    if (DCM_dcmnetLogger.isEnabledFor(OFLogger::TRACE_LOG_LEVEL))
        DCMNET_INFO("Received N-EVENT-REPORT Request");
    else
        DCMNET_INFO("Received N-EVENT-REPORT Request (MsgID " << reqMessage.MessageID << ")");

    // Check if dataset is announced correctly
    if (reqMessage.DataSetType == DIMSE_DATASET_NULL)
    {
        DCMNET_DEBUG(DIMSE_dumpMessage(tempStr, reqMessage, DIMSE_INCOMING, NULL, presID));
        DCMNET_ERROR("Received N-EVENT-REPORT request but no dataset announced, aborting");
        return DIMSE_BADMESSAGE;
    }

    // Receive dataset
    cond = receiveDIMSEDataset(&presIDdset, &dataset);
    if (cond.bad())
    {
        DCMNET_DEBUG(DIMSE_dumpMessage(tempStr, reqMessage, DIMSE_INCOMING, NULL, presID));
        DCMNET_ERROR("Unable to receive N-EVENT-REPORT dataset on presentation context "
                     << OFstatic_cast(unsigned int, presID));
        return DIMSE_BADDATA;
    }

    // Output dataset only if trace level is enabled
    if (DCM_dcmnetLogger.isEnabledFor(OFLogger::TRACE_LOG_LEVEL))
        DCMNET_DEBUG(DIMSE_dumpMessage(tempStr, reqMessage, DIMSE_INCOMING, dataset, presID));
    else
        DCMNET_DEBUG(DIMSE_dumpMessage(tempStr, reqMessage, DIMSE_INCOMING, NULL, presID));

    // Compare presentation context ID of command and data set
    if (presIDdset != presID)
    {
        DCMNET_ERROR("Presentation Context ID of command (" << OFstatic_cast(unsigned int, presID) << ") and data set ("
                                                            << OFstatic_cast(unsigned int, presIDdset) << ") differs");
        delete dataset;
        return makeDcmnetCondition(DIMSEC_INVALIDPRESENTATIONCONTEXTID,
                                   OF_error,
                                   "DIMSE: Presentation Contexts of Command and Data Set differ");
    }

    // Check the request message and dataset and return the DIMSE status code to be used
    rspStatusCode = checkEVENTREPORTRequest(reqMessage, dataset);

    // Send back response
    T_DIMSE_Message response;
    // Make sure everything is zeroed (especially options)
    memset((char*)&response, 0, sizeof(response));
    T_DIMSE_N_EventReportRSP& eventReportRsp = response.msg.NEventReportRSP;
    response.CommandField                    = DIMSE_N_EVENT_REPORT_RSP;
    eventReportRsp.MessageIDBeingRespondedTo = reqMessage.MessageID;
    eventReportRsp.DimseStatus               = rspStatusCode;
    eventReportRsp.DataSetType               = DIMSE_DATASET_NULL;
    // Do not send any optional fields
    eventReportRsp.opts                      = 0;
    eventReportRsp.AffectedSOPClassUID[0]    = 0;
    eventReportRsp.AffectedSOPInstanceUID[0] = 0;

    if (DCM_dcmnetLogger.isEnabledFor(OFLogger::DEBUG_LOG_LEVEL))
    {
        DCMNET_INFO("Sending N-EVENT-REPORT Response");
        DCMNET_DEBUG(DIMSE_dumpMessage(tempStr, response, DIMSE_OUTGOING, NULL, presID));
    }
    else
    {
        DCMNET_INFO("Sending N-EVENT-REPORT Response (" << DU_neventReportStatusString(rspStatusCode) << ")");
    }
    // Send response message
    cond = sendDIMSEMessage(presID, &response, NULL /* dataObject */);
    if (cond.bad())
    {
        DCMNET_ERROR("Failed sending N-EVENT-REPORT response: " << DimseCondition::dump(tempStr, cond));
        delete dataset;
        return cond;
    }

    // Set return values
    reqDataset  = dataset;
    eventTypeID = reqMessage.EventTypeID;

    return cond;
}

OFCondition DcmSCP::sendEVENTREPORTRequest(const T_ASC_PresentationContextID presID,
                                           const OFString& sopInstanceUID,
                                           const Uint16 messageID,
                                           const Uint16 eventTypeID,
                                           DcmDataset* reqDataset,
                                           Uint16& rspStatusCode)
{
    // Do some basic validity checks
    if (m_assoc == NULL)
        return DIMSE_ILLEGALASSOCIATION;
    if (sopInstanceUID.empty() || (reqDataset == NULL))
        return DIMSE_NULLKEY;

    // Prepare DIMSE data structures for issuing request
    OFCondition cond;
    OFString tempStr;
    T_ASC_PresentationContextID pcid = presID;
    T_DIMSE_Message request;
    // Make sure everything is zeroed (especially options)
    memset((char*)&request, 0, sizeof(request));
    T_DIMSE_N_EventReportRQ& eventReportReq = request.msg.NEventReportRQ;
    DcmDataset* statusDetail                = NULL;

    request.CommandField = DIMSE_N_EVENT_REPORT_RQ;

    // Generate a new message ID (?)
    eventReportReq.MessageID = messageID;

    eventReportReq.DataSetType = DIMSE_DATASET_PRESENT;
    eventReportReq.EventTypeID = eventTypeID;

    // Determine SOP Class from presentation context
    OFString abstractSyntax, transferSyntax;

    findPresentationContext(pcid, abstractSyntax, transferSyntax);
    if (abstractSyntax.empty() || transferSyntax.empty())
        return DIMSE_NOVALIDPRESENTATIONCONTEXTID;
    OFStandard::strlcpy(
        eventReportReq.AffectedSOPClassUID, abstractSyntax.c_str(), sizeof(eventReportReq.AffectedSOPClassUID));
    OFStandard::strlcpy(
        eventReportReq.AffectedSOPInstanceUID, sopInstanceUID.c_str(), sizeof(eventReportReq.AffectedSOPInstanceUID));

    // Send request
    if (DCM_dcmnetLogger.isEnabledFor(OFLogger::DEBUG_LOG_LEVEL))
    {
        DCMNET_INFO("Sending N-EVENT-REPORT Request");
        // Output dataset only if trace level is enabled
        if (DCM_dcmnetLogger.isEnabledFor(OFLogger::TRACE_LOG_LEVEL))
            DCMNET_DEBUG(DIMSE_dumpMessage(tempStr, request, DIMSE_OUTGOING, reqDataset, pcid));
        else
            DCMNET_DEBUG(DIMSE_dumpMessage(tempStr, request, DIMSE_OUTGOING, NULL, pcid));
    }
    else
    {
        DCMNET_INFO("Sending N-EVENT-REPORT Request (MsgID " << eventReportReq.MessageID << ")");
    }
    cond = sendDIMSEMessage(pcid, &request, reqDataset);
    if (cond.bad())
    {
        DCMNET_ERROR("Failed sending N-EVENT-REPORT request: " << DimseCondition::dump(tempStr, cond));
        return cond;
    }
    // Receive response
    T_DIMSE_Message response;
    cond = receiveDIMSECommand(&pcid, &response, &statusDetail, NULL /* commandSet */);
    if (cond.bad())
    {
        DCMNET_ERROR("Failed receiving DIMSE response: " << DimseCondition::dump(tempStr, cond));
        return cond;
    }

    // Check command set
    if (response.CommandField == DIMSE_N_EVENT_REPORT_RSP)
    {
        if (DCM_dcmnetLogger.isEnabledFor(OFLogger::DEBUG_LOG_LEVEL))
        {
            DCMNET_INFO("Received N-EVENT-REPORT Response");
            DCMNET_DEBUG(DIMSE_dumpMessage(tempStr, response, DIMSE_INCOMING, NULL, pcid));
        }
        else
        {
            DCMNET_INFO("Received N-EVENT-REPORT Response ("
                        << DU_neventReportStatusString(response.msg.NEventReportRSP.DimseStatus) << ")");
        }
    }
    else
    {
        DCMNET_ERROR("Expected N-EVENT-REPORT response but received DIMSE command 0x"
                     << STD_NAMESPACE hex << STD_NAMESPACE setfill('0') << STD_NAMESPACE setw(4)
                     << OFstatic_cast(unsigned int, response.CommandField));
        DCMNET_DEBUG(DIMSE_dumpMessage(tempStr, response, DIMSE_INCOMING, NULL, pcid));
        delete statusDetail;
        return DIMSE_BADCOMMANDTYPE;
    }
    if (statusDetail != NULL)
    {
        DCMNET_DEBUG("Response has status detail:" << OFendl << DcmObject::PrintHelper(*statusDetail));
        delete statusDetail;
    }

    // Set return value
    T_DIMSE_N_EventReportRSP& eventReportRsp = response.msg.NEventReportRSP;
    rspStatusCode                            = eventReportRsp.DimseStatus;

    // Check whether there is a dataset to be received
    if (eventReportRsp.DataSetType != DIMSE_DATASET_NULL)
    {
        // this should never happen
        DcmDataset* tempDataset = NULL;
        T_ASC_PresentationContextID tempID;
        // Receive dataset
        cond = receiveDIMSEDataset(&tempID, &tempDataset);
        if (cond.good())
        {
            DCMNET_WARN("Received unexpected dataset after N-EVENT-REPORT response, ignoring");
            delete tempDataset;
        }
        else
        {
            DCMNET_ERROR("Failed receiving unexpected dataset after N-EVENT-REPORT response: "
                         << DimseCondition::dump(tempStr, cond));
            return DIMSE_BADDATA;
        }
    }

    // Check whether the message ID being responded to is equal to the message ID of the request
    if (eventReportRsp.MessageIDBeingRespondedTo != eventReportReq.MessageID)
    {
        DCMNET_ERROR("Received response with wrong message ID (" << eventReportRsp.MessageIDBeingRespondedTo
                                                                 << " instead of " << eventReportReq.MessageID << ")");
        return DIMSE_BADMESSAGE;
    }

    return cond;
}

Uint16 DcmSCP::checkEVENTREPORTRequest(T_DIMSE_N_EventReportRQ& /*reqMessage*/, DcmDataset* /*reqDataset*/)
{
    // we default to success
    return STATUS_Success;
}

/* ************************************************************************* */
/*                         General message handling                          */
/* ************************************************************************* */

void DcmSCP::notifySENDProgress(const unsigned long byteCount)
{
    DCMNET_TRACE("Bytes sent: " << byteCount);
}

void DcmSCP::notifyRECEIVEProgress(const unsigned long byteCount)
{
    DCMNET_TRACE("Bytes received: " << byteCount);
}

/* ************************************************************************* */
/*                            Various helpers                                */
/* ************************************************************************* */

// Sends a DIMSE command and possibly also instance data to the configured peer DICOM application
OFCondition DcmSCP::sendDIMSEMessage(const T_ASC_PresentationContextID presID,
                                     T_DIMSE_Message* message,
                                     DcmDataset* dataObject,
                                     DcmDataset* statusDetail,
                                     DcmDataset** commandSet)
{
    if (m_assoc == NULL)
        return DIMSE_ILLEGALASSOCIATION;
    if (message == NULL)
        return DIMSE_NULLKEY;

    OFCondition cond;
    /* call the corresponding DIMSE function to sent the message */
    if (m_cfg->getProgressNotificationMode())
    {
        cond = DIMSE_sendMessageUsingMemoryData(m_assoc,
                                                presID,
                                                message,
                                                statusDetail,
                                                dataObject,
                                                callbackSENDProgress,
                                                this /*callbackData*/,
                                                commandSet);
    }
    else
    {
        cond = DIMSE_sendMessageUsingMemoryData(
            m_assoc, presID, message, statusDetail, dataObject, NULL /*callback*/, NULL /*callbackData*/, commandSet);
    }
    return cond;
}

// ----------------------------------------------------------------------------

// Receive DIMSE command (excluding dataset!) over the currently open association
OFCondition DcmSCP::receiveDIMSECommand(T_ASC_PresentationContextID* presID,
                                        T_DIMSE_Message* message,
                                        DcmDataset** statusDetail,
                                        DcmDataset** commandSet,
                                        const Uint32 timeout)
{
    if (m_assoc == NULL)
        return DIMSE_ILLEGALASSOCIATION;

    OFCondition cond;
    if (timeout > 0)
    {
        /* call the corresponding DIMSE function to receive the command (use specified timeout) */
        cond = DIMSE_receiveCommand(m_assoc, DIMSE_NONBLOCKING, timeout, presID, message, statusDetail, commandSet);
    }
    else
    {
        /* call the corresponding DIMSE function to receive the command (use default timeout) */
        cond = DIMSE_receiveCommand(m_assoc,
                                    m_cfg->getDIMSEBlockingMode(),
                                    m_cfg->getDIMSETimeout(),
                                    presID,
                                    message,
                                    statusDetail,
                                    commandSet);
    }
    return cond;
}

// ----------------------------------------------------------------------------

// Receives one dataset (of instance data) via network from another DICOM application in memory
OFCondition DcmSCP::receiveDIMSEDataset(T_ASC_PresentationContextID* presID, DcmDataset** dataObject)
{
    if (m_assoc == NULL)
        return DIMSE_ILLEGALASSOCIATION;

    OFCondition cond;
    /* call the corresponding DIMSE function to receive the dataset */
    if (m_cfg->getProgressNotificationMode())
    {
        cond = DIMSE_receiveDataSetInMemory(m_assoc,
                                            m_cfg->getDIMSEBlockingMode(),
                                            m_cfg->getDIMSETimeout(),
                                            presID,
                                            dataObject,
                                            callbackRECEIVEProgress,
                                            this /*callbackData*/);
    }
    else
    {
        cond = DIMSE_receiveDataSetInMemory(m_assoc,
                                            m_cfg->getDIMSEBlockingMode(),
                                            m_cfg->getDIMSETimeout(),
                                            presID,
                                            dataObject,
                                            NULL /*callback*/,
                                            NULL /*callbackData*/);
    }

    if (cond.good())
    {
        DCMNET_DEBUG("Received dataset on presentation context " << OFstatic_cast(unsigned int, *presID));
    }
    else
    {
        OFString tempStr;
        DCMNET_ERROR("Unable to receive dataset on presentation context "
                     << OFstatic_cast(unsigned int, *presID) << ": " << DimseCondition::dump(tempStr, cond));
    }
    return cond;
}

// ----------------------------------------------------------------------------

// Receives one C-STORE request dataset via network from another DICOM application
// (and store it directly to file)
OFCondition DcmSCP::receiveSTORERequestDataset(T_ASC_PresentationContextID* presID,
                                               T_DIMSE_C_StoreRQ& reqMessage,
                                               const OFString& filename)
{
    if (m_assoc == NULL)
        return DIMSE_ILLEGALASSOCIATION;
    if (filename.empty())
        return EC_InvalidFilename;

    OFString tempStr;
    DcmOutputFileStream* filestream = NULL;
    // Receive dataset over the network and write it directly to a file
    OFCondition cond
        = DIMSE_createFilestream(filename, &reqMessage, m_assoc, *presID, OFTrue /*writeMetaheader*/, &filestream);
    if (cond.good())
    {
        if (m_cfg->getProgressNotificationMode())
        {
            cond = DIMSE_receiveDataSetInFile(m_assoc,
                                              m_cfg->getDIMSEBlockingMode(),
                                              m_cfg->getDIMSETimeout(),
                                              presID,
                                              filestream,
                                              callbackRECEIVEProgress,
                                              this /*callbackData*/);
        }
        else
        {
            cond = DIMSE_receiveDataSetInFile(m_assoc,
                                              m_cfg->getDIMSEBlockingMode(),
                                              m_cfg->getDIMSETimeout(),
                                              presID,
                                              filestream,
                                              NULL /*callback*/,
                                              NULL /*callbackData*/);
        }
        if (cond.good()) cond = filestream->fclose();
        delete filestream;
        if (cond.good())
        {
            DCMNET_DEBUG("Received dataset on presentation context " << OFstatic_cast(unsigned int, *presID)
                                                                     << " and stored it directly to file");
        }
        else
        {
            DCMNET_ERROR("Unable to receive dataset on presentation context "
                         << OFstatic_cast(unsigned int, *presID) << ": " << DimseCondition::dump(tempStr, cond));
            // Delete created file in case of error
            OFStandard::deleteFile(filename);
        }
    }
    else
    {

        DCMNET_ERROR("Unable to receive dataset on presentation context "
                     << OFstatic_cast(unsigned int, *presID) << ": " << DimseCondition::dump(tempStr, cond));
        // Could not create the filestream, so ignore the dataset
        DIC_UL bytesRead = 0;
        DIC_UL pdvCount  = 0;
        DCMNET_DEBUG("Ignoring incoming dataset and returning an error status to the SCU");
        cond = DIMSE_ignoreDataSet(
            m_assoc, m_cfg->getDIMSEBlockingMode(), m_cfg->getDIMSETimeout(), &bytesRead, &pdvCount);
        if (cond.good())
        {
            tempStr = "Cannot create file: " + filename;
            cond    = makeDcmnetCondition(DIMSEC_OUTOFRESOURCES, OF_error, tempStr.c_str());
        }
    }
    return cond;
}

// ----------------------------------------------------------------------------

OFBool DcmSCP::addStatusDetail(DcmDataset** statusDetail, const DcmElement* elem)
{
    DCMNET_TRACE("Add element to status detail");
    // If no element was passed, return to the caller.
    if (elem == NULL)
        return OFFalse;

    DcmAttributeTag* at;
    DcmLongString* lo;

    // Create the container object if necessary
    if (*statusDetail == NULL)
        *statusDetail = new DcmDataset();

    if (statusDetail == NULL)
    {
        DCMNET_ERROR("Cannot create status detail object, memory exhausted!");
        return OFFalse;
    }

    // Determine the element's data type
    DcmVR vr(elem->ident());

    // Depending on the element's identification, insert different
    // types of objects into the container.
    switch (elem->ident())
    {
        case EVR_LO:
            lo = new DcmLongString(*((DcmLongString*)elem));
            if (lo->getLength() > vr.getMaxValueLength())
            {
                DCMNET_WARN("Value inside given LO attribute too large for status detail (max "
                            << OFstatic_cast(Uint32, vr.getMaxValueLength()) << ") for " << vr.getVRName());
            }
            (*statusDetail)->insert(lo, OFTrue /*replaceOld*/);
            // Print debug information
            {
                OFOStringStream oss;
                lo->print(oss);
                OFSTRINGSTREAM_GETSTR(oss, strtemp);
                DCMNET_DEBUG("Adding LO status detail information: " << strtemp);
                OFSTRINGSTREAM_FREESTR(tmpString)
            }
            break;
        case EVR_AT:
            at = new DcmAttributeTag(*((DcmAttributeTag*)elem));
            if (at->getLength() > vr.getMaxValueLength())
            {
                DCMNET_WARN("Value inside given AT attribute too large for status detail (max "
                            << OFstatic_cast(Uint32, vr.getMaxValueLength()) << ") for " << vr.getVRName());
            }
            (*statusDetail)->insert(at, OFTrue /*replaceOld*/);
            // Print debug information
            {
                OFOStringStream oss;
                at->print(oss);
                OFSTRINGSTREAM_GETSTR(oss, strtemp);
                DCMNET_DEBUG("Adding AT status detail information: " << strtemp);
                OFSTRINGSTREAM_FREESTR(tmpString)
            }
            break;
        default: // other status detail is not supported
            DCMNET_ERROR("Cannot add status detail, unsupported detail attribute type: " << vr.getVRName());
            return OFFalse;
            break;
    }
    return OFTrue;
}

// ----------------------------------------------------------------------------

void DcmSCP::forceAssociationRefuse(const OFBool doRefuse)
{
    m_cfg->forceAssociationRefuse(doRefuse);
}

// ----------------------------------------------------------------------------

void DcmSCP::setMaxReceivePDULength(const Uint32 maxRecPDU)
{
    m_cfg->setMaxReceivePDULength(maxRecPDU);
}

// ----------------------------------------------------------------------------

OFCondition DcmSCP::setEnableVerification(const OFString& profile)
{

    OFList<OFString> xfers;
    xfers.push_back(UID_LittleEndianExplicitTransferSyntax);
    xfers.push_back(UID_BigEndianExplicitTransferSyntax);
    xfers.push_back(UID_LittleEndianImplicitTransferSyntax);
    return m_cfg->addPresentationContext(UID_VerificationSOPClass, xfers, ASC_SC_ROLE_DEFAULT, profile);
}

// ----------------------------------------------------------------------------

// ----------------------------------------------------------------------------

OFCondition DcmSCP::addPresentationContext(const OFString& abstractSyntax,
                                           const OFList<OFString>& xferSyntaxes,
                                           const T_ASC_SC_ROLE requestorRole,
                                           const OFString& profile)
{
    return m_cfg->addPresentationContext(abstractSyntax, xferSyntaxes, requestorRole, profile);
}

// ----------------------------------------------------------------------------

void DcmSCP::setPort(const Uint16 port)
{
    m_cfg->setPort(port);
}

// ----------------------------------------------------------------------------

void DcmSCP::setAETitle(const OFString& aetitle)
{
    m_cfg->setAETitle(aetitle);
}

// ----------------------------------------------------------------------------

void DcmSCP::setRespondWithCalledAETitle(const OFBool useCalled)
{
    m_cfg->setRespondWithCalledAETitle(useCalled);
}

// ----------------------------------------------------------------------------

OFCondition DcmSCP::loadAssociationCfgFile(const OFString& assocFile)
{
    return m_cfg->loadAssociationCfgFile(assocFile);
}

// ----------------------------------------------------------------------------

OFCondition DcmSCP::setAndCheckAssociationProfile(const OFString& profileName)
{
    return m_cfg->setAndCheckAssociationProfile(profileName);
}

// ----------------------------------------------------------------------------

void DcmSCP::setConnectionBlockingMode(const DUL_BLOCKOPTIONS blockingMode)
{
    m_cfg->setConnectionBlockingMode(blockingMode);
}

// ----------------------------------------------------------------------------

void DcmSCP::setDIMSEBlockingMode(const T_DIMSE_BlockingMode blockingMode)
{
    m_cfg->setDIMSEBlockingMode(blockingMode);
}

// ----------------------------------------------------------------------------

void DcmSCP::setDIMSETimeout(const Uint32 dimseTimeout)
{
    m_cfg->setDIMSETimeout(dimseTimeout);
}

// ----------------------------------------------------------------------------

void DcmSCP::setACSETimeout(const Uint32 acseTimeout)
{
    m_cfg->setACSETimeout(acseTimeout);
}

// ----------------------------------------------------------------------------

void DcmSCP::setConnectionTimeout(const Uint32 timeout)
{
    m_cfg->setConnectionTimeout(timeout);
}

// ----------------------------------------------------------------------------

void DcmSCP::setVerbosePCMode(const OFBool mode)
{
    m_cfg->setVerbosePCMode(mode);
}

// ----------------------------------------------------------------------------

void DcmSCP::setHostLookupEnabled(const OFBool mode)
{
    m_cfg->setHostLookupEnabled(mode);
}

// ----------------------------------------------------------------------------

void DcmSCP::setProgressNotificationMode(const OFBool mode)
{
    m_cfg->setProgressNotificationMode(mode);
}

// ----------------------------------------------------------------------------

void DcmSCP::setAlwaysAcceptDefaultRole(const OFBool enabled)
{
    m_cfg->setAlwaysAcceptDefaultRole(enabled);
}

// ----------------------------------------------------------------------------

/* Get methods for SCP settings and current association information */

OFBool DcmSCP::getRefuseAssociation() const
{
    return m_cfg->getRefuseAssociation();
}

// ----------------------------------------------------------------------------

Uint32 DcmSCP::getMaxReceivePDULength() const
{
    return m_cfg->getMaxReceivePDULength();
}

// ----------------------------------------------------------------------------

Uint16 DcmSCP::getPort() const
{
    return m_cfg->getPort();
}

// ----------------------------------------------------------------------------

const OFString& DcmSCP::getAETitle() const
{
    return m_cfg->getAETitle();
}

// ----------------------------------------------------------------------------

OFBool DcmSCP::getRespondWithCalledAETitle() const
{
    return m_cfg->getRespondWithCalledAETitle();
}

// ----------------------------------------------------------------------------

DUL_BLOCKOPTIONS DcmSCP::getConnectionBlockingMode() const
{
    return m_cfg->getConnectionBlockingMode();
}

// ----------------------------------------------------------------------------

T_DIMSE_BlockingMode DcmSCP::getDIMSEBlockingMode() const
{
    return m_cfg->getDIMSEBlockingMode();
}

// ----------------------------------------------------------------------------

Uint32 DcmSCP::getDIMSETimeout() const
{
    return m_cfg->getDIMSETimeout();
}

// ----------------------------------------------------------------------------

Uint32 DcmSCP::getConnectionTimeout() const
{
    return m_cfg->getConnectionTimeout();
}

// ----------------------------------------------------------------------------

Uint32 DcmSCP::getACSETimeout() const
{
    return m_cfg->getACSETimeout();
}

// ----------------------------------------------------------------------------

OFBool DcmSCP::getVerbosePCMode() const
{
    return m_cfg->getVerbosePCMode();
}

// ----------------------------------------------------------------------------

OFBool DcmSCP::getHostLookupEnabled() const
{
    return m_cfg->getHostLookupEnabled();
}

// ----------------------------------------------------------------------------

OFBool DcmSCP::getProgressNotificationMode() const
{
    return m_cfg->getProgressNotificationMode();
}

// ----------------------------------------------------------------------------

OFBool DcmSCP::isConnected() const
{
    return (m_assoc != NULL) && (m_assoc->DULassociation != NULL);
}

// ----------------------------------------------------------------------------

OFString DcmSCP::getPeerAETitle() const
{
    if (m_assoc == NULL)
        return "";
    return m_assoc->params->DULparams.callingAPTitle;
}

// ----------------------------------------------------------------------------

OFString DcmSCP::getCalledAETitle() const
{
    if (m_assoc == NULL)
        return "";
    return m_assoc->params->DULparams.calledAPTitle;
}

// ----------------------------------------------------------------------------

Uint32 DcmSCP::getPeerMaxPDULength() const
{
    if (m_assoc == NULL)
        return 0;
    return m_assoc->params->theirMaxPDUReceiveSize;
}

// ----------------------------------------------------------------------------

OFString DcmSCP::getPeerIP() const
{
    if (m_assoc == NULL)
        return "";
    return m_assoc->params->DULparams.callingPresentationAddress;
}

// ----------------------------------------------------------------------------

void DcmSCP::dropAndDestroyAssociation()
{

    if (m_assoc)
    {
        ASC_dropSCPAssociation(m_assoc);
        ASC_destroyAssociation(&m_assoc);
    }
}

/* ************************************************************************** */
/*                            Notify functions                                */
/* ************************************************************************** */

void DcmSCP::notifyAssociationRequest(const T_ASC_Parameters& params, DcmSCPActionType& /* desiredAction */)
{
    // Dump some information if required
    DCMNET_INFO("Association Received " << params.DULparams.callingPresentationAddress << ": "
                                        << params.DULparams.callingAPTitle << " -> " << params.DULparams.calledAPTitle);

    // Dump more information if required
    OFString tempStr;
    if (m_cfg->getVerbosePCMode())
        DCMNET_INFO("Incoming Association Request:" << OFendl
                                                    << ASC_dumpParameters(tempStr, m_assoc->params, ASC_ASSOC_RQ));
    else
        DCMNET_DEBUG("Incoming Association Request:" << OFendl
                                                     << ASC_dumpParameters(tempStr, m_assoc->params, ASC_ASSOC_RQ));
}

// ----------------------------------------------------------------------------

OFBool DcmSCP::checkCalledAETitleAccepted(const OFString& calledAETitle)
{
    if (m_cfg->getRespondWithCalledAETitle() || (calledAETitle == m_cfg->getAETitle()))
    {
        return OFTrue;
    }
    else if (calledAETitle != m_cfg->getAETitle())
    {
        DCMNET_ERROR("Called AE Title does not match configured AE Title: " << m_cfg->getAETitle());
        return OFFalse;
    }
    return OFTrue;
}

// ----------------------------------------------------------------------------

OFBool DcmSCP::checkCallingAETitleAccepted(const OFString& /*callingAETitle*/)
{
    return OFTrue;
}

// ----------------------------------------------------------------------------

OFBool DcmSCP::checkCallingHostAccepted(const OFString& /*hostOrIP*/)
{
    return OFTrue;
}

// ----------------------------------------------------------------------------

void DcmSCP::notifyAssociationAcknowledge()
{
    DCMNET_DEBUG("DcmSCP: Association Acknowledged");
}

// ----------------------------------------------------------------------------

void DcmSCP::notifyReleaseRequest()
{
    DCMNET_INFO("Received Association Release Request");
}

// ----------------------------------------------------------------------------

void DcmSCP::notifyAbortRequest()
{
    DCMNET_INFO("Received Association Abort Request");
}

// ----------------------------------------------------------------------------

void DcmSCP::notifyAssociationTermination()
{
    DCMNET_DEBUG("DcmSCP: Association Terminated");
}

// ----------------------------------------------------------------------------

void DcmSCP::notifyConnectionTimeout()
{
    DCMNET_TRACE("Connection timeout encountered in non-blocking mode");
}

// ----------------------------------------------------------------------------

void DcmSCP::notifyDIMSEError(const OFCondition& cond)
{
    OFString tempStr;
    DCMNET_DEBUG("DIMSE Error, detail (if available): " << DimseCondition::dump(tempStr, cond));
}

// ----------------------------------------------------------------------------

OFBool DcmSCP::stopAfterCurrentAssociation()
{
    return OFFalse;
}

// ----------------------------------------------------------------------------

OFBool DcmSCP::stopAfterConnectionTimeout()
{
    return OFFalse;
}

/* ************************************************************************* */
/*                            Callback functions                             */
/* ************************************************************************* */

void DcmSCP::callbackSENDProgress(void* callbackContext, const unsigned long byteCount)
{
    if (callbackContext != NULL)
        OFreinterpret_cast(DcmSCP*, callbackContext)->notifySENDProgress(byteCount);
}

void DcmSCP::callbackRECEIVEProgress(void* callbackContext, const unsigned long byteCount)
{
    if (callbackContext != NULL)
        OFreinterpret_cast(DcmSCP*, callbackContext)->notifyRECEIVEProgress(byteCount);
}

/* ************************************************************************* */
/*                         Static helper functions                           */
/* ************************************************************************* */

OFBool DcmSCP::getPresentationContextInfo(const T_ASC_Association* assoc,
                                          const Uint8 presID,
                                          DcmPresentationContextInfo& presInfo)
{
    if (assoc != NULL)
    {
        DUL_PRESENTATIONCONTEXT* pc
            = findPresentationContextID(assoc->params->DULparams.acceptedPresentationContext, presID);
        if (pc != NULL)
        {
            presInfo.abstractSyntax         = pc->abstractSyntax;
            presInfo.acceptedTransferSyntax = pc->acceptedTransferSyntax;
            presInfo.presentationContextID  = pc->presentationContextID;
            presInfo.proposedSCRole         = pc->proposedSCRole;
            presInfo.acceptedSCRole         = pc->acceptedSCRole;
            return OFTrue;
        }
    }
    return OFFalse;
}
