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