Mercurial > hg > orthanc
changeset 6493:ecdc569d04a2
OrthancPluginDicomConnection + new versions of SCP callbacks
line wrap: on
line diff
--- a/NEWS Mon Nov 24 19:56:36 2025 +0100 +++ b/NEWS Tue Nov 25 15:48:26 2025 +0100 @@ -27,6 +27,17 @@ - OrthancPluginReserveQueueValue() is a replacement for OrthancPluginDequeueValue(). This flavour does not directly remove the value from the queue but reserves it for a certain duration. - OrthancPluginAcknowledgeQueueValue() removes a reserved value from a queue. +* New primitives for handling SCP callbacks that accepts an OrthancPluginDicomConnection instead + of calledAet, remoteAet, ... : + - OrthancPluginRegisterFindCallback2 + - OrthancPluginRegisterMoveCallback2 + - OrthancPluginRegisterWorklistCallback2 + - OrthancPluginRegisterStorageCommitmentScpCallback2 +* New accessors for OrthancPluginDicomConnection: + - OrthancPluginGetConnectionRemoteAet + - OrthancPluginGetConnectionRemoteIp + - OrthancPluginGetConnectionCalledAet + Maintenance -----------
--- a/OrthancFramework/Resources/CMake/OrthancFrameworkConfiguration.cmake Mon Nov 24 19:56:36 2025 +0100 +++ b/OrthancFramework/Resources/CMake/OrthancFrameworkConfiguration.cmake Tue Nov 25 15:48:26 2025 +0100 @@ -571,6 +571,7 @@ list(APPEND ORTHANC_DICOM_SOURCES_INTERNAL ${CMAKE_CURRENT_LIST_DIR}/../../Sources/DicomNetworking/DicomAssociation.cpp ${CMAKE_CURRENT_LIST_DIR}/../../Sources/DicomNetworking/DicomAssociationParameters.cpp + ${CMAKE_CURRENT_LIST_DIR}/../../Sources/DicomNetworking/DicomConnectionInfo.cpp ${CMAKE_CURRENT_LIST_DIR}/../../Sources/DicomNetworking/DicomControlUserConnection.cpp ${CMAKE_CURRENT_LIST_DIR}/../../Sources/DicomNetworking/DicomServer.cpp ${CMAKE_CURRENT_LIST_DIR}/../../Sources/DicomNetworking/DicomStoreUserConnection.cpp
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/OrthancFramework/Sources/DicomNetworking/DicomConnectionInfo.cpp Tue Nov 25 15:48:26 2025 +0100 @@ -0,0 +1,68 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2023 Osimis S.A., Belgium + * Copyright (C) 2024-2025 Orthanc Team SRL, Belgium + * Copyright (C) 2021-2025 Sebastien Jodogne, ICTEAM UCLouvain, Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program. If not, see + * <http://www.gnu.org/licenses/>. + **/ + + +#include "../PrecompiledHeaders.h" +#include "DicomConnectionInfo.h" + +#include "../Compatibility.h" +#include "../Logging.h" +#include "../OrthancException.h" +#include "../SerializationToolbox.h" +#include "../SystemToolbox.h" + +namespace Orthanc +{ + static const char* const CALLED_AET = "CalledAet"; + static const char* const REMOTE_AET = "RemoteAet"; + static const char* const REMOTE_IP = "RemoteIp"; + static const char* const MANUFACTURER = "Manufacturer"; + + void DicomConnectionInfo::Serialize(Json::Value& target) const + { + if (target.type() != Json::objectValue) + { + throw OrthancException(ErrorCode_InternalError); + } + else + { + target[CALLED_AET] = calledAet_; + target[REMOTE_AET] = remoteAet_; + target[REMOTE_IP] = remoteIp_; + target[MANUFACTURER] = EnumerationToString(manufacturer_); + } + } + + + DicomConnectionInfo::DicomConnectionInfo(const Json::Value& serialized) : + manufacturer_(ModalityManufacturer_Generic) + { + calledAet_ = SerializationToolbox::ReadString(serialized, CALLED_AET); + remoteAet_ = SerializationToolbox::ReadString(serialized, REMOTE_AET); + remoteIp_ = SerializationToolbox::ReadString(serialized, REMOTE_IP); + + std::string manufacturer = SerializationToolbox::ReadString(serialized, MANUFACTURER); + manufacturer_ = StringToModalityManufacturer(manufacturer); + } + +} \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/OrthancFramework/Sources/DicomNetworking/DicomConnectionInfo.h Tue Nov 25 15:48:26 2025 +0100 @@ -0,0 +1,90 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2023 Osimis S.A., Belgium + * Copyright (C) 2024-2025 Orthanc Team SRL, Belgium + * Copyright (C) 2021-2025 Sebastien Jodogne, ICTEAM UCLouvain, Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program. If not, see + * <http://www.gnu.org/licenses/>. + **/ + + +#pragma once + +#include "../Compatibility.h" +#include <json/value.h> +#include "../Enumerations.h" + +namespace Orthanc +{ + // A set of informations about a connection. + // This is mainly a way to group arguments and provide getters for plugins. + class ORTHANC_PUBLIC DicomConnectionInfo + { + private: + std::string remoteIp_; + std::string remoteAet_; + std::string calledAet_; + ModalityManufacturer manufacturer_; + + public: + + DicomConnectionInfo(const std::string& remoteIp, + const std::string& remoteAet, + const std::string& calledAet, + const ModalityManufacturer& manufacturer) : + remoteIp_(remoteIp), + remoteAet_(remoteAet), + calledAet_(calledAet), + manufacturer_(manufacturer) + { + } + + DicomConnectionInfo(const std::string& remoteIp, + const std::string& remoteAet, + const std::string& calledAet) : + remoteIp_(remoteIp), + remoteAet_(remoteAet), + calledAet_(calledAet), + manufacturer_(ModalityManufacturer_Generic) + { + } + + DicomConnectionInfo(const Json::Value& serialized); + + void Serialize(Json::Value& target) const; + + const std::string& GetCalledAet() const + { + return calledAet_; + } + + const std::string& GetRemoteAet() const + { + return remoteAet_; + } + + const std::string& GetRemoteIp() const + { + return remoteIp_; + } + + ModalityManufacturer GetModalityManufacturer() const + { + return manufacturer_; + } + }; +}
--- a/OrthancFramework/Sources/DicomNetworking/IFindRequestHandler.h Mon Nov 24 19:56:36 2025 +0100 +++ b/OrthancFramework/Sources/DicomNetworking/IFindRequestHandler.h Tue Nov 25 15:48:26 2025 +0100 @@ -30,6 +30,8 @@ namespace Orthanc { + class DicomConnectionInfo; + class IFindRequestHandler : public boost::noncopyable { public: @@ -40,9 +42,6 @@ virtual void Handle(DicomFindAnswers& answers, const DicomMap& input, const std::list<DicomTag>& sequencesToReturn, - const std::string& remoteIp, - const std::string& remoteAet, - const std::string& calledAet, - ModalityManufacturer manufacturer) = 0; + const DicomConnectionInfo& connection) = 0; }; }
--- a/OrthancFramework/Sources/DicomNetworking/IMoveRequestHandler.h Mon Nov 24 19:56:36 2025 +0100 +++ b/OrthancFramework/Sources/DicomNetworking/IMoveRequestHandler.h Tue Nov 25 15:48:26 2025 +0100 @@ -32,6 +32,8 @@ namespace Orthanc { + class DicomConnectionInfo; + class IMoveRequestIterator : public boost::noncopyable { public: @@ -61,9 +63,7 @@ virtual IMoveRequestIterator* Handle(const std::string& targetAet, const DicomMap& input, - const std::string& originatorIp, - const std::string& originatorAet, - const std::string& calledAet, + const DicomConnectionInfo& connection, uint16_t originatorId) = 0; };
--- a/OrthancFramework/Sources/DicomNetworking/IStorageCommitmentRequestHandler.h Mon Nov 24 19:56:36 2025 +0100 +++ b/OrthancFramework/Sources/DicomNetworking/IStorageCommitmentRequestHandler.h Tue Nov 25 15:48:26 2025 +0100 @@ -30,6 +30,8 @@ namespace Orthanc { + class DicomConnectionInfo; + class IStorageCommitmentRequestHandler : public boost::noncopyable { public: @@ -40,9 +42,7 @@ virtual void HandleRequest(const std::string& transactionUid, const std::vector<std::string>& sopClassUids, const std::vector<std::string>& sopInstanceUids, - const std::string& remoteIp, - const std::string& remoteAet, - const std::string& calledAet) = 0; + const DicomConnectionInfo& connection) = 0; virtual void HandleReport(const std::string& transactionUid, const std::vector<std::string>& successSopClassUids, @@ -50,8 +50,6 @@ const std::vector<std::string>& failedSopClassUids, const std::vector<std::string>& failedSopInstanceUids, const std::vector<StorageCommitmentFailureReason>& failureReasons, - const std::string& remoteIp, - const std::string& remoteAet, - const std::string& calledAet) = 0; + const DicomConnectionInfo& connection) = 0; }; }
--- a/OrthancFramework/Sources/DicomNetworking/IWorklistRequestHandler.h Mon Nov 24 19:56:36 2025 +0100 +++ b/OrthancFramework/Sources/DicomNetworking/IWorklistRequestHandler.h Tue Nov 25 15:48:26 2025 +0100 @@ -37,9 +37,6 @@ virtual void Handle(DicomFindAnswers& answers, ParsedDicomFile& query, - const std::string& remoteIp, - const std::string& remoteAet, - const std::string& calledAet, - ModalityManufacturer manufacturer) = 0; + const DicomConnectionInfo& connection) = 0; }; }
--- a/OrthancFramework/Sources/DicomNetworking/Internals/CommandDispatcher.cpp Mon Nov 24 19:56:36 2025 +0100 +++ b/OrthancFramework/Sources/DicomNetworking/Internals/CommandDispatcher.cpp Tue Nov 25 15:48:26 2025 +0100 @@ -83,6 +83,7 @@ #include "../../Logging.h" #include "../../OrthancException.h" #include "../../Toolbox.h" +#include "../DicomConnectionInfo.h" #include "FindScp.h" #include "GetScp.h" #include "MoveScp.h" @@ -1183,8 +1184,9 @@ (server_.GetStorageCommitmentRequestHandlerFactory(). ConstructStorageCommitmentRequestHandler()); - handler->HandleRequest(transactionUid, sopClassUid, sopInstanceUid, - remoteIp_, remoteAet_, calledAet_); + DicomConnectionInfo connection(remoteIp_, remoteAet_, calledAet_); + + handler->HandleRequest(transactionUid, sopClassUid, sopInstanceUid, connection); dimseStatus = 0; // Success } @@ -1335,9 +1337,11 @@ (server_.GetStorageCommitmentRequestHandlerFactory(). ConstructStorageCommitmentRequestHandler()); + DicomConnectionInfo connection(remoteIp_, remoteAet_, calledAet_); + handler->HandleReport(transactionUid, successSopClassUid, successSopInstanceUid, failedSopClassUid, failedSopInstanceUid, failureReasons, - remoteIp_, remoteAet_, calledAet_); + connection); dimseStatus = 0; // Success }
--- a/OrthancFramework/Sources/DicomNetworking/Internals/FindScp.cpp Mon Nov 24 19:56:36 2025 +0100 +++ b/OrthancFramework/Sources/DicomNetworking/Internals/FindScp.cpp Tue Nov 25 15:48:26 2025 +0100 @@ -78,6 +78,7 @@ #include "../../DicomFormat/DicomArray.h" #include "../../DicomParsing/FromDcmtkBridge.h" #include "../../DicomParsing/ToDcmtkBridge.h" +#include "../DicomConnectionInfo.h" #include "../../Logging.h" #include "../../OrthancException.h" @@ -239,10 +240,10 @@ { ParsedDicomFile query(*requestIdentifiers); FixWorklistQuery(query); + DicomConnectionInfo connection(*data.remoteIp_, *data.remoteAet_, + *data.calledAet_, modality.GetManufacturer()); - data.worklistHandler_->Handle(data.answers_, query, - *data.remoteIp_, *data.remoteAet_, - *data.calledAet_, modality.GetManufacturer()); + data.worklistHandler_->Handle(data.answers_, query, connection); ok = true; } else @@ -284,10 +285,10 @@ DicomMap filtered; FixFindQuery(filtered, input); + DicomConnectionInfo connection(*data.remoteIp_, *data.remoteAet_, + *data.calledAet_, modality.GetManufacturer()); - data.findHandler_->Handle(data.answers_, filtered, sequencesToReturn, - *data.remoteIp_, *data.remoteAet_, - *data.calledAet_, modality.GetManufacturer()); + data.findHandler_->Handle(data.answers_, filtered, sequencesToReturn, connection); ok = true; } else
--- a/OrthancFramework/Sources/DicomNetworking/Internals/MoveScp.cpp Mon Nov 24 19:56:36 2025 +0100 +++ b/OrthancFramework/Sources/DicomNetworking/Internals/MoveScp.cpp Tue Nov 25 15:48:26 2025 +0100 @@ -78,6 +78,7 @@ #include "../../DicomParsing/FromDcmtkBridge.h" #include "../../DicomParsing/ToDcmtkBridge.h" +#include "../DicomConnectionInfo.h" #include "../../Logging.h" #include "../../OrthancException.h" @@ -188,9 +189,9 @@ // The line below was the implementation for Orthanc <= 1.3.2 uint16_t messageId = GetMessageId(input); #endif + DicomConnectionInfo connection(*data.remoteIp_, *data.remoteAet_, *data.calledAet_); - data.iterator_.reset(data.handler_->Handle(data.target_, input, *data.remoteIp_, *data.remoteAet_, - *data.calledAet_, messageId)); + data.iterator_.reset(data.handler_->Handle(data.target_, input, connection, messageId)); if (data.iterator_.get() == NULL) {
--- a/OrthancServer/Plugins/Engine/OrthancPlugins.cpp Mon Nov 24 19:56:36 2025 +0100 +++ b/OrthancServer/Plugins/Engine/OrthancPlugins.cpp Tue Nov 25 15:48:26 2025 +0100 @@ -36,6 +36,7 @@ #include "../../../OrthancFramework/Sources/Compression/GzipCompressor.h" #include "../../../OrthancFramework/Sources/Compression/ZlibCompressor.h" #include "../../../OrthancFramework/Sources/DicomFormat/DicomArray.h" +#include "../../../OrthancFramework/Sources/DicomNetworking/DicomConnectionInfo.h" #include "../../../OrthancFramework/Sources/DicomParsing/DicomWebJsonVisitor.h" #include "../../../OrthancFramework/Sources/DicomParsing/FromDcmtkBridge.h" #include "../../../OrthancFramework/Sources/DicomParsing/Internals/DicomImageDecoder.h" @@ -1545,13 +1546,25 @@ class Handler : public IStorageCommitmentFactory::ILookupHandler { private: - _OrthancPluginRegisterStorageCommitmentScpCallback parameters_; + std::unique_ptr<_OrthancPluginRegisterStorageCommitmentScpCallback> parameters_; + std::unique_ptr<_OrthancPluginRegisterStorageCommitmentScpCallback2> parameters2_; void* handler_; public: Handler(const _OrthancPluginRegisterStorageCommitmentScpCallback& parameters, void* handler) : - parameters_(parameters), + parameters_(new _OrthancPluginRegisterStorageCommitmentScpCallback(parameters)), + handler_(handler) + { + if (handler == NULL) + { + throw OrthancException(ErrorCode_NullPointer); + } + } + + Handler(const _OrthancPluginRegisterStorageCommitmentScpCallback2& parameters, + void* handler) : + parameters2_(new _OrthancPluginRegisterStorageCommitmentScpCallback2(parameters)), handler_(handler) { if (handler == NULL) @@ -1563,7 +1576,14 @@ virtual ~Handler() ORTHANC_OVERRIDE { assert(handler_ != NULL); - parameters_.destructor(handler_); + if (parameters2_.get() != NULL) + { + parameters2_->destructor(handler_); + } + else if (parameters_.get() != NULL) + { + parameters_->destructor(handler_); + } handler_ = NULL; } @@ -1572,10 +1592,18 @@ const std::string& sopInstanceUid) ORTHANC_OVERRIDE { assert(handler_ != NULL); - OrthancPluginStorageCommitmentFailureReason reason = - OrthancPluginStorageCommitmentFailureReason_Success; - OrthancPluginErrorCode error = parameters_.lookup( - &reason, handler_, sopClassUid.c_str(), sopInstanceUid.c_str()); + OrthancPluginStorageCommitmentFailureReason reason = OrthancPluginStorageCommitmentFailureReason_Success; + OrthancPluginErrorCode error = OrthancPluginErrorCode_Success; + + if (parameters2_.get() != NULL) + { + error = parameters2_->lookup(&reason, handler_, sopClassUid.c_str(), sopInstanceUid.c_str()); + } + else if (parameters_.get() != NULL) + { + error = parameters_->lookup(&reason, handler_, sopClassUid.c_str(), sopInstanceUid.c_str()); + } + if (error == OrthancPluginErrorCode_Success) { return Plugins::Convert(reason); @@ -1587,11 +1615,17 @@ } }; - _OrthancPluginRegisterStorageCommitmentScpCallback parameters_; + std::unique_ptr<_OrthancPluginRegisterStorageCommitmentScpCallback> parameters_; + std::unique_ptr<_OrthancPluginRegisterStorageCommitmentScpCallback2> parameters2_; public: explicit StorageCommitmentScp(const _OrthancPluginRegisterStorageCommitmentScpCallback& parameters) : - parameters_(parameters) + parameters_(new _OrthancPluginRegisterStorageCommitmentScpCallback(parameters)) + { + } + + explicit StorageCommitmentScp(const _OrthancPluginRegisterStorageCommitmentScpCallback2& parameters) : + parameters2_(new _OrthancPluginRegisterStorageCommitmentScpCallback2(parameters)) { } @@ -1600,8 +1634,7 @@ const std::string& transactionUid, const std::vector<std::string>& sopClassUids, const std::vector<std::string>& sopInstanceUids, - const std::string& remoteAet, - const std::string& calledAet) ORTHANC_OVERRIDE + const DicomConnectionInfo& connection) ORTHANC_OVERRIDE { const size_t n = sopClassUids.size(); @@ -1620,11 +1653,23 @@ b[i] = sopInstanceUids[i].c_str(); } + OrthancPluginErrorCode error = OrthancPluginErrorCode_Success; void* handler = NULL; - OrthancPluginErrorCode error = parameters_.factory( - &handler, jobId.c_str(), transactionUid.c_str(), - a.empty() ? NULL : &a[0], b.empty() ? NULL : &b[0], static_cast<uint32_t>(n), - remoteAet.c_str(), calledAet.c_str()); + + if (parameters2_.get() != NULL) + { + error = parameters2_->factory( + &handler, jobId.c_str(), transactionUid.c_str(), + a.empty() ? NULL : &a[0], b.empty() ? NULL : &b[0], static_cast<uint32_t>(n), + reinterpret_cast<const OrthancPluginDicomConnection*>(&connection)); + } + else if (parameters_.get() != NULL) + { + error = parameters_->factory( + &handler, jobId.c_str(), transactionUid.c_str(), + a.empty() ? NULL : &a[0], b.empty() ? NULL : &b[0], static_cast<uint32_t>(n), + connection.GetRemoteAet().c_str(), connection.GetCalledAet().c_str()); + } if (error != OrthancPluginErrorCode_Success) { @@ -1635,9 +1680,17 @@ // This plugin won't handle this storage commitment request return NULL; } + else if (parameters2_.get() != NULL) + { + return new Handler(*parameters2_, handler); + } + else if (parameters_.get() != NULL) + { + return new Handler(*parameters_, handler); + } else { - return new Handler(parameters_, handler); + throw OrthancException(ErrorCode_InternalError); } } }; @@ -1723,11 +1776,14 @@ OnStoredCallbacks onStoredCallbacks_; OnChangeCallbacks onChangeCallbacks_; OrthancPluginFindCallback findCallback_; + OrthancPluginFindCallback2 findCallback2_; // New in Orthanc 1.12.10 OrthancPluginWorklistCallback worklistCallback_; + OrthancPluginWorklistCallback2 worklistCallback2_; // New in Orthanc 1.12.10 DecodeImageCallbacks decodeImageCallbacks_; TranscoderCallbacks transcoderCallbacks_; JobsUnserializers jobsUnserializers_; - _OrthancPluginMoveCallback moveCallbacks_; + std::unique_ptr<_OrthancPluginMoveCallback> moveCallbacks_; + std::unique_ptr<_OrthancPluginMoveCallback2> moveCallbacks2_; // New in Orthanc 1.12.10 IncomingHttpRequestFilters incomingHttpRequestFilters_; IncomingHttpRequestFilters2 incomingHttpRequestFilters2_; IncomingDicomInstanceFilters incomingDicomInstanceFilters_; @@ -1769,14 +1825,15 @@ contextRefCount_(0), context_(NULL), findCallback_(NULL), + findCallback2_(NULL), worklistCallback_(NULL), + worklistCallback2_(NULL), receivedInstanceCallback_(NULL), httpAuthentication_(NULL), databaseServerIdentifier_(databaseServerIdentifier), maxDatabaseRetries_(0), hasStorageAreaCustomData_(false) { - memset(&moveCallbacks_, 0, sizeof(moveCallbacks_)); } }; @@ -1805,10 +1862,7 @@ virtual void Handle(DicomFindAnswers& answers, ParsedDicomFile& query, - const std::string& remoteIp, - const std::string& remoteAet, - const std::string& calledAet, - ModalityManufacturer manufacturer) ORTHANC_OVERRIDE + const DicomConnectionInfo& connection) ORTHANC_OVERRIDE { { static const char* LUA_CALLBACK = "IncomingWorklistRequestFilter"; @@ -1825,8 +1879,7 @@ Json::Value source, origin; query.DatasetToJson(source, DicomToJsonFormat_Short, DicomToJsonFlags_None, 0); - OrthancFindRequestHandler::FormatOrigin - (origin, remoteIp, remoteAet, calledAet, manufacturer); + OrthancFindRequestHandler::FormatOrigin(origin, connection); LuaFunctionCall call(lua.GetLua(), LUA_CALLBACK); call.PushJson(source); @@ -1846,13 +1899,28 @@ { boost::mutex::scoped_lock lock(that_.pimpl_->worklistCallbackMutex_); - if (that_.pimpl_->worklistCallback_) + if (that_.pimpl_->worklistCallback2_) + { + + OrthancPluginErrorCode error = that_.pimpl_->worklistCallback2_ + (reinterpret_cast<OrthancPluginWorklistAnswers*>(&answers), + reinterpret_cast<const OrthancPluginWorklistQuery*>(this), + reinterpret_cast<const OrthancPluginDicomConnection*>(&connection)); + + if (error != OrthancPluginErrorCode_Success) + { + Reset(); + that_.GetErrorDictionary().LogError(error, true); + throw OrthancException(static_cast<ErrorCode>(error)); + } + } + else if (that_.pimpl_->worklistCallback_) { OrthancPluginErrorCode error = that_.pimpl_->worklistCallback_ (reinterpret_cast<OrthancPluginWorklistAnswers*>(&answers), reinterpret_cast<const OrthancPluginWorklistQuery*>(this), - remoteAet.c_str(), - calledAet.c_str()); + connection.GetRemoteAet().c_str(), + connection.GetCalledAet().c_str()); if (error != OrthancPluginErrorCode_Success) { @@ -1926,10 +1994,7 @@ virtual void Handle(DicomFindAnswers& answers, const DicomMap& input, const std::list<DicomTag>& sequencesToReturn, - const std::string& remoteIp, - const std::string& remoteAet, - const std::string& calledAet, - ModalityManufacturer manufacturer) ORTHANC_OVERRIDE + const DicomConnectionInfo& connection) ORTHANC_OVERRIDE { DicomMap tmp; tmp.Assign(input); @@ -1947,20 +2012,28 @@ boost::mutex::scoped_lock lock(that_.pimpl_->findCallbackMutex_); currentQuery_.reset(new DicomArray(tmp)); - if (that_.pimpl_->findCallback_) - { - OrthancPluginErrorCode error = that_.pimpl_->findCallback_ + OrthancPluginErrorCode error = OrthancPluginErrorCode_Success; + + if (that_.pimpl_->findCallback2_) + { + error = that_.pimpl_->findCallback2_ (reinterpret_cast<OrthancPluginFindAnswers*>(&answers), reinterpret_cast<const OrthancPluginFindQuery*>(this), - remoteAet.c_str(), - calledAet.c_str()); - - if (error != OrthancPluginErrorCode_Success) - { - Reset(); - that_.GetErrorDictionary().LogError(error, true); - throw OrthancException(static_cast<ErrorCode>(error)); - } + reinterpret_cast<const OrthancPluginDicomConnection*>(&connection)); + } else if (that_.pimpl_->findCallback_) + { + error = that_.pimpl_->findCallback_ + (reinterpret_cast<OrthancPluginFindAnswers*>(&answers), + reinterpret_cast<const OrthancPluginFindQuery*>(this), + connection.GetRemoteAet().c_str(), + connection.GetCalledAet().c_str()); + } + + if (error != OrthancPluginErrorCode_Success) + { + Reset(); + that_.GetErrorDictionary().LogError(error, true); + throw OrthancException(static_cast<ErrorCode>(error)); } Reset(); @@ -2078,7 +2151,8 @@ }; - _OrthancPluginMoveCallback params_; + std::unique_ptr<_OrthancPluginMoveCallback> params_; + std::unique_ptr<_OrthancPluginMoveCallback2> params2_; static std::string ReadTag(const DicomMap& input, @@ -2103,22 +2177,36 @@ explicit MoveHandler(OrthancPlugins& that) { boost::recursive_mutex::scoped_lock lock(that.pimpl_->invokeServiceMutex_); - params_ = that.pimpl_->moveCallbacks_; - - if (params_.callback == NULL || - params_.getMoveSize == NULL || - params_.applyMove == NULL || - params_.freeMove == NULL) - { - throw OrthancException(ErrorCode_Plugin); + + if (that.pimpl_->moveCallbacks2_.get() != NULL) + { + params2_.reset(new _OrthancPluginMoveCallback2(*that.pimpl_->moveCallbacks2_)); + + if (params2_->callback == NULL || + params2_->getMoveSize == NULL || + params2_->applyMove == NULL || + params2_->freeMove == NULL) + { + throw OrthancException(ErrorCode_Plugin); + } + } + else if (that.pimpl_->moveCallbacks_.get() != NULL) + { + params_.reset(new _OrthancPluginMoveCallback(*that.pimpl_->moveCallbacks_)); + + if (params_->callback == NULL || + params_->getMoveSize == NULL || + params_->applyMove == NULL || + params_->freeMove == NULL) + { + throw OrthancException(ErrorCode_Plugin); + } } } virtual IMoveRequestIterator* Handle(const std::string& targetAet, const DicomMap& input, - const std::string& originatorIp, - const std::string& originatorAet, - const std::string& calledAet, + const DicomConnectionInfo& connection, uint16_t originatorId) ORTHANC_OVERRIDE { std::string levelString = ReadTag(input, DICOM_TAG_QUERY_RETRIEVE_LEVEL); @@ -2135,16 +2223,32 @@ level = Plugins::Convert(StringToResourceType(levelString.c_str())); } - void* driver = params_.callback(level, - patientId.empty() ? NULL : patientId.c_str(), - accessionNumber.empty() ? NULL : accessionNumber.c_str(), - studyInstanceUid.empty() ? NULL : studyInstanceUid.c_str(), - seriesInstanceUid.empty() ? NULL : seriesInstanceUid.c_str(), - sopInstanceUid.empty() ? NULL : sopInstanceUid.c_str(), - originatorAet.c_str(), - calledAet.c_str(), - targetAet.c_str(), - originatorId); + void* driver = NULL; + if (params2_.get() != NULL) + { + driver = params2_->callback(level, + patientId.empty() ? NULL : patientId.c_str(), + accessionNumber.empty() ? NULL : accessionNumber.c_str(), + studyInstanceUid.empty() ? NULL : studyInstanceUid.c_str(), + seriesInstanceUid.empty() ? NULL : seriesInstanceUid.c_str(), + sopInstanceUid.empty() ? NULL : sopInstanceUid.c_str(), + reinterpret_cast<const OrthancPluginDicomConnection*>(&connection), + targetAet.c_str(), + originatorId); + } + else if (params_.get() != NULL) + { + driver = params_->callback(level, + patientId.empty() ? NULL : patientId.c_str(), + accessionNumber.empty() ? NULL : accessionNumber.c_str(), + studyInstanceUid.empty() ? NULL : studyInstanceUid.c_str(), + seriesInstanceUid.empty() ? NULL : seriesInstanceUid.c_str(), + sopInstanceUid.empty() ? NULL : sopInstanceUid.c_str(), + connection.GetRemoteAet().c_str(), + connection.GetCalledAet().c_str(), + targetAet.c_str(), + originatorId); + } if (driver == NULL) { @@ -2152,9 +2256,18 @@ "Plugin cannot create a driver for an incoming C-MOVE request"); } - unsigned int size = params_.getMoveSize(driver); - - return new Driver(driver, size, params_.applyMove, params_.freeMove); + if (params2_.get() != NULL) + { + unsigned int size = params2_->getMoveSize(driver); + + return new Driver(driver, size, params2_->applyMove, params2_->freeMove); + } + else + { + unsigned int size = params_->getMoveSize(driver); + + return new Driver(driver, size, params_->applyMove, params_->freeMove); + } } }; @@ -2931,7 +3044,7 @@ boost::mutex::scoped_lock lock(pimpl_->worklistCallbackMutex_); - if (pimpl_->worklistCallback_ != NULL) + if (pimpl_->worklistCallback_ != NULL || pimpl_->worklistCallback2_ != NULL) { throw OrthancException(ErrorCode_Plugin, "Can only register one plugin to handle modality worklists"); @@ -2944,6 +3057,26 @@ } + void OrthancPlugins::RegisterWorklistCallback2(const void* parameters) + { + const _OrthancPluginWorklistCallback2& p = + *reinterpret_cast<const _OrthancPluginWorklistCallback2*>(parameters); + + boost::mutex::scoped_lock lock(pimpl_->worklistCallbackMutex_); + + if (pimpl_->worklistCallback_ != NULL || pimpl_->worklistCallback2_ != NULL) + { + throw OrthancException(ErrorCode_Plugin, + "Can only register one plugin to handle modality worklists"); + } + else + { + CLOG(INFO, PLUGINS) << "Plugin has registered a callback to handle modality worklists (v2)"; + pimpl_->worklistCallback2_ = p.callback; + } + } + + void OrthancPlugins::RegisterFindCallback(const void* parameters) { const _OrthancPluginFindCallback& p = @@ -2951,7 +3084,7 @@ boost::mutex::scoped_lock lock(pimpl_->findCallbackMutex_); - if (pimpl_->findCallback_ != NULL) + if (pimpl_->findCallback2_ != NULL || pimpl_->findCallback_ != NULL) { throw OrthancException(ErrorCode_Plugin, "Can only register one plugin to handle C-FIND requests"); @@ -2964,6 +3097,26 @@ } + void OrthancPlugins::RegisterFindCallback2(const void* parameters) + { + const _OrthancPluginFindCallback2& p = + *reinterpret_cast<const _OrthancPluginFindCallback2*>(parameters); + + boost::mutex::scoped_lock lock(pimpl_->findCallbackMutex_); + + if (pimpl_->findCallback2_ != NULL || pimpl_->findCallback_ != NULL) + { + throw OrthancException(ErrorCode_Plugin, + "Can only register one plugin to handle C-FIND requests"); + } + else + { + CLOG(INFO, PLUGINS) << "Plugin has registered a callback to handle C-FIND requests (v2)"; + pimpl_->findCallback2_ = p.callback; + } + } + + void OrthancPlugins::RegisterMoveCallback(const void* parameters) { // invokeServiceMutex_ is assumed to be locked @@ -2971,7 +3124,7 @@ const _OrthancPluginMoveCallback& p = *reinterpret_cast<const _OrthancPluginMoveCallback*>(parameters); - if (pimpl_->moveCallbacks_.callback != NULL) + if (pimpl_->moveCallbacks_.get() != NULL || pimpl_->moveCallbacks2_.get() != NULL) { throw OrthancException(ErrorCode_Plugin, "Can only register one plugin to handle C-MOVE requests"); @@ -2979,7 +3132,27 @@ else { CLOG(INFO, PLUGINS) << "Plugin has registered a callback to handle C-MOVE requests"; - pimpl_->moveCallbacks_ = p; + pimpl_->moveCallbacks_.reset(new _OrthancPluginMoveCallback(p)); + } + } + + + void OrthancPlugins::RegisterMoveCallback2(const void* parameters) + { + // invokeServiceMutex_ is assumed to be locked + + const _OrthancPluginMoveCallback2& p = + *reinterpret_cast<const _OrthancPluginMoveCallback2*>(parameters); + + if (pimpl_->moveCallbacks_.get() != NULL || pimpl_->moveCallbacks2_.get() != NULL) + { + throw OrthancException(ErrorCode_Plugin, + "Can only register one plugin to handle C-MOVE requests"); + } + else + { + CLOG(INFO, PLUGINS) << "Plugin has registered a callback to handle C-MOVE requests (v2)"; + pimpl_->moveCallbacks2_.reset(new _OrthancPluginMoveCallback2(p)); } } @@ -3107,6 +3280,18 @@ } + void OrthancPlugins::RegisterStorageCommitmentScpCallback2(const void* parameters) + { + const _OrthancPluginRegisterStorageCommitmentScpCallback2& p = + *reinterpret_cast<const _OrthancPluginRegisterStorageCommitmentScpCallback2*>(parameters); + + boost::mutex::scoped_lock lock(pimpl_->storageCommitmentScpMutex_); + CLOG(INFO, PLUGINS) << "Plugin has registered a storage commitment callback (v2)"; + + pimpl_->storageCommitmentScpCallbacks_.push_back(new PImpl::StorageCommitmentScp(p)); + } + + void OrthancPlugins::RegisterHttpAuthentication(const void* parameters) { const _OrthancPluginHttpAuthentication& p = @@ -3856,6 +4041,38 @@ } + void OrthancPlugins::AccessDicomConnection(_OrthancPluginService service, + const void* parameters) + { + const _OrthancPluginAccessDicomConnection& p = + *reinterpret_cast<const _OrthancPluginAccessDicomConnection*>(parameters); + + if (p.connection == NULL) + { + throw OrthancException(ErrorCode_NullPointer); + } + + const DicomConnectionInfo& connection = *(reinterpret_cast<const DicomConnectionInfo*>(p.connection)); + + switch (service) + { + case _OrthancPluginService_GetConnectionCalledAet: + *p.resultString = connection.GetCalledAet().c_str(); + return; + + case _OrthancPluginService_GetConnectionRemoteAet: + *p.resultString = connection.GetRemoteAet().c_str(); + return; + + case _OrthancPluginService_GetConnectionRemoteIp: + *p.resultString = connection.GetRemoteIp().c_str(); + return; + + default: + throw OrthancException(ErrorCode_InternalError); + + } + } void OrthancPlugins::UncompressImage(const void* parameters) { const _OrthancPluginUncompressImage& p = *reinterpret_cast<const _OrthancPluginUncompressImage*>(parameters); @@ -5174,7 +5391,13 @@ AccessDicomInstance2(service, parameters); return true; - case _OrthancPluginService_SetGlobalProperty: + case _OrthancPluginService_GetConnectionRemoteAet: + case _OrthancPluginService_GetConnectionRemoteIp: + case _OrthancPluginService_GetConnectionCalledAet: + AccessDicomConnection(service, parameters); + return true; + + case _OrthancPluginService_SetGlobalProperty: { const _OrthancPluginGlobalProperty& p = *reinterpret_cast<const _OrthancPluginGlobalProperty*>(parameters); @@ -6028,14 +6251,26 @@ RegisterWorklistCallback(parameters); return true; + case _OrthancPluginService_RegisterWorklistCallback2: + RegisterWorklistCallback2(parameters); + return true; + case _OrthancPluginService_RegisterFindCallback: RegisterFindCallback(parameters); return true; + case _OrthancPluginService_RegisterFindCallback2: + RegisterFindCallback2(parameters); + return true; + case _OrthancPluginService_RegisterMoveCallback: RegisterMoveCallback(parameters); return true; + case _OrthancPluginService_RegisterMoveCallback2: + RegisterMoveCallback2(parameters); + return true; + case _OrthancPluginService_RegisterDecodeImageCallback: RegisterDecodeImageCallback(parameters); return true; @@ -6068,6 +6303,10 @@ RegisterStorageCommitmentScpCallback(parameters); return true; + case _OrthancPluginService_RegisterStorageCommitmentScpCallback2: + RegisterStorageCommitmentScpCallback2(parameters); + return true; + case _OrthancPluginService_RegisterHttpAuthentication: RegisterHttpAuthentication(parameters); return true; @@ -6522,7 +6761,7 @@ bool OrthancPlugins::HasWorklistHandler() { boost::mutex::scoped_lock lock(pimpl_->worklistCallbackMutex_); - return pimpl_->worklistCallback_ != NULL; + return pimpl_->worklistCallback_ != NULL || pimpl_->worklistCallback2_ != NULL; } @@ -6542,7 +6781,7 @@ bool OrthancPlugins::HasFindHandler() { boost::mutex::scoped_lock lock(pimpl_->findCallbackMutex_); - return pimpl_->findCallback_ != NULL; + return pimpl_->findCallback_ != NULL || pimpl_->findCallback2_ != NULL; } @@ -6562,7 +6801,7 @@ bool OrthancPlugins::HasMoveHandler() { boost::recursive_mutex::scoped_lock lock(pimpl_->invokeServiceMutex_); - return pimpl_->moveCallbacks_.callback != NULL; + return pimpl_->moveCallbacks_.get() != NULL || pimpl_->moveCallbacks2_.get() != NULL; } @@ -6866,8 +7105,7 @@ const std::string& transactionUid, const std::vector<std::string>& sopClassUids, const std::vector<std::string>& sopInstanceUids, - const std::string& remoteAet, - const std::string& calledAet) + const DicomConnectionInfo& connection) { boost::mutex::scoped_lock lock(pimpl_->storageCommitmentScpMutex_); @@ -6877,7 +7115,7 @@ { assert(*it != NULL); IStorageCommitmentFactory::ILookupHandler* handler = (*it)->CreateStorageCommitment - (jobId, transactionUid, sopClassUids, sopInstanceUids, remoteAet, calledAet); + (jobId, transactionUid, sopClassUids, sopInstanceUids, connection); if (handler != NULL) {
--- a/OrthancServer/Plugins/Engine/OrthancPlugins.h Mon Nov 24 19:56:36 2025 +0100 +++ b/OrthancServer/Plugins/Engine/OrthancPlugins.h Tue Nov 25 15:48:26 2025 +0100 @@ -115,10 +115,16 @@ void RegisterWorklistCallback(const void* parameters); + void RegisterWorklistCallback2(const void* parameters); + void RegisterFindCallback(const void* parameters); + void RegisterFindCallback2(const void* parameters); + void RegisterMoveCallback(const void* parameters); + void RegisterMoveCallback2(const void* parameters); + void RegisterDecodeImageCallback(const void* parameters); void RegisterTranscoderCallback(const void* parameters); @@ -139,6 +145,8 @@ void RegisterStorageCommitmentScpCallback(const void* parameters); + void RegisterStorageCommitmentScpCallback2(const void* parameters); + void RegisterHttpAuthentication(const void* parameters); void RegisterAuditLogHandler(const void* parameters); @@ -173,7 +181,10 @@ void AccessDicomInstance2(_OrthancPluginService service, const void* parameters); - + + void AccessDicomConnection(_OrthancPluginService service, + const void* parameters); + void SendHttpStatusCode(const void* parameters); void SendHttpStatus(const void* parameters); @@ -418,8 +429,7 @@ const std::string& transactionUid, const std::vector<std::string>& sopClassUids, const std::vector<std::string>& sopInstanceUids, - const std::string& remoteAet, - const std::string& calledAet) ORTHANC_OVERRIDE; + const DicomConnectionInfo& connection) ORTHANC_OVERRIDE; // New in Orthanc 1.8.1 (cf. "OrthancPluginGenerateRestApiAuthorizationToken()") bool IsValidAuthorizationToken(const std::string& token) const;
--- a/OrthancServer/Plugins/Include/orthanc/OrthancCPlugin.h Mon Nov 24 19:56:36 2025 +0100 +++ b/OrthancServer/Plugins/Include/orthanc/OrthancCPlugin.h Tue Nov 25 15:48:26 2025 +0100 @@ -19,14 +19,18 @@ * - Possibly register a custom storage area using ::OrthancPluginRegisterStorageArea3(). * - Possibly register a custom database back-end area using OrthancPluginRegisterDatabaseBackendV4(). * - Possibly register a handler for C-Find SCP using OrthancPluginRegisterFindCallback(). + * - Possibly register a handler for C-Find SCP using OrthancPluginRegisterFindCallback2(). * - Possibly register a handler for C-Find SCP against DICOM worklists using OrthancPluginRegisterWorklistCallback(). + * - Possibly register a handler for C-Find SCP against DICOM worklists using OrthancPluginRegisterWorklistCallback2(). * - Possibly register a handler for C-Move SCP using OrthancPluginRegisterMoveCallback(). + * - Possibly register a handler for C-Move SCP using OrthancPluginRegisterMoveCallback2(). * - Possibly register a custom decoder for DICOM images using OrthancPluginRegisterDecodeImageCallback(). * - Possibly register a callback to filter incoming HTTP requests using OrthancPluginRegisterIncomingHttpRequestFilter2(). * - Possibly register a callback to unserialize jobs using OrthancPluginRegisterJobsUnserializer(). * - Possibly register a callback to refresh its metrics using OrthancPluginRegisterRefreshMetricsCallback(). * - Possibly register a callback to answer chunked HTTP transfers using ::OrthancPluginRegisterChunkedRestCallback(). * - Possibly register a callback for Storage Commitment SCP using ::OrthancPluginRegisterStorageCommitmentScpCallback(). + * - Possibly register a callback for Storage Commitment SCP using ::OrthancPluginRegisterStorageCommitmentScpCallback2(). * - Possibly register a callback to keep/discard/modify incoming DICOM instances using OrthancPluginRegisterReceivedInstanceCallback(). * - Possibly register a custom transcoder for DICOM images using OrthancPluginRegisterTranscoderCallback(). * - Possibly register a callback to discard instances received through DICOM C-STORE using OrthancPluginRegisterIncomingCStoreInstanceFilter(). @@ -72,6 +76,9 @@ * * @defgroup DicomInstance DicomInstance * @brief Functions to access DICOM images that are managed by the Orthanc core. + * + * @defgroup DicomConnection DicomConnection + * @brief Functions to access DICOM connection parameters that are managed by the Orthanc core. **/ @@ -546,6 +553,10 @@ _OrthancPluginService_RegisterStorageArea3 = 1020, /* New in Orthanc 1.12.8 */ _OrthancPluginService_RegisterHttpAuthentication = 1021, /* New in Orthanc 1.12.9 */ _OrthancPluginService_RegisterAuditLogHandler = 1022, /* New in Orthanc 1.12.9 */ + _OrthancPluginService_RegisterFindCallback2 = 1023, /* New in Orthanc 1.12.10 */ + _OrthancPluginService_RegisterMoveCallback2 = 1024, /* New in Orthanc 1.12.10 */ + _OrthancPluginService_RegisterWorklistCallback2 = 1025, /* New in Orthanc 1.12.10 */ + _OrthancPluginService_RegisterStorageCommitmentScpCallback2 = 1026, /* New in Orthanc 1.12.0 */ /* Sending answers to REST calls */ _OrthancPluginService_AnswerBuffer = 2000, @@ -664,6 +675,11 @@ _OrthancPluginService_SubmitJob = 9002, _OrthancPluginService_RegisterJobsUnserializer = 9003, _OrthancPluginService_CreateJob2 = 9004, /* New in SDK 1.11.3 */ + + /* Access to DICOM connection */ + _OrthancPluginService_GetConnectionRemoteAet = 10000, /* New in SDK 1.12.10 */ + _OrthancPluginService_GetConnectionRemoteIp = 10001, /* New in SDK 1.12.10 */ + _OrthancPluginService_GetConnectionCalledAet = 10002, /* New in SDK 1.12.10 */ _OrthancPluginService_INTERNAL = 0x7fffffff } _OrthancPluginService; @@ -1384,6 +1400,23 @@ _OrthancPluginDicomWebNode_t OrthancPluginDicomWebNode; + /** + * @brief Opaque structure that represents DICOM connection + * parameters. + * @ingroup DicomConnection + **/ + ORTHANC_PLUGIN_SINCE_SDK("1.12.10") + typedef struct + _OrthancPluginDicomConnection_t OrthancPluginDicomConnection; + + + ORTHANC_PLUGIN_SINCE_SDK("1.12.10") + typedef struct + { + const OrthancPluginDicomConnection* connection; + const char** resultString; + } _OrthancPluginAccessDicomConnection; + /** * @brief Signature of a callback function that answers to a REST request. @@ -1652,6 +1685,24 @@ const char* calledAet); + /** + * @brief Callback to handle the C-Find SCP requests for worklists (v2). + * + * Signature of a callback function that is triggered when Orthanc + * receives a C-Find SCP request against modality worklists. + * + * @param answers The target structure where answers must be stored. + * @param query The worklist query. + * @param connection The DICOM connection from which the request originates. + * @return 0 if success, other value if error. + * @ingroup DicomCallbacks + **/ + ORTHANC_PLUGIN_SINCE_SDK("1.12.10") + typedef OrthancPluginErrorCode (*OrthancPluginWorklistCallback2) ( + OrthancPluginWorklistAnswers* answers, + const OrthancPluginWorklistQuery* query, + const OrthancPluginDicomConnection* connection); + /** * @brief Callback to filter incoming HTTP requests received by Orthanc. @@ -1749,6 +1800,25 @@ const char* calledAet); + /** + * @brief Callback to handle incoming C-Find SCP requests (v2). + * + * Signature of a callback function that is triggered whenever + * Orthanc receives a C-Find SCP request not concerning modality + * worklists. + * + * @param answers The target structure where answers must be stored. + * @param query The worklist query. + * @param connection The DICOM connection from which the request originates. + * @return 0 if success, other value if error. + * @ingroup DicomCallbacks + **/ + ORTHANC_PLUGIN_SINCE_SDK("1.12.10") + typedef OrthancPluginErrorCode (*OrthancPluginFindCallback2) ( + OrthancPluginFindAnswers* answers, + const OrthancPluginFindQuery* query, + const OrthancPluginDicomConnection* connection); + /** * @brief Callback to handle incoming C-Move SCP requests. @@ -1796,7 +1866,52 @@ const char* sourceAet, const char* targetAet, uint16_t originatorId); + + /** + * @brief Callback to handle incoming C-Move SCP requests (v2). + * + * Signature of a callback function that is triggered whenever + * Orthanc receives a C-Move SCP request. The callback receives the + * type of the resource of interest (study, series, instance...) + * together with the DICOM tags containing its identifiers. In turn, + * the plugin must create a driver object that will be responsible + * for driving the successive move suboperations. + * + * @param resourceType The type of the resource of interest. Note + * that this might be set to ResourceType_None if the + * QueryRetrieveLevel (0008,0052) tag was not provided by the + * issuer (i.e. the originator modality). + * @param patientId Content of the PatientID (0x0010, 0x0020) tag of the resource of interest. Might be NULL. + * @param accessionNumber Content of the AccessionNumber (0x0008, 0x0050) tag. Might be NULL. + * @param studyInstanceUid Content of the StudyInstanceUID (0x0020, 0x000d) tag. Might be NULL. + * @param seriesInstanceUid Content of the SeriesInstanceUID (0x0020, 0x000e) tag. Might be NULL. + * @param sopInstanceUid Content of the SOPInstanceUID (0x0008, 0x0018) tag. Might be NULL. + * @param connection The DICOM connection from which the request originates. + * @param targetAet The Application Entity Title (AET) of the + * modality that should receive the DICOM files. + * @param originatorId The Message ID issued by the originator modality, + * as found in tag (0000,0110) of the DICOM query emitted by the issuer. + * + * @return The NULL value if the plugin cannot deal with this query, + * or a pointer to the driver object that is responsible for + * handling the successive move suboperations. + * + * @note If targetAet equals sourceAet, this is actually a query/retrieve operation. + * @ingroup DicomCallbacks + **/ + ORTHANC_PLUGIN_SINCE_SDK("1.12.10") + typedef void* (*OrthancPluginMoveCallback2) ( + OrthancPluginResourceType resourceType, + const char* patientId, + const char* accessionNumber, + const char* studyInstanceUid, + const char* seriesInstanceUid, + const char* sopInstanceUid, + const OrthancPluginDicomConnection* connection, + const char* targetAet, + uint16_t originatorId); + /** * @brief Callback to read the size of a C-Move driver. @@ -1804,7 +1919,7 @@ * Signature of a callback function that returns the number of * C-Move suboperations that are to be achieved by the given C-Move * driver. This driver is the return value of a previous call to the - * OrthancPluginMoveCallback() callback. + * OrthancPluginMoveCallback() or OrthancPluginMoveCallback2() callback. * * @param moveDriver The C-Move driver of interest. * @return The number of suboperations. @@ -1819,7 +1934,7 @@ * Signature of a callback function that applies the next C-Move * suboperation that os to be achieved by the given C-Move * driver. This driver is the return value of a previous call to the - * OrthancPluginMoveCallback() callback. + * OrthancPluginMoveCallback() or OrthancPluginMoveCallback2() callback. * * @param moveDriver The C-Move driver of interest. * @return 0 if success, or the error code if failure. @@ -1834,7 +1949,7 @@ * Signature of a callback function that releases the resources * allocated by the given C-Move driver. This driver is the return * value of a previous call to the OrthancPluginMoveCallback() - * callback. + * or OrthancPluginMoveCallback2() callback. * * @param moveDriver The C-Move driver of interest. * @ingroup DicomCallbacks @@ -5599,6 +5714,34 @@ } + ORTHANC_PLUGIN_SINCE_SDK("1.12.10") + typedef struct + { + OrthancPluginWorklistCallback2 callback; + } _OrthancPluginWorklistCallback2; + + /** + * @brief Register a callback to handle modality worklists requests (v2). + * + * This function registers a callback to handle C-Find SCP requests + * on modality worklists. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param callback The callback. + * @return 0 if success, other value if error. + * @ingroup DicomCallbacks + **/ + ORTHANC_PLUGIN_SINCE_SDK("1.12.10") + ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginRegisterWorklistCallback2( + OrthancPluginContext* context, + OrthancPluginWorklistCallback2 callback) + { + _OrthancPluginWorklistCallback2 params; + params.callback = callback; + + return context->InvokeService(context, _OrthancPluginService_RegisterWorklistCallback2, ¶ms); + } + typedef struct { @@ -8234,7 +8377,44 @@ const char* remoteAet, const char* calledAet); - + + /** + * @brief Callback executed by the storage commitment SCP (v2). + * + * Signature of a factory function that creates an object to handle + * one incoming storage commitment request. + * + * @remark The factory receives the list of the SOP class/instance + * UIDs of interest to the remote storage commitment SCU. This gives + * the factory the possibility to start some prefetch process + * upfront in the background, before the handler object is actually + * queried about the status of these DICOM instances. + * + * @param handler Output variable where the factory puts the handler object it created. + * @param jobId ID of the Orthanc job that is responsible for handling + * the storage commitment request. This job will successively look for the + * status of all the individual queried DICOM instances. + * @param transactionUid UID of the storage commitment transaction + * provided by the storage commitment SCU. It contains the value of the + * (0008,1195) DICOM tag. + * @param sopClassUids Array of the SOP class UIDs (0008,0016) that are queried by the SCU. + * @param sopInstanceUids Array of the SOP instance UIDs (0008,0018) that are queried by the SCU. + * @param countInstances Number of DICOM instances that are queried. This is the size + * of the `sopClassUids` and `sopInstanceUids` arrays. + * @param connection The DICOM connection from which the request originates. + * @return 0 if success, other value if error. + * @ingroup DicomCallbacks + **/ + ORTHANC_PLUGIN_SINCE_SDK("1.12.10") + typedef OrthancPluginErrorCode (*OrthancPluginStorageCommitmentFactory2) ( + void** handler /* out */, + const char* jobId, + const char* transactionUid, + const char* const* sopClassUids, + const char* const* sopInstanceUids, + uint32_t countInstances, + const OrthancPluginDicomConnection* connection); + /** * @brief Callback to free one storage commitment SCP handler. * @@ -8281,6 +8461,16 @@ OrthancPluginStorageCommitmentLookup lookup; } _OrthancPluginRegisterStorageCommitmentScpCallback; + + ORTHANC_PLUGIN_SINCE_SDK("1.12.10") + typedef struct + { + OrthancPluginStorageCommitmentFactory2 factory; + OrthancPluginStorageCommitmentDestructor destructor; + OrthancPluginStorageCommitmentLookup lookup; + } _OrthancPluginRegisterStorageCommitmentScpCallback2; + + /** * @brief Register a callback to handle incoming requests to the storage commitment SCP. * @@ -10791,6 +10981,205 @@ return context->InvokeService(context, _OrthancPluginService_AcknowledgeQueueValue, ¶ms); } + /** + * @brief Get the remote AET of a DICOM connection. + * + * This function returns the Application Entity Title (AET) of the + * DICOM modality from which a DICOM connection originates. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param connection The connection of interest. + * @return The pointer to the AET, NULL in case of error. + * @ingroup DicomConnection + **/ + ORTHANC_PLUGIN_SINCE_SDK("1.12.10") + ORTHANC_PLUGIN_INLINE const char* OrthancPluginGetConnectionRemoteAet( + OrthancPluginContext* context, + const OrthancPluginDicomConnection* connection) + { + const char* result; + + _OrthancPluginAccessDicomConnection params; + memset(¶ms, 0, sizeof(params)); + params.resultString = &result; + params.connection = connection; + + if (context->InvokeService(context, _OrthancPluginService_GetConnectionRemoteAet, ¶ms) != OrthancPluginErrorCode_Success) + { + /* Error */ + return NULL; + } + else + { + return result; + } + } + + + /** + * @brief Get the remote IP of a DICOM connection. + * + * This function returns the IP of the + * DICOM modality from which a DICOM connection originates. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param connection The connection of interest. + * @return The pointer to the IP, NULL in case of error. + * @ingroup DicomConnection + **/ + ORTHANC_PLUGIN_SINCE_SDK("1.12.10") + ORTHANC_PLUGIN_INLINE const char* OrthancPluginGetConnectionRemoteIp( + OrthancPluginContext* context, + const OrthancPluginDicomConnection* connection) + { + const char* result; + + _OrthancPluginAccessDicomConnection params; + memset(¶ms, 0, sizeof(params)); + params.resultString = &result; + params.connection = connection; + + if (context->InvokeService(context, _OrthancPluginService_GetConnectionRemoteIp, ¶ms) != OrthancPluginErrorCode_Success) + { + /* Error */ + return NULL; + } + else + { + return result; + } + } + + + /** + * @brief Get the called AET of a DICOM connection. + * + * This function returns the Orthanc called AET that a + * DICOM modality has used in a DICOM connection. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param connection The connection of interest. + * @return The pointer to the called AET, NULL in case of error. + * @ingroup DicomConnection + **/ + ORTHANC_PLUGIN_SINCE_SDK("1.12.10") + ORTHANC_PLUGIN_INLINE const char* OrthancPluginGetConnectionCalledAet( + OrthancPluginContext* context, + const OrthancPluginDicomConnection* connection) + { + const char* result; + + _OrthancPluginAccessDicomConnection params; + memset(¶ms, 0, sizeof(params)); + params.resultString = &result; + params.connection = connection; + + if (context->InvokeService(context, _OrthancPluginService_GetConnectionCalledAet, ¶ms) != OrthancPluginErrorCode_Success) + { + /* Error */ + return NULL; + } + else + { + return result; + } + } + + + ORTHANC_PLUGIN_SINCE_SDK("1.12.10") + typedef struct + { + OrthancPluginFindCallback2 callback; + } _OrthancPluginFindCallback2; + + /** + * @brief Register a callback to handle C-Find requests (v2). + * + * This function registers a callback to handle C-Find SCP requests + * that are not related to modality worklists. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param callback The callback. + * @return 0 if success, other value if error. + * @ingroup DicomCallbacks + **/ + ORTHANC_PLUGIN_SINCE_SDK("1.12.10") + ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginRegisterFindCallback2( + OrthancPluginContext* context, + OrthancPluginFindCallback2 callback) + { + _OrthancPluginFindCallback2 params; + params.callback = callback; + + return context->InvokeService(context, _OrthancPluginService_RegisterFindCallback2, ¶ms); + } + + ORTHANC_PLUGIN_SINCE_SDK("1.12.10") + typedef struct + { + OrthancPluginMoveCallback2 callback; + OrthancPluginGetMoveSize getMoveSize; + OrthancPluginApplyMove applyMove; + OrthancPluginFreeMove freeMove; + } _OrthancPluginMoveCallback2; + + /** + * @brief Register a callback to handle C-Move requests (v2). + * + * This function registers a callback to handle C-Move SCP requests. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param callback The main callback. + * @param getMoveSize Callback to read the number of C-Move suboperations. + * @param applyMove Callback to apply one C-Move suboperation. + * @param freeMove Callback to free the C-Move driver. + * @return 0 if success, other value if error. + * @ingroup DicomCallbacks + **/ + ORTHANC_PLUGIN_SINCE_SDK("1.12.10") + ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginRegisterMoveCallback2( + OrthancPluginContext* context, + OrthancPluginMoveCallback2 callback, + OrthancPluginGetMoveSize getMoveSize, + OrthancPluginApplyMove applyMove, + OrthancPluginFreeMove freeMove) + { + _OrthancPluginMoveCallback2 params; + params.callback = callback; + params.getMoveSize = getMoveSize; + params.applyMove = applyMove; + params.freeMove = freeMove; + + return context->InvokeService(context, _OrthancPluginService_RegisterMoveCallback2, ¶ms); + } + + /** + * @brief Register a callback to handle incoming requests to the storage commitment SCP (v2). + * + * This function registers a callback to handle storage commitment SCP requests. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param factory Factory function that creates the handler object + * for incoming storage commitment requests. + * @param destructor Destructor function to destroy the handler object. + * @param lookup Callback function to get the status of one DICOM instance. + * @return 0 if success, other value if error. + * @ingroup DicomCallbacks + **/ + ORTHANC_PLUGIN_SINCE_SDK("1.12.10") + ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginRegisterStorageCommitmentScpCallback2( + OrthancPluginContext* context, + OrthancPluginStorageCommitmentFactory2 factory, + OrthancPluginStorageCommitmentDestructor destructor, + OrthancPluginStorageCommitmentLookup lookup) + { + _OrthancPluginRegisterStorageCommitmentScpCallback2 params; + params.factory = factory; + params.destructor = destructor; + params.lookup = lookup; + return context->InvokeService(context, _OrthancPluginService_RegisterStorageCommitmentScpCallback2, ¶ms); + } + #ifdef __cplusplus } #endif
--- a/OrthancServer/Plugins/Include/orthanc/OrthancPluginCodeModel.json Mon Nov 24 19:56:36 2025 +0100 +++ b/OrthancServer/Plugins/Include/orthanc/OrthancPluginCodeModel.json Tue Nov 25 15:48:26 2025 +0100 @@ -1,6 +1,68 @@ { "classes": [ { + "methods": [ + { + "args": [], + "c_function": "OrthancPluginGetConnectionRemoteAet", + "const": true, + "documentation": { + "args": {}, + "description": [ + "This function returns the Application Entity Title (AET) of the DICOM modality from which a DICOM connection originates." + ], + "return": "The pointer to the AET, NULL in case of error.", + "summary": "Get the remote AET of a DICOM connection." + }, + "return_sdk_type": "const char *", + "since_sdk": [ + 1, + 12, + 10 + ] + }, + { + "args": [], + "c_function": "OrthancPluginGetConnectionRemoteIp", + "const": true, + "documentation": { + "args": {}, + "description": [ + "This function returns the IP of the DICOM modality from which a DICOM connection originates." + ], + "return": "The pointer to the IP, NULL in case of error.", + "summary": "Get the remote IP of a DICOM connection." + }, + "return_sdk_type": "const char *", + "since_sdk": [ + 1, + 12, + 10 + ] + }, + { + "args": [], + "c_function": "OrthancPluginGetConnectionCalledAet", + "const": true, + "documentation": { + "args": {}, + "description": [ + "This function returns the Orthanc called AET that a DICOM modality has used in a DICOM connection." + ], + "return": "The pointer to the called AET, NULL in case of error.", + "summary": "Get the called AET of a DICOM connection." + }, + "return_sdk_type": "const char *", + "since_sdk": [ + 1, + 12, + 10 + ] + } + ], + "name": "OrthancPluginDicomConnection" + }, + { "destructor": "OrthancPluginFreeDicomInstance", "methods": [ { @@ -5529,6 +5591,7 @@ "OrthancPluginGetImageBuffer", "OrthancPluginRestApiGet2", "OrthancPluginRegisterWorklistCallback", + "OrthancPluginRegisterWorklistCallback2", "OrthancPluginRegisterDecodeImageCallback", "OrthancPluginCreateImageAccessor", "OrthancPluginLookupDictionary", @@ -5570,6 +5633,9 @@ "OrthancPluginSetStableStatus", "OrthancPluginRegisterHttpAuthentication", "OrthancPluginRegisterAuditLogHandler", - "OrthancPluginReserveQueueValue" + "OrthancPluginReserveQueueValue", + "OrthancPluginRegisterFindCallback2", + "OrthancPluginRegisterMoveCallback2", + "OrthancPluginRegisterStorageCommitmentScpCallback2" ] } \ No newline at end of file
--- a/OrthancServer/Plugins/Samples/MultitenantDicom/FindRequestHandler.cpp Mon Nov 24 19:56:36 2025 +0100 +++ b/OrthancServer/Plugins/Samples/MultitenantDicom/FindRequestHandler.cpp Tue Nov 25 15:48:26 2025 +0100 @@ -34,10 +34,7 @@ void FindRequestHandler::Handle(Orthanc::DicomFindAnswers& answers, const Orthanc::DicomMap& input, const std::list<Orthanc::DicomTag>& sequencesToReturn, - const std::string& remoteIp, - const std::string& remoteAet, - const std::string& calledAet, - Orthanc::ModalityManufacturer manufacturer) + const Orthanc::DicomConnectionInfo& connection) { std::set<Orthanc::DicomTag> tags; input.GetTags(tags);
--- a/OrthancServer/Plugins/Samples/MultitenantDicom/FindRequestHandler.h Mon Nov 24 19:56:36 2025 +0100 +++ b/OrthancServer/Plugins/Samples/MultitenantDicom/FindRequestHandler.h Tue Nov 25 15:48:26 2025 +0100 @@ -26,6 +26,7 @@ #include "PluginEnumerations.h" #include "../../../../OrthancFramework/Sources/DicomNetworking/IFindRequestHandler.h" +#include "../../../../OrthancFramework/Sources/DicomNetworking/DicomConnectionInfo.h" class FindRequestHandler : public Orthanc::IFindRequestHandler @@ -49,8 +50,5 @@ virtual void Handle(Orthanc::DicomFindAnswers& answers, const Orthanc::DicomMap& input, const std::list<Orthanc::DicomTag>& sequencesToReturn, - const std::string& remoteIp, - const std::string& remoteAet, - const std::string& calledAet, - Orthanc::ModalityManufacturer manufacturer) ORTHANC_OVERRIDE; + const Orthanc::DicomConnectionInfo& remoteIp) ORTHANC_OVERRIDE; };
--- a/OrthancServer/Plugins/Samples/MultitenantDicom/MoveRequestHandler.cpp Mon Nov 24 19:56:36 2025 +0100 +++ b/OrthancServer/Plugins/Samples/MultitenantDicom/MoveRequestHandler.cpp Tue Nov 25 15:48:26 2025 +0100 @@ -27,6 +27,7 @@ #include "../../../../OrthancFramework/Sources/OrthancException.h" #include "../../../../OrthancFramework/Sources/Toolbox.h" +#include "../../../../OrthancFramework/Sources/DicomNetworking/DicomConnectionInfo.h" #include "../Common/OrthancPluginCppWrapper.h" @@ -169,9 +170,7 @@ Orthanc::IMoveRequestIterator* MoveRequestHandler::Handle(const std::string& targetAet, const Orthanc::DicomMap& input, - const std::string& originatorIp, - const std::string& originatorAet, - const std::string& calledAet, + const Orthanc::DicomConnectionInfo& connection, uint16_t originatorId) { std::set<std::string> publicIds; @@ -221,8 +220,8 @@ } Json::Value body; - body["CalledAet"] = calledAet; - body["MoveOriginatorAet"] = originatorAet; + body["CalledAet"] = connection.GetCalledAet(); + body["MoveOriginatorAet"] = connection.GetRemoteAet(); body["MoveOriginatorID"] = originatorId; body["Resources"] = resources; body["Synchronous"] = isSynchronous_;
--- a/OrthancServer/Plugins/Samples/MultitenantDicom/MoveRequestHandler.h Mon Nov 24 19:56:36 2025 +0100 +++ b/OrthancServer/Plugins/Samples/MultitenantDicom/MoveRequestHandler.h Tue Nov 25 15:48:26 2025 +0100 @@ -62,8 +62,6 @@ virtual Orthanc::IMoveRequestIterator* Handle(const std::string& targetAet, const Orthanc::DicomMap& input, - const std::string& originatorIp, - const std::string& originatorAet, - const std::string& calledAet, + const Orthanc::DicomConnectionInfo& connection, uint16_t originatorId) ORTHANC_OVERRIDE; };
--- a/OrthancServer/Plugins/Samples/MultitenantDicom/OrthancFrameworkDependencies.cpp Mon Nov 24 19:56:36 2025 +0100 +++ b/OrthancServer/Plugins/Samples/MultitenantDicom/OrthancFrameworkDependencies.cpp Tue Nov 25 15:48:26 2025 +0100 @@ -48,6 +48,7 @@ #include "../../../../OrthancFramework/Sources/DicomFormat/DicomValue.cpp" #include "../../../../OrthancFramework/Sources/DicomFormat/Window.cpp" #include "../../../../OrthancFramework/Sources/DicomNetworking/DicomAssociation.cpp" +#include "../../../../OrthancFramework/Sources/DicomNetworking/DicomConnectionInfo.cpp" #include "../../../../OrthancFramework/Sources/DicomNetworking/DicomAssociationParameters.cpp" #include "../../../../OrthancFramework/Sources/DicomNetworking/DicomFindAnswers.cpp" #include "../../../../OrthancFramework/Sources/DicomNetworking/DicomServer.cpp"
--- a/OrthancServer/Sources/OrthancFindRequestHandler.cpp Mon Nov 24 19:56:36 2025 +0100 +++ b/OrthancServer/Sources/OrthancFindRequestHandler.cpp Tue Nov 25 15:48:26 2025 +0100 @@ -25,6 +25,7 @@ #include "OrthancFindRequestHandler.h" #include "../../OrthancFramework/Sources/DicomFormat/DicomArray.h" +#include "../../OrthancFramework/Sources/DicomNetworking/DicomConnectionInfo.h" #include "../../OrthancFramework/Sources/DicomParsing/FromDcmtkBridge.h" #include "../../OrthancFramework/Sources/Logging.h" #include "../../OrthancFramework/Sources/Lua/LuaFunctionCall.h" @@ -115,10 +116,7 @@ bool OrthancFindRequestHandler::ApplyLuaFilter(DicomMap& target, const DicomMap& source, - const std::string& remoteIp, - const std::string& remoteAet, - const std::string& calledAet, - ModalityManufacturer manufacturer) + const DicomConnectionInfo& connection) { static const char* LUA_CALLBACK = "IncomingFindRequestFilter"; @@ -131,7 +129,7 @@ else { Json::Value origin; - FormatOrigin(origin, remoteIp, remoteAet, calledAet, manufacturer); + FormatOrigin(origin, connection); LuaFunctionCall call(lock.GetLua(), LUA_CALLBACK); call.PushDicom(source); @@ -272,10 +270,7 @@ void OrthancFindRequestHandler::Handle(DicomFindAnswers& answers, const DicomMap& input, const std::list<DicomTag>& sequencesToReturn, - const std::string& remoteIp, - const std::string& remoteAet, - const std::string& calledAet, - ModalityManufacturer manufacturer) + const DicomConnectionInfo& connection) { MetricsRegistry::Timer timer(context_.GetMetricsRegistry(), "orthanc_find_scp_duration_ms"); @@ -291,18 +286,18 @@ caseSensitivePN = lock.GetConfiguration().GetBooleanParameter("CaseSensitivePN", false); RemoteModalityParameters remote; - if (!lock.GetConfiguration().LookupDicomModalityUsingAETitle(remote, remoteAet)) + if (!lock.GetConfiguration().LookupDicomModalityUsingAETitle(remote, connection.GetRemoteAet())) { if (lock.GetConfiguration().GetBooleanParameter("DicomAlwaysAllowFind", false)) { - CLOG(INFO, DICOM) << "C-FIND: Allowing SCU request from unknown modality with AET: " << remoteAet; + CLOG(INFO, DICOM) << "C-FIND: Allowing SCU request from unknown modality with AET: " << connection.GetRemoteAet(); } else { // This should never happen, given the test at bottom of // "OrthancApplicationEntityFilter::IsAllowedRequest()" throw OrthancException(ErrorCode_InexistentItem, - "C-FIND: Rejecting SCU request from unknown modality with AET: " + remoteAet); + "C-FIND: Rejecting SCU request from unknown modality with AET: " + connection.GetRemoteAet()); } } } @@ -316,7 +311,7 @@ DicomMap lua; const DicomMap* filteredInput = &input; - if (ApplyLuaFilter(lua, input, remoteIp, remoteAet, calledAet, manufacturer)) + if (ApplyLuaFilter(lua, input, connection)) { filteredInput = &lua; } @@ -416,7 +411,7 @@ continue; } - if (FilterQueryTag(level, tag, manufacturer)) + if (FilterQueryTag(level, tag, connection.GetModalityManufacturer())) { ValueRepresentation vr = FromDcmtkBridge::LookupValueRepresentation(tag); @@ -453,15 +448,12 @@ void OrthancFindRequestHandler::FormatOrigin(Json::Value& origin, - const std::string& remoteIp, - const std::string& remoteAet, - const std::string& calledAet, - ModalityManufacturer manufacturer) + const DicomConnectionInfo& connection) { origin = Json::objectValue; - origin["RemoteIp"] = remoteIp; - origin["RemoteAet"] = remoteAet; - origin["CalledAet"] = calledAet; - origin["Manufacturer"] = EnumerationToString(manufacturer); + origin["RemoteIp"] = connection.GetRemoteIp(); + origin["RemoteAet"] = connection.GetRemoteAet(); + origin["CalledAet"] = connection.GetCalledAet(); + origin["Manufacturer"] = EnumerationToString(connection.GetModalityManufacturer()); } }
--- a/OrthancServer/Sources/OrthancFindRequestHandler.h Mon Nov 24 19:56:36 2025 +0100 +++ b/OrthancServer/Sources/OrthancFindRequestHandler.h Tue Nov 25 15:48:26 2025 +0100 @@ -46,10 +46,7 @@ bool ApplyLuaFilter(DicomMap& target, const DicomMap& source, - const std::string& remoteIp, - const std::string& remoteAet, - const std::string& calledAet, - ModalityManufacturer manufacturer); + const DicomConnectionInfo& connection); public: explicit OrthancFindRequestHandler(ServerContext& context); @@ -57,10 +54,7 @@ virtual void Handle(DicomFindAnswers& answers, const DicomMap& input, const std::list<DicomTag>& sequencesToReturn, - const std::string& remoteIp, - const std::string& remoteAet, - const std::string& calledAet, - ModalityManufacturer manufacturer) ORTHANC_OVERRIDE; + const DicomConnectionInfo& connection) ORTHANC_OVERRIDE; unsigned int GetMaxResults() const { @@ -83,9 +77,6 @@ } static void FormatOrigin(Json::Value& origin, - const std::string& remoteIp, - const std::string& remoteAet, - const std::string& calledAet, - ModalityManufacturer manufacturer); + const DicomConnectionInfo& connection); }; }
--- a/OrthancServer/Sources/OrthancMoveRequestHandler.cpp Mon Nov 24 19:56:36 2025 +0100 +++ b/OrthancServer/Sources/OrthancMoveRequestHandler.cpp Tue Nov 25 15:48:26 2025 +0100 @@ -25,6 +25,7 @@ #include "OrthancMoveRequestHandler.h" #include "../../OrthancFramework/Sources/DicomFormat/DicomArray.h" +#include "../../OrthancFramework/Sources/DicomNetworking/DicomConnectionInfo.h" #include "../../OrthancFramework/Sources/DicomParsing/FromDcmtkBridge.h" #include "../../OrthancFramework/Sources/Logging.h" #include "../../OrthancFramework/Sources/MetricsRegistry.h" @@ -317,9 +318,7 @@ IMoveRequestIterator* OrthancMoveRequestHandler::Handle(const std::string& targetAet, const DicomMap& input, - const std::string& originatorIp, - const std::string& originatorAet, - const std::string& calledAet, + const DicomConnectionInfo& connection, uint16_t originatorId) { MetricsRegistry::Timer timer(context_.GetMetricsRegistry(), "orthanc_move_scp_duration_ms"); @@ -362,7 +361,7 @@ LookupIdentifiers(publicIds, ResourceType_Study, input) || LookupIdentifiers(publicIds, ResourceType_Patient, input)) { - return CreateIterator(context_, targetAet, publicIds, originatorAet, originatorId); + return CreateIterator(context_, targetAet, publicIds, connection.GetRemoteAet(), originatorId); } else { @@ -383,7 +382,7 @@ if (LookupIdentifiers(publicIds, level, input)) { - return CreateIterator(context_, targetAet, publicIds, originatorAet, originatorId); + return CreateIterator(context_, targetAet, publicIds, connection.GetRemoteAet(), originatorId); } else {
--- a/OrthancServer/Sources/OrthancMoveRequestHandler.h Mon Nov 24 19:56:36 2025 +0100 +++ b/OrthancServer/Sources/OrthancMoveRequestHandler.h Tue Nov 25 15:48:26 2025 +0100 @@ -46,9 +46,7 @@ virtual IMoveRequestIterator* Handle(const std::string& targetAet, const DicomMap& input, - const std::string& originatorIp, - const std::string& originatorAet, - const std::string& calledAet, + const DicomConnectionInfo& connection, uint16_t originatorId) ORTHANC_OVERRIDE; }; }
--- a/OrthancServer/Sources/OrthancRestApi/OrthancRestModalities.cpp Mon Nov 24 19:56:36 2025 +0100 +++ b/OrthancServer/Sources/OrthancRestApi/OrthancRestModalities.cpp Tue Nov 25 15:48:26 2025 +0100 @@ -2575,7 +2575,7 @@ .SetTag("Networking") .SetSummary("Remove after storage commitment") .SetDescription("Remove out of Orthanc, the DICOM instances that have been reported to have been properly " - "received the storage commitment report whose identifier is provided in the URL. This is " + "received in the storage commitment report whose identifier is provided in the URL. This is " "only possible if the `Status` of the storage commitment report is `Success`. " "https://orthanc.uclouvain.be/book/users/storage-commitment.html#removing-the-instances") .SetUriArgument("id", "Identifier of the storage commitment report");
--- a/OrthancServer/Sources/ServerContext.cpp Mon Nov 24 19:56:36 2025 +0100 +++ b/OrthancServer/Sources/ServerContext.cpp Tue Nov 25 15:48:26 2025 +0100 @@ -1828,14 +1828,13 @@ const std::string& transactionUid, const std::vector<std::string>& sopClassUids, const std::vector<std::string>& sopInstanceUids, - const std::string& remoteAet, - const std::string& calledAet) + const DicomConnectionInfo& connection) { #if ORTHANC_ENABLE_PLUGINS == 1 if (HasPlugins()) { return GetPlugins().CreateStorageCommitment( - jobId, transactionUid, sopClassUids, sopInstanceUids, remoteAet, calledAet); + jobId, transactionUid, sopClassUids, sopInstanceUids, connection); } #endif
--- a/OrthancServer/Sources/ServerContext.h Mon Nov 24 19:56:36 2025 +0100 +++ b/OrthancServer/Sources/ServerContext.h Tue Nov 25 15:48:26 2025 +0100 @@ -554,8 +554,7 @@ const std::string& transactionUid, const std::vector<std::string>& sopClassUids, const std::vector<std::string>& sopInstanceUids, - const std::string& remoteAet, - const std::string& calledAet) ORTHANC_OVERRIDE; + const DicomConnectionInfo& connection) ORTHANC_OVERRIDE; StorageCommitmentReports& GetStorageCommitmentReports() {
--- a/OrthancServer/Sources/ServerJobs/IStorageCommitmentFactory.h Mon Nov 24 19:56:36 2025 +0100 +++ b/OrthancServer/Sources/ServerJobs/IStorageCommitmentFactory.h Tue Nov 25 15:48:26 2025 +0100 @@ -28,6 +28,8 @@ namespace Orthanc { + class DicomConnectionInfo; + class IStorageCommitmentFactory : public boost::noncopyable { public: @@ -50,7 +52,6 @@ const std::string& transactionUid, const std::vector<std::string>& sopClassUids, const std::vector<std::string>& sopInstanceUids, - const std::string& remoteAet, - const std::string& calledAet) = 0; + const DicomConnectionInfo& connection) = 0; }; }
--- a/OrthancServer/Sources/ServerJobs/StorageCommitmentScpJob.cpp Mon Nov 24 19:56:36 2025 +0100 +++ b/OrthancServer/Sources/ServerJobs/StorageCommitmentScpJob.cpp Tue Nov 25 15:48:26 2025 +0100 @@ -25,6 +25,7 @@ #include "StorageCommitmentScpJob.h" #include "../../../OrthancFramework/Sources/DicomNetworking/DicomAssociation.h" +#include "../../../OrthancFramework/Sources/DicomNetworking/DicomConnectionInfo.h" #include "../../../OrthancFramework/Sources/Logging.h" #include "../../../OrthancFramework/Sources/OrthancException.h" #include "../../../OrthancFramework/Sources/SerializationToolbox.h" @@ -33,10 +34,11 @@ static const char* ANSWER = "Answer"; -static const char* CALLED_AET = "CalledAet"; +static const char* CALLED_AET = "CalledAet"; // obsolete from 1.12.10 static const char* INDEX = "Index"; static const char* LOOKUP = "Lookup"; -static const char* REMOTE_MODALITY = "RemoteModality"; +static const char* REMOTE_MODALITY = "RemoteModality"; // obsolete from 1.12.10 +static const char* CONNECTION = "Connection"; // new in 1.12.10 static const char* SETUP = "Setup"; static const char* SOP_CLASS_UIDS = "SopClassUids"; static const char* SOP_INSTANCE_UIDS = "SopInstanceUids"; @@ -249,9 +251,8 @@ { CheckInvariants(); - const std::string& remoteAet = remoteModality_.GetApplicationEntityTitle(); lookupHandler_.reset(context_.CreateStorageCommitment(jobId, transactionUid_, sopClassUids_, - sopInstanceUids_, remoteAet, calledAet_)); + sopInstanceUids_, *connection_)); } @@ -338,7 +339,7 @@ throw OrthancException(ErrorCode_InternalError); } - DicomAssociationParameters parameters(calledAet_, remoteModality_); + DicomAssociationParameters parameters(connection_->GetCalledAet(), remoteModality_); DicomAssociation::ReportStorageCommitment( parameters, transactionUid_, sopClassUids_, sopInstanceUids_, failureReasons); } @@ -346,19 +347,18 @@ StorageCommitmentScpJob::StorageCommitmentScpJob(ServerContext& context, const std::string& transactionUid, - const std::string& remoteAet, - const std::string& calledAet) : + const DicomConnectionInfo& connection) : context_(context), ready_(false), transactionUid_(transactionUid), - calledAet_(calledAet) + connection_(new DicomConnectionInfo(connection)) { { OrthancConfiguration::ReaderLock lock; - if (!lock.GetConfiguration().LookupDicomModalityUsingAETitle(remoteModality_, remoteAet)) + if (!lock.GetConfiguration().LookupDicomModalityUsingAETitle(remoteModality_, connection_->GetRemoteAet())) { throw OrthancException(ErrorCode_InexistentItem, - "Unknown remote modality for storage commitment SCP: " + remoteAet); + "Unknown remote modality for storage commitment SCP: " + connection_->GetRemoteAet()); } } @@ -409,8 +409,9 @@ { SetOfCommandsJob::GetPublicContent(value); - value["CalledAet"] = calledAet_; - value["RemoteAet"] = remoteModality_.GetApplicationEntityTitle(); + value["CalledAet"] = connection_->GetCalledAet(); + value["RemoteAet"] = connection_->GetRemoteAet(); + value["RemoteIp"] = connection_->GetRemoteIp(); value["TransactionUid"] = transactionUid_; } @@ -420,16 +421,35 @@ SetOfCommandsJob(new Unserializer(*this), serialized), context_(context), ready_(false), - transactionUid_(SerializationToolbox::ReadString(serialized, TRANSACTION_UID)), - calledAet_(SerializationToolbox::ReadString(serialized, CALLED_AET)) + transactionUid_(SerializationToolbox::ReadString(serialized, TRANSACTION_UID)) { - if (serialized.type() != Json::objectValue || - !serialized.isMember(REMOTE_MODALITY)) + + if (serialized.isMember(CONNECTION)) + { + connection_.reset(new DicomConnectionInfo(serialized[CONNECTION])); + } + else if (serialized.isMember(REMOTE_MODALITY) && serialized[REMOTE_MODALITY].isObject()) // the job was serialized prior to 1.12.10 { - throw OrthancException(ErrorCode_BadFileFormat); + std::string calledAet = SerializationToolbox::ReadString(serialized, CALLED_AET); + std::string remoteAet = SerializationToolbox::ReadString(serialized[REMOTE_MODALITY], "AET"); + std::string remoteIp = SerializationToolbox::ReadString(serialized[REMOTE_MODALITY], "Host"); + connection_.reset(new DicomConnectionInfo(remoteIp, remoteAet, calledAet)); + } + + if (connection_.get() == NULL) + { + throw OrthancException(ErrorCode_InternalError, "Failed to unserialize StorageCommitmentScpJob"); } - - remoteModality_ = RemoteModalityParameters(serialized[REMOTE_MODALITY]); + + { + OrthancConfiguration::ReaderLock lock; + if (!lock.GetConfiguration().LookupDicomModalityUsingAETitle(remoteModality_, connection_->GetRemoteAet())) + { + throw OrthancException(ErrorCode_InexistentItem, + "Unknown remote modality for storage commitment SCP: " + connection_->GetRemoteAet()); + } + } + SerializationToolbox::ReadArrayOfStrings(sopClassUids_, serialized, SOP_CLASS_UIDS); SerializationToolbox::ReadArrayOfStrings(sopInstanceUids_, serialized, SOP_INSTANCE_UIDS); } @@ -444,8 +464,15 @@ else { target[TRANSACTION_UID] = transactionUid_; - remoteModality_.Serialize(target[REMOTE_MODALITY], true /* force advanced format */); - target[CALLED_AET] = calledAet_; + + if (connection_.get() == NULL) + { + throw (OrthancException(ErrorCode_InternalError)); + } + + target[CONNECTION] = Json::objectValue; + connection_->Serialize(target[CONNECTION]); + SerializationToolbox::WriteArrayOfStrings(target, sopClassUids_, SOP_CLASS_UIDS); SerializationToolbox::WriteArrayOfStrings(target, sopInstanceUids_, SOP_INSTANCE_UIDS); return true;
--- a/OrthancServer/Sources/ServerJobs/StorageCommitmentScpJob.h Mon Nov 24 19:56:36 2025 +0100 +++ b/OrthancServer/Sources/ServerJobs/StorageCommitmentScpJob.h Tue Nov 25 15:48:26 2025 +0100 @@ -25,6 +25,7 @@ #include "../../../OrthancFramework/Sources/Compatibility.h" #include "../../../OrthancFramework/Sources/DicomNetworking/RemoteModalityParameters.h" +#include "../../../OrthancFramework/Sources/DicomNetworking/DicomConnectionInfo.h" #include "../../../OrthancFramework/Sources/JobsEngine/SetOfCommandsJob.h" #include "IStorageCommitmentFactory.h" @@ -55,7 +56,7 @@ bool ready_; std::string transactionUid_; RemoteModalityParameters remoteModality_; - std::string calledAet_; + std::unique_ptr<DicomConnectionInfo> connection_; std::vector<std::string> sopClassUids_; std::vector<std::string> sopInstanceUids_; @@ -72,8 +73,7 @@ public: StorageCommitmentScpJob(ServerContext& context, const std::string& transactionUid, - const std::string& remoteAet, - const std::string& calledAet); + const DicomConnectionInfo& connection); StorageCommitmentScpJob(ServerContext& context, const Json::Value& serialized);
--- a/OrthancServer/Sources/main.cpp Mon Nov 24 19:56:36 2025 +0100 +++ b/OrthancServer/Sources/main.cpp Tue Nov 25 15:48:26 2025 +0100 @@ -130,9 +130,7 @@ virtual void HandleRequest(const std::string& transactionUid, const std::vector<std::string>& referencedSopClassUids, const std::vector<std::string>& referencedSopInstanceUids, - const std::string& remoteIp, - const std::string& remoteAet, - const std::string& calledAet) ORTHANC_OVERRIDE + const DicomConnectionInfo& connection) ORTHANC_OVERRIDE { if (referencedSopClassUids.size() != referencedSopInstanceUids.size()) { @@ -140,7 +138,7 @@ } std::unique_ptr<StorageCommitmentScpJob> job( - new StorageCommitmentScpJob(context_, transactionUid, remoteAet, calledAet)); + new StorageCommitmentScpJob(context_, transactionUid, connection)); for (size_t i = 0; i < referencedSopClassUids.size(); i++) { @@ -158,9 +156,7 @@ const std::vector<std::string>& failedSopClassUids, const std::vector<std::string>& failedSopInstanceUids, const std::vector<StorageCommitmentFailureReason>& failureReasons, - const std::string& remoteIp, - const std::string& remoteAet, - const std::string& calledAet) ORTHANC_OVERRIDE + const DicomConnectionInfo& connection) ORTHANC_OVERRIDE { if (successSopClassUids.size() != successSopInstanceUids.size() || failedSopClassUids.size() != failedSopInstanceUids.size() || @@ -170,7 +166,7 @@ } std::unique_ptr<StorageCommitmentReports::Report> report( - new StorageCommitmentReports::Report(remoteAet)); + new StorageCommitmentReports::Report(connection.GetRemoteAet())); for (size_t i = 0; i < successSopClassUids.size(); i++) {
