# HG changeset patch # User Sebastien Jodogne # Date 1611160583 -3600 # Node ID 522e13a60cfc4875dab1af05fbd8f6614ac20e1d # Parent da460bef88f86ffc0084ff2f6e614fba3687ff65 "LocalAet" in "DicomModalities" to overwrite global "DicomAet" for SCU on a per-modality basis diff -r da460bef88f8 -r 522e13a60cfc NEWS --- a/NEWS Wed Jan 20 17:02:10 2021 +0100 +++ b/NEWS Wed Jan 20 17:36:23 2021 +0100 @@ -12,6 +12,7 @@ - "DicomTlsTrustedCertificates" to provide the list of TLS certificates to be trusted by Orthanc - "UseDicomTls" in "DicomModalities" to enable DICOM TLS in outgoing SCU on a per-modality basis - "MaximumPduLength" to tune the maximum PDU length (Protocol Data Unit) + - "LocalAet" in "DicomModalities" to overwrite global "DicomAet" for SCU on a per-modality basis * New command-line option: "--openapi" to write the OpenAPI documentation of the REST API to a file * New metadata automatically computed at the series level: "RemoteAET" diff -r da460bef88f8 -r 522e13a60cfc OrthancFramework/Sources/DicomNetworking/DicomAssociation.cpp --- a/OrthancFramework/Sources/DicomNetworking/DicomAssociation.cpp Wed Jan 20 17:02:10 2021 +0100 +++ b/OrthancFramework/Sources/DicomNetworking/DicomAssociation.cpp Wed Jan 20 17:36:23 2021 +0100 @@ -265,9 +265,15 @@ "No presentation context was proposed"); } + std::string localAet = parameters.GetLocalApplicationEntityTitle(); + if (parameters.GetRemoteModality().HasLocalAet()) + { + localAet = parameters.GetRemoteModality().GetLocalAet(); + } + CLOG(INFO, DICOM) << "Opening a DICOM SCU connection " << (parameters.GetRemoteModality().IsDicomTlsEnabled() ? "using DICOM TLS" : "without DICOM TLS") - << " from AET \"" << parameters.GetLocalApplicationEntityTitle() + << " from AET \"" << localAet << "\" to AET \"" << parameters.GetRemoteModality().GetApplicationEntityTitle() << "\" on host " << parameters.GetRemoteModality().GetHost() << ":" << parameters.GetRemoteModality().GetPortNumber() @@ -298,7 +304,7 @@ // Set this application's title and the called application's title in the params CheckConnecting(parameters, ASC_setAPTitles( - params_, parameters.GetLocalApplicationEntityTitle().c_str(), + params_, localAet.c_str(), parameters.GetRemoteModality().GetApplicationEntityTitle().c_str(), NULL)); // Set the network addresses of the local and remote entities diff -r da460bef88f8 -r 522e13a60cfc OrthancFramework/Sources/DicomNetworking/RemoteModalityParameters.cpp --- a/OrthancFramework/Sources/DicomNetworking/RemoteModalityParameters.cpp Wed Jan 20 17:02:10 2021 +0100 +++ b/OrthancFramework/Sources/DicomNetworking/RemoteModalityParameters.cpp Wed Jan 20 17:36:23 2021 +0100 @@ -45,6 +45,7 @@ static const char* KEY_MANUFACTURER = "Manufacturer"; static const char* KEY_PORT = "Port"; static const char* KEY_USE_DICOM_TLS = "UseDicomTls"; +static const char* KEY_LOCAL_AET = "LocalAet"; namespace Orthanc @@ -64,6 +65,7 @@ allowNEventReport_ = true; // For storage commitment allowTranscoding_ = true; useDicomTls_ = false; + localAet_.clear(); } @@ -286,6 +288,11 @@ { useDicomTls_ = SerializationToolbox::ReadBoolean(serialized, KEY_USE_DICOM_TLS); } + + if (serialized.isMember(KEY_LOCAL_AET)) + { + localAet_ = SerializationToolbox::ReadString(serialized, KEY_LOCAL_AET); + } } @@ -369,7 +376,8 @@ !allowNAction_ || !allowNEventReport_ || !allowTranscoding_ || - useDicomTls_); + useDicomTls_ || + HasLocalAet()); } @@ -393,6 +401,7 @@ target[KEY_ALLOW_N_EVENT_REPORT] = allowNEventReport_; target[KEY_ALLOW_TRANSCODING] = allowTranscoding_; target[KEY_USE_DICOM_TLS] = useDicomTls_; + target[KEY_LOCAL_AET] = localAet_; } else { @@ -443,4 +452,33 @@ { useDicomTls_ = enabled; } + + bool RemoteModalityParameters::HasLocalAet() const + { + return !localAet_.empty(); + } + + const std::string& RemoteModalityParameters::GetLocalAet() const + { + if (localAet_.empty()) + { + throw OrthancException(ErrorCode_BadSequenceOfCalls, "You should have called HasLocalAet()"); + } + else + { + return localAet_; + } + } + + void RemoteModalityParameters::SetLocalAet(const std::string& aet) + { + if (aet.empty()) + { + throw OrthancException(ErrorCode_ParameterOutOfRange); + } + else + { + localAet_ = aet; + } + } } diff -r da460bef88f8 -r 522e13a60cfc OrthancFramework/Sources/DicomNetworking/RemoteModalityParameters.h --- a/OrthancFramework/Sources/DicomNetworking/RemoteModalityParameters.h Wed Jan 20 17:02:10 2021 +0100 +++ b/OrthancFramework/Sources/DicomNetworking/RemoteModalityParameters.h Wed Jan 20 17:36:23 2021 +0100 @@ -46,6 +46,7 @@ bool allowNEventReport_; bool allowTranscoding_; bool useDicomTls_; + std::string localAet_; void Clear(); @@ -100,5 +101,11 @@ bool IsDicomTlsEnabled() const; void SetDicomTlsEnabled(bool enabled); + + bool HasLocalAet() const; + + const std::string& GetLocalAet() const; + + void SetLocalAet(const std::string& aet); }; } diff -r da460bef88f8 -r 522e13a60cfc OrthancFramework/UnitTestsSources/JobsTests.cpp --- a/OrthancFramework/UnitTestsSources/JobsTests.cpp Wed Jan 20 17:02:10 2021 +0100 +++ b/OrthancFramework/UnitTestsSources/JobsTests.cpp Wed Jan 20 17:36:23 2021 +0100 @@ -1275,6 +1275,7 @@ { RemoteModalityParameters modality(s); + ASSERT_FALSE(modality.IsAdvancedFormatNeeded()); ASSERT_EQ("ORTHANC", modality.GetApplicationEntityTitle()); ASSERT_EQ("127.0.0.1", modality.GetHost()); ASSERT_EQ(104u, modality.GetPortNumber()); @@ -1288,6 +1289,8 @@ ASSERT_TRUE(modality.IsRequestAllowed(DicomRequestType_NEventReport)); ASSERT_TRUE(modality.IsTranscodingAllowed()); ASSERT_FALSE(modality.IsDicomTlsEnabled()); + ASSERT_FALSE(modality.HasLocalAet()); + ASSERT_THROW(modality.GetLocalAet(), OrthancException); } s = Json::nullValue; @@ -1303,6 +1306,7 @@ ASSERT_FALSE(modality.IsAdvancedFormatNeeded()); modality.Serialize(s, true); ASSERT_EQ(Json::objectValue, s.type()); + ASSERT_FALSE(modality.HasLocalAet()); } { @@ -1320,6 +1324,7 @@ ASSERT_TRUE(modality.IsRequestAllowed(DicomRequestType_NEventReport)); ASSERT_TRUE(modality.IsTranscodingAllowed()); ASSERT_FALSE(modality.IsDicomTlsEnabled()); + ASSERT_FALSE(modality.HasLocalAet()); } s["Port"] = "46"; @@ -1372,6 +1377,23 @@ } } + s = Json::nullValue; + + { + RemoteModalityParameters modality; + modality.SetLocalAet("hello"); + ASSERT_TRUE(modality.IsAdvancedFormatNeeded()); + modality.Serialize(s, true); + ASSERT_EQ(Json::objectValue, s.type()); + ASSERT_TRUE(modality.HasLocalAet()); + } + + { + RemoteModalityParameters modality(s); + ASSERT_TRUE(modality.HasLocalAet()); + ASSERT_EQ("hello", modality.GetLocalAet()); + } + { Json::Value t; t["AllowStorageCommitment"] = false; @@ -1388,6 +1410,8 @@ ASSERT_FALSE(modality.IsRequestAllowed(DicomRequestType_NEventReport)); ASSERT_TRUE(modality.IsTranscodingAllowed()); ASSERT_FALSE(modality.IsDicomTlsEnabled()); + ASSERT_FALSE(modality.HasLocalAet()); + ASSERT_THROW(modality.GetLocalAet(), OrthancException); } { @@ -1399,6 +1423,7 @@ t["Port"] = "104"; t["AllowTranscoding"] = false; t["UseDicomTls"] = true; + t["LocalAet"] = "world"; RemoteModalityParameters modality(t); ASSERT_TRUE(modality.IsAdvancedFormatNeeded()); @@ -1409,6 +1434,8 @@ ASSERT_TRUE(modality.IsRequestAllowed(DicomRequestType_NEventReport)); ASSERT_FALSE(modality.IsTranscodingAllowed()); ASSERT_TRUE(modality.IsDicomTlsEnabled()); + ASSERT_TRUE(modality.HasLocalAet()); + ASSERT_EQ("world", modality.GetLocalAet()); } { @@ -1428,6 +1455,8 @@ ASSERT_TRUE(modality.IsRequestAllowed(DicomRequestType_NEventReport)); ASSERT_TRUE(modality.IsTranscodingAllowed()); ASSERT_FALSE(modality.IsDicomTlsEnabled()); + ASSERT_FALSE(modality.HasLocalAet()); + ASSERT_THROW(modality.GetLocalAet(), OrthancException); } } @@ -1456,6 +1485,8 @@ ASSERT_EQ("ORTHANC", b.GetLocalApplicationEntityTitle()); ASSERT_EQ(DicomAssociationParameters::GetDefaultMaximumPduLength(), b.GetMaximumPduLength()); ASSERT_FALSE(b.GetRemoteModality().IsDicomTlsEnabled()); + ASSERT_FALSE(b.GetRemoteModality().HasLocalAet()); + ASSERT_THROW(b.GetRemoteModality().GetLocalAet(), OrthancException); } { diff -r da460bef88f8 -r 522e13a60cfc OrthancServer/Resources/Configuration.json --- a/OrthancServer/Resources/Configuration.json Wed Jan 20 17:02:10 2021 +0100 +++ b/OrthancServer/Resources/Configuration.json Wed Jan 20 17:36:23 2021 +0100 @@ -330,6 +330,10 @@ * The "UseDicomTls" option specifies whether DICOM TLS should be * used when opening a SCU connection from Orthanc to this remote * modality. By default, DICOM TLS is not enabled. + * + * The "LocalAet" allows to overwrite the global "DicomAet" + * configuration option in order to specify another AET for + * Orthanc when initiating an SCU to this very specific modality. **/ //"untrusted" : { // "AET" : "ORTHANC", @@ -344,6 +348,7 @@ // "AllowStorageCommitment" : false, // new in 1.6.0 // "AllowTranscoding" : true, // new in 1.7.0 // "UseDicomTls" : false // new in 1.9.0 + // "LocalAet" : "HELLO" // new in 1.9.0 //} }, diff -r da460bef88f8 -r 522e13a60cfc OrthancServer/Sources/OrthancRestApi/OrthancRestModalities.cpp --- a/OrthancServer/Sources/OrthancRestApi/OrthancRestModalities.cpp Wed Jan 20 17:02:10 2021 +0100 +++ b/OrthancServer/Sources/OrthancRestApi/OrthancRestModalities.cpp Wed Jan 20 17:36:23 2021 +0100 @@ -671,7 +671,8 @@ "Whether to normalize the query, i.e. whether to wipe out from the query, the DICOM tags " "that are not applicable for the query-retrieve level of interest", false) .SetRequestField(KEY_LOCAL_AET, RestApiCallDocumentation::Type_String, - "Local AET that is used for this commands, defaults to `DicomAet` configuration option", false) + "Local AET that is used for this commands, defaults to `DicomAet` configuration option. " + "Ignored if `DicomModalities` already sets `LocalAet` for this modality.", false) .SetAnswerField("ID", RestApiCallDocumentation::Type_JsonObject, "Identifier of the query, to be used with `/queries/{id}`") .SetAnswerField("Path", RestApiCallDocumentation::Type_JsonObject, @@ -1397,7 +1398,8 @@ .SetRequestField(KEY_RESOURCES, RestApiCallDocumentation::Type_JsonListOfStrings, "List of the Orthanc identifiers of all the DICOM resources to be sent", true) .SetRequestField(KEY_LOCAL_AET, RestApiCallDocumentation::Type_String, - "Local AET that is used for this commands, defaults to `DicomAet` configuration option", false) + "Local AET that is used for this commands, defaults to `DicomAet` configuration option. " + "Ignored if `DicomModalities` already sets `LocalAet` for this modality.", false) .SetRequestField(KEY_MOVE_ORIGINATOR_AET, RestApiCallDocumentation::Type_String, "Move originator AET that is used for this commands, in order to fake a C-MOVE SCU", false) .SetRequestField(KEY_MOVE_ORIGINATOR_ID, RestApiCallDocumentation::Type_Number, @@ -1506,7 +1508,8 @@ .SetRequestField(KEY_LEVEL, RestApiCallDocumentation::Type_String, "Level of the query (`Patient`, `Study`, `Series` or `Instance`)", true) .SetRequestField(KEY_LOCAL_AET, RestApiCallDocumentation::Type_String, - "Local AET that is used for this commands, defaults to `DicomAet` configuration option", false) + "Local AET that is used for this commands, defaults to `DicomAet` configuration option. " + "Ignored if `DicomModalities` already sets `LocalAet` for this modality.", false) .SetRequestField(KEY_TARGET_AET, RestApiCallDocumentation::Type_String, "Target AET that will be used by the remote DICOM modality as a target for its C-STORE SCU " "commands, defaults to `DicomAet` configuration option in order to do a simple query/retrieve", false)