Mercurial > hg > orthanc
view 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 source
/** * 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; } } }