changeset 6422:36aff9f84264 queues-timeout tip

integration mainline->queues-timeout
author Sebastien Jodogne <s.jodogne@gmail.com>
date Sat, 15 Nov 2025 12:28:27 +0100
parents b86411c11092 (current diff) c557f6bdcbfd (diff)
children
files NEWS OrthancServer/Plugins/Engine/OrthancPlugins.cpp OrthancServer/Sources/Database/StatelessDatabaseOperations.cpp
diffstat 42 files changed, 526 insertions(+), 295 deletions(-) [+]
line wrap: on
line diff
--- a/NEWS	Wed Nov 12 19:30:44 2025 +0100
+++ b/NEWS	Sat Nov 15 12:28:27 2025 +0100
@@ -15,12 +15,11 @@
 
 * Fix: C-Get SCU jobs or HTTP responses were always successful even if the C-Get operation
   actually failed.
-* C-Store, C-Move and C-Get jobs and HTTP responses now include a new "DimseErrorStatus"
-  field (ONLY if the operation fails).
+* When an error occurs while Orthanc handles an HTTP request, it may now include a new
+  "ErrorPayload" in the HTTP response with details about the failed operation.
+  This is currently implemented for C-Store, C-Move and C-Get operations.
 * C-Move and C-Get jobs and HTTP responses now include a new "Details" field with the "DimseErrorStatus"
-  of each operation and the list of "RetrievedInstancesIds".  Note that, if you have multiple
-  resources to retrieve and one of them fails, you'll only get those details if you have set
-  "Permissive" to true to allow other operations to continue after the failure.
+  of each operation and the list of "RetrievedInstancesIds".
 
 Maintenance
 -----------
--- a/OrthancFramework/Resources/CMake/OrthancFrameworkConfiguration.cmake	Wed Nov 12 19:30:44 2025 +0100
+++ b/OrthancFramework/Resources/CMake/OrthancFrameworkConfiguration.cmake	Sat Nov 15 12:28:27 2025 +0100
@@ -574,6 +574,7 @@
       ${CMAKE_CURRENT_LIST_DIR}/../../Sources/DicomNetworking/DicomControlUserConnection.cpp
       ${CMAKE_CURRENT_LIST_DIR}/../../Sources/DicomNetworking/DicomServer.cpp
       ${CMAKE_CURRENT_LIST_DIR}/../../Sources/DicomNetworking/DicomStoreUserConnection.cpp
+      ${CMAKE_CURRENT_LIST_DIR}/../../Sources/DicomNetworking/DimseErrorPayload.cpp
       ${CMAKE_CURRENT_LIST_DIR}/../../Sources/DicomNetworking/Internals/CommandDispatcher.cpp
       ${CMAKE_CURRENT_LIST_DIR}/../../Sources/DicomNetworking/Internals/FindScp.cpp
       ${CMAKE_CURRENT_LIST_DIR}/../../Sources/DicomNetworking/Internals/MoveScp.cpp
--- a/OrthancFramework/Sources/DicomNetworking/DicomControlUserConnection.cpp	Wed Nov 12 19:30:44 2025 +0100
+++ b/OrthancFramework/Sources/DicomNetworking/DicomControlUserConnection.cpp	Sat Nov 15 12:28:27 2025 +0100
@@ -24,6 +24,7 @@
 
 #include "../PrecompiledHeaders.h"
 #include "DicomControlUserConnection.h"
+#include "DimseErrorPayload.h"
 
 #include "../Compatibility.h"
 #include "../DicomFormat/DicomArray.h"
@@ -389,17 +390,19 @@
       if (response.DimseStatus == STATUS_FIND_Failed_UnableToProcess)
       {
         throw OrthancException(ErrorCode_NetworkProtocol,
-                               HttpStatus_422_UnprocessableEntity,
                                "C-FIND SCU to AET \"" +
                                parameters_.GetRemoteModality().GetApplicationEntityTitle() +
                                "\" has failed with DIMSE status " + DimseToHexString(response.DimseStatus) +
-                               " (unable to process - invalid query ?)");
+                               " (unable to process - invalid query ?)")
+          .SetHttpStatus(HttpStatus_422_UnprocessableEntity)
+          .SetPayload(MakeDimseErrorStatusPayload(response.DimseStatus));
       }
       else
       {
         throw OrthancException(ErrorCode_NetworkProtocol, "C-FIND SCU to AET \"" +
                                parameters_.GetRemoteModality().GetApplicationEntityTitle() +
-                               "\" has failed with DIMSE status " + DimseToHexString(response.DimseStatus));
+                               "\" has failed with DIMSE status " + DimseToHexString(response.DimseStatus))
+          .SetPayload(MakeDimseErrorStatusPayload(response.DimseStatus));
       }
     }
   }
@@ -517,19 +520,19 @@
       if (response.DimseStatus == STATUS_MOVE_Failed_UnableToProcess)
       {
         throw OrthancException(ErrorCode_NetworkProtocol,
-                               HttpStatus_422_UnprocessableEntity,
                                "C-MOVE SCU to AET \"" +
                                parameters_.GetRemoteModality().GetApplicationEntityTitle() +
                                "\" has failed with DIMSE status " + DimseToHexString(response.DimseStatus) +
-                               " (unable to process - resource not found ?)",
-                               response.DimseStatus);
+                               " (unable to process - resource not found ?)")
+          .SetHttpStatus(HttpStatus_422_UnprocessableEntity)
+          .SetPayload(MakeDimseErrorStatusPayload(response.DimseStatus));
       }
       else
       {
         throw OrthancException(ErrorCode_NetworkProtocol, "C-MOVE SCU to AET \"" +
                                parameters_.GetRemoteModality().GetApplicationEntityTitle() +
-                               "\" has failed with DIMSE status " + DimseToHexString(response.DimseStatus),
-                               response.DimseStatus);
+                               "\" has failed with DIMSE status " + DimseToHexString(response.DimseStatus))
+          .SetPayload(MakeDimseErrorStatusPayload(response.DimseStatus));
       }
     }
   }
@@ -664,8 +667,8 @@
             throw OrthancException(ErrorCode_NetworkProtocol,
                                    "C-GET SCU to AET \"" +
                                    parameters_.GetRemoteModality().GetApplicationEntityTitle() +
-                                   "\" has failed with DIMSE status " + DimseToHexString(rsp.msg.CGetRSP.DimseStatus),
-                                   rsp.msg.CGetRSP.DimseStatus);
+                                   "\" has failed with DIMSE status " + DimseToHexString(rsp.msg.CGetRSP.DimseStatus))
+              .SetPayload(MakeDimseErrorStatusPayload(rsp.msg.CGetRSP.DimseStatus));
           }
         }
         // Handle C-STORE Request
--- a/OrthancFramework/Sources/DicomNetworking/DicomStoreUserConnection.cpp	Wed Nov 12 19:30:44 2025 +0100
+++ b/OrthancFramework/Sources/DicomNetworking/DicomStoreUserConnection.cpp	Sat Nov 15 12:28:27 2025 +0100
@@ -24,6 +24,7 @@
 
 #include "../PrecompiledHeaders.h"
 #include "DicomStoreUserConnection.h"
+#include "DimseErrorPayload.h"
 
 #include "../DicomParsing/FromDcmtkBridge.h"
 #include "../DicomParsing/ParsedDicomFile.h"
@@ -463,8 +464,8 @@
       throw OrthancException(ErrorCode_NetworkProtocol,
                              "C-STORE SCU to AET \"" +
                              GetParameters().GetRemoteModality().GetApplicationEntityTitle() +
-                             "\" has failed with DIMSE status " + DimseToHexString(response.DimseStatus),
-                             response.DimseStatus);
+                             "\" has failed with DIMSE status " + DimseToHexString(response.DimseStatus))
+        .SetPayload(MakeDimseErrorStatusPayload(response.DimseStatus));
     }
   }
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/OrthancFramework/Sources/DicomNetworking/DimseErrorPayload.cpp	Sat Nov 15 12:28:27 2025 +0100
@@ -0,0 +1,66 @@
+/**
+ * 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 "DimseErrorPayload.h"
+
+#include "../SerializationToolbox.h"
+
+namespace Orthanc
+{
+  static const char* const DIMSE_ERROR_STATUS = "DimseErrorStatus";  // uint16_t
+
+
+  ErrorPayload MakeDimseErrorStatusPayload(uint16_t dimseErrorStatus)
+  {
+    Json::Value content;
+    content[DIMSE_ERROR_STATUS] = dimseErrorStatus;
+
+    ErrorPayload payload;
+    payload.SetContent(ErrorPayloadType_Dimse, content);
+    return payload;
+  }
+
+
+  uint16_t GetDimseErrorStatusFromPayload(const ErrorPayload& payload)
+  {
+    if (payload.GetType() == ErrorPayloadType_Dimse)
+    {
+      unsigned int status = SerializationToolbox::ReadUnsignedInteger(payload.GetContent(), DIMSE_ERROR_STATUS);
+
+      if (status <= 65535)
+      {
+        return static_cast<uint16_t>(status);
+      }
+      else
+      {
+        throw OrthancException(ErrorCode_BadFileFormat);
+      }
+    }
+    else
+    {
+      throw OrthancException(ErrorCode_BadParameterType);
+    }
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/OrthancFramework/Sources/DicomNetworking/DimseErrorPayload.h	Sat Nov 15 12:28:27 2025 +0100
@@ -0,0 +1,39 @@
+/**
+ * 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
+
+#if ORTHANC_ENABLE_DCMTK_NETWORKING != 1
+#  error The macro ORTHANC_ENABLE_DCMTK_NETWORKING must be set to 1
+#endif
+
+#include "../OrthancException.h"
+
+
+namespace Orthanc
+{
+  ErrorPayload MakeDimseErrorStatusPayload(uint16_t dimseErrorStatus);
+
+  uint16_t GetDimseErrorStatusFromPayload(const ErrorPayload& payload);
+}
--- a/OrthancFramework/Sources/DicomNetworking/Internals/CommandDispatcher.cpp	Wed Nov 12 19:30:44 2025 +0100
+++ b/OrthancFramework/Sources/DicomNetworking/Internals/CommandDispatcher.cpp	Sat Nov 15 12:28:27 2025 +0100
@@ -141,7 +141,7 @@
   T_ASC_SC_ROLE acceptedRole)
 {
   int n, i, k;
-  DUL_PRESENTATIONCONTEXT *dpc;
+  const DUL_PRESENTATIONCONTEXT *dpc;
   T_ASC_PresentationContext pc;
 
   n = ASC_countPresentationContexts(params);
--- a/OrthancFramework/Sources/DicomParsing/Internals/DicomImageDecoder.cpp	Wed Nov 12 19:30:44 2025 +0100
+++ b/OrthancFramework/Sources/DicomParsing/Internals/DicomImageDecoder.cpp	Sat Nov 15 12:28:27 2025 +0100
@@ -275,10 +275,10 @@
       else if (DecodePsmctRle1(psmct_, dataset))
       {
         LOG(INFO) << "The PMSCT_RLE1 decoding has succeeded";
-        Uint8* pixData = NULL;
+        const Uint8* pixData = NULL;
         if (psmct_.size() > 0)
         {
-          pixData = reinterpret_cast<Uint8*>(&psmct_[0]);
+          pixData = reinterpret_cast<const Uint8*>(&psmct_[0]);
         }
 
         slowAccessor_.reset(new DicomIntegerPixelAccessor(m, pixData, psmct_.size()));
@@ -966,12 +966,12 @@
     {
       throw OrthancException(ErrorCode_NotImplemented,
                              "The built-in DCMTK decoder cannot decode some DICOM instance "
-                             "whose transfer syntax is: " + std::string(GetTransferSyntaxUid(s)), false /* don't log here*/);
+                             "whose transfer syntax is: " + std::string(GetTransferSyntaxUid(s)), false);
     }
     else
     {
       throw OrthancException(ErrorCode_NotImplemented,
-                             "The built-in DCMTK decoder cannot decode some DICOM instance", false /* don't log here*/);
+                             "The built-in DCMTK decoder cannot decode some DICOM instance", false);
     }
   }
 
--- a/OrthancFramework/Sources/Enumerations.cpp	Wed Nov 12 19:30:44 2025 +0100
+++ b/OrthancFramework/Sources/Enumerations.cpp	Sat Nov 15 12:28:27 2025 +0100
@@ -1192,6 +1192,19 @@
   }
 
 
+  const char* EnumerationToString(ErrorPayloadType type)
+  {
+    switch (type)
+    {
+      case ErrorPayloadType_Dimse:
+        return "Dimse";
+
+      default:
+        throw OrthancException(ErrorCode_ParameterOutOfRange);
+    }
+  }
+
+
   Encoding StringToEncoding(const char* encoding)
   {
     std::string s(encoding);
@@ -1913,6 +1926,19 @@
   }
 
 
+  ErrorPayloadType StringToErrorPayloadType(const std::string& type)
+  {
+    if (type == "Dimse")
+    {
+      return ErrorPayloadType_Dimse;
+    }
+    else
+    {
+      throw OrthancException(ErrorCode_ParameterOutOfRange);
+    }
+  }
+
+
   unsigned int GetBytesPerPixel(PixelFormat format)
   {
     switch (format)
--- a/OrthancFramework/Sources/Enumerations.h	Wed Nov 12 19:30:44 2025 +0100
+++ b/OrthancFramework/Sources/Enumerations.h	Sat Nov 15 12:28:27 2025 +0100
@@ -802,6 +802,12 @@
     RetrieveMethod_SystemDefault = 65535
   };
 
+  enum ErrorPayloadType
+  {
+    ErrorPayloadType_None = 1,
+    ErrorPayloadType_Dimse = 2
+  };
+
 
   ORTHANC_PUBLIC
   const char* EnumerationToString(ErrorCode code);
@@ -861,6 +867,9 @@
   const char* EnumerationToString(RetrieveMethod method);
 
   ORTHANC_PUBLIC
+  const char* EnumerationToString(ErrorPayloadType type);
+
+  ORTHANC_PUBLIC
   Encoding StringToEncoding(const char* encoding);
 
   ORTHANC_PUBLIC
@@ -893,6 +902,9 @@
   
   ORTHANC_PUBLIC
   DicomToJsonFormat StringToDicomToJsonFormat(const std::string& format);
+
+  ORTHANC_PUBLIC
+  ErrorPayloadType StringToErrorPayloadType(const std::string& type);
   
   ORTHANC_PUBLIC
   bool LookupMimeType(MimeType& target,
@@ -915,7 +927,7 @@
   bool IsResourceLevelAboveOrEqual(ResourceType level,
                                    ResourceType reference);
 
-ORTHANC_PUBLIC
+  ORTHANC_PUBLIC
   const char* ResourceTypeToDicomQueryRetrieveLevel(ResourceType type);
 
   ORTHANC_PUBLIC
--- a/OrthancFramework/Sources/HttpServer/HttpOutput.cpp	Wed Nov 12 19:30:44 2025 +0100
+++ b/OrthancFramework/Sources/HttpServer/HttpOutput.cpp	Sat Nov 15 12:28:27 2025 +0100
@@ -619,7 +619,7 @@
       throw OrthancException(ErrorCode_InternalError);
     }
     
-    boundary = boundary.substr(0, 70);
+    boundary.resize(70);
     
     contentTypeHeader = ("multipart/" + subType + "; type=" + tmp + "; boundary=" + boundary);
   }
--- a/OrthancFramework/Sources/Images/ImageAccessor.cpp	Wed Nov 12 19:30:44 2025 +0100
+++ b/OrthancFramework/Sources/Images/ImageAccessor.cpp	Sat Nov 15 12:28:27 2025 +0100
@@ -141,7 +141,7 @@
 
   size_t ImageAccessor::GetSize() const
   {
-    return GetHeight() * GetPitch();
+    return static_cast<size_t>(GetHeight()) * static_cast<size_t>(GetPitch());
   }
 
   const void *ImageAccessor::GetConstBuffer() const
--- a/OrthancFramework/Sources/Images/ImageProcessing.cpp	Wed Nov 12 19:30:44 2025 +0100
+++ b/OrthancFramework/Sources/Images/ImageProcessing.cpp	Sat Nov 15 12:28:27 2025 +0100
@@ -1224,7 +1224,7 @@
     for (unsigned int y = 0; y < height; y++)
     {
       uint8_t* q = reinterpret_cast<uint8_t*>(image.GetRow(y));
-      uint8_t* a = reinterpret_cast<uint8_t*>(alpha.GetRow(y));
+      const uint8_t* a = reinterpret_cast<uint8_t*>(alpha.GetRow(y));
 
       for (unsigned int x = 0; x < width; x++)
       {
--- a/OrthancFramework/Sources/Images/PamReader.cpp	Wed Nov 12 19:30:44 2025 +0100
+++ b/OrthancFramework/Sources/Images/PamReader.cpp	Sat Nov 15 12:28:27 2025 +0100
@@ -207,8 +207,17 @@
     if (enforceAligned_)
     {
       if (alignedImageBuffer_ != NULL)
+      {
         free(alignedImageBuffer_);
+      }
+
       alignedImageBuffer_ = malloc(pitch * height);
+      
+      if (alignedImageBuffer_ == NULL)
+      {
+        throw OrthancException(ErrorCode_NotEnoughMemory);
+      }
+      
       memcpy(alignedImageBuffer_, &content_[offset], pitch* height);
       content_ = "";
       AssignWritable(format, width, height, pitch, alignedImageBuffer_);
--- a/OrthancFramework/Sources/JobsEngine/IJob.h	Wed Nov 12 19:30:44 2025 +0100
+++ b/OrthancFramework/Sources/JobsEngine/IJob.h	Sat Nov 15 12:28:27 2025 +0100
@@ -24,6 +24,7 @@
 
 #pragma once
 
+#include "../OrthancException.h"
 #include "JobStepResult.h"
 
 #include <boost/noncopyable.hpp>
@@ -81,5 +82,7 @@
     virtual bool GetUserData(Json::Value& userData) const = 0;
 
     virtual void SetUserData(const Json::Value& userData) = 0;
+
+    virtual void LookupErrorPayload(ErrorPayload& payload) const = 0;
   };
 }
--- a/OrthancFramework/Sources/JobsEngine/JobInfo.cpp	Wed Nov 12 19:30:44 2025 +0100
+++ b/OrthancFramework/Sources/JobsEngine/JobInfo.cpp	Sat Nov 15 12:28:27 2025 +0100
@@ -196,9 +196,9 @@
       target["UserData"] = status_.GetUserData();
     }
 
-    if (status_.HasDimseErrorStatus())
+    if (status_.GetErrorPayload().HasContent())
     {
-      target["DimseErrorStatus"] = status_.GetDimseErrorStatus();
+      status_.GetErrorPayload().Format(target["ErrorPayload"]);
     }
 
     target["Type"] = status_.GetJobType();
--- a/OrthancFramework/Sources/JobsEngine/JobStatus.cpp	Wed Nov 12 19:30:44 2025 +0100
+++ b/OrthancFramework/Sources/JobsEngine/JobStatus.cpp	Sat Nov 15 12:28:27 2025 +0100
@@ -34,13 +34,19 @@
     progress_(0),
     jobType_("Invalid"),
     publicContent_(Json::objectValue),
-    hasSerialized_(false),
-    hasDimseErrorStatus_(false),
-    dimseErrorStatus_(0x0000)
+    hasSerialized_(false)
   {
   }
 
-  void JobStatus::InitInternal(const IJob& job)
+  
+  JobStatus::JobStatus(ErrorCode code,
+                       const std::string& details,
+                       const IJob& job) :
+    errorCode_(code),
+    progress_(job.GetProgress()),
+    publicContent_(Json::objectValue),
+    hasSerialized_(false),
+    details_(details)
   {
     if (progress_ < 0)
     {
@@ -59,36 +65,6 @@
     hasSerialized_ = job.Serialize(serialized_);
   }
 
-  JobStatus::JobStatus(ErrorCode code,
-                       const std::string& details,
-                       const IJob& job,
-                       uint16_t dimseErrorStatus) :
-    errorCode_(code),
-    progress_(job.GetProgress()),
-    publicContent_(Json::objectValue),
-    hasSerialized_(false),
-    details_(details),
-    hasDimseErrorStatus_(true),
-    dimseErrorStatus_(dimseErrorStatus)
-  {
-    InitInternal(job);
-  }
-
-
-  JobStatus::JobStatus(ErrorCode code,
-                       const std::string& details,
-                       const IJob& job) :
-    errorCode_(code),
-    progress_(job.GetProgress()),
-    publicContent_(Json::objectValue),
-    hasSerialized_(false),
-    details_(details),
-    hasDimseErrorStatus_(false),
-    dimseErrorStatus_(0x0000)
-  {
-    InitInternal(job);
-  }
-
 
   const Json::Value& JobStatus::GetSerialized() const
   {
--- a/OrthancFramework/Sources/JobsEngine/JobStatus.h	Wed Nov 12 19:30:44 2025 +0100
+++ b/OrthancFramework/Sources/JobsEngine/JobStatus.h	Sat Nov 15 12:28:27 2025 +0100
@@ -25,7 +25,7 @@
 #pragma once
 
 #include "IJob.h"
-#include "../OrthancException.h"
+#include "../OrthancException.h"  // For ErrorPayload
 
 namespace Orthanc
 {
@@ -39,22 +39,14 @@
     Json::Value    serialized_;
     bool           hasSerialized_;
     std::string    details_;
-    bool           hasDimseErrorStatus_;
-    uint16_t       dimseErrorStatus_;
     Json::Value    userData_;
+    ErrorPayload   errorPayload_;
 
-    void InitInternal(const IJob& job);
-    
   public:
     JobStatus();
 
     JobStatus(ErrorCode code,
               const std::string& details,
-              const IJob& job,
-              uint16_t dimseErrorStatus);
-
-    JobStatus(ErrorCode code,
-              const std::string& details,
               const IJob& job);
 
     ErrorCode GetErrorCode() const
@@ -109,19 +101,14 @@
       return userData_;
     }
 
-    bool HasDimseErrorStatus() const
+    ErrorPayload& GetErrorPayload()
     {
-      return hasDimseErrorStatus_;
+      return errorPayload_;
     }
 
-    uint16_t GetDimseErrorStatus() const
+    const ErrorPayload& GetErrorPayload() const
     {
-      if (!hasDimseErrorStatus_)
-      {
-        throw OrthancException(ErrorCode_BadSequenceOfCalls);
-      }
-
-      return dimseErrorStatus_;
+      return errorPayload_;
     }
   };
 }
--- a/OrthancFramework/Sources/JobsEngine/JobStepResult.cpp	Wed Nov 12 19:30:44 2025 +0100
+++ b/OrthancFramework/Sources/JobsEngine/JobStepResult.cpp	Sat Nov 15 12:28:27 2025 +0100
@@ -32,9 +32,7 @@
   JobStepResult::JobStepResult() :
     code_(JobStepCode_Failure),
     timeout_(0),
-    error_(ErrorCode_InternalError),
-    hasDimseErrorStatus_(false),
-    dimseErrorStatus_(0x0000)
+    error_(ErrorCode_InternalError)
   {
   }
 
@@ -72,12 +70,10 @@
 
   JobStepResult JobStepResult::Failure(const ErrorCode& error,
                                        const char* details,
-                                       uint16_t dimseErrorStatus)
+                                       const ErrorPayload& payload)
   {
     JobStepResult result = Failure(error, details);
-
-    result.hasDimseErrorStatus_ = true;
-    result.dimseErrorStatus_ = dimseErrorStatus;
+    result.failurePayload_ = payload;
 
     return result;
   }
@@ -85,11 +81,11 @@
 
   JobStepResult JobStepResult::Failure(const OrthancException& exception)
   {
-    if (exception.HasDimseErrorStatus())
+    if (exception.GetPayload().HasContent())
     {
       return Failure(exception.GetErrorCode(),
                      exception.HasDetails() ? exception.GetDetails() : NULL,
-                     exception.GetDimseErrorStatus());
+                     exception.GetPayload());
     }
     else
     {
@@ -141,20 +137,4 @@
       throw OrthancException(ErrorCode_BadSequenceOfCalls);
     }
   }
-
-  bool JobStepResult::HasDimseErrorStatus() const
-  {
-    return hasDimseErrorStatus_;
-  }
-
-  uint16_t JobStepResult::GetDimseErrorStatus() const
-  {
-    if (!hasDimseErrorStatus_)
-    {
-      throw OrthancException(ErrorCode_BadSequenceOfCalls);
-    }
-
-    return dimseErrorStatus_;
-  }
-
 }
--- a/OrthancFramework/Sources/JobsEngine/JobStepResult.h	Wed Nov 12 19:30:44 2025 +0100
+++ b/OrthancFramework/Sources/JobsEngine/JobStepResult.h	Sat Nov 15 12:28:27 2025 +0100
@@ -25,14 +25,14 @@
 #pragma once
 
 #include "../Enumerations.h"
+#include "../OrthancException.h"
 
+#include <boost/shared_ptr.hpp>
 #include <json/value.h>
 #include <stdint.h>
 
 namespace Orthanc
 {
-  class OrthancException;
-  
   class ORTHANC_PUBLIC JobStepResult
   {
   private:
@@ -40,16 +40,12 @@
     unsigned int  timeout_;
     ErrorCode     error_;
     std::string   failureDetails_;
-    bool          hasDimseErrorStatus_;
-    uint16_t      dimseErrorStatus_;
-
+    ErrorPayload  failurePayload_;
     
     explicit JobStepResult(JobStepCode code) :
       code_(code),
       timeout_(0),
-      error_(ErrorCode_Success),
-      hasDimseErrorStatus_(false),
-      dimseErrorStatus_(0x0000)
+      error_(ErrorCode_Success)
     {
     }
 
@@ -67,7 +63,7 @@
 
     static JobStepResult Failure(const ErrorCode& error,
                                  const char* details,
-                                 uint16_t dimseErrorStatus);
+                                 const ErrorPayload& payload);
 
     static JobStepResult Failure(const OrthancException& exception);
 
@@ -79,8 +75,9 @@
 
     const std::string& GetFailureDetails() const;
 
-    bool HasDimseErrorStatus() const;
-
-    uint16_t GetDimseErrorStatus() const;
+    const ErrorPayload& GetFailurePayload() const
+    {
+      return failurePayload_;
+    }
   };
 }
--- a/OrthancFramework/Sources/JobsEngine/JobsEngine.cpp	Wed Nov 12 19:30:44 2025 +0100
+++ b/OrthancFramework/Sources/JobsEngine/JobsEngine.cpp	Sat Nov 15 12:28:27 2025 +0100
@@ -87,14 +87,7 @@
 
       case JobStepCode_Failure:
         running.GetJob().Stop(JobStopReason_Failure);
-        if (result.HasDimseErrorStatus())
-        {
-          running.UpdateStatus(result.GetFailureCode(), result.GetFailureDetails(), result.GetDimseErrorStatus());
-        }
-        else
-        {
-          running.UpdateStatus(result.GetFailureCode(), result.GetFailureDetails());
-        }
+        running.UpdateStatus(result.GetFailureCode(), result.GetFailureDetails(), result.GetFailurePayload());
         running.MarkFailure();
         return false;
 
--- a/OrthancFramework/Sources/JobsEngine/JobsRegistry.cpp	Wed Nov 12 19:30:44 2025 +0100
+++ b/OrthancFramework/Sources/JobsEngine/JobsRegistry.cpp	Sat Nov 15 12:28:27 2025 +0100
@@ -44,7 +44,8 @@
   static const char* ERROR_CODE = "ErrorCode";
   static const char* ERROR_DETAILS = "ErrorDetails";
   static const char* USER_DATA = "UserData";
-  static const char* DIMSE_ERROR_STATUS = "DimseErrorStatus";
+  static const char* ERROR_PAYLOAD_TYPE = "ErrorPayloadType";
+  static const char* ERROR_PAYLOAD = "ErrorPayload";
 
 
   class JobsRegistry::JobHandler : public boost::noncopyable
@@ -305,9 +306,10 @@
           target[USER_DATA] = userData;
         }
         
-        if (lastStatus_.HasDimseErrorStatus())
+        if (lastStatus_.GetErrorPayload().HasContent())
         {
-          target[DIMSE_ERROR_STATUS] = lastStatus_.GetDimseErrorStatus();
+          target[ERROR_PAYLOAD_TYPE] = lastStatus_.GetErrorPayload().GetType();
+          target[ERROR_PAYLOAD] = lastStatus_.GetErrorPayload().GetContent();
         }
 
         return true;
@@ -361,13 +363,13 @@
         job_->SetUserData(serialized[USER_DATA]);
       }
 
-      if (serialized.isMember(DIMSE_ERROR_STATUS))
+      lastStatus_ = JobStatus(errorCode, details, *job_);
+
+      if (serialized.isMember(ERROR_PAYLOAD_TYPE) &&
+          serialized.isMember(ERROR_PAYLOAD))
       {
-        lastStatus_ = JobStatus(errorCode, details, *job_, static_cast<uint16_t>(serialized[DIMSE_ERROR_STATUS].asUInt()));
-      }
-      else
-      {
-        lastStatus_ = JobStatus(errorCode, details, *job_);
+        const std::string type = SerializationToolbox::ReadString(serialized, ERROR_PAYLOAD_TYPE);
+        lastStatus_.GetErrorPayload().SetContent(StringToErrorPayloadType(type.c_str()), serialized[ERROR_PAYLOAD]);
       }
 
       job_->Start();
@@ -896,9 +898,17 @@
             ErrorCode code = it->second->GetLastStatus().GetErrorCode();
             const std::string& details = it->second->GetLastStatus().GetDetails();
             
-            if (it->second->GetLastStatus().HasDimseErrorStatus())
+            // Prefer the error payload from the job level if there is one since it should contain more information than the step error payload.
+            ErrorPayload jobErrorPayload;
+            it->second->GetJob().LookupErrorPayload(jobErrorPayload);
+
+            if (jobErrorPayload.HasContent())
             {
-              throw OrthancException(code, details, it->second->GetLastStatus().GetDimseErrorStatus());
+              throw OrthancException(code, details).SetPayload(jobErrorPayload);
+            }
+            else if (it->second->GetLastStatus().GetErrorPayload().HasContent())
+            {
+              throw OrthancException(code, details).SetPayload(it->second->GetLastStatus().GetErrorPayload());
             }
             else if (!details.empty())
             {
@@ -1465,7 +1475,7 @@
 
   void JobsRegistry::RunningJob::UpdateStatus(ErrorCode code,
                                               const std::string& details,
-                                              uint16_t dimseErrorStatus)
+                                              const ErrorPayload& errorPayload)
   {
     if (!IsValid())
     {
@@ -1473,7 +1483,8 @@
     }
     else
     {
-      JobStatus status(code, details, *job_, dimseErrorStatus);
+      JobStatus status(code, details, *job_);
+      status.GetErrorPayload() = errorPayload;
 
       boost::mutex::scoped_lock lock(registry_.mutex_);
       registry_.CheckInvariants();
--- a/OrthancFramework/Sources/JobsEngine/JobsRegistry.h	Wed Nov 12 19:30:44 2025 +0100
+++ b/OrthancFramework/Sources/JobsEngine/JobsRegistry.h	Sat Nov 15 12:28:27 2025 +0100
@@ -246,7 +246,7 @@
 
       void UpdateStatus(ErrorCode code,
                         const std::string& details,
-                        uint16_t dimseErrorStatus);
+                        const ErrorPayload& errorPayload);
     };
   };
 }
--- a/OrthancFramework/Sources/JobsEngine/Operations/SequenceOfOperationsJob.h	Wed Nov 12 19:30:44 2025 +0100
+++ b/OrthancFramework/Sources/JobsEngine/Operations/SequenceOfOperationsJob.h	Sat Nov 15 12:28:27 2025 +0100
@@ -150,5 +150,9 @@
     {
       return false;
     }
+
+    virtual void LookupErrorPayload(ErrorPayload& payload) const ORTHANC_OVERRIDE
+    {
+    }
   };
 }
--- a/OrthancFramework/Sources/JobsEngine/SetOfCommandsJob.h	Wed Nov 12 19:30:44 2025 +0100
+++ b/OrthancFramework/Sources/JobsEngine/SetOfCommandsJob.h	Sat Nov 15 12:28:27 2025 +0100
@@ -133,5 +133,8 @@
       return false;
     }
 
+    virtual void LookupErrorPayload(ErrorPayload& payload) const ORTHANC_OVERRIDE
+    {
+    }
   };
 }
--- a/OrthancFramework/Sources/OrthancException.cpp	Wed Nov 12 19:30:44 2025 +0100
+++ b/OrthancFramework/Sources/OrthancException.cpp	Sat Nov 15 12:28:27 2025 +0100
@@ -30,12 +30,92 @@
 
 namespace Orthanc
 {
+  ErrorPayload::ErrorPayload(const ErrorPayload& other) :
+    type_(other.type_)
+  {
+    if (other.content_.get() != NULL)
+    {
+      content_.reset(new Json::Value(*other.content_));
+    }
+  }
+
+
+  ErrorPayload& ErrorPayload::operator= (const ErrorPayload& other)
+  {
+    if (other.HasContent())
+    {
+      SetContent(other.GetType(), other.GetContent());
+    }
+    else
+    {
+      ClearContent();
+    }
+
+    return *this;
+  }
+
+
+  void ErrorPayload::ClearContent()
+  {
+    type_ = ErrorPayloadType_None;
+    content_.reset(NULL);
+  }
+
+
+  void ErrorPayload::SetContent(ErrorPayloadType type,
+                                const Json::Value& content)
+  {
+    if (type == ErrorPayloadType_None ||
+        content.isNull())
+    {
+      throw OrthancException(ErrorCode_ParameterOutOfRange);
+    }
+    else
+    {
+      type_ = type;
+      content_.reset(new Json::Value(content));
+    }
+  }
+
+
+  ErrorPayloadType ErrorPayload::GetType() const
+  {
+    if (HasContent())
+    {
+      return type_;
+    }
+    else
+    {
+      throw OrthancException(ErrorCode_BadSequenceOfCalls);
+    }
+  }
+
+
+  const Json::Value& ErrorPayload::GetContent() const
+  {
+    if (HasContent())
+    {
+      return *content_;
+    }
+    else
+    {
+      throw OrthancException(ErrorCode_BadSequenceOfCalls);
+    }
+  }
+
+
+  void ErrorPayload::Format(Json::Value& target) const
+  {
+    target["Type"] = EnumerationToString(GetType());
+    target["Content"] = GetContent();
+  }
+
+
   OrthancException::OrthancException(const OrthancException& other) : 
     errorCode_(other.errorCode_),
     httpStatus_(other.httpStatus_),
-    logged_(false),
-    hasDimseErrorStatus_(other.hasDimseErrorStatus_),
-    dimseErrorStatus_(other.dimseErrorStatus_)
+    logged_(other.logged_),
+    payload_(other.payload_)
   {
     if (other.details_.get() != NULL)
     {
@@ -46,9 +126,7 @@
   OrthancException::OrthancException(ErrorCode errorCode) : 
     errorCode_(errorCode),
     httpStatus_(ConvertErrorCodeToHttpStatus(errorCode)),
-    logged_(false),
-    hasDimseErrorStatus_(false),
-    dimseErrorStatus_(0x0000)
+    logged_(false)
   {
   }
 
@@ -58,28 +136,7 @@
     errorCode_(errorCode),
     httpStatus_(ConvertErrorCodeToHttpStatus(errorCode)),
     logged_(log),
-    details_(new std::string(details)),
-    hasDimseErrorStatus_(false),
-    dimseErrorStatus_(0x0000)
-  {
-#if ORTHANC_ENABLE_LOGGING == 1
-    if (log)
-    {
-      LOG(ERROR) << EnumerationToString(errorCode_) << ": " << details;
-    }
-#endif
-  }
-
-  OrthancException::OrthancException(ErrorCode errorCode,
-                                     const std::string& details,
-                                     uint16_t dimseErrorStatus,
-                                     bool log) :
-    errorCode_(errorCode),
-    httpStatus_(ConvertErrorCodeToHttpStatus(errorCode)),
-    logged_(log),
-    details_(new std::string(details)),
-    hasDimseErrorStatus_(true),
-    dimseErrorStatus_(dimseErrorStatus)
+    details_(new std::string(details))
   {
 #if ORTHANC_ENABLE_LOGGING == 1
     if (log)
@@ -89,54 +146,6 @@
 #endif
   }
 
-  OrthancException::OrthancException(ErrorCode errorCode,
-                                     HttpStatus httpStatus) :
-    errorCode_(errorCode),
-    httpStatus_(httpStatus),
-    logged_(false),
-    hasDimseErrorStatus_(false),
-    dimseErrorStatus_(0x0000)
-  {
-  }
-
-  OrthancException::OrthancException(ErrorCode errorCode,
-                                     HttpStatus httpStatus,
-                                     const std::string& details,
-                                     uint16_t dimseErrorStatus,
-                                     bool log) :
-    errorCode_(errorCode),
-    httpStatus_(httpStatus),
-    logged_(log),
-    details_(new std::string(details)),
-    hasDimseErrorStatus_(true),
-    dimseErrorStatus_(dimseErrorStatus)
-  {
-#if ORTHANC_ENABLE_LOGGING == 1
-    if (log)
-    {
-      LOG(ERROR) << EnumerationToString(errorCode_) << ": " << details;
-    }
-#endif
-  }
-
-  OrthancException::OrthancException(ErrorCode errorCode,
-                                     HttpStatus httpStatus,
-                                     const std::string& details,
-                                     bool log) :
-    errorCode_(errorCode),
-    httpStatus_(httpStatus),
-    logged_(log),
-    details_(new std::string(details)),
-    hasDimseErrorStatus_(false),
-    dimseErrorStatus_(0x0000)
-  {
-#if ORTHANC_ENABLE_LOGGING == 1
-    if (log)
-    {
-      LOG(ERROR) << EnumerationToString(errorCode_) << ": " << details;
-    }
-#endif
-  }
 
   ErrorCode OrthancException::GetErrorCode() const
   {
@@ -169,25 +178,4 @@
       return details_->c_str();
     }
   }
-
-  bool OrthancException::HasDimseErrorStatus() const
-  {
-    return hasDimseErrorStatus_;
-  }
-
-  uint16_t OrthancException::GetDimseErrorStatus() const
-  {
-    if (!hasDimseErrorStatus_)
-    {
-      throw OrthancException(ErrorCode_BadSequenceOfCalls);
-    }
-
-    return dimseErrorStatus_;
-  }
-
-  bool OrthancException::HasBeenLogged() const
-  {
-    return logged_;
-  }
-
 }
--- a/OrthancFramework/Sources/OrthancException.h	Wed Nov 12 19:30:44 2025 +0100
+++ b/OrthancFramework/Sources/OrthancException.h	Sat Nov 15 12:28:27 2025 +0100
@@ -28,10 +28,47 @@
 #include "Enumerations.h"
 #include "OrthancFramework.h"
 
+#include <json/value.h>
 #include <stdint.h>  // For uint16_t
 
 namespace Orthanc
 {
+  class ORTHANC_PUBLIC ErrorPayload
+  {
+  private:
+    ErrorPayloadType              type_;
+    std::unique_ptr<Json::Value>  content_;
+
+  public:
+    ErrorPayload() :
+      type_(ErrorPayloadType_None)
+    {
+    }
+
+    ErrorPayload(const ErrorPayload& other);
+
+    ErrorPayload& operator= (const ErrorPayload& other);
+
+    void ClearContent();
+
+    void SetContent(ErrorPayloadType type,
+                    const Json::Value& content);
+
+    bool HasContent() const
+    {
+      return content_.get() != NULL;
+    }
+
+    ErrorPayloadType GetType() const;
+
+    const Json::Value& GetContent() const;
+
+    void Format(Json::Value& target) const;
+  };
+
+
+  // TODO: Shouldn't copies of OrthancException be avoided completely
+  // (i.e., tag OrthancException as boost::noncopyable)?
   class ORTHANC_PUBLIC OrthancException
   {
   private:
@@ -47,10 +84,10 @@
     std::unique_ptr<std::string>  details_;
     
     // New in Orthanc 1.12.10
-    bool       hasDimseErrorStatus_;
-    uint16_t   dimseErrorStatus_;
-    
+    ErrorPayload  payload_;
+
   public:
+    // WARNING: Do not add additional constructors, to prevent ambiguities in overloading
     OrthancException(const OrthancException& other);
 
     explicit OrthancException(ErrorCode errorCode);
@@ -59,25 +96,6 @@
                      const std::string& details,
                      bool log = true);
 
-    OrthancException(ErrorCode errorCode,
-                     const std::string& details,
-                     uint16_t dimseErrorStatus,
-                     bool log = true);
-
-    OrthancException(ErrorCode errorCode,
-                     HttpStatus httpStatus);
-
-    OrthancException(ErrorCode errorCode,
-                     HttpStatus httpStatus,
-                     const std::string& details,
-                     bool log = true);
-
-    OrthancException(ErrorCode errorCode,
-                     HttpStatus httpStatus,
-                     const std::string& details,
-                     uint16_t dimseErrorStatus,
-                     bool log = true);
-
     ErrorCode GetErrorCode() const;
 
     HttpStatus GetHttpStatus() const;
@@ -88,10 +106,33 @@
 
     const char* GetDetails() const;
 
-    bool HasBeenLogged() const;
+    bool HasBeenLogged() const
+    {
+      return logged_;
+    }
+
+    OrthancException& SetHttpStatus(HttpStatus status)
+    {
+      httpStatus_ = status;
+      return *this;
+    }
 
-    bool HasDimseErrorStatus() const;
-    
-    uint16_t GetDimseErrorStatus() const;
+    OrthancException& SetPayload(const ErrorPayload& payload)
+    {
+      payload_ = payload;
+      return *this;
+    }
+
+    OrthancException& SetPayload(ErrorPayloadType type,
+                                 const Json::Value& content)
+    {
+      payload_.SetContent(type, content);
+      return *this;
+    }
+
+    const ErrorPayload& GetPayload() const
+    {
+      return payload_;
+    }
   };
 }
--- a/OrthancFramework/Sources/SystemToolbox.cpp	Wed Nov 12 19:30:44 2025 +0100
+++ b/OrthancFramework/Sources/SystemToolbox.cpp	Sat Nov 15 12:28:27 2025 +0100
@@ -228,8 +228,7 @@
     if (!IsRegularFile(path))
     {
       throw OrthancException(ErrorCode_RegularFileExpected,
-                             "The path does not point to a regular file: " + PathToUtf8(path),
-                             log);
+                             "The path does not point to a regular file: " + PathToUtf8(path), log);
     }
 
     try
@@ -238,8 +237,7 @@
       f.open(path, std::ifstream::in | std::ifstream::binary);
       if (!f.good())
       {
-        throw OrthancException(ErrorCode_InexistentFile, "File not found: " + PathToUtf8(path),
-                               log);
+        throw OrthancException(ErrorCode_InexistentFile, "File not found: " + PathToUtf8(path), log);
       }
 
       std::streamsize size = GetStreamSize(f);
--- a/OrthancFramework/UnitTestsSources/FrameworkTests.cpp	Wed Nov 12 19:30:44 2025 +0100
+++ b/OrthancFramework/UnitTestsSources/FrameworkTests.cpp	Sat Nov 15 12:28:27 2025 +0100
@@ -1793,3 +1793,64 @@
   ASSERT_FALSE(Toolbox::IsVersionAbove("18.19.20", 18, 20, 0));
   ASSERT_FALSE(Toolbox::IsVersionAbove("18.19.20", 19, 0, 0));
 }
+
+
+TEST(OrthancException, ErrorPayload)
+{
+  ErrorPayload b;
+
+  {
+    ErrorPayload a;
+    ASSERT_THROW(a.SetContent(ErrorPayloadType_None, ""), OrthancException);
+    ASSERT_FALSE(a.HasContent());
+    ASSERT_THROW(a.GetType(), OrthancException);
+    ASSERT_THROW(a.GetContent(), OrthancException);
+
+    {
+      ErrorPayload c(a);
+      ASSERT_FALSE(c.HasContent());
+      ASSERT_THROW(c.GetType(), OrthancException);
+      ASSERT_THROW(c.GetContent(), OrthancException);
+    }
+
+    b = a;
+  }
+
+  ASSERT_FALSE(b.HasContent());
+  ASSERT_THROW(b.GetType(), OrthancException);
+  ASSERT_THROW(b.GetContent(), OrthancException);
+
+  {
+    ErrorPayload a;
+    a.SetContent(ErrorPayloadType_Dimse, "Hello");
+    ASSERT_TRUE(a.HasContent());
+    ASSERT_EQ(ErrorPayloadType_Dimse, a.GetType());
+    ASSERT_TRUE(a.GetContent().isString());
+    ASSERT_EQ("Hello", a.GetContent().asString());
+
+    {
+      ErrorPayload c(a);
+      ASSERT_TRUE(c.HasContent());
+      ASSERT_EQ(ErrorPayloadType_Dimse, c.GetType());
+      ASSERT_TRUE(c.GetContent().isString());
+      ASSERT_EQ("Hello", c.GetContent().asString());
+
+      c = ErrorPayload();
+      ASSERT_FALSE(c.HasContent());
+      ASSERT_THROW(c.GetType(), OrthancException);
+      ASSERT_THROW(c.GetContent(), OrthancException);
+    }
+
+    b = a;
+
+    a.ClearContent();
+    ASSERT_FALSE(a.HasContent());
+    ASSERT_THROW(a.GetType(), OrthancException);
+    ASSERT_THROW(a.GetContent(), OrthancException);
+  }
+
+  ASSERT_TRUE(b.HasContent());
+  ASSERT_EQ(ErrorPayloadType_Dimse, b.GetType());
+  ASSERT_TRUE(b.GetContent().isString());
+  ASSERT_EQ("Hello", b.GetContent().asString());
+}
--- a/OrthancFramework/UnitTestsSources/JobsTests.cpp	Wed Nov 12 19:30:44 2025 +0100
+++ b/OrthancFramework/UnitTestsSources/JobsTests.cpp	Sat Nov 15 12:28:27 2025 +0100
@@ -147,6 +147,10 @@
     {
       return false;
     }
+
+    virtual void LookupErrorPayload(ErrorPayload& payload) const ORTHANC_OVERRIDE
+    {
+    }
   };
 
 
--- a/OrthancServer/Plugins/Engine/OrthancPlugins.cpp	Wed Nov 12 19:30:44 2025 +0100
+++ b/OrthancServer/Plugins/Engine/OrthancPlugins.cpp	Sat Nov 15 12:28:27 2025 +0100
@@ -1453,9 +1453,7 @@
 
           if (HasErrorDetails())
           {
-            throw OrthancException(static_cast<ErrorCode>(error),
-                                   GetErrorDetails(),
-                                   IsLogDetails());
+            throw OrthancException(static_cast<ErrorCode>(error), GetErrorDetails(), IsLogDetails());
           }
           else
           {
@@ -2547,7 +2545,7 @@
   }
 
 
-  static std::string GetAllowedMethods(_OrthancPluginChunkedRestCallback parameters)
+  static std::string GetAllowedMethods(const _OrthancPluginChunkedRestCallback& parameters)
   {
     std::string s;
 
--- a/OrthancServer/Plugins/Engine/PluginsJob.h	Wed Nov 12 19:30:44 2025 +0100
+++ b/OrthancServer/Plugins/Engine/PluginsJob.h	Sat Nov 15 12:28:27 2025 +0100
@@ -95,6 +95,9 @@
       return false;
     }
 
+    virtual void LookupErrorPayload(ErrorPayload& payload) const ORTHANC_OVERRIDE
+    {
+    }
   };
 }
 
--- a/OrthancServer/Resources/RunCppCheck.sh	Wed Nov 12 19:30:44 2025 +0100
+++ b/OrthancServer/Resources/RunCppCheck.sh	Sat Nov 15 12:28:27 2025 +0100
@@ -1,5 +1,5 @@
 #!/bin/bash
-
+# note: this script was last tuned to run with cppcheck v2.17.1
 set -ex
 
 CPPCHECK=cppcheck
@@ -9,18 +9,11 @@
 fi
 
 cat <<EOF > /tmp/cppcheck-suppressions.txt
-constParameter:../../OrthancFramework/Sources/DicomParsing/FromDcmtkBridge.cpp
-knownArgument:../../OrthancFramework/UnitTestsSources/ImageTests.cpp
 knownConditionTrueFalse:../../OrthancServer/Plugins/Engine/OrthancPlugins.cpp
 nullPointer:../../OrthancFramework/UnitTestsSources/RestApiTests.cpp:322
 stlFindInsert:../../OrthancFramework/Sources/DicomFormat/DicomMap.cpp:1525
 stlFindInsert:../../OrthancFramework/Sources/RestApi/RestApiCallDocumentation.cpp:166
 stlFindInsert:../../OrthancFramework/Sources/RestApi/RestApiCallDocumentation.cpp:74
-stlFindInsert:../../OrthancServer/Sources/Database/MainDicomTagsRegistry.cpp:65
-stlFindInsert:../../OrthancServer/Sources/OrthancWebDav.cpp:328
-stlFindInsert:../../OrthancServer/Sources/ServerJobs/MergeStudyJob.cpp:41
-stlFindInsert:../../OrthancServer/Sources/ServerJobs/SplitStudyJob.cpp:191
-stlFindInsert:../../OrthancServer/Sources/ServerJobs/ResourceModificationJob.cpp:361
 syntaxError:../../OrthancFramework/Sources/SQLite/FunctionContext.h:53
 syntaxError:../../OrthancFramework/UnitTestsSources/DicomMapTests.cpp:74
 syntaxError:../../OrthancFramework/UnitTestsSources/ZipTests.cpp:133
@@ -29,13 +22,9 @@
 useInitializationList:../../OrthancFramework/Sources/Images/PngReader.cpp:91
 useInitializationList:../../OrthancFramework/Sources/Images/PngWriter.cpp:99
 useInitializationList:../../OrthancServer/Sources/ServerJobs/DicomModalityStoreJob.cpp:275
-assertWithSideEffect:../../OrthancServer/Plugins/Engine/OrthancPluginDatabase.cpp:277
-assertWithSideEffect:../../OrthancServer/Plugins/Engine/OrthancPluginDatabase.cpp:1026
 assertWithSideEffect:../../OrthancServer/Sources/Database/Compatibility/DatabaseLookup.cpp:292
 assertWithSideEffect:../../OrthancServer/Sources/Database/Compatibility/DatabaseLookup.cpp:391
-assertWithSideEffect:../../OrthancServer/Sources/Database/StatelessDatabaseOperations.cpp:3066
 assertWithSideEffect:../../OrthancServer/Sources/ServerJobs/ResourceModificationJob.cpp:286
-assertWithSideEffect:../../OrthancFramework/Sources/DicomNetworking/Internals/CommandDispatcher.cpp:454
 variableScope:../../OrthancServer/Sources/OrthancRestApi/OrthancRestApi.cpp:228
 variableScope:../../OrthancServer/Sources/ServerJobs/OrthancPeerStoreJob.cpp:94
 uselessOverride:../../OrthancFramework/Sources/MultiThreading/IRunnableBySteps.h:35
@@ -66,15 +55,20 @@
 throwInNoexceptFunction:../../OrthancServer/Plugins/Samples/Common/OrthancPluginCppWrapper.h:496
 rethrowNoCurrentException:../../OrthancFramework/UnitTestsSources/FromDcmtkTests.cpp
 rethrowNoCurrentException:../../OrthancFramework/UnitTestsSources/RestApiTests.cpp
+constParameterPointer:../../OrthancFramework/Sources/Toolbox.cpp:3046
+constParameterCallback:../../OrthancServer/Sources/OrthancGetRequestHandler.cpp
+constParameterCallback:../../OrthancFramework/Sources/DicomNetworking/DicomStoreUserConnection.cpp
+constParameterCallback:../../OrthancFramework/Sources/DicomNetworking/Internals/StoreScp.cpp
+constParameterCallback:../../OrthancFramework/Sources/Pkcs11.cpp
+constParameterCallback:../../OrthancServer/Plugins/Samples/Common/OrthancPluginCppWrapper.cpp:3449
+unknownMacro:../../OrthancFramework/Sources/DicomParsing/DicomModification.cpp:39
 EOF
 
-# TODO: re-enable nullPointerOutOfMemory
 
 ${CPPCHECK} -j 8 --enable=all --quiet --std=c++11 \
             --suppress=missingIncludeSystem \
             --suppress=missingInclude \
             --suppress=useStlAlgorithm \
-            --suppress=nullPointerOutOfMemory \
             --check-level=exhaustive \
             --suppressions-list=/tmp/cppcheck-suppressions.txt \
             -DBOOST_HAS_DATE_TIME=1 \
--- a/OrthancServer/Sources/Database/StatelessDatabaseOperations.cpp	Wed Nov 12 19:30:44 2025 +0100
+++ b/OrthancServer/Sources/Database/StatelessDatabaseOperations.cpp	Sat Nov 15 12:28:27 2025 +0100
@@ -2718,12 +2718,14 @@
             if (transaction.HasReachedMaxStorageSize(maximumStorageSize_, instanceSize))
             {
               storeStatus_ = StoreStatus_StorageFull;
-              throw OrthancException(ErrorCode_FullStorage, HttpStatus_507_InsufficientStorage, "Maximum storage size reached"); // throw to cancel the transaction
+              throw OrthancException(ErrorCode_FullStorage, "Maximum storage size reached") // throw to cancel the transaction
+                .SetHttpStatus(HttpStatus_507_InsufficientStorage);
             }
             if (transaction.HasReachedMaxPatientCount(maximumPatientCount_, hashPatient_))
             {
               storeStatus_ = StoreStatus_StorageFull;
-              throw OrthancException(ErrorCode_FullStorage, HttpStatus_507_InsufficientStorage, "Maximum patient count reached");  // throw to cancel the transaction
+              throw OrthancException(ErrorCode_FullStorage, "Maximum patient count reached")  // throw to cancel the transaction
+                .SetHttpStatus(HttpStatus_507_InsufficientStorage);
             }
           }
           else
@@ -3015,7 +3017,7 @@
         int64_t resourceId;
         if (!transaction.LookupResource(resourceId, resourceType, publicId_))
         {
-          throw OrthancException(ErrorCode_InexistentItem, HttpStatus_404_NotFound);
+          throw OrthancException(ErrorCode_InexistentItem).SetHttpStatus(HttpStatus_404_NotFound);
         }
         else
         {
--- a/OrthancServer/Sources/DicomInstanceOrigin.cpp	Wed Nov 12 19:30:44 2025 +0100
+++ b/OrthancServer/Sources/DicomInstanceOrigin.cpp	Sat Nov 15 12:28:27 2025 +0100
@@ -121,7 +121,7 @@
   {
     if (origin_ == RequestOrigin_DicomProtocol)
     {
-      result = dicomRemoteAet_.c_str();
+      result = dicomRemoteAet_;
       return true;
     }
     else
--- a/OrthancServer/Sources/ServerContext.cpp	Wed Nov 12 19:30:44 2025 +0100
+++ b/OrthancServer/Sources/ServerContext.cpp	Sat Nov 15 12:28:27 2025 +0100
@@ -855,11 +855,11 @@
 
           case StoreStatus_Failure:
             LOG(ERROR) << "Unknown store failure while storing instance " << resultPublicId;
-            throw OrthancException(ErrorCode_InternalError, HttpStatus_500_InternalServerError);
+            throw OrthancException(ErrorCode_InternalError).SetHttpStatus(HttpStatus_500_InternalServerError);
 
           case StoreStatus_StorageFull:
             LOG(ERROR) << "Storage full while storing instance " << resultPublicId;
-            throw OrthancException(ErrorCode_FullStorage, HttpStatus_507_InsufficientStorage);
+            throw OrthancException(ErrorCode_FullStorage).SetHttpStatus(HttpStatus_507_InsufficientStorage);
 
           default:
             // This should never happen
--- a/OrthancServer/Sources/ServerJobs/ArchiveJob.h	Wed Nov 12 19:30:44 2025 +0100
+++ b/OrthancServer/Sources/ServerJobs/ArchiveJob.h	Sat Nov 15 12:28:27 2025 +0100
@@ -153,5 +153,9 @@
       }
       return false;
     }
+
+    virtual void LookupErrorPayload(ErrorPayload& payload) const ORTHANC_OVERRIDE
+    {
+    }
   };
 }
--- a/OrthancServer/Sources/ServerJobs/DicomRetrieveScuBaseJob.cpp	Wed Nov 12 19:30:44 2025 +0100
+++ b/OrthancServer/Sources/ServerJobs/DicomRetrieveScuBaseJob.cpp	Sat Nov 15 12:28:27 2025 +0100
@@ -24,6 +24,7 @@
 #include "DicomGetScuJob.h"
 
 #include "../../../OrthancFramework/Sources/DicomParsing/FromDcmtkBridge.h"
+#include "../../../OrthancFramework/Sources/DicomNetworking/DimseErrorPayload.h"
 #include "../../../OrthancFramework/Sources/SerializationToolbox.h"
 #include "../ServerContext.h"
 #include <dcmtk/dcmnet/dimse.h>
@@ -100,8 +101,13 @@
     }
     catch (OrthancException& e)
     {
-      dimseErrorStatus_ = e.GetDimseErrorStatus();
-      throw e;
+      if (e.GetPayload().HasContent() &&
+          e.GetPayload().GetType() == ErrorPayloadType_Dimse)
+      {
+        dimseErrorStatus_ = GetDimseErrorStatusFromPayload(e.GetPayload());
+      }
+
+      throw;
     }
     return true;
   }
@@ -368,4 +374,12 @@
       currentCommand_->AddReceivedInstance(instanceId);
     }
   }
+
+  void DicomRetrieveScuBaseJob::LookupErrorPayload(ErrorPayload& payload) const
+  {
+    Json::Value publicContent;
+    GetPublicContent(publicContent);
+
+    payload.SetContent(ErrorPayloadType_Dimse, publicContent["Details"]);
+  }
 }
--- a/OrthancServer/Sources/ServerJobs/DicomRetrieveScuBaseJob.h	Wed Nov 12 19:30:44 2025 +0100
+++ b/OrthancServer/Sources/ServerJobs/DicomRetrieveScuBaseJob.h	Sat Nov 15 12:28:27 2025 +0100
@@ -173,5 +173,7 @@
     static void AddReceivedInstanceFromCStore(uint16_t originatorMessageId, 
                                               const std::string& originatorAet, 
                                               const std::string& instanceId);
+
+    virtual void LookupErrorPayload(ErrorPayload& payload) const ORTHANC_OVERRIDE;
   };
 }
--- a/OrthancServer/Sources/ServerJobs/ThreadedSetOfInstancesJob.h	Wed Nov 12 19:30:44 2025 +0100
+++ b/OrthancServer/Sources/ServerJobs/ThreadedSetOfInstancesJob.h	Sat Nov 15 12:28:27 2025 +0100
@@ -174,5 +174,9 @@
     virtual void SetUserData(const Json::Value& userData) ORTHANC_OVERRIDE;
 
     virtual bool GetUserData(Json::Value& userData) const ORTHANC_OVERRIDE;
+
+    virtual void LookupErrorPayload(ErrorPayload& payload) const ORTHANC_OVERRIDE
+    {
+    }
   };
 }
--- a/OrthancServer/Sources/main.cpp	Wed Nov 12 19:30:44 2025 +0100
+++ b/OrthancServer/Sources/main.cpp	Sat Nov 15 12:28:27 2025 +0100
@@ -714,9 +714,9 @@
         message["Details"] = exception.GetDetails();
       }
 
-      if (exception.HasDimseErrorStatus())
+      if (exception.GetPayload().HasContent())
       {
-        message["DimseErrorStatus"] = exception.GetDimseErrorStatus();
+        exception.GetPayload().Format(message["ErrorPayload"]);
       }
 
       std::string info = message.toStyledString();
--- a/OrthancServer/UnitTestsSources/ServerJobsTests.cpp	Wed Nov 12 19:30:44 2025 +0100
+++ b/OrthancServer/UnitTestsSources/ServerJobsTests.cpp	Sat Nov 15 12:28:27 2025 +0100
@@ -151,6 +151,10 @@
     {
       return false;
     }
+
+    virtual void LookupErrorPayload(ErrorPayload& payload) const ORTHANC_OVERRIDE
+    {
+    }
   };
 
 
@@ -226,6 +230,10 @@
     {
       return false;
     }
+
+    virtual void LookupErrorPayload(ErrorPayload& payload) const ORTHANC_OVERRIDE
+    {
+    }
   };