# HG changeset patch # User Sebastien Jodogne # Date 1579542287 -3600 # Node ID 7ae553d9c3664823469d78981cd615df33a96077 # Parent d0ecb355db33107229b8af7d1c3c8d7390205624 created DicomUserConnection::RequestStorageCommitment() diff -r d0ecb355db33 -r 7ae553d9c366 Core/DicomNetworking/DicomUserConnection.cpp --- 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& sopClassUids, + const std::vector& 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; + } + } } diff -r d0ecb355db33 -r 7ae553d9c366 Core/DicomNetworking/DicomUserConnection.h --- 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& successSopInstanceUids, const std::vector& failureSopClassUids, const std::vector& failureSopInstanceUids); + + void RequestStorageCommitment( + std::string& transactionUid, + const std::vector& sopClassUids, + const std::vector& sopInstanceUids); }; } diff -r d0ecb355db33 -r 7ae553d9c366 Core/Toolbox.cpp --- 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 decimal; + decimal.push_back(0); + + for (size_t i = 0; i < hex.size(); i++) + { + uint8_t hexDigit = static_cast(Hex2Dec(hex[i])); + assert(hexDigit <= 15); + + for (size_t j = 0; j < decimal.size(); j++) + { + uint8_t val = static_cast(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); + } } diff -r d0ecb355db33 -r 7ae553d9c366 Core/Toolbox.h --- 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(); } } diff -r d0ecb355db33 -r 7ae553d9c366 OrthancServer/main.cpp --- 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 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)); } }; diff -r d0ecb355db33 -r 7ae553d9c366 UnitTestsSources/ToolboxTests.cpp --- 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(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)); +}