changeset 3608:7ae553d9c366 storage-commitment

created DicomUserConnection::RequestStorageCommitment()
author Sebastien Jodogne <s.jodogne@gmail.com>
date Mon, 20 Jan 2020 18:44:47 +0100
parents d0ecb355db33
children f7ade98d8229
files Core/DicomNetworking/DicomUserConnection.cpp Core/DicomNetworking/DicomUserConnection.h Core/Toolbox.cpp Core/Toolbox.h OrthancServer/main.cpp UnitTestsSources/ToolboxTests.cpp
diffstat 6 files changed, 252 insertions(+), 6 deletions(-) [+]
line wrap: on
line diff
--- a/Core/DicomNetworking/DicomUserConnection.cpp	Fri Jan 17 15:56:02 2020 +0100
+++ b/Core/DicomNetworking/DicomUserConnection.cpp	Mon Jan 20 18:44:47 2020 +0100
@@ -306,6 +306,7 @@
         break;
       }
 
+      case Mode_RequestStorageCommitment:
       case Mode_ReportStorageCommitment:
       {
         const char* as = UID_StorageCommitmentPushModelSOPClass;
@@ -314,8 +315,23 @@
         ts.push_back(UID_LittleEndianExplicitTransferSyntax);
         ts.push_back(UID_LittleEndianImplicitTransferSyntax);
 
+        T_ASC_SC_ROLE role;
+        switch (mode)
+        {
+          case Mode_RequestStorageCommitment:
+            role = ASC_SC_ROLE_DEFAULT;
+            break;
+            
+          case Mode_ReportStorageCommitment:
+            role = ASC_SC_ROLE_SCP;
+            break;
+
+          default:
+            throw OrthancException(ErrorCode_InternalError);
+        }
+        
         Check(ASC_addPresentationContext(pimpl_->params_, 1 /*presentationContextId*/,
-                                         as, &ts[0], ts.size(), ASC_SC_ROLE_SCP),
+                                         as, &ts[0], ts.size(), role),
               remoteAet_, "initializing");
               
         break;
@@ -1428,7 +1444,7 @@
                 << "\" about storage commitment transaction: " << transactionUid
                 << " (" << successSopClassUids.size() << " successes, " 
                 << failureSopClassUids.size() << " failures)";
-      DIC_US messageId = pimpl_->assoc_->nextMsgID;
+      const DIC_US messageId = pimpl_->assoc_->nextMsgID++;
       
       {
         T_DIMSE_Message message;
@@ -1521,4 +1537,96 @@
       throw;
     }
   }
+
+
+  
+  void DicomUserConnection::RequestStorageCommitment(
+    std::string& transactionUid,
+    const std::vector<std::string>& sopClassUids,
+    const std::vector<std::string>& sopInstanceUids)
+  {
+    if (sopClassUids.size() != sopInstanceUids.size())
+    {
+      throw OrthancException(ErrorCode_ParameterOutOfRange);
+    }
+
+    if (IsOpen())
+    {
+      Close();
+    }
+
+    try
+    {
+      OpenInternal(Mode_RequestStorageCommitment);
+
+      /**
+       * N-ACTION
+       * 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
+       *
+       * Status code:
+       * http://dicom.nema.org/medical/dicom/2019a/output/chtml/part07/chapter_10.html#sect_10.1.1.1.8
+       **/
+
+      /**
+       * Send the "N_ACTION_RQ" request
+       **/
+
+      printf("ICI\n");
+
+      LOG(INFO) << "Request to modality \"" << remoteAet_
+                << "\" about storage commitment for " << sopClassUids.size() << " instances";
+      const DIC_US messageId = pimpl_->assoc_->nextMsgID++;
+      
+      {
+        T_DIMSE_Message message;
+        memset(&message, 0, sizeof(message));
+        message.CommandField = DIMSE_N_ACTION_RQ;
+
+        T_DIMSE_N_ActionRQ& content = message.msg.NActionRQ;
+        content.MessageID = messageId;
+        strncpy(content.RequestedSOPClassUID, UID_StorageCommitmentPushModelSOPClass, DIC_UI_LEN);
+        strncpy(content.RequestedSOPInstanceUID, UID_StorageCommitmentPushModelSOPInstance, DIC_UI_LEN);
+        content.ActionTypeID = 1;  // "Request Storage Commitment"
+        content.DataSetType = DIMSE_DATASET_PRESENT;
+
+        DcmDataset dataset;
+        if (!dataset.putAndInsertString(DCM_TransactionUID, transactionUid.c_str()).good())
+        {
+          throw OrthancException(ErrorCode_InternalError);
+        }
+
+        FillSopSequence(dataset, DCM_ReferencedSOPSequence, sopClassUids, sopInstanceUids);
+
+        int presID = ASC_findAcceptedPresentationContextID(
+          pimpl_->assoc_, UID_StorageCommitmentPushModelSOPClass);
+        if (presID == 0)
+        {
+          throw OrthancException(ErrorCode_NetworkProtocol, "Storage commitment - "
+                                 "Unable to send N-EVENT-REPORT request to AET: " + remoteAet_);
+        }
+
+        if (!DIMSE_sendMessageUsingMemoryData(
+              pimpl_->assoc_, presID, &message, NULL /* status detail */,
+              &dataset, NULL /* callback */, NULL /* callback context */,
+              NULL /* commandSet */).good())
+        {
+          throw OrthancException(ErrorCode_NetworkProtocol);
+        }
+      }
+
+      /**
+       * Read the "N_ACTION_RSP" response
+       **/
+
+      // TODO
+
+      Close();
+    }
+    catch (OrthancException&)
+    {
+      Close();
+      throw;
+    }
+  }
 }
--- a/Core/DicomNetworking/DicomUserConnection.h	Fri Jan 17 15:56:02 2020 +0100
+++ b/Core/DicomNetworking/DicomUserConnection.h	Mon Jan 20 18:44:47 2020 +0100
@@ -57,7 +57,8 @@
     enum Mode
     {
       Mode_Generic,
-      Mode_ReportStorageCommitment
+      Mode_ReportStorageCommitment,
+      Mode_RequestStorageCommitment
     };
     
     // Connection parameters
@@ -231,5 +232,10 @@
       const std::vector<std::string>& successSopInstanceUids,
       const std::vector<std::string>& failureSopClassUids,
       const std::vector<std::string>& failureSopInstanceUids);      
+
+    void RequestStorageCommitment(
+      std::string& transactionUid,
+      const std::vector<std::string>& sopClassUids,
+      const std::vector<std::string>& sopInstanceUids);
   };
 }
--- a/Core/Toolbox.cpp	Fri Jan 17 15:56:02 2020 +0100
+++ b/Core/Toolbox.cpp	Mon Jan 20 18:44:47 2020 +0100
@@ -2073,6 +2073,108 @@
       throw OrthancException(ErrorCode_BadFileFormat, "Invalid UTF-8 string");
     }
   }
+
+
+  std::string Toolbox::LargeHexadecimalToDecimal(const std::string& hex)
+  {
+    /**
+     * NB: Focus of the code below is *not* efficiency, but
+     * readability!
+     **/
+    
+    for (size_t i = 0; i < hex.size(); i++)
+    {
+      const char c = hex[i];
+      if (!((c >= 'A' && c <= 'F') ||
+            (c >= 'a' && c <= 'f') ||
+            (c >= '0' && c <= '9')))
+      {
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange,
+                                        "Not an hexadecimal number");
+      }
+    }
+    
+    std::vector<uint8_t> decimal;
+    decimal.push_back(0);
+
+    for (size_t i = 0; i < hex.size(); i++)
+    {
+      uint8_t hexDigit = static_cast<uint8_t>(Hex2Dec(hex[i]));
+      assert(hexDigit <= 15);
+
+      for (size_t j = 0; j < decimal.size(); j++)
+      {
+        uint8_t val = static_cast<uint8_t>(decimal[j]) * 16 + hexDigit;  // Maximum: 9 * 16 + 15
+        assert(val <= 159 /* == 9 * 16 + 15 */);
+      
+        decimal[j] = val % 10;
+        hexDigit = val / 10;
+        assert(hexDigit <= 15 /* == 159 / 10 */);
+      }
+
+      while (hexDigit > 0)
+      {
+        decimal.push_back(hexDigit % 10);
+        hexDigit /= 10;
+      }
+    }
+
+    size_t start = 0;
+    while (start < decimal.size() &&
+           decimal[start] == '0')
+    {
+      start++;
+    }
+
+    std::string s;
+    s.reserve(decimal.size() - start);
+
+    for (size_t i = decimal.size(); i > start; i--)
+    {
+      s.push_back(decimal[i - 1] + '0');
+    }
+
+    return s;
+  }
+
+
+  std::string Toolbox::GenerateDicomPrivateUniqueIdentifier()
+  {
+    /**
+     * REFERENCE: "Creating a Privately Defined Unique Identifier
+     * (Informative)" / "UUID Derived UID"
+     * http://dicom.nema.org/medical/dicom/2019a/output/chtml/part05/sect_B.2.html
+     * https://stackoverflow.com/a/46316162/881731
+     **/
+
+    std::string uuid = GenerateUuid();
+    assert(IsUuid(uuid) && uuid.size() == 36);
+
+    /**
+     * After removing the four dashes ("-") out of the 36-character
+     * UUID, we get a large hexadecimal number with 32 characters,
+     * each of those characters lying in the range [0,16[. The large
+     * number is thus in the [0,16^32[ = [0,256^16[ range. This number
+     * has a maximum of 39 decimal digits, as can be seen in Python:
+     * 
+     * # python -c 'import math; print(math.log(16**32))/math.log(10))'
+     * 38.531839445
+     *
+     * We now to convert the large hexadecimal number to a decimal
+     * number with up to 39 digits, remove the leading zeros, then
+     * prefix it with "2.25."
+     **/
+
+    // Remove the dashes
+    std::string hex = (uuid.substr(0, 8) +
+                       uuid.substr(9, 4) +
+                       uuid.substr(14, 4) +
+                       uuid.substr(19, 4) +
+                       uuid.substr(24, 12));
+    assert(hex.size() == 32);
+
+    return "2.25." + LargeHexadecimalToDecimal(hex);
+  }
 }
 
 
--- a/Core/Toolbox.h	Fri Jan 17 15:56:02 2020 +0100
+++ b/Core/Toolbox.h	Mon Jan 20 18:44:47 2020 +0100
@@ -257,6 +257,11 @@
                                 size_t& utf8Length,
                                 const std::string& utf8,
                                 size_t position);
+
+    std::string LargeHexadecimalToDecimal(const std::string& hex);
+
+    // http://dicom.nema.org/medical/dicom/2019a/output/chtml/part05/sect_B.2.html
+    std::string GenerateDicomPrivateUniqueIdentifier();
   }
 }
 
--- a/OrthancServer/main.cpp	Fri Jan 17 15:56:02 2020 +0100
+++ b/OrthancServer/main.cpp	Mon Jan 20 18:44:47 2020 +0100
@@ -97,8 +97,10 @@
   ServerContext& server_;
 
   // TODO - Remove this
-  static void Toto()
+  static void Toto(std::string* t)
   {
+    std::auto_ptr<std::string> tt(t);
+    
     printf("Sleeping\n");
     boost::this_thread::sleep(boost::posix_time::milliseconds(100));
     printf("Connect back\n");
@@ -110,7 +112,7 @@
     a.push_back("a");  b.push_back("b");
     a.push_back("c");  b.push_back("d");
     
-    scu.ReportStorageCommitment("transaction", a, b, c, d);
+    scu.ReportStorageCommitment(tt->c_str(), a, b, c, d);
     //scu.ReportStorageCommitment("transaction", a, b, a, b);
 
     /**
@@ -136,7 +138,7 @@
   {
     // TODO - Enqueue a Storage commitment job
 
-    boost::thread t(Toto);
+    boost::thread t(Toto, new std::string(transactionUid));
   }
 };
 
--- a/UnitTestsSources/ToolboxTests.cpp	Fri Jan 17 15:56:02 2020 +0100
+++ b/UnitTestsSources/ToolboxTests.cpp	Mon Jan 20 18:44:47 2020 +0100
@@ -134,3 +134,26 @@
   printf("decoding took %zu ms\n", (std::chrono::duration_cast<std::chrono::milliseconds>(afterDecoding - afterEncoding)));
 }
 #endif
+
+
+TEST(Toolbox, LargeHexadecimalToDecimal)
+{
+  // https://stackoverflow.com/a/16967286/881731
+  ASSERT_EQ(
+    "166089946137986168535368849184301740204613753693156360462575217560130904921953976324839782808018277000296027060873747803291797869684516494894741699267674246881622658654267131250470956587908385447044319923040838072975636163137212887824248575510341104029461758594855159174329892125993844566497176102668262139513",
+    Toolbox::LargeHexadecimalToDecimal("EC851A69B8ACD843164E10CFF70CF9E86DC2FEE3CF6F374B43C854E3342A2F1AC3E30C741CC41E679DF6D07CE6FA3A66083EC9B8C8BF3AF05D8BDBB0AA6Cb3ef8c5baa2a5e531ba9e28592f99e0fe4f95169a6c63f635d0197e325c5ec76219b907e4ebdcd401fb1986e4e3ca661ff73e7e2b8fd9988e753b7042b2bbca76679"));
+
+  ASSERT_EQ("0", Toolbox::LargeHexadecimalToDecimal(""));
+  ASSERT_EQ("0", Toolbox::LargeHexadecimalToDecimal("0"));
+  ASSERT_EQ("0", Toolbox::LargeHexadecimalToDecimal("0000"));
+  ASSERT_EQ("255", Toolbox::LargeHexadecimalToDecimal("00000ff"));
+
+  ASSERT_THROW(Toolbox::LargeHexadecimalToDecimal("g"), Orthanc::OrthancException);
+}
+
+
+TEST(Toolbox, GenerateDicomPrivateUniqueIdentifier)
+{
+  std::string s = Toolbox::GenerateDicomPrivateUniqueIdentifier();
+  ASSERT_EQ("2.25.", s.substr(0, 5));
+}