changeset 6493:ecdc569d04a2

OrthancPluginDicomConnection + new versions of SCP callbacks
author Alain Mazy <am@orthanc.team>
date Tue, 25 Nov 2025 15:48:26 +0100
parents 6ab61244cdd3
children f9fd84b5ffeb
files NEWS OrthancFramework/Resources/CMake/OrthancFrameworkConfiguration.cmake OrthancFramework/Sources/DicomNetworking/DicomConnectionInfo.cpp OrthancFramework/Sources/DicomNetworking/DicomConnectionInfo.h OrthancFramework/Sources/DicomNetworking/IFindRequestHandler.h OrthancFramework/Sources/DicomNetworking/IMoveRequestHandler.h OrthancFramework/Sources/DicomNetworking/IStorageCommitmentRequestHandler.h OrthancFramework/Sources/DicomNetworking/IWorklistRequestHandler.h OrthancFramework/Sources/DicomNetworking/Internals/CommandDispatcher.cpp OrthancFramework/Sources/DicomNetworking/Internals/FindScp.cpp OrthancFramework/Sources/DicomNetworking/Internals/MoveScp.cpp OrthancServer/Plugins/Engine/OrthancPlugins.cpp OrthancServer/Plugins/Engine/OrthancPlugins.h OrthancServer/Plugins/Include/orthanc/OrthancCPlugin.h OrthancServer/Plugins/Include/orthanc/OrthancPluginCodeModel.json OrthancServer/Plugins/Samples/MultitenantDicom/FindRequestHandler.cpp OrthancServer/Plugins/Samples/MultitenantDicom/FindRequestHandler.h OrthancServer/Plugins/Samples/MultitenantDicom/MoveRequestHandler.cpp OrthancServer/Plugins/Samples/MultitenantDicom/MoveRequestHandler.h OrthancServer/Plugins/Samples/MultitenantDicom/OrthancFrameworkDependencies.cpp OrthancServer/Sources/OrthancFindRequestHandler.cpp OrthancServer/Sources/OrthancFindRequestHandler.h OrthancServer/Sources/OrthancMoveRequestHandler.cpp OrthancServer/Sources/OrthancMoveRequestHandler.h OrthancServer/Sources/OrthancRestApi/OrthancRestModalities.cpp OrthancServer/Sources/ServerContext.cpp OrthancServer/Sources/ServerContext.h OrthancServer/Sources/ServerJobs/IStorageCommitmentFactory.h OrthancServer/Sources/ServerJobs/StorageCommitmentScpJob.cpp OrthancServer/Sources/ServerJobs/StorageCommitmentScpJob.h OrthancServer/Sources/main.cpp
diffstat 31 files changed, 1081 insertions(+), 213 deletions(-) [+]
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, &params);
+  }
+
   
   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, &params);
   }
 
+  /**
+   * @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(&params, 0, sizeof(params));
+    params.resultString = &result;
+    params.connection = connection;
+
+    if (context->InvokeService(context, _OrthancPluginService_GetConnectionRemoteAet, &params) != 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(&params, 0, sizeof(params));
+    params.resultString = &result;
+    params.connection = connection;
+
+    if (context->InvokeService(context, _OrthancPluginService_GetConnectionRemoteIp, &params) != 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(&params, 0, sizeof(params));
+    params.resultString = &result;
+    params.connection = connection;
+
+    if (context->InvokeService(context, _OrthancPluginService_GetConnectionCalledAet, &params) != 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, &params);
+  }
+
+  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, &params);
+  }
+
+  /**
+   * @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, &params);
+  }
+
 #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++)
     {