diff --git a/src/dicom/MPPSSCU.cpp b/src/dicom/MPPSSCU.cpp new file mode 100644 index 0000000..0c5df02 --- /dev/null +++ b/src/dicom/MPPSSCU.cpp @@ -0,0 +1,269 @@ +#include "MPPSSCU.h" + +#include "dcmtk/config/osconfig.h" /* make sure OS specific configuration is included first */ +#include "dcmtk/dcmnet/diutil.h" /* for dcmnet logger */ +#include "dcmtk/dcmdata/dcuid.h" /* for dcmFindUIDName() */ +#include "dcmtk/dcmdata/dcostrmf.h" /* for class DcmOutputFileStream */ +#include "dcmtk/ofstd/ofmem.h" /* for OFunique_ptr */ + +Uint16 MPPSSCU::nextMessageID() +{ + if (!isConnected()) + return 0; + else + return messageID++; +} + +OFCondition MPPSSCU::sendNCreateRequest(const T_ASC_PresentationContextID presID, + const OFString &sopInstanceUID, + DcmDataset *reqDataset, + Uint16 &rspStatusCode) +{ + // Do some basic validity checks + if (!isConnected()) + 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) + bzero((char *)&request, sizeof(request)); + T_DIMSE_N_CreateRQ &actionReq = request.msg.NCreateRQ; + DcmDataset *statusDetail = NULL; + + request.CommandField = DIMSE_N_CREATE_RQ; + actionReq.MessageID = nextMessageID(); + actionReq.DataSetType = DIMSE_DATASET_PRESENT; + + // Determine SOP Class from presentation context + OFString abstractSyntax, transferSyntax; + findPresentationContext(pcid, abstractSyntax, transferSyntax); + if (abstractSyntax.empty() || transferSyntax.empty()) + return DIMSE_NOVALIDPRESENTATIONCONTEXTID; + OFStandard::strlcpy(actionReq.AffectedSOPClassUID, abstractSyntax.c_str(), sizeof(actionReq.AffectedSOPClassUID)); + if (sopInstanceUID.size() > 0) + { + OFStandard::strlcpy(actionReq.AffectedSOPInstanceUID, sopInstanceUID.c_str(), sizeof(actionReq.AffectedSOPInstanceUID)); + actionReq.opts = O_NCREATE_AFFECTEDSOPINSTANCEUID; + } + else + { + actionReq.AffectedSOPInstanceUID[0] = 0; + actionReq.opts = 0; + } + + // Send request + // if (DCM_dcmnetLogger.isEnabledFor(OFLogger::DEBUG_LOG_LEVEL)) + // { + // DCMNET_INFO("Sending N-ACTION 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-ACTION Request (MsgID " << actionReq.MessageID << ")"); + // } + cond = sendDIMSEMessage(pcid, &request, reqDataset); + if (cond.bad()) + { + DCMNET_ERROR("Failed sending N-Create request: " << DimseCondition::dump(tempStr, cond)); + return cond; + } + + // Receive response + T_DIMSE_Message response; + bzero((char *)&response, sizeof(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_CREATE_RSP) + { + // if (DCM_dcmnetLogger.isEnabledFor(OFLogger::DEBUG_LOG_LEVEL)) + // { + // DCMNET_INFO("Received N-ACTION Response"); + // DCMNET_DEBUG(DIMSE_dumpMessage(tempStr, response, DIMSE_INCOMING, NULL, pcid)); + // } else { + // DCMNET_INFO("Received N-ACTION Response (" << DU_nactionStatusString(response.msg.NActionRSP.DimseStatus) << ")"); + // } + } + else + { + DCMNET_ERROR("Expected N-ACTION 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_CreateRSP &actionRsp = response.msg.NCreateRSP; + rspStatusCode = actionRsp.DimseStatus; + + // Check whether there is a dataset to be received + if (actionRsp.DataSetType == DIMSE_DATASET_PRESENT) + { + // this should never happen + DcmDataset *tempDataset = NULL; + T_ASC_PresentationContextID tempID; + // DCMNET_WARN("Trying to retrieve unexpected dataset in N-ACTION response"); + cond = receiveDIMSEDataset(&tempID, &tempDataset); + if (cond.good()) + { + // DCMNET_WARN("Received unexpected dataset after N-ACTION response, ignoring"); + delete tempDataset; + } + else + { + return DIMSE_BADDATA; + } + } + if (actionRsp.MessageIDBeingRespondedTo != actionReq.MessageID) + { + // since we only support synchronous communication, the message ID in the response + // should be identical to the one in the request + // DCMNET_ERROR("Received response with wrong message ID (" << actionRsp.MessageIDBeingRespondedTo + // << " instead of " << actionReq.MessageID << ")"); + return DIMSE_BADMESSAGE; + } + + return cond; +} + +OFCondition MPPSSCU::sendNSetRequest(const T_ASC_PresentationContextID presID, + const OFString &sopInstanceUID, + DcmDataset *reqDataset, + Uint16 &rspStatusCode) +{ + // Do some basic validity checks + if (!isConnected()) + 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) + bzero((char *)&request, sizeof(request)); + T_DIMSE_N_SetRQ &actionReq = request.msg.NSetRQ; + DcmDataset *statusDetail = NULL; + + request.CommandField = DIMSE_N_SET_RQ; + actionReq.MessageID = nextMessageID(); + actionReq.DataSetType = DIMSE_DATASET_PRESENT; + + // Determine SOP Class from presentation context + OFString abstractSyntax, transferSyntax; + findPresentationContext(pcid, abstractSyntax, transferSyntax); + if (abstractSyntax.empty() || transferSyntax.empty()) + return DIMSE_NOVALIDPRESENTATIONCONTEXTID; + OFStandard::strlcpy(actionReq.RequestedSOPClassUID, abstractSyntax.c_str(), sizeof(actionReq.RequestedSOPClassUID)); + + OFStandard::strlcpy(actionReq.RequestedSOPInstanceUID, sopInstanceUID.c_str(), sizeof(actionReq.RequestedSOPInstanceUID)); + + // Send request + // if (DCM_dcmnetLogger.isEnabledFor(OFLogger::DEBUG_LOG_LEVEL)) + // { + // DCMNET_INFO("Sending N-ACTION 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-ACTION Request (MsgID " << actionReq.MessageID << ")"); + // } + cond = sendDIMSEMessage(pcid, &request, reqDataset); + if (cond.bad()) + { + DCMNET_ERROR("Failed sending N-Create request: " << DimseCondition::dump(tempStr, cond)); + return cond; + } + + // Receive response + T_DIMSE_Message response; + bzero((char *)&response, sizeof(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_SET_RSP) + { + // if (DCM_dcmnetLogger.isEnabledFor(OFLogger::DEBUG_LOG_LEVEL)) + // { + // DCMNET_INFO("Received N-ACTION Response"); + // DCMNET_DEBUG(DIMSE_dumpMessage(tempStr, response, DIMSE_INCOMING, NULL, pcid)); + // } else { + // DCMNET_INFO("Received N-ACTION Response (" << DU_nactionStatusString(response.msg.NActionRSP.DimseStatus) << ")"); + // } + } + else + { + DCMNET_ERROR("Expected N-SET 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_SetRSP &actionRsp = response.msg.NSetRSP; + rspStatusCode = actionRsp.DimseStatus; + + // Check whether there is a dataset to be received + if (actionRsp.DataSetType == DIMSE_DATASET_PRESENT) + { + // this should never happen + DcmDataset *tempDataset = NULL; + T_ASC_PresentationContextID tempID; + DCMNET_WARN("Trying to retrieve unexpected dataset in N-SET response"); + cond = receiveDIMSEDataset(&tempID, &tempDataset); + if (cond.good()) + { + DCMNET_WARN("Received unexpected dataset after N-SET response, ignoring"); + delete tempDataset; + } + else + { + return DIMSE_BADDATA; + } + } + if (actionRsp.MessageIDBeingRespondedTo != actionReq.MessageID) + { + // since we only support synchronous communication, the message ID in the response + // should be identical to the one in the request + DCMNET_ERROR("Received response with wrong message ID (" << actionRsp.MessageIDBeingRespondedTo + << " instead of " << actionReq.MessageID << ")"); + return DIMSE_BADMESSAGE; + } + + return cond; +} diff --git a/src/dicom/MPPSSCU.h b/src/dicom/MPPSSCU.h new file mode 100644 index 0000000..a6e1c9b --- /dev/null +++ b/src/dicom/MPPSSCU.h @@ -0,0 +1,25 @@ +#ifndef E087D231_BC4C_4D23_9152_F51B7B4509FB +#define E087D231_BC4C_4D23_9152_F51B7B4509FB +#include "dcmtk/dcmnet/scu.h" + +class DCMTK_DCMNET_EXPORT MPPSSCU : public DcmSCU +{ + +public: + MPPSSCU() = default; + virtual ~MPPSSCU() = default; + virtual OFCondition sendNCreateRequest(const T_ASC_PresentationContextID presID, + const OFString &sopInstanceUID, + DcmDataset *reqDataset, + Uint16 &rspStatusCode); + virtual OFCondition sendNSetRequest(const T_ASC_PresentationContextID presID, + const OFString &sopInstanceUID, + DcmDataset *reqDataset, + Uint16 &rspStatusCode); + +private: + Uint16 nextMessageID(); + Uint16 messageID = 0; +}; + +#endif /* E087D231_BC4C_4D23_9152_F51B7B4509FB */ \ No newline at end of file