/*========================================================================= * * Copyright NumFOCUS * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0.txt * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * *=========================================================================*/ #include "gdcmULConnectionManager.h" #include "gdcmUserInformation.h" #include "gdcmULEvent.h" #include "gdcmPDUFactory.h" #include "gdcmReader.h" #include "gdcmAAssociateRQPDU.h" #include "gdcmAttribute.h" #include "gdcmBaseRootQuery.h" #include "gdcmDataSetEvent.h" #include "gdcmAReleaseRPPDU.h" #include "gdcmULBasicCallback.h" #include #include //for setting up the local socket #include "gdcmTrace.h" #include "gdcmPrinter.h" namespace gdcm { namespace network { ULConnectionManager::ULConnectionManager() { mConnection = nullptr; mSecondaryConnection = nullptr; } ULConnectionManager::~ULConnectionManager() { if (mConnection != nullptr) { delete mConnection; mConnection = nullptr; } if (mSecondaryConnection != nullptr) { delete mSecondaryConnection; mSecondaryConnection = nullptr; } } bool ULConnectionManager::EstablishConnection(const std::string& inAETitle, const std::string& inConnectAETitle, const std::string& inComputerName, long inIPAddress, unsigned short inConnectPort, double inTimeout, std::vector const & pcVector) { //generate a ULConnectionInfo object UserInformation userInfo; ULConnectionInfo connectInfo; if (inConnectAETitle.size() > 16) return false;//too long an AETitle, probably need better failure message if (inAETitle.size() > 16) return false; //as above if (!connectInfo.Initialize(userInfo, inConnectAETitle.c_str(), inAETitle.c_str(), inIPAddress, inConnectPort, inComputerName)) { return false; } if (mConnection != nullptr) { delete mConnection; } mConnection = new ULConnection(connectInfo); mConnection->GetTimer().SetTimeout(inTimeout); // Warning PresentationContextID is important // this is a sort of uniq key used by the recevier. Eg. // if one push_pack // (1, Secondary) // (1, Verification) // Then the last one is preferred (DCMTK 3.5.5) // The following only works for C-STORE / C-ECHO // however it does not make much sense to add a lot of abstract syntax // when doing only C-ECHO. // FIXME is there a way to know here if we are in C-ECHO ? //there is now! //the presentation context will now be part of the connection, so that this //initialization for the association-rq will use parameters from the connection #if 0 AbstractSyntax as; std::vector pcVector; PresentationContextRQ pc; TransferSyntaxSub ts; ts.SetNameFromUID( UIDs::ImplicitVRLittleEndianDefaultTransferSyntaxforDICOM ); pc.AddTransferSyntax( ts ); ts.SetNameFromUID( UIDs::ExplicitVRLittleEndian ); //ts.SetNameFromUID( UIDs::JPEGLosslessNonHierarchicalFirstOrderPredictionProcess14SelectionValue1DefaultTransferSyntaxforLosslessJPEGImageCompression); //pc.AddTransferSyntax( ts ); // we do not support explicit (mm) switch (inConnectionType){ case eEcho: pc.SetPresentationContextID( eVerificationSOPClass ); as.SetNameFromUID( UIDs::VerificationSOPClass ); pc.SetAbstractSyntax( as ); pcVector.push_back(pc); break; case eFind: pc.SetPresentationContextID( ePatientRootQueryRetrieveInformationModelFIND ); as.SetNameFromUID( UIDs::PatientRootQueryRetrieveInformationModelFIND ); pc.SetAbstractSyntax( as ); pcVector.push_back(pc); pc.SetPresentationContextID(eStudyRootQueryRetrieveInformationModelFIND ); as.SetNameFromUID( UIDs::StudyRootQueryRetrieveInformationModelFIND ); pc.SetAbstractSyntax( as ); pcVector.push_back(pc); pc.SetPresentationContextID( ePatientStudyOnlyQueryRetrieveInformationModelFINDRetired ); as.SetNameFromUID( UIDs::PatientStudyOnlyQueryRetrieveInformationModelFINDRetired ); pc.SetAbstractSyntax( as ); pcVector.push_back(pc); pc.SetPresentationContextID( eModalityWorklistInformationModelFIND ); as.SetNameFromUID( UIDs::ModalityWorklistInformationModelFIND ); pc.SetAbstractSyntax( as ); pcVector.push_back(pc); pc.SetPresentationContextID( eGeneralPurposeWorklistInformationModelFIND ); as.SetNameFromUID( UIDs::GeneralPurposeWorklistInformationModelFIND ); pc.SetAbstractSyntax( as ); pcVector.push_back(pc); break; //our spec does not require C-GET support // case eGet: // break; /* case eMove: // should we also send stuff from FIND ? // E: Move PresCtx but no Find (accepting for now) pc.SetPresentationContextID( ePatientRootQueryRetrieveInformationModelFIND ); as.SetNameFromUID( UIDs::PatientRootQueryRetrieveInformationModelFIND ); pc.SetAbstractSyntax( as ); pcVector.push_back(pc); // move pc.SetPresentationContextID( ePatientRootQueryRetrieveInformationModelMOVE ); as.SetNameFromUID( UIDs::PatientRootQueryRetrieveInformationModelMOVE ); pc.SetAbstractSyntax( as ); pcVector.push_back(pc); pc.SetPresentationContextID( eStudyRootQueryRetrieveInformationModelFIND ); as.SetNameFromUID( UIDs::StudyRootQueryRetrieveInformationModelFIND ); pc.SetAbstractSyntax( as ); pcVector.push_back(pc); pc.SetPresentationContextID( eStudyRootQueryRetrieveInformationModelMOVE ); as.SetNameFromUID( UIDs::StudyRootQueryRetrieveInformationModelMOVE ); pc.SetAbstractSyntax( as ); pcVector.push_back(pc); break;*/ case eStore: std::string uidName; pc.SetPresentationContextID( PresentationContextRQ::AssignPresentationContextID(inDS, uidName) ); if (pc.GetPresentationContextID() != eVerificationSOPClass){ as.SetNameFromUIDString( uidName ); pc.SetAbstractSyntax( as ); pcVector.push_back(pc); } break; } if (pcVector.empty()){ gdcmWarningMacro("Unable to establish presentation context; ensure that dataset has tags 0x8,0x16 and 0x8,0x18 defined." <SetPresentationContexts(pcVector); //now, try to establish a connection by starting the transition table and the event loop. //here's the thing //if there's nothing on the event loop, assume that it's done & the function can exit. //otherwise, keep rolling the event loop ULEvent theEvent(eAASSOCIATERequestLocalUser, nullptr); //no callback, assume that no data is transferred back, because there shouldn't be any EStateID theState = RunEventLoop(theEvent, mConnection, nullptr, false); if(theState != eSta6TransferReady) { std::vector const & thePDUs = theEvent.GetPDUs(); for( std::vector::const_iterator itor = thePDUs.begin(); itor != thePDUs.end(); itor++) { //assert(*itor); if (*itor == nullptr) continue; //can have a nulled pdu, apparently (*itor)->Print(Trace::GetErrorStream()); } } else if (Trace::GetDebugFlag()) { std::vector const & thePDUs = theEvent.GetPDUs(); for( std::vector::const_iterator itor = thePDUs.begin(); itor != thePDUs.end(); itor++) { assert(*itor); if (*itor == nullptr) continue; //can have a nulled pdu, apparently (*itor)->Print(Trace::GetDebugStream()); } } return (theState == eSta6TransferReady);//ie, finished the transitions } /// returns true for above reasons, but contains the special 'move' port bool ULConnectionManager::EstablishConnectionMove(const std::string& inAETitle, const std::string& inConnectAETitle, const std::string& inComputerName, long inIPAddress, uint16_t inConnectPort, double inTimeout, uint16_t inReturnPort, std::vector const & pcVector) { gdcmDebugMacro( "Start EstablishConnectionMove" ); //generate a ULConnectionInfo object UserInformation userInfo; ULConnectionInfo connectInfo; if (inConnectAETitle.size() > 16) return false;//too long an AETitle, probably need better failure message if (inAETitle.size() > 16) return false; //as above if (!connectInfo.Initialize(userInfo,inAETitle.c_str(), inConnectAETitle.c_str(), inIPAddress, inReturnPort, inComputerName)) { gdcmDebugMacro( "Could not Initialize connectInfo" ); return false; } gdcmDebugMacro( "SCP: First connection established on port " << inReturnPort ); if (mSecondaryConnection != nullptr) { gdcmDebugMacro( "delete mSecondaryConnection" ); delete mSecondaryConnection; } mSecondaryConnection = new ULConnection(connectInfo); mSecondaryConnection->GetTimer().SetTimeout(inTimeout); //generate a ULConnectionInfo object UserInformation userInfo2; ULConnectionInfo connectInfo2; if (inConnectAETitle.size() > 16) return false;//too long an AETitle, probably need better failure message if (inAETitle.size() > 16) return false; //as above if (!connectInfo2.Initialize(userInfo2, inConnectAETitle.c_str(), inAETitle.c_str(), inIPAddress, inConnectPort, inComputerName)) { gdcmDebugMacro( "Could not Initialize connectInfo2" ); return false; } gdcmDebugMacro( "SCU: Second connection established on port " << inConnectPort ); if (mConnection!= nullptr) { gdcmDebugMacro( "delete mConnection" ); delete mConnection; } mConnection = new ULConnection(connectInfo2); mConnection->GetTimer().SetTimeout(inTimeout); // Warning PresentationContextID is important // this is a sort of uniq key used by the recevier. Eg. // if one push_pack // (1, Secondary) // (1, Verification) // Then the last one is preferred (DCMTK 3.5.5) // The following only works for C-STORE / C-ECHO // however it does not make much sense to add a lot of abstract syntax // when doing only C-ECHO. // FIXME is there a way to know here if we are in C-ECHO ? //there is now! //the presentation context will now be part of the connection, so that this //initialization for the association-rq will use parameters from the connection AbstractSyntax as; #if 0 std::vector pcVector; PresentationContextRQ pc; TransferSyntaxSub ts; ts.SetNameFromUID( UIDs::ImplicitVRLittleEndianDefaultTransferSyntaxforDICOM ); pc.AddTransferSyntax( ts ); ts.SetNameFromUID( UIDs::ExplicitVRLittleEndian ); //pc.AddTransferSyntax( ts ); // we do not support explicit (mm) // should we also send stuff from FIND ? // E: Move PresCtx but no Find (accepting for now) pc.SetPresentationContextID( ePatientRootQueryRetrieveInformationModelFIND ); as.SetNameFromUID( UIDs::PatientRootQueryRetrieveInformationModelFIND ); pc.SetAbstractSyntax( as ); pcVector.push_back(pc); // move pc.SetPresentationContextID( ePatientRootQueryRetrieveInformationModelMOVE ); as.SetNameFromUID( UIDs::PatientRootQueryRetrieveInformationModelMOVE ); pc.SetAbstractSyntax( as ); pcVector.push_back(pc); pc.SetPresentationContextID( eStudyRootQueryRetrieveInformationModelFIND ); as.SetNameFromUID( UIDs::StudyRootQueryRetrieveInformationModelFIND ); pc.SetAbstractSyntax( as ); pcVector.push_back(pc); pc.SetPresentationContextID( eStudyRootQueryRetrieveInformationModelMOVE ); as.SetNameFromUID( UIDs::StudyRootQueryRetrieveInformationModelMOVE ); pc.SetAbstractSyntax( as ); pcVector.push_back(pc); #endif mConnection->SetPresentationContexts(pcVector); //now, try to establish a connection by starting the transition table and the event loop. //here's the thing //if there's nothing on the event loop, assume that it's done & the function can exit. //otherwise, keep rolling the event loop ULEvent theEvent(eAASSOCIATERequestLocalUser, nullptr); std::vector empty; //No data should be returned when connections are established EStateID theState = RunEventLoop(theEvent, mConnection, nullptr, false); if (Trace::GetDebugFlag()) { std::vector thePDUs = theEvent.GetPDUs(); std::vector::iterator itor; for (itor = thePDUs.begin(); itor != thePDUs.end(); itor++) { if (*itor == NULL) continue; //can have a nulled pdu, apparently (*itor)->Print(Trace::GetStream()); } } return (theState == eSta6TransferReady);//ie, finished the transitions } //send the Data PDU associated with Echo (ie, a default DataPDU) //this lets the user confirm that the connection is alive. //the user should look to cout to see the response of the echo command std::vector ULConnectionManager::SendEcho(){ std::vector theDataPDU = PDUFactory::CreateCEchoPDU(*mConnection);//pass NULL for C-Echo ULEvent theEvent(ePDATArequest, theDataPDU); EStateID theState = RunEventLoop(theEvent, mConnection, nullptr, false); //theEvent should contain the PDU for the echo! if (theState == eSta6TransferReady){//ie, finished the transitions return PDUFactory::GetPDVs(theEvent.GetPDUs()); } else { std::vector empty; return empty; } } std::vector ULConnectionManager::SendMove(const BaseRootQuery* inRootQuery) { ULBasicCallback theCallback; SendMove(inRootQuery, &theCallback); return theCallback.GetDataSets(); } bool ULConnectionManager::SendMove(const BaseRootQuery* inRootQuery, ULConnectionCallback* inCallback) { if (mConnection == nullptr) { gdcmErrorMacro( "mConnection is NULL" ); return false; } std::vector theDataPDU = PDUFactory::CreateCMovePDU( *mConnection, inRootQuery ); ULEvent theEvent(ePDATArequest, theDataPDU); EStateID stateid = RunMoveEventLoop(theEvent, inCallback); gdcmDebugMacro( "Final StateID: " << (int) stateid ); return stateid == gdcm::network::eSta6TransferReady; } std::vector ULConnectionManager::SendFind(const BaseRootQuery* inRootQuery) { ULBasicCallback theCallback; SendFind(inRootQuery, &theCallback); return theCallback.GetDataSets(); } void ULConnectionManager::SendFind(const BaseRootQuery* inRootQuery, ULConnectionCallback* inCallback) { if (mConnection == nullptr) { return; } std::vector theDataPDU = PDUFactory::CreateCFindPDU( *mConnection, inRootQuery ); ULEvent theEvent(ePDATArequest, theDataPDU); RunEventLoop(theEvent, mConnection, inCallback, false); } std::vector ULConnectionManager::SendStore(const File &file, std::istream * pStream /*= NULL*/, std::streampos dataSetOffset/*=0*/ ) { ULBasicCallback theCallback; SendStore(file, &theCallback, pStream, dataSetOffset ); return theCallback.GetResponses(); } void ULConnectionManager::SendStore(const File & file, ULConnectionCallback* inCallback, std::istream * pStream /*= NULL*/, std::streampos dataSetOffset/*=0*/ ) { if (mConnection == nullptr) { return; } bool writeDataSet = pStream == nullptr ; std::vector theDataPDU = PDUFactory::CreateCStoreRQPDU(*mConnection, file, writeDataSet); const DataSet* inDataSet = &file.GetDataSet(); DataSetEvent dse( inDataSet ); this->InvokeEvent( dse ); ULEvent theEvent(ePDATArequest, theDataPDU, pStream, dataSetOffset ); EStateID theState = RunEventLoop(theEvent, mConnection, inCallback, false); assert( theState == eSta6TransferReady || theState == eStaDoesNotExist ); (void)theState; } std::vector ULConnectionManager::SendNEventReport (const BaseQuery* inQuery) { ULBasicCallback theCallback; SendNEventReport(inQuery, &theCallback); return theCallback.GetDataSets(); } void ULConnectionManager::SendNEventReport (const BaseQuery* inQuery, ULConnectionCallback* inCallback) { if (mConnection == nullptr) { return; } std::vector theDataPDU = PDUFactory::CreateNEventReportPDU( *mConnection, inQuery ); ULEvent theEvent(ePDATArequest, theDataPDU); RunEventLoop(theEvent, mConnection, inCallback, false); } std::vector ULConnectionManager::SendNGet (const BaseQuery* inQuery) { ULBasicCallback theCallback; SendNGet(inQuery, &theCallback); return theCallback.GetDataSets(); } void ULConnectionManager::SendNGet (const BaseQuery* inQuery, ULConnectionCallback* inCallback) { if (mConnection == nullptr) { return; } std::vector theDataPDU = PDUFactory::CreateNGetPDU( *mConnection, inQuery ); ULEvent theEvent(ePDATArequest, theDataPDU); RunEventLoop(theEvent, mConnection, inCallback, false); } std::vector ULConnectionManager::SendNSet (const BaseQuery* inQuery) { ULBasicCallback theCallback; SendNSet(inQuery, &theCallback); return theCallback.GetDataSets(); } void ULConnectionManager::SendNSet (const BaseQuery* inQuery, ULConnectionCallback* inCallback) { if (mConnection == nullptr) { return; } std::vector theDataPDU = PDUFactory::CreateNSetPDU( *mConnection, inQuery ); ULEvent theEvent(ePDATArequest, theDataPDU); RunEventLoop(theEvent, mConnection, inCallback, false); } std::vector ULConnectionManager::SendNAction (const BaseQuery* inQuery) { ULBasicCallback theCallback; SendNAction(inQuery, &theCallback); return theCallback.GetDataSets(); } void ULConnectionManager::SendNAction (const BaseQuery* inQuery, ULConnectionCallback* inCallback) { if (mConnection == nullptr) { return; } std::vector theDataPDU = PDUFactory::CreateNActionPDU( *mConnection, inQuery ); ULEvent theEvent(ePDATArequest, theDataPDU); RunEventLoop(theEvent, mConnection, inCallback, false); } std::vector ULConnectionManager::SendNCreate (const BaseQuery* inQuery) { ULBasicCallback theCallback; SendNCreate(inQuery, &theCallback); return theCallback.GetDataSets(); } void ULConnectionManager::SendNCreate (const BaseQuery* inQuery, ULConnectionCallback* inCallback) { if (mConnection == nullptr) { return; } std::vector theDataPDU = PDUFactory::CreateNCreatePDU( *mConnection, inQuery ); ULEvent theEvent(ePDATArequest, theDataPDU); RunEventLoop(theEvent, mConnection, inCallback, false); } std::vector ULConnectionManager::SendNDelete (const BaseQuery* inQuery) { ULBasicCallback theCallback; SendNDelete(inQuery, &theCallback); return theCallback.GetDataSets(); } void ULConnectionManager::SendNDelete (const BaseQuery* inQuery, ULConnectionCallback* inCallback) { if (mConnection == nullptr) { return; } std::vector theDataPDU = PDUFactory::CreateNDeletePDU( *mConnection, inQuery ); ULEvent theEvent(ePDATArequest, theDataPDU); RunEventLoop(theEvent, mConnection, inCallback, false); } bool ULConnectionManager::BreakConnection(const double& inTimeOut){ std::vector theResult; if (mConnection == nullptr){ return false; } BasePDU* thePDU = PDUFactory::ConstructReleasePDU(); ULEvent theEvent(eARELEASERequest, thePDU); mConnection->GetTimer().SetTimeout(inTimeOut); //assume no data coming back when dying, no need for callback EStateID theState = RunEventLoop(theEvent, mConnection, nullptr, false); return (theState == eSta1Idle);//ie, finished the transitions } void ULConnectionManager::BreakConnectionNow(){ BasePDU* thePDU = PDUFactory::ConstructAbortPDU(); ULEvent theEvent(eAABORTRequest, thePDU); //assume no data coming back when dying, no need for callback EStateID theState = RunEventLoop(theEvent, mConnection, nullptr, false); (void)theState; } //event handler loop for move-- will interweave the two event loops, //one for storescp and the other for movescu. Perhaps complicated, but //avoids starting a second process. EStateID ULConnectionManager::RunMoveEventLoop(ULEvent& currentEvent, ULConnectionCallback* inCallback){ gdcmDebugMacro( "Start RunMoveEventLoop" ); EStateID theState = eStaDoesNotExist; bool waitingForEvent; EEventID raisedEvent; bool receivingData = false; bool justWaiting = false; //when receiving data from a find, etc, then justWaiting is true and only receiving is done //eventually, could add cancel into the mix... but that would be through a callback or something similar do { gdcmDebugMacro( "Before mTransitions.HandleEvent" ); if (!justWaiting){ mTransitions.HandleEvent(this,currentEvent, *mConnection, waitingForEvent, raisedEvent); } theState = mConnection->GetState(); std::istream &is = *mConnection->GetProtocol(); //std::ostream &os = *mConnection->GetProtocol(); //When doing a C-MOVE we receive the Requested DataSet over //another channel (technically this is send to an SCP) //in our case we use another port to receive it. EStateID theCStoreStateID = eSta6TransferReady; bool secondConnectionEstablished = false; gdcmDebugMacro( "Before mSecondaryConnection.GetProtocol" ); if (mSecondaryConnection->GetProtocol() == nullptr){ //establish the connection //can fail if is_readready doesn't return true, ie, the connection //wasn't opened on the other side because the other side isn't sending data yet //for whatever reason (maybe there's nothing to get?) gdcmDebugMacro( "Before mSecondaryConnection.InitializeIncomingConnection" ); secondConnectionEstablished = mSecondaryConnection->InitializeIncomingConnection(); } gdcmDebugMacro( "After mSecondaryConnection.InitializeIncomingConnection: " << "secondConnectionEstablished=" << secondConnectionEstablished << " GetState() =" << (int)mSecondaryConnection->GetState() ); if (!secondConnectionEstablished ) { gdcmErrorMacro( "Could not establish 2nd connection" ); //return eStaDoesNotExist; } if (secondConnectionEstablished && (mSecondaryConnection->GetState()== eSta1Idle || mSecondaryConnection->GetState() == eSta2Open)){ ULEvent theCStoreEvent(eEventDoesNotExist, nullptr);//have to fill this in, we're in passive mode now theCStoreStateID = RunEventLoop(theCStoreEvent, mSecondaryConnection, inCallback, true); } gdcmDebugMacro( "After mSecondaryConnection / RunEventLoop: " << (int)theCStoreStateID ); //just as for the regular event loop, but we have to alternate between the connections. //it may be that nothing comes back over the is connection, but lots over the //isSCP connection. So, if is fails, meh. But if isSCP fails, that's not so meh. //we care only about the datasets coming back from isSCP, ultimately, though the datasets //from is will contain progress info. std::vector incomingPDUs; if (waitingForEvent){ while (waitingForEvent) {//loop for reading in the events that come down the wire uint8_t itemtype = 0x0; gdcmDebugMacro( "Waiting for ItemType (#2)" ); is.read( (char*)&itemtype, 1 ); BasePDU* thePDU = PDUFactory::ConstructPDU(itemtype); if (thePDU != nullptr) { incomingPDUs.push_back(thePDU); thePDU->Read(is); gdcmDebugMacro("PDU code: " << static_cast(itemtype) << std::endl); if (Trace::GetDebugFlag()) { thePDU->Print(Trace::GetStream()); } if (thePDU->IsLastFragment()) waitingForEvent = false; } else { waitingForEvent = false; //because no PDU means not waiting anymore } } //now, we have to figure out the event that just happened based on the PDU that was received. if (!incomingPDUs.empty()) { currentEvent.SetEvent(PDUFactory::DetermineEventByPDU(incomingPDUs[0])); currentEvent.SetPDU(incomingPDUs); if (mConnection->GetTimer().GetHasExpired()) { currentEvent.SetEvent(eARTIMTimerExpired); } if (theState == eSta6TransferReady){//ie, finished the transitions //with find, the results now come down the wire. //the pdu we already have from the event will tell us how many to expect. uint32_t pendingDE1, pendingDE2, success, theVal; pendingDE1 = 0xff01; pendingDE2 = 0xff00; success = 0x0000; theVal = pendingDE1; uint32_t theNumLeft = 0; // the number of pending sub operations left. //so here's the thing: dcmtk responds with 'success' as it first cmove rsp //which is retarded and, I think, wrong. However, dcm4chee responds with 'pending' //so, we look either for pending, or for the number of operations left // (tag 0000, 1020) if the value is success, and that number should be 0. DataSet theRSP = PresentationDataValue::ConcatenatePDVBlobs(PDUFactory::GetPDVs(currentEvent.GetPDUs())); if (Trace::GetDebugFlag()) { Printer thePrinter; Trace::GetStream() << "Response: " << std::endl; thePrinter.PrintDataSet(theRSP, Trace::GetStream()); Trace::GetStream() << std::endl; } if (theRSP.FindDataElement(Tag(0x0, 0x0800))){ DataElement const & de = theRSP.GetDataElement(Tag(0x0,0x0800)); Attribute<0x0,0x0800> at; at.SetFromDataElement( de ); unsigned short datasettype = at.GetValue(); assert( datasettype == 0x0101 || datasettype == 0x1 ); (void)datasettype; } if (theRSP.FindDataElement(Tag(0x0, 0x0900))){ DataElement const & de = theRSP.GetDataElement(Tag(0x0,0x0900)); Attribute<0x0,0x0900> at; at.SetFromDataElement( de ); theVal = at.GetValues()[0]; //if theVal is Pending or Success, then we need to enter the loop below, //because we need the data PDUs. //so, the loop below is a do/while loop; there should be at least a second packet //with the dataset, even if the status is 'success' //success == 0000H } uint32_t theCommandCode = 0; if (theRSP.FindDataElement(Tag(0x0,0x0100))){ DataElement const & de = theRSP.GetDataElement(Tag(0x0,0x0100)); Attribute<0x0,0x0100> at; at.SetFromDataElement( de ); theCommandCode = at.GetValues()[0]; } if (theRSP.FindDataElement(Tag(0x0, 0x1020))){ DataElement de = theRSP.GetDataElement(Tag(0x0,0x1020)); Attribute<0x0,0x1020> at; at.SetFromDataElement( de ); theNumLeft = at.GetValues()[0]; //if theVal is Pending or Success, then we need to enter the loop below, //because we need the data PDUs. //so, the loop below is a do/while loop; there should be at least a second packet //with the dataset, even if the status is 'success' //success == 0000H } if (theVal != pendingDE1 && theVal != pendingDE2 && theVal != success){ //check for other error fields const ByteValue *err1 = nullptr, *err2 = nullptr; gdcmErrorMacro( "Transfer failed with code " << theVal << std::endl); switch (theVal){ case 0xA701: gdcmErrorMacro( "Refused: Out of Resources Unable to calculate number of matches" << std::endl); break; case 0xA702: gdcmErrorMacro( "Refused: Out of Resources Unable to perform sub-operations" << std::endl); break; case 0xA801: gdcmErrorMacro( "Refused: Move Destination unknown" << std::endl); break; case 0xA900: gdcmErrorMacro( "Identifier does not match SOP Class" << std::endl); break; case 0xAA00: gdcmErrorMacro( "None of the frames requested were found in the SOP Instance" << std::endl); break; case 0xAA01: gdcmErrorMacro( "Unable to create new object for this SOP class" << std::endl); break; case 0xAA02: gdcmErrorMacro( "Unable to extract frames" << std::endl); break; case 0xAA03: gdcmErrorMacro( "Time-based request received for a non-time-based original SOP Instance. " << std::endl); break; case 0xAA04: gdcmErrorMacro( "Invalid Request" << std::endl); break; case 0xFE00: gdcmErrorMacro( "Sub-operations terminated due to Cancel Indication" << std::endl); break; case 0xB000: gdcmErrorMacro( "Sub-operations Complete One or more Failures or Warnings" << std::endl); break; default: gdcmErrorMacro( "Unable to process" << std::endl); break; } if (theRSP.FindDataElement(Tag(0x0,0x0901))){ DataElement de = theRSP.GetDataElement(Tag(0x0,0x0901)); err1 = de.GetByteValue(); gdcmErrorMacro( " Tag 0x0,0x901 reported as " << *err1 << std::endl); (void)err1; } if (theRSP.FindDataElement(Tag(0x0,0x0902))){ DataElement de = theRSP.GetDataElement(Tag(0x0,0x0902)); err2 = de.GetByteValue(); gdcmErrorMacro( " Tag 0x0,0x902 reported as " << *err2 << std::endl); (void)err2; } } receivingData = false; justWaiting = false; if (theVal == pendingDE1 || theVal == pendingDE2 || (theVal == success && theNumLeft != 0)) { receivingData = true; //wait for more data as more PDUs (findrsps, for instance) justWaiting = true; waitingForEvent = true; //ok, if we're pending, then let's open the cstorescp connection here //(if it's not already open), and then from here start a storescp event loop. //just don't listen to the cmove event loop until this is done. //could cause a pileup on the main connection, I suppose. //could also report the progress here, if we liked. if (theCommandCode == 0x8021){//cmove response, so prep the retrieval loop on the back connection bool dataSetCountIncremented = true;//false once the number of incoming datasets doesn't change. if (mSecondaryConnection->GetProtocol() == nullptr){ //establish the connection //can fail if is_readready doesn't return true, ie, the connection //wasn't opened on the other side because the other side isn't sending data yet //for whatever reason (maybe there's nothing to get?) secondConnectionEstablished = mSecondaryConnection->InitializeIncomingConnection(); if (secondConnectionEstablished && (mSecondaryConnection->GetState()== eSta1Idle || mSecondaryConnection->GetState() == eSta2Open)){ ULEvent theCStoreEvent(eEventDoesNotExist, nullptr);//have to fill this in, we're in passive mode now theCStoreStateID = RunEventLoop(theCStoreEvent, mSecondaryConnection, inCallback, true); } else {//something broke, can't establish secondary move connection here gdcmErrorMacro( "Unable to establish secondary connection with server, aborting." << std::endl); return eStaDoesNotExist; } } if (secondConnectionEstablished){ while (theCStoreStateID == eSta6TransferReady && dataSetCountIncremented){ ULEvent theCStoreEvent(eEventDoesNotExist, nullptr);//have to fill this in, we're in passive mode now //now, get data from across the network theCStoreStateID = RunEventLoop(theCStoreEvent, mSecondaryConnection, inCallback, true); if (inCallback){ dataSetCountIncremented = true; inCallback->ResetHandledDataSet(); } else { dataSetCountIncremented = false; } } } //force the abort from our side // ULEvent theCStoreEvent(eAABORTRequest, NULL);//have to fill this in, we're in passive mode now // theCStoreStateID = RunEventLoop(theCStoreEvent, outDataSet, mSecondaryConnection, true); } else {//not dealing with cmove progress updates, apparently //keep looping if we haven't succeeded or failed; these are the values for 'pending' //first, dynamically cast that pdu in the event //should be a data pdu //then, look for tag 0x0,0x900 //only add datasets that are _not_ part of the network response std::vector final; std::vector theData; BasePDU* thePDU;//outside the loop for the do/while stopping condition bool interrupted = false; do { uint8_t itemtype = 0x0; is.read( (char*)&itemtype, 1 ); //what happens if nothing's read? thePDU = PDUFactory::ConstructPDU(itemtype); if (itemtype != 0x4 && thePDU != nullptr){ //ie, not a pdatapdu std::vector interruptingPDUs; currentEvent.SetEvent(PDUFactory::DetermineEventByPDU(interruptingPDUs[0])); currentEvent.SetPDU(interruptingPDUs); interrupted= true; break; } if (thePDU != nullptr){ thePDU->Read(is); theData.push_back(thePDU); } else{ break; } //!!!need to handle incoming PDUs that are not data, ie, an abort } while(/*!is.eof() &&*/ !thePDU->IsLastFragment()); if (!interrupted){//ie, if the remote server didn't hang up DataSet theCompleteFindResponse = PresentationDataValue::ConcatenatePDVBlobs(PDUFactory::GetPDVs(theData)); //note that it's the responsibility of the event to delete the PDU in theFindRSP for (size_t i = 0; i < theData.size(); i++){ delete theData[i]; } //outDataSet.push_back(theCompleteFindResponse); if (inCallback){ inCallback->HandleDataSet(theCompleteFindResponse); } } } } } } else { raisedEvent = eEventDoesNotExist; waitingForEvent = false; } } else { currentEvent.SetEvent(raisedEvent);//actions that cause transitions in the state table //locally just raise local events that will therefore cause the trigger to be pulled. } } while (currentEvent.GetEvent() != eEventDoesNotExist && theState != eStaDoesNotExist && theState != eSta13AwaitingClose && theState != eSta1Idle && (theState != eSta6TransferReady || (theState == eSta6TransferReady && receivingData ))); //stop when the AE is done, or when ready to transfer data (ie, the next PDU should be sent in), //or when the connection is idle after a disconnection. //or, if in state 6 and receiving data, until all data is received. return theState; } //event handler loop. //will just keep running until the current event is nonexistent. //at which point, it will return the current state of the connection //to do this, execute an event, and then see if there's a response on the //incoming connection (with a reasonable amount of timeout). //if no response, assume that the connection is broken. //if there's a response, then yay. //note that this is the ARTIM timeout event EStateID ULConnectionManager::RunEventLoop(ULEvent& currentEvent, ULConnection* inWhichConnection, ULConnectionCallback* inCallback, const bool& startWaiting = false){ gdcmDebugMacro( "Start RunEventLoop" ); EStateID theState = eStaDoesNotExist; bool waitingForEvent = startWaiting;//overwritten if not starting waiting, but if waiting, then wait EEventID raisedEvent; bool receivingData = false; //bool justWaiting = startWaiting; //not sure justwaiting is useful; for now, go back to waiting for event //when receiving data from a find, etc, then justWaiting is true and only receiving is done //eventually, could add cancel into the mix... but that would be through a callback or something similar do { gdcmDebugMacro( "Before mTransitions.HandleEvent #2" ); raisedEvent = eEventDoesNotExist; if (!waitingForEvent){//justWaiting){ mTransitions.HandleEvent(this, currentEvent, *inWhichConnection, waitingForEvent, raisedEvent); //this gathering of the state is for scus that have just sent out a request theState = inWhichConnection->GetState(); } std::istream * tempProtocolStream = inWhichConnection->GetProtocol(); if(tempProtocolStream == nullptr) { throw Exception("ProtocolStream as nullptr is invalid"); } std::istream &is = *tempProtocolStream; //std::ostream &os = *inWhichConnection->GetProtocol(); BasePDU* theFirstPDU = nullptr;// the first pdu read in during this event loop, //used to make sure the presentation context ID is correct //read the connection, as that's an event as well. //waiting for an object to come back across the connection, so that it can get handled. //ie, accept, reject, timeout, etc. //of course, if the connection is down, just leave the loop. //also leave the loop if nothing's waiting. //use the PDUFactory to create the appropriate pdu, which has its own //internal mechanisms for handling itself (but will, of course, be put inside the event object). //but, and here's the important thing, only read on the socket when we should. std::vector incomingPDUs; if (waitingForEvent){ while (waitingForEvent){//loop for reading in the events that come down the wire uint8_t itemtype = 0x0; try { gdcmDebugMacro( "Waiting for ItemType" ); is.read( (char*)&itemtype, 1 ); gdcmDebugMacro( "Received ItemType #" << (int)itemtype ); //what happens if nothing's read? theFirstPDU = PDUFactory::ConstructPDU(itemtype); if (theFirstPDU != nullptr){ incomingPDUs.push_back(theFirstPDU); theFirstPDU->Read(is); gdcmDebugMacro("PDU code: " << static_cast(itemtype) << std::endl); if (Trace::GetDebugFlag()) { theFirstPDU->Print(Trace::GetStream()); } if (theFirstPDU->IsLastFragment()) waitingForEvent = false; } else { gdcmDebugMacro( "NULL theFirstPDU for ItemType" << (int)itemtype ); waitingForEvent = false; //because no PDU means not waiting anymore return eStaDoesNotExist; } } catch (...) { //handle the exception, which is basically that nothing came in over the pipe. gdcmAssertAlwaysMacro( 0 ); } } //now, we have to figure out the event that just happened based on the PDU that was received. //this state gathering is for scps, especially the cstore for cmove. theState = inWhichConnection->GetState(); if (!incomingPDUs.empty()){ currentEvent.SetEvent(PDUFactory::DetermineEventByPDU(incomingPDUs[0])); currentEvent.SetPDU(incomingPDUs); //here's the scp handling code if (mConnection->GetTimer().GetHasExpired()){ currentEvent.SetEvent(eARTIMTimerExpired); } switch(currentEvent.GetEvent()){ case ePDATATFPDU: { //if (theState == eSta6TransferReady){//ie, finished the transitions //with find, the results now come down the wire. //the pdu we already have from the event will tell us how many to expect. uint32_t pendingDE1, pendingDE2, success, theVal; pendingDE1 = 0xff01; pendingDE2 = 0xff00; success = 0x0000; theVal = pendingDE1; uint32_t theCommandCode = 0;//for now, a nothing value DataSet theRSP = PresentationDataValue::ConcatenatePDVBlobs( PDUFactory::GetPDVs(currentEvent.GetPDUs())); if (inCallback) { inCallback->HandleResponse(theRSP); } if (theRSP.FindDataElement(Tag(0x0, 0x0900))){ DataElement de = theRSP.GetDataElement(Tag(0x0,0x0900)); Attribute<0x0,0x0900> at; at.SetFromDataElement( de ); theVal = at.GetValues()[0]; //if theVal is Pending or Success, then we need to enter the loop below, //because we need the data PDUs. //so, the loop below is a do/while loop; there should be at least a second packet //with the dataset, even if the status is 'success' //success == 0000H } if (Trace::GetDebugFlag()) { Printer thePrinter; thePrinter.PrintDataSet(theRSP, Trace::GetStream()); } //check to see if this is a cstorerq if (theRSP.FindDataElement(Tag(0x0, 0x0100))) { DataElement de2 = theRSP.GetDataElement(Tag(0x0,0x0100)); Attribute<0x0,0x0100> at2; at2.SetFromDataElement( de2 ); theCommandCode = at2.GetValues()[0]; } if (theVal != pendingDE1 && theVal != pendingDE2 && theVal != success) { //check for other error fields const ByteValue *err1 = nullptr, *err2 = nullptr; gdcmErrorMacro( "Transfer failed with code " << theVal << std::endl); switch (theVal){ case 0xA701: gdcmErrorMacro( "Refused: Out of Resources Unable to calculate number of matches" << std::endl); break; case 0xA702: gdcmErrorMacro( "Refused: Out of Resources Unable to perform sub-operations" << std::endl); break; case 0xA801: gdcmErrorMacro( "Refused: Move Destination unknown" << std::endl); break; case 0xA900: gdcmErrorMacro( "Identifier does not match SOP Class" << std::endl); break; case 0xAA00: gdcmErrorMacro( "None of the frames requested were found in the SOP Instance" << std::endl); break; case 0xAA01: gdcmErrorMacro( "Unable to create new object for this SOP class" << std::endl); break; case 0xAA02: gdcmErrorMacro( "Unable to extract frames" << std::endl); break; case 0xAA03: gdcmErrorMacro( "Time-based request received for a non-time-based original SOP Instance. " << std::endl); break; case 0xAA04: gdcmErrorMacro( "Invalid Request" << std::endl); break; case 0xFE00: gdcmErrorMacro( "Sub-operations terminated due to Cancel Indication" << std::endl); break; case 0xB000: gdcmErrorMacro( "Sub-operations Complete One or more Failures or Warnings" << std::endl); break; default: gdcmErrorMacro( "Unable to process" << std::endl); break; } if (theRSP.FindDataElement(Tag(0x0,0x0901))){ DataElement de = theRSP.GetDataElement(Tag(0x0,0x0901)); err1 = de.GetByteValue(); gdcmErrorMacro( " Tag 0x0,0x901 reported as " << *err1 << std::endl); (void)err1; } if (theRSP.FindDataElement(Tag(0x0,0x0902))){ DataElement de = theRSP.GetDataElement(Tag(0x0,0x0902)); err2 = de.GetByteValue(); gdcmErrorMacro( " Tag 0x0,0x902 reported as " << *err2 << std::endl); (void)err2; } } receivingData = false; //justWaiting = false; if (theVal == pendingDE1 || theVal == pendingDE2) { receivingData = true; //wait for more data as more PDUs (findrsps, for instance) //justWaiting = true; waitingForEvent = true; } if (theVal == pendingDE1 || theVal == pendingDE2 /*|| theVal == success*/){//keep looping if we haven't succeeded or failed; these are the values for 'pending' //first, dynamically cast that pdu in the event //should be a data pdu //then, look for tag 0x0,0x900 //only add datasets that are _not_ part of the network response std::vector final; std::vector theData; BasePDU* thePDU;//outside the loop for the do/while stopping condition bool interrupted = false; do { uint8_t itemtype = 0x0; is.read( (char*)&itemtype, 1 ); //what happens if nothing's read? thePDU = PDUFactory::ConstructPDU(itemtype); if (itemtype != 0x4 && thePDU != nullptr){ //ie, not a pdatapdu std::vector interruptingPDUs; interruptingPDUs.push_back(thePDU); currentEvent.SetEvent(PDUFactory::DetermineEventByPDU(interruptingPDUs[0])); currentEvent.SetPDU(interruptingPDUs); interrupted= true; break; } if (thePDU != nullptr){ thePDU->Read(is); theData.push_back(thePDU); } else{ break; } //!!!need to handle incoming PDUs that are not data, ie, an abort } while(!thePDU->IsLastFragment()); if (!interrupted){//ie, if the remote server didn't hang up bool useimplicit = true; TransferSyntaxSub ts1; ts1.SetNameFromUID( UIDs::ImplicitVRLittleEndianDefaultTransferSyntaxforDICOM ); if( mSecondaryConnection ) { const TransferSyntaxSub & ts_ = mSecondaryConnection->GetCStoreTransferSyntax(); if( strcmp(ts_.GetName(), ts1.GetName()) != 0) { useimplicit = false; } } DataSet theCompleteFindResponse; if( useimplicit ) { inCallback->SetImplicitFlag(true); theCompleteFindResponse = PresentationDataValue::ConcatenatePDVBlobs(PDUFactory::GetPDVs(theData)); } else { inCallback->SetImplicitFlag(false); theCompleteFindResponse = PresentationDataValue::ConcatenatePDVBlobsAsExplicit(PDUFactory::GetPDVs(theData)); } //note that it's the responsibility of the event to delete the PDU in theFindRSP for (size_t i = 0; i < theData.size(); i++) { delete theData[i]; } assert(inCallback); { inCallback->HandleDataSet(theCompleteFindResponse); } // DataSetEvent dse( &theCompleteFindResponse ); // this->InvokeEvent( dse ); if (theCommandCode == 1){//if we're doing cstore scp stuff, send information back along the connection. std::vector theCStoreRSPPDU = PDUFactory::CreateCStoreRSPPDU(&theRSP, theFirstPDU);//pass NULL for C-Echo //send them directly back over the connection //ideall, should go through the transition table, but we know this should work //and it won't change the state (unless something breaks?, but then an exception should throw) std::vector::iterator itor; for (itor = theCStoreRSPPDU.begin(); itor < theCStoreRSPPDU.end(); itor++){ (*itor)->Write(*inWhichConnection->GetProtocol()); } inWhichConnection->GetProtocol()->flush(); // FIXME added MM / Oct 30 2010 //AReleaseRPPDU rel; //rel.Write( *inWhichConnection->GetProtocol() ); //inWhichConnection->GetProtocol()->flush(); receivingData = false; //gotta get data on the other connection for a cmove // cleanup for (itor = theCStoreRSPPDU.begin(); itor < theCStoreRSPPDU.end(); itor++){ delete *itor; } } } } } break; case eARELEASERequest://process this via the transition table waitingForEvent = false; break; case eARELEASE_RQPDUReceivedOpen://process this via the transition table waitingForEvent = false; receivingData = true; //to continue the loop to process the release break; case eAABORTPDUReceivedOpen: raisedEvent = eEventDoesNotExist; theState = eStaDoesNotExist; /* fall through */ case eAABORTRequest: waitingForEvent = false; inWhichConnection->StopProtocol(); break; case eASSOCIATE_ACPDUreceived: default: waitingForEvent = false; break; } } //} else { // raisedEvent = eEventDoesNotExist; // waitingForEvent = false; //} } else { currentEvent.SetEvent(raisedEvent);//actions that cause transitions in the state table //locally just raise local events that will therefore cause the trigger to be pulled. } } while (currentEvent.GetEvent() != eEventDoesNotExist && theState != eStaDoesNotExist && theState != eSta13AwaitingClose && theState != eSta1Idle && (theState != eSta6TransferReady || (theState == eSta6TransferReady && receivingData ))); //stop when the AE is done, or when ready to transfer data (ie, the next PDU should be sent in), //or when the connection is idle after a disconnection. //or, if in state 6 and receiving data, until all data is received. return theState; } } // end namespace network } // end namespace gdcm