Mercurial > hg > orthanc
diff Core/DicomNetworking/Internals/CommandDispatcher.cpp @ 3604:e327b44780bb storage-commitment
abstraction: storage commitment handler
author | Sebastien Jodogne <s.jodogne@gmail.com> |
---|---|
date | Thu, 16 Jan 2020 18:14:43 +0100 |
parents | 1bcc35e4615a |
children | 22eef03feed7 |
line wrap: on
line diff
--- a/Core/DicomNetworking/Internals/CommandDispatcher.cpp Thu Jan 16 18:10:57 2020 +0100 +++ b/Core/DicomNetworking/Internals/CommandDispatcher.cpp Thu Jan 16 18:14:43 2020 +0100 @@ -92,9 +92,12 @@ #include "MoveScp.h" #include "../../Toolbox.h" #include "../../Logging.h" +#include "../../OrthancException.h" +#include <dcmtk/dcmdata/dcdeftag.h> /* for storage commitment */ +#include <dcmtk/dcmdata/dcsequen.h> /* for class DcmSequenceOfItems */ +#include <dcmtk/dcmdata/dcuid.h> /* for variable dcmAllStorageSOPClassUIDs */ #include <dcmtk/dcmnet/dcasccfg.h> /* for class DcmAssociationConfiguration */ -#include <dcmtk/dcmdata/dcuid.h> /* for variable dcmAllStorageSOPClassUIDs */ #include <boost/lexical_cast.hpp> @@ -298,6 +301,12 @@ knownAbstractSyntaxes.push_back(UID_MOVEPatientRootQueryRetrieveInformationModel); } + // For Storage Commitment + if (server.HasStorageCommitmentRequestHandlerFactory()) + { + knownAbstractSyntaxes.push_back(UID_StorageCommitmentPushModelSOPClass); + } + cond = ASC_receiveAssociation(net, &assoc, /*opt_maxPDU*/ ASC_DEFAULTMAXPDU, NULL, NULL, @@ -689,6 +698,11 @@ supported = true; break; + case DIMSE_N_ACTION_RQ: + request = DicomRequestType_NAction; + supported = true; + break; + default: // we cannot handle this kind of message cond = DIMSE_BADCOMMANDTYPE; @@ -770,6 +784,10 @@ } break; + case DicomRequestType_NAction: + cond = NActionScp(&msg, presID); + break; + default: // Should never happen break; @@ -823,5 +841,187 @@ } return cond; } + + + OFCondition CommandDispatcher::NActionScp(T_DIMSE_Message* msg, + T_ASC_PresentationContextID presID) + { + /** + * Starting with Orthanc 1.6.0, only storage commitment is + * supported with DICOM N-ACTION. This corresponds to the case + * where "Action Type ID" equals "1". + * http://dicom.nema.org/medical/dicom/2019a/output/chtml/part04/sect_J.3.2.html + * http://dicom.nema.org/medical/dicom/2019a/output/chtml/part07/chapter_10.html#table_10.1-4 + **/ + + if (msg->CommandField != DIMSE_N_ACTION_RQ /* value == 304 == 0x0130 */ || + !server_.HasStorageCommitmentRequestHandlerFactory()) + { + throw OrthancException(ErrorCode_InternalError); + } + + + /** + * Check that the storage commitment request is correctly formatted. + **/ + + const T_DIMSE_N_ActionRQ& request = msg->msg.NActionRQ; + + if (request.ActionTypeID != 1) + { + throw OrthancException(ErrorCode_NotImplemented, + "Only storage commitment is implemented for DICOM N-ACTION SCP"); + } + + if (std::string(request.RequestedSOPClassUID) != UID_StorageCommitmentPushModelSOPClass || + std::string(request.RequestedSOPInstanceUID) != UID_StorageCommitmentPushModelSOPInstance) + { + throw OrthancException(ErrorCode_NetworkProtocol, + "Unexpected incoming SOP class or instance UID for storage commitment"); + } + + if (request.DataSetType != DIMSE_DATASET_PRESENT) + { + throw OrthancException(ErrorCode_NetworkProtocol, + "Incoming storage commitment request without a dataset"); + } + + + /** + * Extract the DICOM dataset that is associated with the DIMSE + * message. The content of this dataset is documented in "Table + * J.3-1. Storage Commitment Request - Action Information": + * http://dicom.nema.org/medical/dicom/2019a/output/chtml/part04/sect_J.3.2.html#table_J.3-1 + **/ + + std::auto_ptr<DcmDataset> dataset; + + { + DcmDataset *tmp = NULL; + T_ASC_PresentationContextID presIdData; + + OFCondition cond = DIMSE_receiveDataSetInMemory( + assoc_, /*opt_blockMode*/ DIMSE_BLOCKING, + /*opt_dimse_timeout*/ 0, &presIdData, &tmp, NULL, NULL); + if (!cond.good()) + { + return cond; + } + + if (tmp == NULL) + { + LOG(ERROR) << "Cannot read the dataset in N-ACTION SCP"; + return EC_InvalidStream; + } + + dataset.reset(tmp); + } + + std::string transactionUid; + std::vector<std::string> referencedSopClassUid, referencedSopInstanceUid; + + { + const char* s = NULL; + if (!dataset->findAndGetString(DCM_TransactionUID, s).good() || + s == NULL) + { + LOG(ERROR) << "Missing Transaction UID in storage commitment request"; + return EC_InvalidStream; + } + + transactionUid.assign(s); + } + + { + DcmSequenceOfItems* sequence = NULL; + if (!dataset->findAndGetSequence(DCM_ReferencedSOPSequence, sequence).good() || + sequence == NULL) + { + LOG(ERROR) << "Missing Referenced SOP Sequence in storage commitment request"; + return EC_InvalidStream; + } + + referencedSopClassUid.reserve(sequence->card()); + referencedSopInstanceUid.reserve(sequence->card()); + + for (unsigned long i = 0; i < sequence->card(); i++) + { + const char* a = NULL; + const char* b = NULL; + if (!sequence->getItem(i)->findAndGetString(DCM_ReferencedSOPClassUID, a).good() || + !sequence->getItem(i)->findAndGetString(DCM_ReferencedSOPInstanceUID, b).good() || + a == NULL || + b == NULL) + { + LOG(ERROR) << "Missing Referenced SOP Class/Instance UID in storage commitment request"; + return EC_InvalidStream; + } + + referencedSopClassUid.push_back(a); + referencedSopInstanceUid.push_back(b); + } + } + + LOG(INFO) << "Incoming storage commitment transaction, with UID: " << transactionUid; + + for (size_t i = 0; i < referencedSopClassUid.size(); i++) + { + LOG(INFO) << " (" << (i + 1) << "/" << referencedSopClassUid.size() + << ") queried SOP Class/Instance UID: " + << referencedSopClassUid[i] << " / " << referencedSopInstanceUid[i]; + } + + + /** + * Call the Orthanc handler. The list of available DIMSE status + * codes can be found at: + * http://dicom.nema.org/medical/dicom/2019a/output/chtml/part07/chapter_10.html#sect_10.1.4.1.10 + **/ + + DIC_US dimseStatus; + + try + { + std::auto_ptr<IStorageCommitmentRequestHandler> handler + (server_.GetStorageCommitmentRequestHandlerFactory(). + ConstructStorageCommitmentRequestHandler()); + + handler->Handle(transactionUid, referencedSopClassUid, referencedSopInstanceUid, + remoteIp_, remoteAet_, calledAet_); + + dimseStatus = 0; // Success + } + catch (OrthancException& e) + { + LOG(ERROR) << "Error while processing an incoming storage commitment request: " << e.What(); + + // Code 0x0110 - "General failure in processing the operation was encountered" + dimseStatus = STATUS_N_ProcessingFailure; + } + + + /** + * Send the DIMSE status back to the SCU. + **/ + + { + T_DIMSE_Message response; + memset(&response, 0, sizeof(response)); + response.CommandField = DIMSE_N_ACTION_RSP; + + T_DIMSE_N_ActionRSP& content = response.msg.NActionRSP; + content.MessageIDBeingRespondedTo = request.MessageID; + strncpy(content.AffectedSOPClassUID, UID_StorageCommitmentPushModelSOPClass, DIC_UI_LEN); + content.DimseStatus = dimseStatus; + strncpy(content.AffectedSOPInstanceUID, UID_StorageCommitmentPushModelSOPInstance, DIC_UI_LEN); + content.ActionTypeID = 0; // Not present, as "O_NACTION_ACTIONTYPEID" not set in "opts" + content.DataSetType = DIMSE_DATASET_NULL; // Dataset is absent in storage commitment response + content.opts = O_NACTION_AFFECTEDSOPCLASSUID | O_NACTION_AFFECTEDSOPINSTANCEUID; + + return DIMSE_sendMessageUsingMemoryData( + assoc_, presID, &response, NULL /* no dataset */, NULL /* dataObject */, + NULL /* callback */, NULL /* callback context */, NULL /* commandSet */); + } + } } }