comparison 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
comparison
equal deleted inserted replaced
3603:7e303ba837d9 3604:e327b44780bb
90 #include "FindScp.h" 90 #include "FindScp.h"
91 #include "StoreScp.h" 91 #include "StoreScp.h"
92 #include "MoveScp.h" 92 #include "MoveScp.h"
93 #include "../../Toolbox.h" 93 #include "../../Toolbox.h"
94 #include "../../Logging.h" 94 #include "../../Logging.h"
95 95 #include "../../OrthancException.h"
96
97 #include <dcmtk/dcmdata/dcdeftag.h> /* for storage commitment */
98 #include <dcmtk/dcmdata/dcsequen.h> /* for class DcmSequenceOfItems */
99 #include <dcmtk/dcmdata/dcuid.h> /* for variable dcmAllStorageSOPClassUIDs */
96 #include <dcmtk/dcmnet/dcasccfg.h> /* for class DcmAssociationConfiguration */ 100 #include <dcmtk/dcmnet/dcasccfg.h> /* for class DcmAssociationConfiguration */
97 #include <dcmtk/dcmdata/dcuid.h> /* for variable dcmAllStorageSOPClassUIDs */
98 101
99 #include <boost/lexical_cast.hpp> 102 #include <boost/lexical_cast.hpp>
100 103
101 static OFBool opt_rejectWithoutImplementationUID = OFFalse; 104 static OFBool opt_rejectWithoutImplementationUID = OFFalse;
102 105
296 { 299 {
297 knownAbstractSyntaxes.push_back(UID_MOVEStudyRootQueryRetrieveInformationModel); 300 knownAbstractSyntaxes.push_back(UID_MOVEStudyRootQueryRetrieveInformationModel);
298 knownAbstractSyntaxes.push_back(UID_MOVEPatientRootQueryRetrieveInformationModel); 301 knownAbstractSyntaxes.push_back(UID_MOVEPatientRootQueryRetrieveInformationModel);
299 } 302 }
300 303
304 // For Storage Commitment
305 if (server.HasStorageCommitmentRequestHandlerFactory())
306 {
307 knownAbstractSyntaxes.push_back(UID_StorageCommitmentPushModelSOPClass);
308 }
309
301 cond = ASC_receiveAssociation(net, &assoc, 310 cond = ASC_receiveAssociation(net, &assoc,
302 /*opt_maxPDU*/ ASC_DEFAULTMAXPDU, 311 /*opt_maxPDU*/ ASC_DEFAULTMAXPDU,
303 NULL, NULL, 312 NULL, NULL,
304 /*opt_secureConnection*/ OFFalse, 313 /*opt_secureConnection*/ OFFalse,
305 DUL_NOBLOCK, 1); 314 DUL_NOBLOCK, 1);
687 case DIMSE_C_FIND_RQ: 696 case DIMSE_C_FIND_RQ:
688 request = DicomRequestType_Find; 697 request = DicomRequestType_Find;
689 supported = true; 698 supported = true;
690 break; 699 break;
691 700
701 case DIMSE_N_ACTION_RQ:
702 request = DicomRequestType_NAction;
703 supported = true;
704 break;
705
692 default: 706 default:
693 // we cannot handle this kind of message 707 // we cannot handle this kind of message
694 cond = DIMSE_BADCOMMANDTYPE; 708 cond = DIMSE_BADCOMMANDTYPE;
695 LOG(ERROR) << "cannot handle command: 0x" << std::hex << msg.CommandField; 709 LOG(ERROR) << "cannot handle command: 0x" << std::hex << msg.CommandField;
696 break; 710 break;
768 findHandler.get(), worklistHandler.get(), 782 findHandler.get(), worklistHandler.get(),
769 remoteIp_, remoteAet_, calledAet_); 783 remoteIp_, remoteAet_, calledAet_);
770 } 784 }
771 break; 785 break;
772 786
787 case DicomRequestType_NAction:
788 cond = NActionScp(&msg, presID);
789 break;
790
773 default: 791 default:
774 // Should never happen 792 // Should never happen
775 break; 793 break;
776 } 794 }
777 } 795 }
821 { 839 {
822 LOG(ERROR) << "Echo SCP Failed: " << cond.text(); 840 LOG(ERROR) << "Echo SCP Failed: " << cond.text();
823 } 841 }
824 return cond; 842 return cond;
825 } 843 }
844
845
846 OFCondition CommandDispatcher::NActionScp(T_DIMSE_Message* msg,
847 T_ASC_PresentationContextID presID)
848 {
849 /**
850 * Starting with Orthanc 1.6.0, only storage commitment is
851 * supported with DICOM N-ACTION. This corresponds to the case
852 * where "Action Type ID" equals "1".
853 * http://dicom.nema.org/medical/dicom/2019a/output/chtml/part04/sect_J.3.2.html
854 * http://dicom.nema.org/medical/dicom/2019a/output/chtml/part07/chapter_10.html#table_10.1-4
855 **/
856
857 if (msg->CommandField != DIMSE_N_ACTION_RQ /* value == 304 == 0x0130 */ ||
858 !server_.HasStorageCommitmentRequestHandlerFactory())
859 {
860 throw OrthancException(ErrorCode_InternalError);
861 }
862
863
864 /**
865 * Check that the storage commitment request is correctly formatted.
866 **/
867
868 const T_DIMSE_N_ActionRQ& request = msg->msg.NActionRQ;
869
870 if (request.ActionTypeID != 1)
871 {
872 throw OrthancException(ErrorCode_NotImplemented,
873 "Only storage commitment is implemented for DICOM N-ACTION SCP");
874 }
875
876 if (std::string(request.RequestedSOPClassUID) != UID_StorageCommitmentPushModelSOPClass ||
877 std::string(request.RequestedSOPInstanceUID) != UID_StorageCommitmentPushModelSOPInstance)
878 {
879 throw OrthancException(ErrorCode_NetworkProtocol,
880 "Unexpected incoming SOP class or instance UID for storage commitment");
881 }
882
883 if (request.DataSetType != DIMSE_DATASET_PRESENT)
884 {
885 throw OrthancException(ErrorCode_NetworkProtocol,
886 "Incoming storage commitment request without a dataset");
887 }
888
889
890 /**
891 * Extract the DICOM dataset that is associated with the DIMSE
892 * message. The content of this dataset is documented in "Table
893 * J.3-1. Storage Commitment Request - Action Information":
894 * http://dicom.nema.org/medical/dicom/2019a/output/chtml/part04/sect_J.3.2.html#table_J.3-1
895 **/
896
897 std::auto_ptr<DcmDataset> dataset;
898
899 {
900 DcmDataset *tmp = NULL;
901 T_ASC_PresentationContextID presIdData;
902
903 OFCondition cond = DIMSE_receiveDataSetInMemory(
904 assoc_, /*opt_blockMode*/ DIMSE_BLOCKING,
905 /*opt_dimse_timeout*/ 0, &presIdData, &tmp, NULL, NULL);
906 if (!cond.good())
907 {
908 return cond;
909 }
910
911 if (tmp == NULL)
912 {
913 LOG(ERROR) << "Cannot read the dataset in N-ACTION SCP";
914 return EC_InvalidStream;
915 }
916
917 dataset.reset(tmp);
918 }
919
920 std::string transactionUid;
921 std::vector<std::string> referencedSopClassUid, referencedSopInstanceUid;
922
923 {
924 const char* s = NULL;
925 if (!dataset->findAndGetString(DCM_TransactionUID, s).good() ||
926 s == NULL)
927 {
928 LOG(ERROR) << "Missing Transaction UID in storage commitment request";
929 return EC_InvalidStream;
930 }
931
932 transactionUid.assign(s);
933 }
934
935 {
936 DcmSequenceOfItems* sequence = NULL;
937 if (!dataset->findAndGetSequence(DCM_ReferencedSOPSequence, sequence).good() ||
938 sequence == NULL)
939 {
940 LOG(ERROR) << "Missing Referenced SOP Sequence in storage commitment request";
941 return EC_InvalidStream;
942 }
943
944 referencedSopClassUid.reserve(sequence->card());
945 referencedSopInstanceUid.reserve(sequence->card());
946
947 for (unsigned long i = 0; i < sequence->card(); i++)
948 {
949 const char* a = NULL;
950 const char* b = NULL;
951 if (!sequence->getItem(i)->findAndGetString(DCM_ReferencedSOPClassUID, a).good() ||
952 !sequence->getItem(i)->findAndGetString(DCM_ReferencedSOPInstanceUID, b).good() ||
953 a == NULL ||
954 b == NULL)
955 {
956 LOG(ERROR) << "Missing Referenced SOP Class/Instance UID in storage commitment request";
957 return EC_InvalidStream;
958 }
959
960 referencedSopClassUid.push_back(a);
961 referencedSopInstanceUid.push_back(b);
962 }
963 }
964
965 LOG(INFO) << "Incoming storage commitment transaction, with UID: " << transactionUid;
966
967 for (size_t i = 0; i < referencedSopClassUid.size(); i++)
968 {
969 LOG(INFO) << " (" << (i + 1) << "/" << referencedSopClassUid.size()
970 << ") queried SOP Class/Instance UID: "
971 << referencedSopClassUid[i] << " / " << referencedSopInstanceUid[i];
972 }
973
974
975 /**
976 * Call the Orthanc handler. The list of available DIMSE status
977 * codes can be found at:
978 * http://dicom.nema.org/medical/dicom/2019a/output/chtml/part07/chapter_10.html#sect_10.1.4.1.10
979 **/
980
981 DIC_US dimseStatus;
982
983 try
984 {
985 std::auto_ptr<IStorageCommitmentRequestHandler> handler
986 (server_.GetStorageCommitmentRequestHandlerFactory().
987 ConstructStorageCommitmentRequestHandler());
988
989 handler->Handle(transactionUid, referencedSopClassUid, referencedSopInstanceUid,
990 remoteIp_, remoteAet_, calledAet_);
991
992 dimseStatus = 0; // Success
993 }
994 catch (OrthancException& e)
995 {
996 LOG(ERROR) << "Error while processing an incoming storage commitment request: " << e.What();
997
998 // Code 0x0110 - "General failure in processing the operation was encountered"
999 dimseStatus = STATUS_N_ProcessingFailure;
1000 }
1001
1002
1003 /**
1004 * Send the DIMSE status back to the SCU.
1005 **/
1006
1007 {
1008 T_DIMSE_Message response;
1009 memset(&response, 0, sizeof(response));
1010 response.CommandField = DIMSE_N_ACTION_RSP;
1011
1012 T_DIMSE_N_ActionRSP& content = response.msg.NActionRSP;
1013 content.MessageIDBeingRespondedTo = request.MessageID;
1014 strncpy(content.AffectedSOPClassUID, UID_StorageCommitmentPushModelSOPClass, DIC_UI_LEN);
1015 content.DimseStatus = dimseStatus;
1016 strncpy(content.AffectedSOPInstanceUID, UID_StorageCommitmentPushModelSOPInstance, DIC_UI_LEN);
1017 content.ActionTypeID = 0; // Not present, as "O_NACTION_ACTIONTYPEID" not set in "opts"
1018 content.DataSetType = DIMSE_DATASET_NULL; // Dataset is absent in storage commitment response
1019 content.opts = O_NACTION_AFFECTEDSOPCLASSUID | O_NACTION_AFFECTEDSOPINSTANCEUID;
1020
1021 return DIMSE_sendMessageUsingMemoryData(
1022 assoc_, presID, &response, NULL /* no dataset */, NULL /* dataObject */,
1023 NULL /* callback */, NULL /* callback context */, NULL /* commandSet */);
1024 }
1025 }
826 } 1026 }
827 } 1027 }