/*
*
* Copyright (C) 2009-2021, 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.
* @warning This class is EXPERIMENTAL. Be careful to use it in production environment.
*/
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:
*
* - 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.
* - 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.
*
* Other error codes include
*
* - NET_EC_InvalidSCPAssociationProfile: Returned if the SCP's presentation
* context information is invalid (e.g. no presentation contexts have
* been added).
*
* - 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.
*
- EC_setuidFailed: Returned (on Unix-like systems) if the DcmSCP
* was not able to drop root privileges.
*
*/
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& 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.
*/
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 (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 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 OFTrue.
* @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