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 */);
+      }
+    }
   }
 }