# HG changeset patch # User Sebastien Jodogne # Date 1579622506 -3600 # Node ID c1e2b91c2ab4f9076f3d23ca8f457d8294e09b6d # Parent 22eef03feed7c249bca80beb2cc85ca918da7e72 all the abstractions for storage commitment are available diff -r 22eef03feed7 -r c1e2b91c2ab4 Core/DicomNetworking/DicomUserConnection.cpp --- a/Core/DicomNetworking/DicomUserConnection.cpp Tue Jan 21 14:20:50 2020 +0100 +++ b/Core/DicomNetworking/DicomUserConnection.cpp Tue Jan 21 17:01:46 2020 +0100 @@ -1388,13 +1388,17 @@ static void FillSopSequence(DcmDataset& dataset, const DcmTagKey& tag, const std::vector& sopClassUids, - const std::vector& sopInstanceUids) + const std::vector& sopInstanceUids, + bool hasFailureReason, + Uint16 failureReason) { for (size_t i = 0; i < sopClassUids.size(); i++) { std::auto_ptr item(new DcmItem); if (!item->putAndInsertString(DCM_ReferencedSOPClassUID, sopClassUids[i].c_str()).good() || !item->putAndInsertString(DCM_ReferencedSOPInstanceUID, sopInstanceUids[i].c_str()).good() || + (hasFailureReason && + !item->putAndInsertUint16(DCM_FailureReason, failureReason).good()) || !dataset.insertSequenceItem(tag, item.release()).good()) { throw OrthancException(ErrorCode_InternalError); @@ -1463,7 +1467,8 @@ throw OrthancException(ErrorCode_InternalError); } - FillSopSequence(dataset, DCM_ReferencedSOPSequence, successSopClassUids, successSopInstanceUids); + FillSopSequence(dataset, DCM_ReferencedSOPSequence, successSopClassUids, + successSopInstanceUids, false, 0); // http://dicom.nema.org/medical/dicom/2019a/output/chtml/part04/sect_J.3.3.html if (failureSopClassUids.empty()) @@ -1473,7 +1478,11 @@ else { content.EventTypeID = 2; // "Storage Commitment Request Complete - Failures Exist" - FillSopSequence(dataset, DCM_FailedSOPSequence, failureSopClassUids, failureSopInstanceUids); + + // Failure reason + // http://dicom.nema.org/medical/dicom/2019a/output/chtml/part03/sect_C.14.html#sect_C.14.1.1 + FillSopSequence(dataset, DCM_FailedSOPSequence, failureSopClassUids, + failureSopInstanceUids, true, 0x0112 /* No such object instance == 274 */); } int presID = ASC_findAcceptedPresentationContextID( @@ -1601,7 +1610,7 @@ throw OrthancException(ErrorCode_InternalError); } - FillSopSequence(dataset, DCM_ReferencedSOPSequence, sopClassUids, sopInstanceUids); + FillSopSequence(dataset, DCM_ReferencedSOPSequence, sopClassUids, sopInstanceUids, false, 0); int presID = ASC_findAcceptedPresentationContextID( pimpl_->assoc_, UID_StorageCommitmentPushModelSOPClass); diff -r 22eef03feed7 -r c1e2b91c2ab4 Core/DicomNetworking/IStorageCommitmentRequestHandler.h --- a/Core/DicomNetworking/IStorageCommitmentRequestHandler.h Tue Jan 21 14:20:50 2020 +0100 +++ b/Core/DicomNetworking/IStorageCommitmentRequestHandler.h Tue Jan 21 17:01:46 2020 +0100 @@ -33,7 +33,9 @@ #pragma once -#include "DicomFindAnswers.h" +#include +#include +#include namespace Orthanc { @@ -44,11 +46,20 @@ { } - virtual void Handle(const std::string& transactionUid, - const std::vector& referencedSopClassUids, - const std::vector& referencedSopInstanceUids, - const std::string& remoteIp, - const std::string& remoteAet, - const std::string& calledAet) = 0; + virtual void HandleRequest(const std::string& transactionUid, + const std::vector& sopClassUids, + const std::vector& sopInstanceUids, + const std::string& remoteIp, + const std::string& remoteAet, + const std::string& calledAet) = 0; + + virtual void HandleReport(const std::string& transactionUid, + const std::vector& successSopClassUids, + const std::vector& successSopInstanceUids, + const std::vector& failedSopClassUids, + const std::vector& failedSopInstanceUids, + const std::string& remoteIp, + const std::string& remoteAet, + const std::string& calledAet) = 0; }; } diff -r 22eef03feed7 -r c1e2b91c2ab4 Core/DicomNetworking/Internals/CommandDispatcher.cpp --- a/Core/DicomNetworking/Internals/CommandDispatcher.cpp Tue Jan 21 14:20:50 2020 +0100 +++ b/Core/DicomNetworking/Internals/CommandDispatcher.cpp Tue Jan 21 17:01:46 2020 +0100 @@ -730,6 +730,11 @@ supported = true; break; + case DIMSE_N_EVENT_REPORT_RQ: + request = DicomRequestType_NEventReport; + supported = true; + break; + default: // we cannot handle this kind of message cond = DIMSE_BADCOMMANDTYPE; @@ -815,6 +820,10 @@ cond = NActionScp(&msg, presID); break; + case DicomRequestType_NEventReport: + cond = NEventReportScp(&msg, presID); + break; + default: // Should never happen break; @@ -869,6 +878,80 @@ return cond; } + + static DcmDataset* ReadDataset(T_ASC_Association* assoc, + const char* errorMessage) + { + 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() || + tmp == NULL) + { + throw OrthancException(ErrorCode_NetworkProtocol, errorMessage); + } + + return tmp; + } + + + static std::string ReadString(DcmDataset& dataset, + const DcmTagKey& tag) + { + const char* s = NULL; + if (!dataset.findAndGetString(tag, s).good() || + s == NULL) + { + char buf[64]; + sprintf(buf, "Missing mandatory tag in dataset: (%04X,%04X)", + tag.getGroup(), tag.getElement()); + throw OrthancException(ErrorCode_NetworkProtocol, buf); + } + + return std::string(s); + } + + + static void ReadSopSequence(std::vector& sopClassUids, + std::vector& sopInstanceUids, + DcmDataset& dataset, + const DcmTagKey& tag) + { + DcmSequenceOfItems* sequence = NULL; + if (!dataset.findAndGetSequence(tag, sequence).good() || + sequence == NULL) + { + char buf[64]; + sprintf(buf, "Missing mandatory sequence in dataset: (%04X,%04X)", + tag.getGroup(), tag.getElement()); + throw OrthancException(ErrorCode_NetworkProtocol, buf); + } + + sopClassUids.reserve(sequence->card()); + sopInstanceUids.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) + { + throw OrthancException(ErrorCode_NetworkProtocol, + "Missing Referenced SOP Class/Instance UID " + "in storage commitment request"); + } + + sopClassUids.push_back(a); + sopInstanceUids.push_back(b); + } + } + OFCondition CommandDispatcher::NActionScp(T_DIMSE_Message* msg, T_ASC_PresentationContextID presID) @@ -921,81 +1004,22 @@ * http://dicom.nema.org/medical/dicom/2019a/output/chtml/part04/sect_J.3.2.html#table_J.3-1 **/ - std::auto_ptr 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; - } + std::auto_ptr dataset( + ReadDataset(assoc_, "Cannot read the dataset in N-ACTION SCP")); - if (tmp == NULL) - { - LOG(ERROR) << "Cannot read the dataset in N-ACTION SCP"; - return EC_InvalidStream; - } - - dataset.reset(tmp); - } - - std::string transactionUid; - std::vector 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); - } + std::string transactionUid = ReadString(*dataset, DCM_TransactionUID); - { - 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; - } + std::vector sopClassUid, sopInstanceUid; + ReadSopSequence(sopClassUid, sopInstanceUid, + *dataset, DCM_ReferencedSOPSequence); - referencedSopClassUid.reserve(sequence->card()); - referencedSopInstanceUid.reserve(sequence->card()); + LOG(INFO) << "Incoming storage commitment request, with transaction UID: " << transactionUid; - 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++) + for (size_t i = 0; i < sopClassUid.size(); i++) { - LOG(INFO) << " (" << (i + 1) << "/" << referencedSopClassUid.size() + LOG(INFO) << " (" << (i + 1) << "/" << sopClassUid.size() << ") queried SOP Class/Instance UID: " - << referencedSopClassUid[i] << " / " << referencedSopInstanceUid[i]; + << sopClassUid[i] << " / " << sopInstanceUid[i]; } @@ -1013,8 +1037,8 @@ (server_.GetStorageCommitmentRequestHandlerFactory(). ConstructStorageCommitmentRequestHandler()); - handler->Handle(transactionUid, referencedSopClassUid, referencedSopInstanceUid, - remoteIp_, remoteAet_, calledAet_); + handler->HandleRequest(transactionUid, sopClassUid, sopInstanceUid, + remoteIp_, remoteAet_, calledAet_); dimseStatus = 0; // Success } @@ -1050,5 +1074,139 @@ NULL /* callback */, NULL /* callback context */, NULL /* commandSet */); } } + + + OFCondition CommandDispatcher::NEventReportScp(T_DIMSE_Message* msg, + T_ASC_PresentationContextID presID) + { + /** + * Starting with Orthanc 1.6.0, handling N-EVENT-REPORT for + * storage commitment. + * http://dicom.nema.org/medical/dicom/2019a/output/chtml/part04/sect_J.3.3.html + * http://dicom.nema.org/medical/dicom/2019a/output/chtml/part07/chapter_10.html#table_10.1-1 + **/ + + if (msg->CommandField != DIMSE_N_EVENT_REPORT_RQ /* value == 256 == 0x0100 */ || + !server_.HasStorageCommitmentRequestHandlerFactory()) + { + throw OrthancException(ErrorCode_InternalError); + } + + + /** + * Check that the storage commitment report is correctly formatted. + **/ + + const T_DIMSE_N_EventReportRQ& report = msg->msg.NEventReportRQ; + + if (report.EventTypeID != 1 /* successful */ && + report.EventTypeID != 2 /* failures exist */) + { + throw OrthancException(ErrorCode_NotImplemented, + "Unknown event for DICOM N-EVENT-REPORT SCP"); + } + + if (std::string(report.AffectedSOPClassUID) != UID_StorageCommitmentPushModelSOPClass || + std::string(report.AffectedSOPInstanceUID) != UID_StorageCommitmentPushModelSOPInstance) + { + throw OrthancException(ErrorCode_NetworkProtocol, + "Unexpected incoming SOP class or instance UID for storage commitment"); + } + + if (report.DataSetType != DIMSE_DATASET_PRESENT) + { + throw OrthancException(ErrorCode_NetworkProtocol, + "Incoming storage commitment report 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-2. Storage Commitment Result - Event Information": + * http://dicom.nema.org/medical/dicom/2019a/output/chtml/part04/sect_J.3.3.html#table_J.3-2 + **/ + + std::auto_ptr dataset( + ReadDataset(assoc_, "Cannot read the dataset in N-EVENT-REPORT SCP")); + + std::string transactionUid = ReadString(*dataset, DCM_TransactionUID); + + std::vector successSopClassUid, successSopInstanceUid; + ReadSopSequence(successSopClassUid, successSopInstanceUid, + *dataset, DCM_ReferencedSOPSequence); + + std::vector failedSopClassUid, failedSopInstanceUid; + ReadSopSequence(failedSopClassUid, failedSopInstanceUid, + *dataset, DCM_FailedSOPSequence); + + LOG(INFO) << "Incoming storage commitment report, with transaction UID: " << transactionUid; + + for (size_t i = 0; i < successSopClassUid.size(); i++) + { + LOG(INFO) << " (success " << (i + 1) << "/" << successSopClassUid.size() + << ") SOP Class/Instance UID: " + << successSopClassUid[i] << " / " << successSopInstanceUid[i]; + } + + for (size_t i = 0; i < failedSopClassUid.size(); i++) + { + LOG(INFO) << " (failure " << (i + 1) << "/" << failedSopClassUid.size() + << ") SOP Class/Instance UID: " + << failedSopClassUid[i] << " / " << failedSopInstanceUid[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 handler + (server_.GetStorageCommitmentRequestHandlerFactory(). + ConstructStorageCommitmentRequestHandler()); + + handler->HandleReport(transactionUid, successSopClassUid, successSopInstanceUid, + failedSopClassUid, failedSopInstanceUid, + remoteIp_, remoteAet_, calledAet_); + + dimseStatus = 0; // Success + } + catch (OrthancException& e) + { + LOG(ERROR) << "Error while processing an incoming storage commitment report: " << 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_EVENT_REPORT_RSP; + + T_DIMSE_N_EventReportRSP& content = response.msg.NEventReportRSP; + content.MessageIDBeingRespondedTo = report.MessageID; + strncpy(content.AffectedSOPClassUID, UID_StorageCommitmentPushModelSOPClass, DIC_UI_LEN); + content.DimseStatus = dimseStatus; + strncpy(content.AffectedSOPInstanceUID, UID_StorageCommitmentPushModelSOPInstance, DIC_UI_LEN); + content.EventTypeID = 0; // Not present, as "O_NEVENTREPORT_EVENTTYPEID" not set in "opts" + content.DataSetType = DIMSE_DATASET_NULL; // Dataset is absent in storage commitment response + content.opts = O_NEVENTREPORT_AFFECTEDSOPCLASSUID | O_NEVENTREPORT_AFFECTEDSOPINSTANCEUID; + + return DIMSE_sendMessageUsingMemoryData( + assoc_, presID, &response, NULL /* no dataset */, NULL /* dataObject */, + NULL /* callback */, NULL /* callback context */, NULL /* commandSet */); + } + } } } diff -r 22eef03feed7 -r c1e2b91c2ab4 Core/DicomNetworking/Internals/CommandDispatcher.h --- a/Core/DicomNetworking/Internals/CommandDispatcher.h Tue Jan 21 14:20:50 2020 +0100 +++ b/Core/DicomNetworking/Internals/CommandDispatcher.h Tue Jan 21 17:01:46 2020 +0100 @@ -58,6 +58,9 @@ OFCondition NActionScp(T_DIMSE_Message* msg, T_ASC_PresentationContextID presID); + + OFCondition NEventReportScp(T_DIMSE_Message* msg, + T_ASC_PresentationContextID presID); public: CommandDispatcher(const DicomServer& server, diff -r 22eef03feed7 -r c1e2b91c2ab4 Core/DicomNetworking/RemoteModalityParameters.cpp --- a/Core/DicomNetworking/RemoteModalityParameters.cpp Tue Jan 21 14:20:50 2020 +0100 +++ b/Core/DicomNetworking/RemoteModalityParameters.cpp Tue Jan 21 17:01:46 2020 +0100 @@ -49,6 +49,8 @@ static const char* KEY_ALLOW_MOVE = "AllowMove"; static const char* KEY_ALLOW_STORE = "AllowStore"; static const char* KEY_ALLOW_N_ACTION = "AllowNAction"; +static const char* KEY_ALLOW_N_EVENT_REPORT = "AllowEventReport"; +static const char* KEY_ALLOW_STORAGE_COMMITMENT = "AllowStorageCommitment"; static const char* KEY_HOST = "Host"; static const char* KEY_MANUFACTURER = "Manufacturer"; static const char* KEY_PORT = "Port"; @@ -68,6 +70,7 @@ allowMove_ = true; allowGet_ = true; allowNAction_ = true; // For storage commitment + allowNEventReport_ = true; // For storage commitment } @@ -218,6 +221,18 @@ { allowNAction_ = SerializationToolbox::ReadBoolean(serialized, KEY_ALLOW_N_ACTION); } + + if (serialized.isMember(KEY_ALLOW_N_EVENT_REPORT)) + { + allowNEventReport_ = SerializationToolbox::ReadBoolean(serialized, KEY_ALLOW_N_EVENT_REPORT); + } + + if (serialized.isMember(KEY_ALLOW_STORAGE_COMMITMENT)) + { + bool allow = SerializationToolbox::ReadBoolean(serialized, KEY_ALLOW_STORAGE_COMMITMENT); + allowNAction_ = allow; + allowNEventReport_ = allow; + } } @@ -243,6 +258,9 @@ case DicomRequestType_NAction: return allowNAction_; + case DicomRequestType_NEventReport: + return allowNEventReport_; + default: throw OrthancException(ErrorCode_ParameterOutOfRange); } @@ -278,6 +296,10 @@ allowNAction_ = allowed; break; + case DicomRequestType_NEventReport: + allowNEventReport_ = allowed; + break; + default: throw OrthancException(ErrorCode_ParameterOutOfRange); } @@ -291,7 +313,8 @@ !allowFind_ || !allowGet_ || !allowMove_ || - !allowNAction_); + !allowNAction_ || + !allowNEventReport_); } @@ -312,6 +335,7 @@ target[KEY_ALLOW_GET] = allowGet_; target[KEY_ALLOW_MOVE] = allowMove_; target[KEY_ALLOW_N_ACTION] = allowNAction_; + target[KEY_ALLOW_N_EVENT_REPORT] = allowNEventReport_; } else { diff -r 22eef03feed7 -r c1e2b91c2ab4 Core/DicomNetworking/RemoteModalityParameters.h --- a/Core/DicomNetworking/RemoteModalityParameters.h Tue Jan 21 14:20:50 2020 +0100 +++ b/Core/DicomNetworking/RemoteModalityParameters.h Tue Jan 21 17:01:46 2020 +0100 @@ -54,7 +54,8 @@ bool allowMove_; bool allowGet_; bool allowNAction_; - + bool allowNEventReport_; + void Clear(); void UnserializeArray(const Json::Value& serialized); diff -r 22eef03feed7 -r c1e2b91c2ab4 Core/Enumerations.cpp --- a/Core/Enumerations.cpp Tue Jan 21 14:20:50 2020 +0100 +++ b/Core/Enumerations.cpp Tue Jan 21 17:01:46 2020 +0100 @@ -864,7 +864,11 @@ break; case DicomRequestType_NAction: - return "N-Action"; + return "N-ACTION"; + break; + + case DicomRequestType_NEventReport: + return "N-EVENT-REPORT"; break; default: diff -r 22eef03feed7 -r c1e2b91c2ab4 Core/Enumerations.h --- a/Core/Enumerations.h Tue Jan 21 14:20:50 2020 +0100 +++ b/Core/Enumerations.h Tue Jan 21 17:01:46 2020 +0100 @@ -624,7 +624,8 @@ DicomRequestType_Get, DicomRequestType_Move, DicomRequestType_Store, - DicomRequestType_NAction + DicomRequestType_NAction, + DicomRequestType_NEventReport }; enum TransferSyntax diff -r 22eef03feed7 -r c1e2b91c2ab4 OrthancServer/OrthancRestApi/OrthancRestModalities.cpp --- a/OrthancServer/OrthancRestApi/OrthancRestModalities.cpp Tue Jan 21 14:20:50 2020 +0100 +++ b/OrthancServer/OrthancRestApi/OrthancRestModalities.cpp Tue Jan 21 17:01:46 2020 +0100 @@ -1315,6 +1315,8 @@ std::vector sopClassUids, sopInstanceUids; sopClassUids.push_back("a"); sopInstanceUids.push_back("b"); + sopClassUids.push_back("1.2.840.10008.5.1.4.1.1.6.1"); + sopInstanceUids.push_back("1.2.840.113543.6.6.4.7.64234348190163144631511103849051737563212"); std::string t = Toolbox::GenerateDicomPrivateUniqueIdentifier(); scu.RequestStorageCommitment(t, sopClassUids, sopInstanceUids); diff -r 22eef03feed7 -r c1e2b91c2ab4 OrthancServer/main.cpp --- a/OrthancServer/main.cpp Tue Jan 21 14:20:50 2020 +0100 +++ b/OrthancServer/main.cpp Tue Jan 21 17:01:46 2020 +0100 @@ -129,16 +129,30 @@ { } - virtual void Handle(const std::string& transactionUid, - const std::vector& referencedSopClassUids, - const std::vector& referencedSopInstanceUids, - const std::string& remoteIp, - const std::string& remoteAet, - const std::string& calledAet) + virtual void HandleRequest(const std::string& transactionUid, + const std::vector& referencedSopClassUids, + const std::vector& referencedSopInstanceUids, + const std::string& remoteIp, + const std::string& remoteAet, + const std::string& calledAet) { // TODO - Enqueue a Storage commitment job boost::thread t(Toto, new std::string(transactionUid)); + + printf("HANDLE REQUEST\n"); + } + + virtual void HandleReport(const std::string& transactionUid, + const std::vector& successSopClassUids, + const std::vector& successSopInstanceUids, + const std::vector& failedSopClassUids, + const std::vector& failedSopInstanceUids, + const std::string& remoteIp, + const std::string& remoteAet, + const std::string& calledAet) + { + printf("HANDLE REPORT\n"); } }; diff -r 22eef03feed7 -r c1e2b91c2ab4 Resources/Configuration.json --- a/Resources/Configuration.json Tue Jan 21 14:20:50 2020 +0100 +++ b/Resources/Configuration.json Tue Jan 21 17:01:46 2020 +0100 @@ -204,13 +204,13 @@ /** * By default, the Orthanc SCP accepts all DICOM commands (C-ECHO, - * C-STORE, C-FIND, C-MOVE) issued by the registered remote SCU - * modalities. Starting with Orthanc 1.5.0, it is possible to - * specify which DICOM commands are allowed, separately for each - * remote modality, using the syntax below. The "AllowEcho" (resp. - * "AllowStore") option only has an effect respectively if global - * option "DicomAlwaysAllowEcho" (resp. "DicomAlwaysAllowStore") - * is set to false. + * C-STORE, C-FIND, C-MOVE, and storage commitment) issued by the + * registered remote SCU modalities. Starting with Orthanc 1.5.0, + * it is possible to specify which DICOM commands are allowed, + * separately for each remote modality, using the syntax + * below. The "AllowEcho" (resp. "AllowStore") option only has an + * effect respectively if global option "DicomAlwaysAllowEcho" + * (resp. "DicomAlwaysAllowStore") is set to false. **/ //"untrusted" : { // "AET" : "ORTHANC", @@ -220,7 +220,7 @@ // "AllowFind" : false, // "AllowMove" : false, // "AllowStore" : true, - // "AllowNAction" : false // Allow storage commitment (new in 1.6.0) + // "AllowStorageCommitment" : false // new in 1.6.0 //} }, diff -r 22eef03feed7 -r c1e2b91c2ab4 UnitTestsSources/MultiThreadingTests.cpp --- a/UnitTestsSources/MultiThreadingTests.cpp Tue Jan 21 14:20:50 2020 +0100 +++ b/UnitTestsSources/MultiThreadingTests.cpp Tue Jan 21 17:01:46 2020 +0100 @@ -1898,6 +1898,7 @@ ASSERT_TRUE(modality.IsRequestAllowed(DicomRequestType_Store)); ASSERT_TRUE(modality.IsRequestAllowed(DicomRequestType_Move)); ASSERT_TRUE(modality.IsRequestAllowed(DicomRequestType_NAction)); + ASSERT_TRUE(modality.IsRequestAllowed(DicomRequestType_NEventReport)); } s = Json::nullValue; @@ -1927,6 +1928,7 @@ ASSERT_TRUE(modality.IsRequestAllowed(DicomRequestType_Store)); ASSERT_TRUE(modality.IsRequestAllowed(DicomRequestType_Move)); ASSERT_TRUE(modality.IsRequestAllowed(DicomRequestType_NAction)); + ASSERT_TRUE(modality.IsRequestAllowed(DicomRequestType_NEventReport)); } s["Port"] = "46"; @@ -1947,8 +1949,9 @@ operations.insert(DicomRequestType_Move); operations.insert(DicomRequestType_Store); operations.insert(DicomRequestType_NAction); + operations.insert(DicomRequestType_NEventReport); - ASSERT_EQ(6u, operations.size()); + ASSERT_EQ(7u, operations.size()); for (std::set::const_iterator it = operations.begin(); it != operations.end(); ++it) @@ -1977,4 +1980,54 @@ } } } + + { + Json::Value s; + s["AllowStorageCommitment"] = false; + s["AET"] = "AET"; + s["Host"] = "host"; + s["Port"] = "104"; + + RemoteModalityParameters modality(s); + ASSERT_TRUE(modality.IsAdvancedFormatNeeded()); + ASSERT_EQ("AET", modality.GetApplicationEntityTitle()); + ASSERT_EQ("host", modality.GetHost()); + ASSERT_EQ(104u, modality.GetPortNumber()); + ASSERT_FALSE(modality.IsRequestAllowed(DicomRequestType_NAction)); + ASSERT_FALSE(modality.IsRequestAllowed(DicomRequestType_NEventReport)); + } + + { + Json::Value s; + s["AllowNAction"] = false; + s["AllowNEventReport"] = true; + s["AET"] = "AET"; + s["Host"] = "host"; + s["Port"] = "104"; + + RemoteModalityParameters modality(s); + ASSERT_TRUE(modality.IsAdvancedFormatNeeded()); + ASSERT_EQ("AET", modality.GetApplicationEntityTitle()); + ASSERT_EQ("host", modality.GetHost()); + ASSERT_EQ(104u, modality.GetPortNumber()); + ASSERT_FALSE(modality.IsRequestAllowed(DicomRequestType_NAction)); + ASSERT_TRUE(modality.IsRequestAllowed(DicomRequestType_NEventReport)); + } + + { + Json::Value s; + s["AllowNAction"] = true; + s["AllowNEventReport"] = true; + s["AET"] = "AET"; + s["Host"] = "host"; + s["Port"] = "104"; + + RemoteModalityParameters modality(s); + ASSERT_FALSE(modality.IsAdvancedFormatNeeded()); + ASSERT_EQ("AET", modality.GetApplicationEntityTitle()); + ASSERT_EQ("host", modality.GetHost()); + ASSERT_EQ(104u, modality.GetPortNumber()); + ASSERT_TRUE(modality.IsRequestAllowed(DicomRequestType_NAction)); + ASSERT_TRUE(modality.IsRequestAllowed(DicomRequestType_NEventReport)); + } }