changeset 3960:4d36d6e64c6d

integration c-get->mainline
author Sebastien Jodogne <s.jodogne@gmail.com>
date Wed, 20 May 2020 18:37:31 +0200
parents 6e14f2da7c7e (current diff) 76a24be12912 (diff)
children d30eb4ae5bb6
files
diffstat 22 files changed, 1367 insertions(+), 5 deletions(-) [+]
line wrap: on
line diff
--- a/CMakeLists.txt	Wed May 20 16:42:44 2020 +0200
+++ b/CMakeLists.txt	Wed May 20 18:37:31 2020 +0200
@@ -73,6 +73,7 @@
   OrthancServer/OrthancHttpHandler.cpp
   OrthancServer/OrthancInitialization.cpp
   OrthancServer/OrthancMoveRequestHandler.cpp
+  OrthancServer/OrthancGetRequestHandler.cpp
   OrthancServer/OrthancRestApi/OrthancRestAnonymizeModify.cpp
   OrthancServer/OrthancRestApi/OrthancRestApi.cpp
   OrthancServer/OrthancRestApi/OrthancRestArchive.cpp
--- a/Core/DicomNetworking/DicomServer.cpp	Wed May 20 16:42:44 2020 +0200
+++ b/Core/DicomNetworking/DicomServer.cpp	Wed May 20 18:37:31 2020 +0200
@@ -92,6 +92,7 @@
     modalities_ = NULL;
     findRequestHandlerFactory_ = NULL;
     moveRequestHandlerFactory_ = NULL;
+    getRequestHandlerFactory_ = NULL;
     storeRequestHandlerFactory_ = NULL;
     worklistRequestHandlerFactory_ = NULL;
     storageCommitmentFactory_ = NULL;
@@ -244,6 +245,29 @@
     }
   }
 
+  void DicomServer::SetGetRequestHandlerFactory(IGetRequestHandlerFactory& factory)
+  {
+    Stop();
+    getRequestHandlerFactory_ = &factory;
+  }
+
+  bool DicomServer::HasGetRequestHandlerFactory() const
+  {
+    return (getRequestHandlerFactory_ != NULL);
+  }
+
+  IGetRequestHandlerFactory& DicomServer::GetGetRequestHandlerFactory() const
+  {
+    if (HasGetRequestHandlerFactory())
+    {
+      return *getRequestHandlerFactory_;
+    }
+    else
+    {
+      throw OrthancException(ErrorCode_NoCGetHandler);
+    }
+  }
+
   void DicomServer::SetStoreRequestHandlerFactory(IStoreRequestHandlerFactory& factory)
   {
     Stop();
--- a/Core/DicomNetworking/DicomServer.h	Wed May 20 16:42:44 2020 +0200
+++ b/Core/DicomNetworking/DicomServer.h	Wed May 20 18:37:31 2020 +0200
@@ -39,6 +39,7 @@
 
 #include "IFindRequestHandlerFactory.h"
 #include "IMoveRequestHandlerFactory.h"
+#include "IGetRequestHandlerFactory.h"
 #include "IStoreRequestHandlerFactory.h"
 #include "IWorklistRequestHandlerFactory.h"
 #include "IStorageCommitmentRequestHandlerFactory.h"
@@ -81,6 +82,7 @@
     IRemoteModalities* modalities_;
     IFindRequestHandlerFactory* findRequestHandlerFactory_;
     IMoveRequestHandlerFactory* moveRequestHandlerFactory_;
+    IGetRequestHandlerFactory* getRequestHandlerFactory_;
     IStoreRequestHandlerFactory* storeRequestHandlerFactory_;
     IWorklistRequestHandlerFactory* worklistRequestHandlerFactory_;
     IStorageCommitmentRequestHandlerFactory* storageCommitmentFactory_;
@@ -116,6 +118,10 @@
     bool HasMoveRequestHandlerFactory() const;
     IMoveRequestHandlerFactory& GetMoveRequestHandlerFactory() const;
 
+    void SetGetRequestHandlerFactory(IGetRequestHandlerFactory& handler);
+    bool HasGetRequestHandlerFactory() const;
+    IGetRequestHandlerFactory& GetGetRequestHandlerFactory() const;
+
     void SetStoreRequestHandlerFactory(IStoreRequestHandlerFactory& handler);
     bool HasStoreRequestHandlerFactory() const;
     IStoreRequestHandlerFactory& GetStoreRequestHandlerFactory() const;
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Core/DicomNetworking/IGetRequestHandler.h	Wed May 20 18:37:31 2020 +0200
@@ -0,0 +1,80 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * In addition, as a special exception, the copyright holders of this
+ * program give permission to link the code of its release with the
+ * OpenSSL project's "OpenSSL" library (or with modified versions of it
+ * that use the same license as the "OpenSSL" library), and distribute
+ * the linked executables. You must obey the GNU General Public License
+ * in all respects for all of the code used other than "OpenSSL". If you
+ * modify file(s) with this exception, you may extend this exception to
+ * your version of the file(s), but you are not obligated to do so. If
+ * you do not wish to do so, delete this exception statement from your
+ * version. If you delete this exception statement from all source files
+ * in the program, then also delete it here.
+ * 
+ * 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#include <dcmtk/dcmnet/assoc.h>
+
+#include "../../Core/DicomFormat/DicomMap.h"
+
+#include <vector>
+#include <string>
+
+
+namespace Orthanc
+{
+  class IGetRequestHandler : boost::noncopyable
+  {
+  public:
+    enum Status
+    {
+      Status_Success,
+      Status_Failure,
+      Status_Warning
+    };
+    
+    virtual ~IGetRequestHandler()
+    {
+    }
+
+    virtual bool Handle(const DicomMap& input,
+                        const std::string& originatorIp,
+                        const std::string& originatorAet,
+                        const std::string& calledAet,
+                        uint32_t timeout) = 0;
+    
+    virtual unsigned int GetSubOperationCount() const = 0;
+    
+    virtual Status DoNext(T_ASC_Association *) = 0;
+    
+    virtual unsigned int GetRemainingCount() const = 0;
+    
+    virtual unsigned int GetCompletedCount() const = 0;
+    
+    virtual unsigned int GetWarningCount() const = 0;
+    
+    virtual unsigned int GetFailedCount() const = 0;
+    
+    virtual const std::string& GetFailedUids() const = 0;
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Core/DicomNetworking/IGetRequestHandlerFactory.h	Wed May 20 18:37:31 2020 +0200
@@ -0,0 +1,49 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * In addition, as a special exception, the copyright holders of this
+ * program give permission to link the code of its release with the
+ * OpenSSL project's "OpenSSL" library (or with modified versions of it
+ * that use the same license as the "OpenSSL" library), and distribute
+ * the linked executables. You must obey the GNU General Public License
+ * in all respects for all of the code used other than "OpenSSL". If you
+ * modify file(s) with this exception, you may extend this exception to
+ * your version of the file(s), but you are not obligated to do so. If
+ * you do not wish to do so, delete this exception statement from your
+ * version. If you delete this exception statement from all source files
+ * in the program, then also delete it here.
+ * 
+ * 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#include "IGetRequestHandler.h"
+
+namespace Orthanc
+{
+  class IGetRequestHandlerFactory : public boost::noncopyable
+  {
+  public:
+    virtual ~IGetRequestHandlerFactory()
+    {
+    }
+
+    virtual IGetRequestHandler* ConstructGetRequestHandler() = 0;
+  };
+}
--- a/Core/DicomNetworking/Internals/CommandDispatcher.cpp	Wed May 20 16:42:44 2020 +0200
+++ b/Core/DicomNetworking/Internals/CommandDispatcher.cpp	Wed May 20 18:37:31 2020 +0200
@@ -90,6 +90,7 @@
 #include "FindScp.h"
 #include "StoreScp.h"
 #include "MoveScp.h"
+#include "GetScp.h"
 #include "../../Compatibility.h"
 #include "../../Toolbox.h"
 #include "../../Logging.h"
@@ -275,6 +276,8 @@
       OFString sprofile;
       OFString temp_str;
 
+      
+
       cond = ASC_receiveAssociation(net, &assoc, 
                                     /*opt_maxPDU*/ ASC_DEFAULTMAXPDU, 
                                     NULL, NULL,
@@ -371,6 +374,14 @@
           knownAbstractSyntaxes.push_back(UID_MOVEStudyRootQueryRetrieveInformationModel);
           knownAbstractSyntaxes.push_back(UID_MOVEPatientRootQueryRetrieveInformationModel);
         }
+        
+        // For C-GET
+        if (server.HasGetRequestHandlerFactory())
+        {
+          knownAbstractSyntaxes.push_back(UID_GETStudyRootQueryRetrieveInformationModel);
+          knownAbstractSyntaxes.push_back(UID_GETPatientRootQueryRetrieveInformationModel);
+        }
+
 
         cond = ASC_acceptContextsWithPreferredTransferSyntaxes(
           assoc->params,
@@ -511,6 +522,10 @@
         assert(static_cast<int>(count) == numberOfDcmAllStorageSOPClassUIDs);
 #endif
       
+      // now that C-GET SCP is always enabled, the first branch of this if is useless
+      // TO BE ANALYZED by SJ
+      if (!server.HasGetRequestHandlerFactory())    // dcmqrsrv.cc line 828
+      {
         cond = ASC_acceptContextsWithPreferredTransferSyntaxes(
           assoc->params,
           dcmAllStorageSOPClassUIDs, count,
@@ -521,6 +536,55 @@
           AssociationCleanup(assoc);
           return NULL;
         }
+      }
+      else                                         // see dcmqrsrv.cc lines 839 - 876
+      {
+        /* accept storage syntaxes with proposed role */
+        T_ASC_PresentationContext pc;
+        T_ASC_SC_ROLE role;
+        int npc = ASC_countPresentationContexts(assoc->params);
+        for (int i = 0; i < npc; i++)
+        {
+          ASC_getPresentationContext(assoc->params, i, &pc);
+          if (dcmIsaStorageSOPClassUID(pc.abstractSyntax))
+          {
+            /*
+             ** We are prepared to accept whatever role the caller proposes.
+             ** Normally we can be the SCP of the Storage Service Class.
+             ** When processing the C-GET operation we can be the SCU of the Storage Service Class.
+             */
+            role = pc.proposedRole;
+            
+            /*
+             ** Accept in the order "least wanted" to "most wanted" transfer
+             ** syntax.  Accepting a transfer syntax will override previously
+             ** accepted transfer syntaxes.
+             */
+            for (int k = (int) storageTransferSyntaxes.size() - 1; k >= 0; k--)
+            {
+              for (int j = 0; j < (int)pc.transferSyntaxCount; j++)
+              {
+                /* if the transfer syntax was proposed then we can accept it
+                 * appears in our supported list of transfer syntaxes
+                 */
+                if (strcmp(pc.proposedTransferSyntaxes[j], storageTransferSyntaxes[k]) == 0)
+                {
+                  cond = ASC_acceptPresentationContext(
+                                                       assoc->params, pc.presentationContextID, storageTransferSyntaxes[k], role);
+                  if (cond.bad())
+                  {
+                    LOG(INFO) << cond.text();
+                    AssociationCleanup(assoc);
+                    return NULL;
+                  }
+                }
+              }
+            }
+          }
+        } /* for */
+        
+      }
+      
 
         if (!server.HasApplicationEntityFilter() ||
             server.GetApplicationEntityFilter().IsUnknownSopClassAccepted(remoteIp, remoteAet, calledAet))
@@ -733,6 +797,11 @@
             request = DicomRequestType_Move;
             supported = true;
             break;
+            
+          case DIMSE_C_GET_RQ:
+            request = DicomRequestType_Get;
+            supported = true;
+            break;
 
           case DIMSE_C_FIND_RQ:
             request = DicomRequestType_Find;
@@ -807,6 +876,19 @@
                 }
               }
               break;
+              
+            case DicomRequestType_Get:
+              if (server_.HasGetRequestHandlerFactory()) // Should always be true
+              {
+                std::auto_ptr<IGetRequestHandler> handler
+                  (server_.GetGetRequestHandlerFactory().ConstructGetRequestHandler());
+                
+                if (handler.get() != NULL)
+                {
+                  cond = Internals::getScp(assoc_, &msg, presID, *handler, remoteIp_, remoteAet_, calledAet_, associationTimeout_);
+                }
+              }
+              break;
 
             case DicomRequestType_Find:
               if (server_.HasFindRequestHandlerFactory() || // Should always be true
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Core/DicomNetworking/Internals/GetScp.cpp	Wed May 20 18:37:31 2020 +0200
@@ -0,0 +1,289 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * In addition, as a special exception, the copyright holders of this
+ * program give permission to link the code of its release with the
+ * OpenSSL project's "OpenSSL" library (or with modified versions of it
+ * that use the same license as the "OpenSSL" library), and distribute
+ * the linked executables. You must obey the GNU General Public License
+ * in all respects for all of the code used other than "OpenSSL". If you
+ * modify file(s) with this exception, you may extend this exception to
+ * your version of the file(s), but you are not obligated to do so. If
+ * you do not wish to do so, delete this exception statement from your
+ * version. If you delete this exception statement from all source files
+ * in the program, then also delete it here.
+ * 
+ * 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+
+
+/*=========================================================================
+
+  This file is based on portions of the following project:
+
+  Program: DCMTK 3.6.0
+  Module:  http://dicom.offis.de/dcmtk.php.en
+
+  Copyright (C) 1994-2011, OFFIS e.V.
+  All rights reserved.
+
+  This software and supporting documentation were developed by
+
+  OFFIS e.V.
+  R&D Division Health
+  Escherweg 2
+  26121 Oldenburg, Germany
+
+  Redistribution and use in source and binary forms, with or without
+  modification, are permitted provided that the following conditions
+  are met:
+
+  - Redistributions of source code must retain the above copyright
+  notice, this list of conditions and the following disclaimer.
+
+  - Redistributions in binary form must reproduce the above copyright
+  notice, this list of conditions and the following disclaimer in the
+  documentation and/or other materials provided with the distribution.
+
+  - Neither the name of OFFIS nor the names of its contributors may be
+  used to endorse or promote products derived from this software
+  without specific prior written permission.
+
+  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+  A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+  HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+  SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+  LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+  DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+  THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+  (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+  OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+  =========================================================================*/
+
+
+#include "../../PrecompiledHeaders.h"
+#include <dcmtk/dcmnet/diutil.h>
+#include <dcmtk/dcmdata/dcdeftag.h>
+#include "GetScp.h"
+
+#include <memory>
+
+#include "../../DicomParsing/FromDcmtkBridge.h"
+#include "../../DicomParsing/ToDcmtkBridge.h"
+#include "../../Logging.h"
+#include "../../OrthancException.h"
+
+#include <boost/lexical_cast.hpp>
+
+
+namespace Orthanc
+{
+  namespace
+  {
+    struct GetScpData
+    {
+      //  Handle returns void.
+      IGetRequestHandler* handler_;
+      DcmDataset* lastRequest_;
+      T_ASC_Association * assoc_;
+
+      std::string remoteIp_;
+      std::string remoteAet_;
+      std::string calledAet_;
+      int timeout_;
+
+      GetScpData()
+      {
+        handler_ = NULL;
+        lastRequest_ = NULL;
+        assoc_ = NULL;
+      };
+    };
+      
+    static DcmDataset *BuildFailedInstanceList(const std::string& failedUIDs)
+    {
+      if (failedUIDs.empty())
+      {
+        return NULL;
+      }
+      else
+      {
+        std::unique_ptr<DcmDataset> rspIds(new DcmDataset());
+        
+        if (!DU_putStringDOElement(rspIds.get(), DCM_FailedSOPInstanceUIDList, failedUIDs.c_str()))
+        {
+          throw OrthancException(ErrorCode_InternalError,
+                                 "getSCP: failed to build DCM_FailedSOPInstanceUIDList");
+        }
+
+        return rspIds.release();
+      }
+    }
+
+    static void GetScpCallback(
+      /* in */ 
+      void *callbackData,  
+      OFBool cancelled, 
+      T_DIMSE_C_GetRQ *request, 
+      DcmDataset *requestIdentifiers, 
+      int responseCount,
+      /* out */
+      T_DIMSE_C_GetRSP *response,
+      DcmDataset **responseIdentifiers,
+      DcmDataset **statusDetail)
+    {
+      bzero(response, sizeof(T_DIMSE_C_GetRSP));
+      *statusDetail = NULL;
+      *responseIdentifiers = NULL;   
+
+      GetScpData& data = *reinterpret_cast<GetScpData*>(callbackData);
+      if (data.lastRequest_ == NULL)
+      {
+        DicomMap input;
+        FromDcmtkBridge::ExtractDicomSummary(input, *requestIdentifiers);
+
+        try
+        {
+          if (!data.handler_->Handle(
+                input, data.remoteIp_, data.remoteAet_, data.calledAet_,
+                data.timeout_ < 0 ? 0 : static_cast<uint32_t>(data.timeout_)))
+          {
+            response->DimseStatus = STATUS_GET_Failed_UnableToProcess;
+            return;
+          }
+        }
+        catch (OrthancException& e)
+        {
+          // Internal error!
+          LOG(ERROR) << "IGetRequestHandler Failed: " << e.What();
+          response->DimseStatus = STATUS_GET_Failed_UnableToProcess;
+          return;
+        }
+
+        data.lastRequest_ = requestIdentifiers;
+      }
+      else if (data.lastRequest_ != requestIdentifiers)
+      {
+        // Internal error!
+        LOG(ERROR) << "IGetRequestHandler Failed: Internal error lastRequestIdentifier";
+        response->DimseStatus = STATUS_GET_Failed_UnableToProcess;
+        return;
+      }
+
+      if (data.handler_->GetRemainingCount() == 0)
+      {
+        response->DimseStatus = STATUS_Success;
+      }
+      else
+      {
+        IGetRequestHandler::Status status;
+
+        try
+        {
+          status = data.handler_->DoNext(data.assoc_);
+        }
+        catch (OrthancException& e)
+        {
+          // Internal error!
+          LOG(ERROR) << "IGetRequestHandler Failed: " << e.What();
+          response->DimseStatus = STATUS_GET_Failed_UnableToProcess;
+          return;
+        }
+        
+        if (status == STATUS_Success)
+        {
+          if (responseCount < static_cast<int>(data.handler_->GetRemainingCount()))
+          {
+            response->DimseStatus = STATUS_Pending;
+          }
+          else
+          {
+            response->DimseStatus = STATUS_Success;
+          }
+        }
+        else
+        {
+          response->DimseStatus = STATUS_GET_Failed_UnableToProcess;
+          
+          if (data.handler_->GetFailedCount() > 0 ||
+              data.handler_->GetWarningCount() > 0) 
+          {
+            response->DimseStatus = STATUS_GET_Warning_SubOperationsCompleteOneOrMoreFailures;
+          }
+          
+          /*
+           * if all the sub-operations failed then we need to generate
+           * a failed or refused status.  cf. DICOM part 4, C.4.3.3.1
+           * we choose to generate a "Refused - Out of Resources -
+           * Unable to perform suboperations" status.
+           */
+          if ((data.handler_->GetFailedCount() > 0) &&
+              ((data.handler_->GetCompletedCount() +
+                data.handler_->GetWarningCount()) == 0)) 
+          {
+            response->DimseStatus = STATUS_GET_Refused_OutOfResourcesSubOperations;
+          }
+          
+          *responseIdentifiers = BuildFailedInstanceList(data.handler_->GetFailedUids());
+        }
+      }
+      
+      response->NumberOfRemainingSubOperations = data.handler_->GetRemainingCount();
+      response->NumberOfCompletedSubOperations = data.handler_->GetCompletedCount();
+      response->NumberOfFailedSubOperations = data.handler_->GetFailedCount();
+      response->NumberOfWarningSubOperations = data.handler_->GetWarningCount();
+    }
+  }
+
+  OFCondition Internals::getScp(T_ASC_Association * assoc,
+                                T_DIMSE_Message * msg, 
+                                T_ASC_PresentationContextID presID,
+                                IGetRequestHandler& handler,
+                                std::string remoteIp,
+                                std::string remoteAet,
+                                std::string calledAet,
+                                int timeout)
+  {
+    GetScpData data;
+    data.lastRequest_ = NULL;
+    data.handler_ = &handler;
+    data.assoc_ = assoc;
+    data.remoteIp_ = remoteIp;
+    data.remoteAet_ = remoteAet;
+    data.calledAet_ = calledAet;
+    data.timeout_ = timeout;
+
+    OFCondition cond = DIMSE_getProvider(assoc, presID, &msg->msg.CGetRQ, 
+                                         GetScpCallback, &data,
+                                         /*opt_blockMode*/ (timeout ? DIMSE_NONBLOCKING : DIMSE_BLOCKING),
+                                         /*opt_dimse_timeout*/ timeout);
+    
+    // if some error occured, dump corresponding information and remove the outfile if necessary
+    if (cond.bad())
+    {
+      OFString temp_str;
+      LOG(ERROR) << "Get SCP Failed: " << cond.text();
+    }
+
+    return cond;
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Core/DicomNetworking/Internals/GetScp.h	Wed May 20 18:37:31 2020 +0200
@@ -0,0 +1,51 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * In addition, as a special exception, the copyright holders of this
+ * program give permission to link the code of its release with the
+ * OpenSSL project's "OpenSSL" library (or with modified versions of it
+ * that use the same license as the "OpenSSL" library), and distribute
+ * the linked executables. You must obey the GNU General Public License
+ * in all respects for all of the code used other than "OpenSSL". If you
+ * modify file(s) with this exception, you may extend this exception to
+ * your version of the file(s), but you are not obligated to do so. If
+ * you do not wish to do so, delete this exception statement from your
+ * version. If you delete this exception statement from all source files
+ * in the program, then also delete it here.
+ * 
+ * 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#include "../IGetRequestHandler.h"
+
+namespace Orthanc
+{
+  namespace Internals
+  {
+    OFCondition getScp(T_ASC_Association * assoc,
+                       T_DIMSE_Message * msg, 
+                       T_ASC_PresentationContextID presID,
+                       IGetRequestHandler& handler,
+                       std::string remoteIp,
+                       std::string remoteAet,
+                       std::string calledAet,
+                       int timeout);
+  }
+}
--- a/Core/Enumerations.cpp	Wed May 20 16:42:44 2020 +0200
+++ b/Core/Enumerations.cpp	Wed May 20 18:37:31 2020 +0200
@@ -369,6 +369,9 @@
       case ErrorCode_AlreadyExistingTag:
         return "Cannot override the value of a tag that already exists";
 
+      case ErrorCode_NoCGetHandler:
+        return "No request handler factory for DICOM C-GET SCP";
+
       case ErrorCode_NoStorageCommitmentHandler:
         return "No request handler factory for DICOM N-ACTION SCP (storage commitment)";
 
--- a/Core/Enumerations.h	Wed May 20 16:42:44 2020 +0200
+++ b/Core/Enumerations.h	Wed May 20 18:37:31 2020 +0200
@@ -241,6 +241,7 @@
     ErrorCode_NoWorklistHandler = 2041    /*!< No request handler factory for DICOM C-Find Modality SCP */,
     ErrorCode_AlreadyExistingTag = 2042    /*!< Cannot override the value of a tag that already exists */,
     ErrorCode_NoStorageCommitmentHandler = 2043    /*!< No request handler factory for DICOM N-ACTION SCP (storage commitment) */,
+    ErrorCode_NoCGetHandler = 2044    /*!< No request handler factory for DICOM C-GET SCP */,
     ErrorCode_UnsupportedMediaType = 3000    /*!< Unsupported media type */,
     ErrorCode_START_PLUGINS = 1000000
   };
--- a/NEWS	Wed May 20 16:42:44 2020 +0200
+++ b/NEWS	Wed May 20 18:37:31 2020 +0200
@@ -10,6 +10,7 @@
 * New configuration options related to transcoding:
   "TranscodeDicomProtocol", "BuiltinDecoderTranscoderOrder",
   "IngestTranscoding" and "DicomLossyTranscodingQuality"
+* Support of DICOM C-GET SCP (contribution by Varian)
 
 REST API
 --------
@@ -57,9 +58,9 @@
 * Moved the GDCM sample plugin out of the Orthanc repository as a separate plugin
 * Fix missing body in "OrthancPluginHttpPost()" and "OrthancPluginHttpPut()"
 * Fix issue #169 (TransferSyntaxUID change from Explicit to Implicit during C-STORE SCU)
+* Fix issue #179 (deadlock in Python plugins)
 * Upgraded dependencies for static builds (notably on Windows and LSB):
   - openssl 1.1.1g
-* Fix issue #179 (deadlock in Python plugins)
 
 
 Version 1.6.1 (2020-04-21)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/OrthancServer/OrthancGetRequestHandler.cpp	Wed May 20 18:37:31 2020 +0200
@@ -0,0 +1,567 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * In addition, as a special exception, the copyright holders of this
+ * program give permission to link the code of its release with the
+ * OpenSSL project's "OpenSSL" library (or with modified versions of it
+ * that use the same license as the "OpenSSL" library), and distribute
+ * the linked executables. You must obey the GNU General Public License
+ * in all respects for all of the code used other than "OpenSSL". If you
+ * modify file(s) with this exception, you may extend this exception to
+ * your version of the file(s), but you are not obligated to do so. If
+ * you do not wish to do so, delete this exception statement from your
+ * version. If you delete this exception statement from all source files
+ * in the program, then also delete it here.
+ * 
+ * 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+#include "PrecompiledHeadersServer.h"
+#include "OrthancGetRequestHandler.h"
+
+#include <dcmtk/dcmdata/dcdeftag.h>
+#include <dcmtk/dcmdata/dcfilefo.h>
+#include <dcmtk/dcmdata/dcistrmb.h>
+#include <dcmtk/dcmnet/assoc.h>
+#include <dcmtk/dcmnet/dimse.h>
+#include <dcmtk/dcmnet/diutil.h>
+
+#include "../../Core/DicomParsing/FromDcmtkBridge.h"
+#include "../Core/DicomFormat/DicomArray.h"
+#include "../Core/Logging.h"
+#include "../Core/MetricsRegistry.h"
+#include "OrthancConfiguration.h"
+#include "ServerContext.h"
+#include "ServerJobs/DicomModalityStoreJob.h"
+
+
+namespace Orthanc
+{
+  namespace
+  {
+    // Anonymous namespace to avoid clashes between compilation modules
+    
+    static void GetSubOpProgressCallback(
+      void * /* callbackData == pointer to the "OrthancGetRequestHandler" object */,
+      T_DIMSE_StoreProgress *progress,
+      T_DIMSE_C_StoreRQ * /*req*/)
+    {
+      // SBL - no logging to be done here.
+    }
+  }
+
+  OrthancGetRequestHandler::Status
+  OrthancGetRequestHandler::DoNext(T_ASC_Association* assoc)
+  {
+    if (position_ >= instances_.size())
+    {
+      return Status_Failure;
+    }
+    
+    const std::string& id = instances_[position_++];
+
+    std::string dicom;
+    context_.ReadDicom(dicom, id);
+    
+    if (dicom.size() <= 0)
+    {
+      return Status_Failure;
+    }
+
+    std::unique_ptr<DcmFileFormat> parsed(
+      FromDcmtkBridge::LoadFromMemoryBuffer(dicom.c_str(), dicom.size()));
+
+    if (parsed.get() == NULL ||
+        parsed->getDataset() == NULL)
+    {
+      throw OrthancException(ErrorCode_InternalError);
+    }
+    
+    DcmDataset& dataset = *parsed->getDataset();
+    
+    OFString a, b;
+    if (!dataset.findAndGetOFString(DCM_SOPClassUID, a).good() ||
+        !dataset.findAndGetOFString(DCM_SOPInstanceUID, b).good())
+    {
+      throw OrthancException(ErrorCode_NoSopClassOrInstance,
+                             "Unable to determine the SOP class/instance for C-STORE with AET " +
+                             originatorAet_);
+    }
+
+    std::string sopClassUid(a.c_str());
+    std::string sopInstanceUid(b.c_str());
+    
+    OFCondition cond = PerformGetSubOp(assoc, sopClassUid, sopInstanceUid, parsed.release());
+    
+    if (getCancelled_)
+    {
+      LOG(INFO) << "C-GET SCP: Received C-Cancel RQ";
+    }
+    
+    if (cond.bad() || getCancelled_)
+    {
+      return Status_Failure;
+    }
+    
+    return Status_Success;
+  }
+
+  
+  void OrthancGetRequestHandler::AddFailedUIDInstance(const std::string& sopInstance)
+  {
+    if (failedUIDs_.empty())
+    {
+      failedUIDs_ = sopInstance;
+    }
+    else
+    {
+      failedUIDs_ += "\\" + sopInstance;
+    }
+  }
+
+
+  static bool SelectPresentationContext(T_ASC_PresentationContextID& selectedPresentationId,
+                                        DicomTransferSyntax& selectedSyntax,
+                                        T_ASC_Association* assoc,
+                                        const std::string& sopClassUid,
+                                        DicomTransferSyntax sourceSyntax,
+                                        bool allowTranscoding)
+  {
+    typedef std::map<DicomTransferSyntax, T_ASC_PresentationContextID> Accepted;
+
+    Accepted accepted;
+
+    /**
+     * 1. Inspect and index all the accepted transfer syntaxes. This
+     * is similar to the code from "DicomAssociation::Open()".
+     **/
+
+    LST_HEAD **l = &assoc->params->DULparams.acceptedPresentationContext;
+    if (*l != NULL)
+    {
+      DUL_PRESENTATIONCONTEXT* pc = (DUL_PRESENTATIONCONTEXT*) LST_Head(l);
+      LST_Position(l, (LST_NODE*)pc);
+      while (pc)
+      {
+        if (pc->result == ASC_P_ACCEPTANCE &&
+            std::string(pc->abstractSyntax) == sopClassUid)
+        {
+          DicomTransferSyntax transferSyntax;
+          if (LookupTransferSyntax(transferSyntax, pc->acceptedTransferSyntax))
+          {
+            accepted[transferSyntax] = pc->presentationContextID;
+          }
+          else
+          {
+            LOG(WARNING) << "C-GET: Unknown transfer syntax received: "
+                         << pc->acceptedTransferSyntax;
+          }
+        }
+            
+        pc = (DUL_PRESENTATIONCONTEXT*) LST_Next(l);
+      }
+    }
+
+    
+    /**
+     * 2. Select the preferred transfer syntaxes, which corresponds to
+     * the source transfer syntax, plus all the uncompressed transfer
+     * syntaxes if transcoding is enabled.
+     **/
+    
+    std::list<DicomTransferSyntax> preferred;
+    preferred.push_back(sourceSyntax);
+
+    if (allowTranscoding)
+    {
+      if (sourceSyntax != DicomTransferSyntax_LittleEndianImplicit)
+      {
+        // Default Transfer Syntax for DICOM
+        preferred.push_back(DicomTransferSyntax_LittleEndianImplicit);
+      }
+
+      if (sourceSyntax != DicomTransferSyntax_LittleEndianExplicit)
+      {
+        preferred.push_back(DicomTransferSyntax_LittleEndianExplicit);
+      }
+
+      if (sourceSyntax != DicomTransferSyntax_BigEndianExplicit)
+      {
+        // Retired
+        preferred.push_back(DicomTransferSyntax_BigEndianExplicit);
+      }
+    }
+
+
+    /**
+     * 3. Lookup whether one of the preferred transfer syntaxes was
+     * accepted.
+     **/
+    
+    for (std::list<DicomTransferSyntax>::const_iterator
+           it = preferred.begin(); it != preferred.end(); ++it)
+    {    
+      Accepted::const_iterator found = accepted.find(*it);
+      if (found != accepted.end())
+      {
+        selectedPresentationId = found->second;
+        selectedSyntax = *it;
+        return true;
+      }
+    }
+
+    // No preferred syntax was accepted
+    return false;
+  }                                                           
+
+
+  OFCondition OrthancGetRequestHandler::PerformGetSubOp(T_ASC_Association* assoc,
+                                                        const std::string& sopClassUid,
+                                                        const std::string& sopInstanceUid,
+                                                        DcmFileFormat* dicomRaw)
+  {
+    assert(dicomRaw != NULL);
+    std::unique_ptr<DcmFileFormat> dicom(dicomRaw);
+    
+    DicomTransferSyntax sourceSyntax;
+    if (!FromDcmtkBridge::LookupOrthancTransferSyntax(sourceSyntax, *dicom))
+    {
+      nFailed_++;
+      AddFailedUIDInstance(sopInstanceUid);
+      LOG(ERROR) << "C-GET SCP: Unknown transfer syntax: ("
+                 << dcmSOPClassUIDToModality(sopClassUid.c_str(), "OT") << ") " << sopClassUid;
+      return DIMSE_NOVALIDPRESENTATIONCONTEXTID;
+    }
+
+    bool allowTranscoding = (context_.IsTranscodeDicomProtocol() &&
+                             remote_.IsTranscodingAllowed());
+    
+    T_ASC_PresentationContextID presId;
+    DicomTransferSyntax selectedSyntax;
+    if (!SelectPresentationContext(presId, selectedSyntax, assoc, sopClassUid,
+                                   sourceSyntax, allowTranscoding) ||
+        presId == 0)
+    {
+      nFailed_++;
+      AddFailedUIDInstance(sopInstanceUid);
+      LOG(ERROR) << "C-GET SCP: storeSCU: No presentation context for: ("
+                 << dcmSOPClassUIDToModality(sopClassUid.c_str(), "OT") << ") " << sopClassUid;
+      return DIMSE_NOVALIDPRESENTATIONCONTEXTID;
+    }
+    else
+    {
+      // make sure that we can send images in this presentation context
+      T_ASC_PresentationContext pc;
+      ASC_findAcceptedPresentationContext(assoc->params, presId, &pc);
+      // the acceptedRole is the association requestor role
+      if ((pc.acceptedRole != ASC_SC_ROLE_SCP) &&
+          (pc.acceptedRole != ASC_SC_ROLE_SCUSCP))
+      {
+        // the role is not appropriate
+        nFailed_++;
+        AddFailedUIDInstance(sopInstanceUid);
+        LOG(ERROR) << "C-GET SCP: storeSCU: [No presentation context with requestor SCP role for: ("
+                   << dcmSOPClassUIDToModality(sopClassUid.c_str(), "OT") << ") " << sopClassUid;
+        return DIMSE_NOVALIDPRESENTATIONCONTEXTID;
+      }
+    }
+
+    const DIC_US msgId = assoc->nextMsgID++;
+    
+    T_DIMSE_C_StoreRQ req;
+    memset(&req, 0, sizeof(req));
+    req.MessageID = msgId;
+    strncpy(req.AffectedSOPClassUID, sopClassUid.c_str(), DIC_UI_LEN);
+    strncpy(req.AffectedSOPInstanceUID, sopInstanceUid.c_str(), DIC_UI_LEN);
+    req.DataSetType = DIMSE_DATASET_PRESENT;
+    req.Priority = DIMSE_PRIORITY_MEDIUM;
+    req.opts = 0;
+    
+    T_DIMSE_C_StoreRSP rsp;
+    memset(&rsp, 0, sizeof(rsp));
+
+    LOG(INFO) << "Store SCU RQ: MsgID " << msgId << ", ("
+              << dcmSOPClassUIDToModality(sopClassUid.c_str(), "OT") << ")";
+    
+    T_DIMSE_DetectedCancelParameters cancelParameters;
+    memset(&cancelParameters, 0, sizeof(cancelParameters));
+
+    std::unique_ptr<DcmDataset> stDetail;
+
+    OFCondition cond;
+
+    if (sourceSyntax == selectedSyntax)
+    {
+      // No transcoding is required
+      DcmDataset *stDetailTmp = NULL;
+      cond = DIMSE_storeUser(
+        assoc, presId, &req, NULL /* imageFileName */, dicom->getDataset(),
+        GetSubOpProgressCallback, this /* callbackData */,
+        (timeout_ > 0 ? DIMSE_NONBLOCKING : DIMSE_BLOCKING), timeout_,
+        &rsp, &stDetailTmp, &cancelParameters);
+      stDetail.reset(stDetailTmp);
+    }
+    else
+    {
+      // Transcoding to the selected uncompressed transfer syntax
+      IDicomTranscoder::DicomImage source, transcoded;
+      source.AcquireParsed(dicom.release());
+
+      std::set<DicomTransferSyntax> ts;
+      ts.insert(selectedSyntax);
+      
+      if (context_.Transcode(transcoded, source, ts, true))
+      {
+        // Transcoding has succeeded
+        DcmDataset *stDetailTmp = NULL;
+        cond = DIMSE_storeUser(
+          assoc, presId, &req, NULL /* imageFileName */,
+          transcoded.GetParsed().getDataset(),
+          GetSubOpProgressCallback, this /* callbackData */,
+          (timeout_ > 0 ? DIMSE_NONBLOCKING : DIMSE_BLOCKING), timeout_,
+          &rsp, &stDetailTmp, &cancelParameters);
+        stDetail.reset(stDetailTmp);
+      }
+      else
+      {
+        // Cannot transcode
+        nFailed_++;
+        AddFailedUIDInstance(sopInstanceUid);
+        LOG(ERROR) << "C-GET SCP: Cannot transcode " << sopClassUid
+                   << " from transfer syntax " << GetTransferSyntaxUid(sourceSyntax)
+                   << " to " << GetTransferSyntaxUid(selectedSyntax);
+        return DIMSE_NOVALIDPRESENTATIONCONTEXTID;
+      }      
+    }
+    
+    if (cond.good())
+    {
+      if (cancelParameters.cancelEncountered)
+      {
+        if (origPresId_ == cancelParameters.presId &&
+            origMsgId_ == cancelParameters.req.MessageIDBeingRespondedTo)
+        {
+          getCancelled_ = OFTrue;
+        }
+        else
+        {
+          LOG(ERROR) << "C-GET SCP: Unexpected C-Cancel-RQ encountered: pid=" << (int)cancelParameters.presId
+                     << ", mid=" << (int)cancelParameters.req.MessageIDBeingRespondedTo;
+        }
+      }
+      
+      if (rsp.DimseStatus == STATUS_Success)
+      {
+        // everything ok
+        nCompleted_++;
+      }
+      else if ((rsp.DimseStatus & 0xf000) == 0xb000)
+      {
+        // a warning status message
+        warningCount_++;
+        LOG(ERROR) << "C-GET SCP: Store Warning: Response Status: "
+                   << DU_cstoreStatusString(rsp.DimseStatus);
+      }
+      else
+      {
+        nFailed_++;
+        AddFailedUIDInstance(sopInstanceUid);
+        // print a status message
+        LOG(ERROR) << "C-GET SCP: Store Failed: Response Status: "
+                   << DU_cstoreStatusString(rsp.DimseStatus);
+      }
+    }
+    else
+    {
+      nFailed_++;
+      AddFailedUIDInstance(sopInstanceUid);
+      OFString temp_str;
+      LOG(ERROR) << "C-GET SCP: storeSCU: Store Request Failed: " << DimseCondition::dump(temp_str, cond);
+    }
+    
+    if (stDetail.get() != NULL)
+    {
+      LOG(INFO) << "  Status Detail:" << OFendl << DcmObject::PrintHelper(*stDetail);
+    }
+    
+    return cond;
+  }
+
+  bool OrthancGetRequestHandler::LookupIdentifiers(std::list<std::string>& publicIds,
+                                                   ResourceType level,
+                                                   const DicomMap& input) const
+  {
+    DicomTag tag(0, 0);   // Dummy initialization
+
+    switch (level)
+    {
+      case ResourceType_Patient:
+        tag = DICOM_TAG_PATIENT_ID;
+        break;
+
+      case ResourceType_Study:
+        tag = (input.HasTag(DICOM_TAG_ACCESSION_NUMBER) ?
+               DICOM_TAG_ACCESSION_NUMBER : DICOM_TAG_STUDY_INSTANCE_UID);
+        break;
+        
+      case ResourceType_Series:
+        tag = DICOM_TAG_SERIES_INSTANCE_UID;
+        break;
+        
+      case ResourceType_Instance:
+        tag = DICOM_TAG_SOP_INSTANCE_UID;
+        break;
+
+      default:
+        throw OrthancException(ErrorCode_ParameterOutOfRange);
+    }
+
+    if (!input.HasTag(tag))
+    {
+      return false;
+    }
+
+    const DicomValue& value = input.GetValue(tag);
+    if (value.IsNull() ||
+        value.IsBinary())
+    {
+      return false;
+    }
+    else
+    {
+      std::vector<std::string> tokens;
+      Toolbox::TokenizeString(tokens, value.GetContent(), '\\');
+
+      for (size_t i = 0; i < tokens.size(); i++)
+      {
+        std::vector<std::string> tmp;
+        context_.GetIndex().LookupIdentifierExact(tmp, level, tag, tokens[i]);
+
+        if (tmp.empty())
+        {
+          LOG(ERROR) << "C-GET: Cannot locate resource \"" << tokens[i]
+                     << "\" at the " << EnumerationToString(level) << " level";
+          return false;
+        }
+        else
+        {
+          for (size_t i = 0; i < tmp.size(); i++)
+          {
+            publicIds.push_back(tmp[i]);
+          }
+        }
+      }
+
+      return true;      
+    }
+  }
+
+
+    OrthancGetRequestHandler::OrthancGetRequestHandler(ServerContext& context) :
+      context_(context)
+    {
+      position_ = 0;
+      nRemaining_ = 0;
+      nCompleted_  = 0;
+      warningCount_ = 0;
+      nFailed_ = 0;
+      timeout_ = 0;
+    }
+
+
+  bool OrthancGetRequestHandler::Handle(const DicomMap& input,
+                                        const std::string& originatorIp,
+                                        const std::string& originatorAet,
+                                        const std::string& calledAet,
+                                        uint32_t timeout)
+  {
+    MetricsRegistry::Timer timer(context_.GetMetricsRegistry(), "orthanc_get_scp_duration_ms");
+
+    LOG(WARNING) << "C-GET-SCU request received from AET \"" << originatorAet << "\"";
+
+    {
+      DicomArray query(input);
+      for (size_t i = 0; i < query.GetSize(); i++)
+      {
+        if (!query.GetElement(i).GetValue().IsNull())
+        {
+          LOG(INFO) << "  " << query.GetElement(i).GetTag()
+                    << "  " << FromDcmtkBridge::GetTagName(query.GetElement(i))
+                    << " = " << query.GetElement(i).GetValue().GetContent();
+        }
+      }
+    }
+
+    /**
+     * Retrieve the query level.
+     **/
+
+    const DicomValue* levelTmp = input.TestAndGetValue(DICOM_TAG_QUERY_RETRIEVE_LEVEL);
+
+    assert(levelTmp != NULL);
+    ResourceType level = StringToResourceType(levelTmp->GetContent().c_str());      
+
+
+    /**
+     * Lookup for the resource to be sent.
+     **/
+
+    std::list<std::string> publicIds;
+
+    if (!LookupIdentifiers(publicIds, level, input))
+    {
+      LOG(ERROR) << "Cannot determine what resources are requested by C-GET";
+      return false; 
+    }
+
+    localAet_ = context_.GetDefaultLocalApplicationEntityTitle();
+    position_ = 0;
+    originatorAet_ = originatorAet;
+    
+    {
+      OrthancConfiguration::ReaderLock lock;
+      remote_ = lock.GetConfiguration().GetModalityUsingAet(originatorAet);
+    }
+
+    for (std::list<std::string>::const_iterator
+           resource = publicIds.begin(); resource != publicIds.end(); ++resource)
+    {
+      LOG(INFO) << "C-GET: Sending resource " << *resource
+                << " to modality \"" << originatorAet << "\"";
+      
+      std::list<std::string> tmp;
+      context_.GetIndex().GetChildInstances(tmp, *resource);
+      
+      instances_.reserve(tmp.size());
+      for (std::list<std::string>::iterator it = tmp.begin(); it != tmp.end(); ++it)
+      {
+        instances_.push_back(*it);
+      }
+    }
+
+    failedUIDs_.clear();
+    getCancelled_ = OFFalse;
+
+    nRemaining_ = GetSubOperationCount();
+    nCompleted_ = 0;
+    nFailed_ = 0;
+    warningCount_ = 0;
+    timeout_ = timeout;
+
+    return true;
+  }
+};
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/OrthancServer/OrthancGetRequestHandler.h	Wed May 20 18:37:31 2020 +0200
@@ -0,0 +1,122 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * In addition, as a special exception, the copyright holders of this
+ * program give permission to link the code of its release with the
+ * OpenSSL project's "OpenSSL" library (or with modified versions of it
+ * that use the same license as the "OpenSSL" library), and distribute
+ * the linked executables. You must obey the GNU General Public License
+ * in all respects for all of the code used other than "OpenSSL". If you
+ * modify file(s) with this exception, you may extend this exception to
+ * your version of the file(s), but you are not obligated to do so. If
+ * you do not wish to do so, delete this exception statement from your
+ * version. If you delete this exception statement from all source files
+ * in the program, then also delete it here.
+ * 
+ * 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+#pragma once
+
+#include "../Core/DicomNetworking/IGetRequestHandler.h"
+#include "../Core/DicomNetworking/RemoteModalityParameters.h"
+
+#include <dcmtk/dcmnet/dimse.h>
+
+#include <list>
+
+class DcmFileFormat;
+
+namespace Orthanc
+{
+  class ServerContext;
+  
+  class OrthancGetRequestHandler : public IGetRequestHandler
+  {
+  private:
+    ServerContext& context_;
+    std::string localAet_;
+    std::vector<std::string> instances_;
+    size_t position_;
+    RemoteModalityParameters remote_;
+    std::string originatorAet_;
+    
+    unsigned int nRemaining_;
+    unsigned int nCompleted_;
+    unsigned int warningCount_;
+    unsigned int nFailed_;
+    std::string failedUIDs_;
+    
+    DIC_US origMsgId_;
+    T_ASC_PresentationContextID origPresId_;
+    
+    bool getCancelled_;
+    uint32_t timeout_;
+
+    bool LookupIdentifiers(std::list<std::string>& publicIds,
+                           ResourceType level,
+                           const DicomMap& input) const;
+    
+    OFCondition PerformGetSubOp(T_ASC_Association *assoc,
+                                const std::string& sopClassUid,
+                                const std::string& sopInstanceUid,
+                                DcmFileFormat* datasetRaw);
+    
+    void AddFailedUIDInstance(const std::string& sopInstance);
+
+  public:
+    OrthancGetRequestHandler(ServerContext& context);
+    
+    virtual bool Handle(const DicomMap& input,
+                        const std::string& originatorIp,
+                        const std::string& originatorAet,
+                        const std::string& calledAet,
+                        uint32_t timeout) ORTHANC_OVERRIDE;
+    
+    virtual Status DoNext(T_ASC_Association *assoc) ORTHANC_OVERRIDE;
+    
+    virtual unsigned int GetSubOperationCount() const ORTHANC_OVERRIDE
+    {
+      return static_cast<unsigned int>(instances_.size());
+    }
+    
+    virtual unsigned int GetRemainingCount() const ORTHANC_OVERRIDE
+    {
+      return nRemaining_;
+    }
+    
+    virtual unsigned int GetCompletedCount() const ORTHANC_OVERRIDE
+    {
+      return nCompleted_;
+    }
+    
+    virtual unsigned int GetWarningCount() const ORTHANC_OVERRIDE
+    {
+      return warningCount_;
+    }
+    
+    virtual unsigned int GetFailedCount() const ORTHANC_OVERRIDE
+    {
+      return nFailed_;
+    }
+    
+    virtual const std::string& GetFailedUids() const ORTHANC_OVERRIDE
+    {
+      return failedUIDs_;
+    }    
+  };
+}
--- a/OrthancServer/ServerContext.h	Wed May 20 16:42:44 2020 +0200
+++ b/OrthancServer/ServerContext.h	Wed May 20 18:37:31 2020 +0200
@@ -483,5 +483,10 @@
                            DicomImage& source /* in, "GetParsed()" possibly modified */,
                            const std::set<DicomTransferSyntax>& allowedSyntaxes,
                            bool allowNewSopInstanceUid) ORTHANC_OVERRIDE;
+
+    bool IsTranscodeDicomProtocol() const
+    {
+      return transcodeDicomProtocol_;
+    }
   };
 }
--- a/OrthancServer/main.cpp	Wed May 20 16:42:44 2020 +0200
+++ b/OrthancServer/main.cpp	Wed May 20 18:37:31 2020 +0200
@@ -51,6 +51,7 @@
 #include "OrthancFindRequestHandler.h"
 #include "OrthancInitialization.h"
 #include "OrthancMoveRequestHandler.h"
+#include "OrthancGetRequestHandler.h"
 #include "ServerContext.h"
 #include "ServerJobs/StorageCommitmentScpJob.h"
 #include "ServerToolbox.h"
@@ -191,7 +192,8 @@
 class MyDicomServerFactory : 
   public IStoreRequestHandlerFactory,
   public IFindRequestHandlerFactory, 
-  public IMoveRequestHandlerFactory, 
+  public IMoveRequestHandlerFactory,
+  public IGetRequestHandlerFactory,
   public IStorageCommitmentRequestHandlerFactory
 {
 private:
@@ -244,11 +246,17 @@
   {
     return new OrthancMoveRequestHandler(context_);
   }
-
+  
+  virtual IGetRequestHandler* ConstructGetRequestHandler()
+  {
+    return new OrthancGetRequestHandler(context_);
+  }
+  
   virtual IStorageCommitmentRequestHandler* ConstructStorageCommitmentRequestHandler()
   {
     return new OrthancStorageCommitmentRequestHandler(context_);
   }
+  
 
   void Done()
   {
@@ -761,6 +769,7 @@
     PrintErrorCode(ErrorCode_CannotOrderSlices, "Unable to order the slices of the series");
     PrintErrorCode(ErrorCode_NoWorklistHandler, "No request handler factory for DICOM C-Find Modality SCP");
     PrintErrorCode(ErrorCode_AlreadyExistingTag, "Cannot override the value of a tag that already exists");
+    PrintErrorCode(ErrorCode_NoCGetHandler, "No request handler factory for DICOM C-GET SCP");
     PrintErrorCode(ErrorCode_NoStorageCommitmentHandler, "No request handler factory for DICOM N-ACTION SCP (storage commitment)");
     PrintErrorCode(ErrorCode_UnsupportedMediaType, "Unsupported media type");
   }
@@ -1055,6 +1064,7 @@
     dicomServer.SetRemoteModalities(modalities);
     dicomServer.SetStoreRequestHandlerFactory(serverFactory);
     dicomServer.SetMoveRequestHandlerFactory(serverFactory);
+    dicomServer.SetGetRequestHandlerFactory(serverFactory);
     dicomServer.SetFindRequestHandlerFactory(serverFactory);
     dicomServer.SetStorageCommitmentRequestHandlerFactory(serverFactory);
 
@@ -1139,7 +1149,7 @@
     context.GetHttpHandler().Register(*plugins, false);
   }
 #endif
-
+  
   // Secondly, apply the "static resources" layer
 #if ORTHANC_STANDALONE == 1
   EmbeddedResourceHttpHandler staticResources("/app", EmbeddedResources::ORTHANC_EXPLORER);
--- a/Plugins/Include/orthanc/OrthancCPlugin.h	Wed May 20 16:42:44 2020 +0200
+++ b/Plugins/Include/orthanc/OrthancCPlugin.h	Wed May 20 18:37:31 2020 +0200
@@ -309,6 +309,7 @@
     OrthancPluginErrorCode_NoWorklistHandler = 2041    /*!< No request handler factory for DICOM C-Find Modality SCP */,
     OrthancPluginErrorCode_AlreadyExistingTag = 2042    /*!< Cannot override the value of a tag that already exists */,
     OrthancPluginErrorCode_NoStorageCommitmentHandler = 2043    /*!< No request handler factory for DICOM N-ACTION SCP (storage commitment) */,
+    OrthancPluginErrorCode_NoCGetHandler = 2044    /*!< No request handler factory for DICOM C-GET SCP */,
     OrthancPluginErrorCode_UnsupportedMediaType = 3000    /*!< Unsupported media type */,
 
     _OrthancPluginErrorCode_INTERNAL = 0x7fffffff
--- a/Resources/CMake/OrthancFrameworkConfiguration.cmake	Wed May 20 16:42:44 2020 +0200
+++ b/Resources/CMake/OrthancFrameworkConfiguration.cmake	Wed May 20 18:37:31 2020 +0200
@@ -490,6 +490,7 @@
       ${ORTHANC_ROOT}/Core/DicomNetworking/Internals/CommandDispatcher.cpp
       ${ORTHANC_ROOT}/Core/DicomNetworking/Internals/FindScp.cpp
       ${ORTHANC_ROOT}/Core/DicomNetworking/Internals/MoveScp.cpp
+      ${ORTHANC_ROOT}/Core/DicomNetworking/Internals/GetScp.cpp
       ${ORTHANC_ROOT}/Core/DicomNetworking/Internals/StoreScp.cpp
       ${ORTHANC_ROOT}/Core/DicomNetworking/RemoteModalityParameters.cpp
       ${ORTHANC_ROOT}/Core/DicomNetworking/TimeoutDicomConnectionManager.cpp
--- a/Resources/Configuration.json	Wed May 20 16:42:44 2020 +0200
+++ b/Resources/Configuration.json	Wed May 20 18:37:31 2020 +0200
@@ -205,7 +205,7 @@
 
     /**
      * By default, the Orthanc SCP accepts all DICOM commands (C-ECHO,
-     * C-STORE, C-FIND, C-MOVE, and storage commitment) issued by the
+     * C-STORE, C-FIND, C-MOVE, C-GET and storage commitment) issued by the
      * registered remote SCU modalities. Starting with Orthanc 1.5.0,
      * it is possible to specify which DICOM commands are allowed,
      * separately for each remote modality, using the syntax
@@ -228,6 +228,7 @@
     //  "Manufacturer" : "Generic",
     //  "AllowEcho" : false,
     //  "AllowFind" : false,
+    //  "AllowGet"  : false,
     //  "AllowMove" : false,
     //  "AllowStore" : true,
     //  "AllowStorageCommitment" : false,  // new in 1.6.0
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Resources/DcmtkTools/CMakeLists.txt	Wed May 20 18:37:31 2020 +0200
@@ -0,0 +1,31 @@
+#   $ LSB_CC=gcc-4.8 LSB_CXX=g++-4.8 cmake .. -DCMAKE_BUILD_TYPE=Release -DCMAKE_TOOLCHAIN_FILE=../../Resources/LinuxStandardBaseToolchain.cmake -G Ninja
+
+cmake_minimum_required(VERSION 2.8)
+
+project(DcmtkTools)
+
+set(ORTHANC_ROOT ${CMAKE_SOURCE_DIR}/../../)
+include(${ORTHANC_ROOT}/Resources/CMake/Compiler.cmake)
+include(${ORTHANC_ROOT}/Resources/CMake/DownloadPackage.cmake)
+
+set(STATIC_BUILD ON CACHE BOOL "")
+set(ALLOW_DOWNLOADS ON CACHE BOOL "")
+
+set(DCMTK_STATIC_VERSION "3.6.5" CACHE STRING "")
+set(ENABLE_DCMTK_JPEG ON CACHE BOOL "")
+set(ENABLE_DCMTK_JPEG_LOSSLESS ON CACHE BOOL "")
+set(ENABLE_DCMTK_LOG ON CACHE BOOL "")
+set(ENABLE_DCMTK_NETWORKING ON CACHE BOOL "")
+set(ENABLE_DCMTK_TRANSCODING ON CACHE BOOL "")
+
+include(${ORTHANC_ROOT}/Resources/CMake/DcmtkConfiguration.cmake)
+
+add_library(dcmtk STATIC
+  ${CMAKE_SOURCE_DIR}/dummy.cpp
+  ${DCMTK_SOURCES}
+  )
+
+add_executable(getscu
+  ${DCMTK_SOURCES_DIR}/dcmnet/apps/getscu.cc
+  )
+target_link_libraries(getscu dcmtk)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Resources/DcmtkTools/dummy.cpp	Wed May 20 18:37:31 2020 +0200
@@ -0,0 +1,22 @@
+#include <string>
+
+struct OrthancLinesIterator;
+
+OrthancLinesIterator* OrthancLinesIterator_Create(const std::string& content)
+{
+  return NULL;
+}
+
+bool OrthancLinesIterator_GetLine(std::string& target,
+                                  const OrthancLinesIterator* iterator)
+{
+  return false;
+}
+
+void OrthancLinesIterator_Next(OrthancLinesIterator* iterator)
+{
+}
+
+void OrthancLinesIterator_Free(OrthancLinesIterator* iterator)
+{
+}
--- a/Resources/DicomConformanceStatement.txt	Wed May 20 16:42:44 2020 +0200
+++ b/Resources/DicomConformanceStatement.txt	Wed May 20 18:37:31 2020 +0200
@@ -162,6 +162,16 @@
   MOVEStudyRootQueryRetrieveInformationModel     | 1.2.840.10008.5.1.4.1.2.2.2
 
 
+-------------------
+Get SCP Conformance
+-------------------
+
+Orthanc supports the following SOP Classes as an SCP for C-Get:
+
+  GETPatientRootQueryRetrieveInformationModel    | 1.2.840.10008.5.1.4.1.2.1.3
+  GETStudyRootQueryRetrieveInformationModel      | 1.2.840.10008.5.1.4.1.2.2.3
+
+
 ---------------------
 Echo SCU Conformance
 ---------------------
--- a/Resources/ErrorCodes.json	Wed May 20 16:42:44 2020 +0200
+++ b/Resources/ErrorCodes.json	Wed May 20 18:37:31 2020 +0200
@@ -557,6 +557,11 @@
     "Name": "NoStorageCommitmentHandler", 
     "Description": "No request handler factory for DICOM N-ACTION SCP (storage commitment)"
   },
+  {
+    "Code": 2044,
+    "Name": "NoCGetHandler", 
+    "Description": "No request handler factory for DICOM C-GET SCP"
+  }