Mercurial > hg > orthanc
diff PalantirServer/Internals/CommandDispatcher.cpp @ 0:3959d33612cc
initial commit
author | Sebastien Jodogne <s.jodogne@gmail.com> |
---|---|
date | Thu, 19 Jul 2012 14:32:22 +0200 |
parents | |
children |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/PalantirServer/Internals/CommandDispatcher.cpp Thu Jul 19 14:32:22 2012 +0200 @@ -0,0 +1,403 @@ +/** + * Palantir - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012 Medical Physics Department, CHU of Liege, + * 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. + * + * 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 "CommandDispatcher.h" + +#include "FindScp.h" +#include "StoreScp.h" +#include "MoveScp.h" +#include "../../Core/Toolbox.h" + +#include <dcmtk/dcmnet/dcasccfg.h> /* for class DcmAssociationConfiguration */ + +static OFBool opt_rejectWithoutImplementationUID = OFFalse; + + +namespace Palantir +{ + namespace Internals + { + extern OFLogger Logger; + + + + OFCondition AssociationCleanup(T_ASC_Association *assoc) + { + OFString temp_str; + OFCondition cond = ASC_dropSCPAssociation(assoc); + if (cond.bad()) + { + OFLOG_FATAL(Internals::Logger, DimseCondition::dump(temp_str, cond)); + return cond; + } + + cond = ASC_destroyAssociation(&assoc); + if (cond.bad()) + { + OFLOG_FATAL(Internals::Logger, DimseCondition::dump(temp_str, cond)); + return cond; + } + + return cond; + } + + + + CommandDispatcher* AcceptAssociation(const DicomServer& server, T_ASC_Network *net) + { + DcmAssociationConfiguration asccfg; + char buf[BUFSIZ]; + T_ASC_Association *assoc; + OFCondition cond; + OFString sprofile; + OFString temp_str; + + std::vector<const char*> knownAbstractSyntaxes; + + // For C-STORE + if (server.HasStoreRequestHandlerFactory()) + { + knownAbstractSyntaxes.push_back(UID_VerificationSOPClass); + } + + // For C-FIND + if (server.HasFindRequestHandlerFactory()) + { + knownAbstractSyntaxes.push_back(UID_FINDPatientRootQueryRetrieveInformationModel); + knownAbstractSyntaxes.push_back(UID_FINDStudyRootQueryRetrieveInformationModel); + } + + // For C-MOVE + if (server.HasMoveRequestHandlerFactory()) + { + knownAbstractSyntaxes.push_back(UID_MOVEStudyRootQueryRetrieveInformationModel); + } + + const char* transferSyntaxes[] = { NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL }; + int numTransferSyntaxes = 0; + + cond = ASC_receiveAssociation(net, &assoc, + /*opt_maxPDU*/ ASC_DEFAULTMAXPDU, + NULL, NULL, + /*opt_secureConnection*/ OFFalse, + DUL_NOBLOCK, 1); + + if (cond == DUL_NOASSOCIATIONREQUEST) + { + // Timeout + AssociationCleanup(assoc); + return NULL; + } + + // if some kind of error occured, take care of it + if (cond.bad()) + { + OFLOG_ERROR(Internals::Logger, "Receiving Association failed: " << DimseCondition::dump(temp_str, cond)); + // no matter what kind of error occurred, we need to do a cleanup + AssociationCleanup(assoc); + return NULL; + } + + OFLOG_INFO(Internals::Logger, "Association Received"); + + transferSyntaxes[0] = UID_LittleEndianExplicitTransferSyntax; + transferSyntaxes[1] = UID_BigEndianExplicitTransferSyntax; + transferSyntaxes[2] = UID_LittleEndianImplicitTransferSyntax; + numTransferSyntaxes = 3; + + /* accept the Verification SOP Class if presented */ + cond = ASC_acceptContextsWithPreferredTransferSyntaxes( assoc->params, &knownAbstractSyntaxes[0], knownAbstractSyntaxes.size(), transferSyntaxes, numTransferSyntaxes); + if (cond.bad()) + { + OFLOG_DEBUG(Internals::Logger, DimseCondition::dump(temp_str, cond)); + AssociationCleanup(assoc); + return NULL; + } + + /* the array of Storage SOP Class UIDs comes from dcuid.h */ + cond = ASC_acceptContextsWithPreferredTransferSyntaxes( assoc->params, dcmAllStorageSOPClassUIDs, numberOfAllDcmStorageSOPClassUIDs, transferSyntaxes, numTransferSyntaxes); + if (cond.bad()) + { + OFLOG_DEBUG(Internals::Logger, DimseCondition::dump(temp_str, cond)); + AssociationCleanup(assoc); + return NULL; + } + + /* set our app title */ + ASC_setAPTitles(assoc->params, NULL, NULL, server.GetApplicationEntityTitle().c_str()); + + /* acknowledge or reject this association */ + cond = ASC_getApplicationContextName(assoc->params, buf); + if ((cond.bad()) || strcmp(buf, UID_StandardApplicationContext) != 0) + { + /* reject: the application context name is not supported */ + T_ASC_RejectParameters rej = + { + ASC_RESULT_REJECTEDPERMANENT, + ASC_SOURCE_SERVICEUSER, + ASC_REASON_SU_APPCONTEXTNAMENOTSUPPORTED + }; + + OFLOG_INFO(Internals::Logger, "Association Rejected: Bad Application Context Name: " << buf); + cond = ASC_rejectAssociation(assoc, &rej); + if (cond.bad()) + { + OFLOG_DEBUG(Internals::Logger, DimseCondition::dump(temp_str, cond)); + } + AssociationCleanup(assoc); + return NULL; + } + + /* check the AETs */ + { + DIC_AE callingTitle_C; + DIC_AE calledTitle_C; + DIC_AE callingIP_C; + DIC_AE calledIP_C; + if (ASC_getAPTitles(assoc->params, callingTitle_C, calledTitle_C, NULL).bad() || + ASC_getPresentationAddresses(assoc->params, callingIP_C, calledIP_C).bad()) + { + T_ASC_RejectParameters rej = + { + ASC_RESULT_REJECTEDPERMANENT, + ASC_SOURCE_SERVICEUSER, + ASC_REASON_SU_NOREASON + }; + ASC_rejectAssociation(assoc, &rej); + AssociationCleanup(assoc); + return NULL; + } + + std::string callingIP(OFSTRING_GUARD(callingIP_C)); + std::string callingTitle(OFSTRING_GUARD(callingTitle_C)); + std::string calledTitle(OFSTRING_GUARD(calledTitle_C)); + Toolbox::ToUpperCase(callingIP); + Toolbox::ToUpperCase(callingTitle); + Toolbox::ToUpperCase(calledTitle); + + if (server.HasCalledApplicationEntityTitleCheck() && + calledTitle != server.GetApplicationEntityTitle()) + { + T_ASC_RejectParameters rej = + { + ASC_RESULT_REJECTEDPERMANENT, + ASC_SOURCE_SERVICEUSER, + ASC_REASON_SU_CALLEDAETITLENOTRECOGNIZED + }; + ASC_rejectAssociation(assoc, &rej); + AssociationCleanup(assoc); + return NULL; + } + + if (server.HasApplicationEntityFilter() && + !server.GetApplicationEntityFilter().IsAllowed(callingIP, callingTitle)) + { + T_ASC_RejectParameters rej = + { + ASC_RESULT_REJECTEDPERMANENT, + ASC_SOURCE_SERVICEUSER, + ASC_REASON_SU_CALLINGAETITLENOTRECOGNIZED + }; + ASC_rejectAssociation(assoc, &rej); + AssociationCleanup(assoc); + return NULL; + } + } + + if (opt_rejectWithoutImplementationUID && strlen(assoc->params->theirImplementationClassUID) == 0) + { + /* reject: the no implementation Class UID provided */ + T_ASC_RejectParameters rej = + { + ASC_RESULT_REJECTEDPERMANENT, + ASC_SOURCE_SERVICEUSER, + ASC_REASON_SU_NOREASON + }; + + OFLOG_INFO(Internals::Logger, "Association Rejected: No Implementation Class UID provided"); + cond = ASC_rejectAssociation(assoc, &rej); + if (cond.bad()) + { + OFLOG_DEBUG(Internals::Logger, DimseCondition::dump(temp_str, cond)); + } + AssociationCleanup(assoc); + return NULL; + } + + { + cond = ASC_acknowledgeAssociation(assoc); + if (cond.bad()) + { + OFLOG_ERROR(Internals::Logger, DimseCondition::dump(temp_str, cond)); + AssociationCleanup(assoc); + return NULL; + } + OFLOG_INFO(Internals::Logger, "Association Acknowledged (Max Send PDV: " << assoc->sendPDVLength << ")"); + if (ASC_countAcceptedPresentationContexts(assoc->params) == 0) + OFLOG_INFO(Internals::Logger, " (but no valid presentation contexts)"); + } + + return new CommandDispatcher(server, assoc); + } + + bool CommandDispatcher::Step() + /* + * This function receives DIMSE commmands over the network connection + * and handles these commands correspondingly. Note that in case of + * storscp only C-ECHO-RQ and C-STORE-RQ commands can be processed. + */ + { + bool finished = false; + + // receive a DIMSE command over the network, with a timeout of 1 second + DcmDataset *statusDetail = NULL; + T_ASC_PresentationContextID presID = 0; + T_DIMSE_Message msg; + + OFCondition cond = DIMSE_receiveCommand(assoc_, DIMSE_NONBLOCKING, 1, &presID, &msg, &statusDetail); + elapsedTimeSinceLastCommand_++; + + // if the command which was received has extra status + // detail information, dump this information + if (statusDetail != NULL) + { + OFLOG_WARN(Internals::Logger, "Status Detail:" << OFendl << DcmObject::PrintHelper(*statusDetail)); + delete statusDetail; + } + + if (cond == DIMSE_OUTOFRESOURCES) + { + finished = true; + } + else if (cond == DIMSE_NODATAAVAILABLE) + { + // Timeout due to DIMSE_NONBLOCKING + if (clientTimeout_ != 0 && + elapsedTimeSinceLastCommand_ >= clientTimeout_) + { + // This timeout is actually a client timeout + finished = true; + } + } + else if (cond == EC_Normal) + { + // Reset the client timeout counter + elapsedTimeSinceLastCommand_ = 0; + + // in case we received a valid message, process this command + // note that storescp can only process a C-ECHO-RQ and a C-STORE-RQ + switch (msg.CommandField) + { + case DIMSE_C_ECHO_RQ: + // process C-ECHO-Request + cond = EchoScp(assoc_, &msg, presID); + break; + + case DIMSE_C_STORE_RQ: + // process C-STORE-Request + if (server_.HasStoreRequestHandlerFactory()) + { + std::auto_ptr<IStoreRequestHandler> handler + (server_.GetStoreRequestHandlerFactory().ConstructStoreRequestHandler()); + cond = Internals::storeScp(assoc_, &msg, presID, *handler); + } + else + cond = DIMSE_BADCOMMANDTYPE; // Should never happen + break; + + case DIMSE_C_MOVE_RQ: + // process C-MOVE-Request + if (server_.HasMoveRequestHandlerFactory()) + { + std::auto_ptr<IMoveRequestHandler> handler + (server_.GetMoveRequestHandlerFactory().ConstructMoveRequestHandler()); + cond = Internals::moveScp(assoc_, &msg, presID, *handler); + } + else + cond = DIMSE_BADCOMMANDTYPE; // Should never happen + break; + + case DIMSE_C_FIND_RQ: + // process C-FIND-Request + if (server_.HasFindRequestHandlerFactory()) + { + std::auto_ptr<IFindRequestHandler> handler + (server_.GetFindRequestHandlerFactory().ConstructFindRequestHandler()); + cond = Internals::findScp(assoc_, &msg, presID, *handler); + } + else + cond = DIMSE_BADCOMMANDTYPE; // Should never happen + break; + + default: + // we cannot handle this kind of message + cond = DIMSE_BADCOMMANDTYPE; + OFLOG_ERROR(Internals::Logger, "cannot handle command: 0x" + << STD_NAMESPACE hex << OFstatic_cast(unsigned, msg.CommandField)); + break; + } + } + else + { + // Bad status, which indicates the closing of the connection by + // the peer or a network error + finished = true; + } + + if (finished) + { + if (cond == DUL_PEERREQUESTEDRELEASE) + { + OFLOG_INFO(Internals::Logger, "Association Release"); + ASC_acknowledgeRelease(assoc_); + } + else if (cond == DUL_PEERABORTEDASSOCIATION) + { + OFLOG_INFO(Internals::Logger, "Association Aborted"); + } + else + { + OFString temp_str; + OFLOG_ERROR(Internals::Logger, "DIMSE failure (aborting association): " << DimseCondition::dump(temp_str, cond)); + /* some kind of error so abort the association */ + ASC_abortAssociation(assoc_); + } + } + + return !finished; + } + + + OFCondition EchoScp( T_ASC_Association * assoc, T_DIMSE_Message * msg, T_ASC_PresentationContextID presID) + { + OFString temp_str; + OFLOG_INFO(Internals::Logger, "Received Echo Request"); + OFLOG_DEBUG(Internals::Logger, DIMSE_dumpMessage(temp_str, msg->msg.CEchoRQ, DIMSE_INCOMING, NULL, presID)); + + /* the echo succeeded !! */ + OFCondition cond = DIMSE_sendEchoResponse(assoc, presID, &msg->msg.CEchoRQ, STATUS_Success, NULL); + if (cond.bad()) + { + OFLOG_ERROR(Internals::Logger, "Echo SCP Failed: " << DimseCondition::dump(temp_str, cond)); + } + return cond; + } + } +}