view OrthancServer/OrthancGetRequestHandler.cpp @ 3927:ed5515cd3262 c-get

updated conformance statement
author Sebastien Jodogne <s.jodogne@gmail.com>
date Thu, 14 May 2020 07:51:12 +0200
parents d30bce4bdae9
children 620e87e9e816
line wrap: on
line source

/**
 * 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/dcmnet/assoc.h>
#include <dcmtk/dcmnet/dimse.h>
#include <dcmtk/dcmdata/dcdeftag.h>
#include <dcmtk/dcmdata/dcistrmb.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 */,
                                         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;
    }
    
    DcmInputBufferStream is;
    is.setBuffer(&dicom[0], dicom.size());
    is.setEos();
    
    DcmFileFormat dcmff;
    OFCondition cond = dcmff.read(is, EXS_Unknown, EGL_noChange, DCM_MaxReadLength);
    if(cond.bad())
    {
      return Status_Failure;
    }
    
    // Determine the storage SOP class UID for this instance
    DIC_UI sopClass;
    DIC_UI sopInstance;
    
#if DCMTK_VERSION_NUMBER >= 364
    if (!DU_findSOPClassAndInstanceInDataSet(static_cast<DcmItem *> (dcmff.getDataset()),
                                             sopClass, sizeof(sopClass),
                                             sopInstance, sizeof(sopInstance)))
#else
      if (!DU_findSOPClassAndInstanceInDataSet(dcmff.getDataset(), sopClass, sopInstance))
#endif
      {
        throw OrthancException(ErrorCode_NoSopClassOrInstance,
                               "Unable to determine the SOP class/instance for C-STORE with AET " +
                               originatorAet_);
      }
    
    
    
    
    cond = performGetSubOp(assoc, sopClass, sopInstance, dcmff.getDataset());
    if (getCancelled_) {
      LOG (INFO) << "Get SCP: Received C-Cancel RQ";
    }
    
    if(cond.bad() || getCancelled_)
    {
      return Status_Failure;
    }
    
    return Status_Success;
  }
  
  void OrthancGetRequestHandler::addFailedUIDInstance(const char *sopInstance)
  {
    size_t len;
    
    if (failedUIDs_ == NULL) {
      if ((failedUIDs_ = (char*)malloc(DIC_UI_LEN+1)) == NULL) {
        LOG (ERROR) << "malloc failure: addFailedUIDInstance";
        return;
      }
      strcpy(failedUIDs_, sopInstance);
    } else {
      len = strlen(failedUIDs_);
      if ((failedUIDs_ = (char*)realloc(failedUIDs_,
                                        (len+strlen(sopInstance)+2))) == NULL) {
        LOG (ERROR) << "realloc failure: addFailedUIDInstance";
        return;
      }
      // tag sopInstance onto end of old with '\' between
      strcat(failedUIDs_, "\\");
      strcat(failedUIDs_, sopInstance);
    }
  }


  OFCondition OrthancGetRequestHandler::performGetSubOp(T_ASC_Association* assoc, 
                                                        DIC_UI sopClass, 
                                                        DIC_UI sopInstance, 
                                                        DcmDataset *dataset)
  {
    OFCondition cond = EC_Normal;
    T_DIMSE_C_StoreRQ req;
    T_DIMSE_C_StoreRSP rsp;
    DIC_US msgId;
    T_ASC_PresentationContextID presId;
    DcmDataset *stDetail = NULL;
    
    
    msgId = assoc->nextMsgID++;
    
    // which presentation context should be used
    presId = ASC_findAcceptedPresentationContextID(assoc,
                                                   sopClass);
    if (presId == 0) {
      nFailed_++;
      addFailedUIDInstance(sopInstance);
      LOG (ERROR) << "Get SCP: storeSCU: No presentation context for: ("
      << dcmSOPClassUIDToModality(sopClass, "OT") << ") " << sopClass;
      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(sopInstance);
        LOG (ERROR) <<"Get SCP: storeSCU: [No presentation context with requestor SCP role for: ("
        << dcmSOPClassUIDToModality(sopClass, "OT") << ") " << sopClass;
        return DIMSE_NOVALIDPRESENTATIONCONTEXTID;
      }
    }
    
    req.MessageID = msgId;
    strcpy(req.AffectedSOPClassUID, sopClass);
    strcpy(req.AffectedSOPInstanceUID, sopInstance);
    req.DataSetType = DIMSE_DATASET_PRESENT;
    req.Priority = priority_;
    req.opts = 0;
    
    LOG (INFO) << "Store SCU RQ: MsgID " << msgId << ", ("
    << dcmSOPClassUIDToModality(sopClass, "OT") << ")";
    
    T_DIMSE_DetectedCancelParameters cancelParameters;
    
    cond = DIMSE_storeUser(assoc, presId, &req,
                           NULL, dataset, getSubOpProgressCallback, this, DIMSE_BLOCKING, 0,
                           &rsp, &stDetail, &cancelParameters);
    
    if (cond.good()) {
      if (cancelParameters.cancelEncountered) {
        if (origPresId == cancelParameters.presId &&
            origMsgId == cancelParameters.req.MessageIDBeingRespondedTo) {
          getCancelled_ = OFTrue;
        } else {
          LOG (ERROR) << "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) << "Get SCP: Store Warning: Response Status: " << DU_cstoreStatusString(rsp.DimseStatus);
      } else {
        nFailed_++;
        addFailedUIDInstance(sopInstance);
        // print a status message
        LOG (ERROR) << "Get SCP: Store Failed: Response Status: "
        << DU_cstoreStatusString(rsp.DimseStatus);
      }
    } else {
      nFailed_++;
      addFailedUIDInstance(sopInstance);
      OFString temp_str;
      LOG (ERROR) << "Get SCP: storeSCU: Store Request Failed: " << DimseCondition::dump(temp_str, cond);
    }
    if (stDetail) {
      LOG (INFO) << "  Status Detail:" << OFendl << DcmObject::PrintHelper(*stDetail);
      delete stDetail;
    }
    return cond;
  }

  bool OrthancGetRequestHandler::LookupIdentifiers(std::vector<std::string>& publicIds,
                                                    ResourceType level,
                                                    const DicomMap& input)
  {
    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
    {
      const std::string& content = value.GetContent();
      context_.GetIndex().LookupIdentifierExact(publicIds, level, tag, content);
      return true;
    }
  }


  bool OrthancGetRequestHandler::Handle(const DicomMap& input,
                                        const std::string& originatorIp,
                                        const std::string& originatorAet,
                                        const std::string& calledAet)
  {
    MetricsRegistry::Timer timer(context_.GetMetricsRegistry(), "orthanc_get_scp_duration_ms");

    LOG(WARNING) << "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::vector<std::string> publicIds;

    bool retVal = LookupIdentifiers(publicIds, level, input);
    localAet_ = context_.GetDefaultLocalApplicationEntityTitle();
    position_ = 0;
    originatorAet_ = originatorAet;
    
    {
      OrthancConfiguration::ReaderLock lock;
      remote_ = lock.GetConfiguration().GetModalityUsingAet(originatorAet);
    }
    
    for (size_t i = 0; i < publicIds.size(); i++)
    {
      LOG(INFO) << "Sending resource " << publicIds[i] << " to modality \""
      << originatorAet << "\" in synchronous mode";
      
      std::list<std::string> tmp;
      context_.GetIndex().GetChildInstances(tmp, publicIds[i]);
      
      instances_.reserve(tmp.size());
      for (std::list<std::string>::iterator it = tmp.begin(); it != tmp.end(); ++it)
      {
        instances_.push_back(*it);
      }
    }
    failedUIDs_ = NULL;
    getCancelled_ = OFFalse;
    
    nRemaining_ = GetSubOperationCount();
    nCompleted_ = 0;
    nFailed_ = 0;
    warningCount_ = 0;

    return retVal;

    
  }
};