Mercurial > hg > orthanc
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 } |