1 /*
2  *
3  *  Copyright (C) 2008-2019, OFFIS e.V.
4  *  All rights reserved.  See COPYRIGHT file for details.
5  *
6  *  This software and supporting documentation were developed by
7  *
8  *    OFFIS e.V.
9  *    R&D Division Health
10  *    Escherweg 2
11  *    D-26121 Oldenburg, Germany
12  *
13  *
14  *  Module:  dcmnet
15  *
16  *  Author:  Michael Onken
17  *
18  *  Purpose: Base class for Service Class Users (SCUs)
19  *
20  */
21 
22 #include "dcmtk/config/osconfig.h" /* make sure OS specific configuration is included first */
23 
24 #include "dcmtk/dcmdata/dcostrmf.h" /* for class DcmOutputFileStream */
25 #include "dcmtk/dcmdata/dcuid.h"    /* for dcmFindUIDName() */
26 #include "dcmtk/dcmnet/diutil.h"    /* for dcmnet logger */
27 #include "dcmtk/dcmnet/scu.h"
28 #include "dcmtk/ofstd/ofmem.h" /* for OFunique_ptr */
29 
30 #ifdef WITH_ZLIB
31 #include <zlib.h> /* for zlibVersion() */
32 #endif
33 
DcmSCU()34 DcmSCU::DcmSCU()
35     : m_assoc(NULL)
36     , m_net(NULL)
37     , m_params(NULL)
38     , m_assocConfigFilename()
39     , m_assocConfigProfile()
40     , m_presContexts()
41     , m_assocConfigFile()
42     , m_openDIMSERequest(NULL)
43     , m_maxReceivePDULength(ASC_DEFAULTMAXPDU)
44     , m_blockMode(DIMSE_BLOCKING)
45     , m_ourAETitle("ANY-SCU")
46     , m_peer()
47     , m_peerAETitle("ANY-SCP")
48     , m_peerPort(104)
49     , m_dimseTimeout(0)
50     , m_acseTimeout(30)
51     , m_storageDir()
52     , m_storageMode(DCMSCU_STORAGE_DISK)
53     , m_verbosePCMode(OFFalse)
54     , m_datasetConversionMode(OFFalse)
55     , m_progressNotificationMode(OFTrue)
56 {
57     OFStandard::initializeNetwork();
58 }
59 
freeNetwork()60 void DcmSCU::freeNetwork()
61 {
62     if ((m_assoc != NULL) || (m_net != NULL) || (m_params != NULL))
63         DCMNET_DEBUG("Cleaning up internal association and network structures");
64     /* destroy association parameters, i.e. free memory of T_ASC_Parameters.
65        Usually this is done in ASC_destroyAssociation; however, if we already
66        have association parameters but not yet an association (e.g. after calling
67        initNetwork() and negotiateAssociation()), the latter approach may fail.
68     */
69     if (m_params)
70     {
71         ASC_destroyAssociationParameters(&m_params);
72         m_params = NULL;
73         // make sure destroyAssociation does not try to free params a second time
74         // (happens in case we have already have an association structure)
75         if (m_assoc)
76             m_assoc->params = NULL;
77     }
78     // destroy the association, i.e. free memory of T_ASC_Association* structure.
79     ASC_destroyAssociation(&m_assoc);
80     // drop the network, i.e. free memory of T_ASC_Network* structure.
81     ASC_dropNetwork(&m_net);
82     // Cleanup old DIMSE request if any
83     delete m_openDIMSERequest;
84     m_openDIMSERequest = NULL;
85 }
86 
~DcmSCU()87 DcmSCU::~DcmSCU()
88 {
89     // abort association (if any) and destroy dcmnet data structures
90     if (isConnected())
91     {
92         closeAssociation(DCMSCU_ABORT_ASSOCIATION); // also frees network
93     }
94     else
95     {
96         freeNetwork();
97     }
98 
99     OFStandard::shutdownNetwork();
100 }
101 
initNetwork()102 OFCondition DcmSCU::initNetwork()
103 {
104     /* Return if SCU is already connected */
105     if (isConnected())
106         return NET_EC_AlreadyConnected;
107 
108     /* Be sure internal network structures are clean (delete old) */
109     freeNetwork();
110 
111     OFString tempStr;
112     /* initialize network, i.e. create an instance of T_ASC_Network*. */
113     OFCondition cond = ASC_initializeNetwork(NET_REQUESTOR, 0, m_acseTimeout, &m_net);
114     if (cond.bad())
115     {
116         DimseCondition::dump(tempStr, cond);
117         DCMNET_ERROR(tempStr);
118         return cond;
119     }
120 
121     /* initialize association parameters, i.e. create an instance of T_ASC_Parameters*. */
122     cond = ASC_createAssociationParameters(&m_params, m_maxReceivePDULength);
123     if (cond.bad())
124     {
125         DCMNET_ERROR(DimseCondition::dump(tempStr, cond));
126         return cond;
127     }
128 
129     /* sets this application's title and the called application's title in the params */
130     /* structure. The default values are "ANY-SCU" and "ANY-SCP". */
131     ASC_setAPTitles(m_params, m_ourAETitle.c_str(), m_peerAETitle.c_str(), NULL);
132 
133     /* Figure out the presentation addresses and copy the */
134     /* corresponding values into the association parameters.*/
135     DIC_NODENAME peerHost;
136     const OFString localHost = OFStandard::getHostName();
137     /* Since the underlying dcmnet structures reserve only 64 bytes for peer
138        as well as local host name, we check here for buffer overflow.
139      */
140     if ((m_peer.length() + 5 /* max 65535 */) + 1 /* for ":" */ > 63)
141     {
142         DCMNET_ERROR("Maximum length of peer host name '" << m_peer << "' is longer than maximum of 57 characters");
143         return EC_IllegalCall; // TODO: need to find better error code
144     }
145     if (localHost.size() + 1 > 63)
146     {
147         DCMNET_ERROR("Maximum length of local host name '" << localHost << "' is longer than maximum of 62 characters");
148         return EC_IllegalCall; // TODO: need to find better error code
149     }
150     sprintf(peerHost, "%s:%d", m_peer.c_str(), OFstatic_cast(int, m_peerPort));
151     ASC_setPresentationAddresses(m_params, localHost.c_str(), peerHost);
152 
153     /* Add presentation contexts */
154 
155     // First, import from config file, if specified
156     OFCondition result;
157     if (!m_assocConfigFilename.empty())
158     {
159         DcmAssociationConfiguration assocConfig;
160         result = DcmAssociationConfigurationFile::initialize(assocConfig, m_assocConfigFilename.c_str());
161         if (result.bad())
162         {
163             DCMNET_WARN("Unable to parse association configuration file " << m_assocConfigFilename
164                                                                           << " (ignored): " << result.text());
165             return result;
166         }
167         else
168         {
169             /* perform name mangling for config file key */
170             OFString profileName;
171             const unsigned char* c = OFreinterpret_cast(const unsigned char*, m_assocConfigProfile.c_str());
172             while (*c)
173             {
174                 if (!isspace(*c))
175                     profileName += OFstatic_cast(char, toupper(*c));
176                 ++c;
177             }
178 
179             result = assocConfig.setAssociationParameters(profileName.c_str(), *m_params);
180             if (result.bad())
181             {
182                 DCMNET_WARN("Unable to apply association configuration file " << m_assocConfigFilename
183                                                                               << " (ignored): " << result.text());
184                 return result;
185             }
186         }
187     }
188 
189     // Adapt presentation context ID to existing presentation contexts.
190     // It's important that presentation context IDs are numerated 1,3,5,7...!
191     Uint32 nextFreePresID = 257;
192     Uint32 numContexts    = ASC_countPresentationContexts(m_params);
193     if (numContexts <= 127)
194     {
195         // Need Uint16 to avoid overflow in currPresID (unsigned char)
196         nextFreePresID = 2 * numContexts + 1; /* add 1 to point to the next free ID*/
197     }
198     // Print warning if number of overall presentation contexts exceeds 128
199     if ((numContexts + m_presContexts.size()) > 128)
200     {
201         DCMNET_WARN("Number of presentation contexts exceeds 128 (" << numContexts + m_presContexts.size()
202                                                                     << "). Some contexts will not be negotiated");
203     }
204     else
205     {
206         DCMNET_TRACE("Configured " << numContexts << " presentation contexts from config file");
207         if (m_presContexts.size() > 0)
208             DCMNET_TRACE("Adding another " << m_presContexts.size() << " presentation contexts configured for SCU");
209     }
210 
211     // Add presentation contexts not originating from config file
212     OFListIterator(DcmSCUPresContext) contIt             = m_presContexts.begin();
213     OFListConstIterator(DcmSCUPresContext) endOfContList = m_presContexts.end();
214     while ((contIt != endOfContList) && (nextFreePresID <= 255))
215     {
216         const Uint16 numTransferSyntaxes = OFstatic_cast(Uint16, (*contIt).transferSyntaxes.size());
217         const char** transferSyntaxes    = new const char*[numTransferSyntaxes];
218 
219         // Iterate over transfer syntaxes within one presentation context
220         OFListIterator(OFString) syntaxIt        = (*contIt).transferSyntaxes.begin();
221         OFListIterator(OFString) endOfSyntaxList = (*contIt).transferSyntaxes.end();
222         Uint16 sNum                              = 0;
223         // copy all transfer syntaxes to array
224         while (syntaxIt != endOfSyntaxList)
225         {
226             transferSyntaxes[sNum] = (*syntaxIt).c_str();
227             ++syntaxIt;
228             ++sNum;
229         }
230 
231         // add the presentation context
232         cond = ASC_addPresentationContext(m_params,
233                                           OFstatic_cast(Uint8, nextFreePresID),
234                                           (*contIt).abstractSyntaxName.c_str(),
235                                           transferSyntaxes,
236                                           numTransferSyntaxes,
237                                           (*contIt).roleSelect);
238         // if adding was successful, prepare presentation context ID for next addition
239         delete[] transferSyntaxes;
240         transferSyntaxes = NULL;
241         if (cond.bad())
242             return cond;
243         contIt++;
244         // goto next free number, only odd presentation context IDs permitted
245         nextFreePresID += 2;
246     }
247 
248     numContexts = ASC_countPresentationContexts(m_params);
249     if (numContexts == 0)
250     {
251         DCMNET_ERROR("Cannot initialize network: No presentation contexts defined");
252         return NET_EC_NoPresentationContextsDefined;
253     }
254     DCMNET_DEBUG("Configured a total of " << numContexts << " presentation contexts for SCU");
255 
256     return cond;
257 }
258 
negotiateAssociation()259 OFCondition DcmSCU::negotiateAssociation()
260 {
261     /* Return error if SCU is already connected */
262     if (isConnected())
263         return NET_EC_AlreadyConnected;
264 
265     /* dump presentation contexts if required */
266     OFString tempStr;
267     if (m_verbosePCMode)
268         DCMNET_INFO("Request Parameters:" << OFendl << ASC_dumpParameters(tempStr, m_params, ASC_ASSOC_RQ));
269     else
270         DCMNET_DEBUG("Request Parameters:" << OFendl << ASC_dumpParameters(tempStr, m_params, ASC_ASSOC_RQ));
271 
272     /* create association, i.e. try to establish a network connection to another */
273     /* DICOM application. This call creates an instance of T_ASC_Association*. */
274     DCMNET_INFO("Requesting Association");
275     OFCondition cond = ASC_requestAssociation(m_net, m_params, &m_assoc);
276     if (cond.bad())
277     {
278         if (cond == DUL_ASSOCIATIONREJECTED)
279         {
280             T_ASC_RejectParameters rej;
281             ASC_getRejectParameters(m_params, &rej);
282             DCMNET_DEBUG("Association Rejected:" << OFendl << ASC_printRejectParameters(tempStr, &rej));
283             return cond;
284         }
285         else
286         {
287             DCMNET_DEBUG("Association Request Failed: " << DimseCondition::dump(tempStr, cond));
288             return cond;
289         }
290     }
291 
292     /* dump the presentation contexts which have been accepted/refused */
293     if (m_verbosePCMode)
294         DCMNET_INFO("Association Parameters Negotiated:" << OFendl
295                                                          << ASC_dumpParameters(tempStr, m_params, ASC_ASSOC_AC));
296     else
297         DCMNET_DEBUG("Association Parameters Negotiated:" << OFendl
298                                                           << ASC_dumpParameters(tempStr, m_params, ASC_ASSOC_AC));
299 
300     /* count the presentation contexts which have been accepted by the SCP */
301     /* If there are none, finish the execution */
302     if (ASC_countAcceptedPresentationContexts(m_params) == 0)
303     {
304         DCMNET_ERROR("No Acceptable Presentation Contexts");
305         return NET_EC_NoAcceptablePresentationContexts;
306     }
307 
308     /* dump general information concerning the establishment of the network connection if required */
309     DCMNET_INFO("Association Accepted (Max Send PDV: " << OFstatic_cast(unsigned long, m_assoc->sendPDVLength) << ")");
310     return EC_Normal;
311 }
312 
addPresentationContext(const OFString & abstractSyntax,const OFList<OFString> & xferSyntaxes,const T_ASC_SC_ROLE role)313 OFCondition DcmSCU::addPresentationContext(const OFString& abstractSyntax,
314                                            const OFList<OFString>& xferSyntaxes,
315                                            const T_ASC_SC_ROLE role)
316 
317 {
318 
319     DcmSCUPresContext presContext;
320     presContext.abstractSyntaxName          = abstractSyntax;
321     OFListConstIterator(OFString) it        = xferSyntaxes.begin();
322     OFListConstIterator(OFString) endOfList = xferSyntaxes.end();
323     while (it != endOfList)
324     {
325         presContext.transferSyntaxes.push_back(*it);
326         it++;
327     }
328     presContext.roleSelect = role;
329     m_presContexts.push_back(presContext);
330     return EC_Normal;
331 }
332 
useSecureConnection(DcmTransportLayer * tlayer)333 OFCondition DcmSCU::useSecureConnection(DcmTransportLayer* tlayer)
334 {
335     OFCondition cond = ASC_setTransportLayer(m_net, tlayer, OFFalse /* do not take over ownership */);
336     if (cond.good())
337         cond = ASC_setTransportLayerType(m_params, OFTrue /* use TLS */);
338     return cond;
339 }
340 
clearPresentationContexts()341 void DcmSCU::clearPresentationContexts()
342 {
343     m_presContexts.clear();
344     m_assocConfigFilename.clear();
345     m_assocConfigProfile.clear();
346 }
347 
348 // Returns usable presentation context ID for a given abstract syntax UID and
349 // transfer syntax UID. 0 if none matches.
findPresentationContextID(const OFString & abstractSyntax,const OFString & transferSyntax,const T_ASC_SC_ROLE requestorRole)350 T_ASC_PresentationContextID DcmSCU::findPresentationContextID(const OFString& abstractSyntax,
351                                                               const OFString& transferSyntax,
352                                                               const T_ASC_SC_ROLE requestorRole)
353 {
354     if (!isConnected())
355         return 0;
356 
357     DUL_PRESENTATIONCONTEXT* pc;
358     LST_HEAD** l;
359     OFBool found = OFFalse;
360 
361     if (abstractSyntax.empty())
362         return 0;
363 
364     /* first of all we look for a presentation context
365      * matching both abstract and transfer syntax
366      */
367     l  = &m_assoc->params->DULparams.acceptedPresentationContext;
368     pc = (DUL_PRESENTATIONCONTEXT*)LST_Head(l);
369     (void)LST_Position(l, (LST_NODE*)pc);
370     while (pc && !found)
371     {
372         found = (strcmp(pc->abstractSyntax, abstractSyntax.c_str()) == 0);
373         found &= (pc->result == ASC_P_ACCEPTANCE);
374         if (!transferSyntax.empty()) // ignore transfer syntax if not specified
375             found &= (strcmp(pc->acceptedTransferSyntax, transferSyntax.c_str()) == 0);
376         if (found)
377             found &= pc->acceptedSCRole == ascRole2dulRole(requestorRole);
378         if (!found)
379             pc = (DUL_PRESENTATIONCONTEXT*)LST_Next(l);
380     }
381     if (found)
382         return pc->presentationContextID;
383 
384     return 0; /* not found */
385 }
386 
387 // Returns the presentation context ID that best matches the given abstract syntax UID and
388 // transfer syntax UID.
findAnyPresentationContextID(const OFString & abstractSyntax,const OFString & transferSyntax)389 T_ASC_PresentationContextID DcmSCU::findAnyPresentationContextID(const OFString& abstractSyntax,
390                                                                  const OFString& transferSyntax)
391 {
392     if (m_assoc == NULL)
393         return 0;
394 
395     DUL_PRESENTATIONCONTEXT* pc;
396     LST_HEAD** l;
397     OFBool found = OFFalse;
398 
399     if (abstractSyntax.empty())
400         return 0;
401 
402     /* first of all we look for a presentation context
403      * matching both abstract and transfer syntax
404      */
405     l  = &m_assoc->params->DULparams.acceptedPresentationContext;
406     pc = (DUL_PRESENTATIONCONTEXT*)LST_Head(l);
407     (void)LST_Position(l, (LST_NODE*)pc);
408     while (pc && !found)
409     {
410         found = (strcmp(pc->abstractSyntax, abstractSyntax.c_str()) == 0);
411         found &= (pc->result == ASC_P_ACCEPTANCE);
412         if (!transferSyntax.empty()) // ignore transfer syntax if not specified
413             found &= (strcmp(pc->acceptedTransferSyntax, transferSyntax.c_str()) == 0);
414         if (!found)
415             pc = (DUL_PRESENTATIONCONTEXT*)LST_Next(l);
416     }
417     if (found)
418         return pc->presentationContextID;
419 
420     /* now we look for an explicit VR uncompressed PC. */
421     l  = &m_assoc->params->DULparams.acceptedPresentationContext;
422     pc = (DUL_PRESENTATIONCONTEXT*)LST_Head(l);
423     (void)LST_Position(l, (LST_NODE*)pc);
424     while (pc && !found)
425     {
426         found = (strcmp(pc->abstractSyntax, abstractSyntax.c_str()) == 0) && (pc->result == ASC_P_ACCEPTANCE)
427             && ((strcmp(pc->acceptedTransferSyntax, UID_LittleEndianExplicitTransferSyntax) == 0)
428                 || (strcmp(pc->acceptedTransferSyntax, UID_BigEndianExplicitTransferSyntax) == 0));
429         if (!found)
430             pc = (DUL_PRESENTATIONCONTEXT*)LST_Next(l);
431     }
432     if (found)
433         return pc->presentationContextID;
434 
435     /* now we look for an implicit VR uncompressed PC. */
436     l  = &m_assoc->params->DULparams.acceptedPresentationContext;
437     pc = (DUL_PRESENTATIONCONTEXT*)LST_Head(l);
438     (void)LST_Position(l, (LST_NODE*)pc);
439     while (pc && !found)
440     {
441         found = (strcmp(pc->abstractSyntax, abstractSyntax.c_str()) == 0) && (pc->result == ASC_P_ACCEPTANCE)
442             && (strcmp(pc->acceptedTransferSyntax, UID_LittleEndianImplicitTransferSyntax) == 0);
443         if (!found)
444             pc = (DUL_PRESENTATIONCONTEXT*)LST_Next(l);
445     }
446     if (found)
447         return pc->presentationContextID;
448 
449     /* finally we accept everything we get.
450        returns 0 if abstract syntax is not supported
451     */
452     return ASC_findAcceptedPresentationContextID(m_assoc, abstractSyntax.c_str());
453 }
454 
findPresentationContext(const T_ASC_PresentationContextID presID,OFString & abstractSyntax,OFString & transferSyntax)455 void DcmSCU::findPresentationContext(const T_ASC_PresentationContextID presID,
456                                      OFString& abstractSyntax,
457                                      OFString& transferSyntax)
458 {
459     transferSyntax.clear();
460     abstractSyntax.clear();
461     if (m_assoc == NULL)
462         return;
463 
464     DUL_PRESENTATIONCONTEXT* pc;
465     LST_HEAD** l;
466 
467     /* we look for a presentation context matching
468      * both abstract and transfer syntax
469      */
470     l  = &m_assoc->params->DULparams.acceptedPresentationContext;
471     pc = (DUL_PRESENTATIONCONTEXT*)LST_Head(l);
472     (void)LST_Position(l, (LST_NODE*)pc);
473     while (pc)
474     {
475         if (presID == pc->presentationContextID)
476         {
477             if (pc->result == ASC_P_ACCEPTANCE)
478             {
479                 // found a match
480                 transferSyntax = pc->acceptedTransferSyntax;
481                 abstractSyntax = pc->abstractSyntax;
482             }
483             break;
484         }
485         pc = (DUL_PRESENTATIONCONTEXT*)LST_Next(l);
486     }
487 }
488 
nextMessageID()489 Uint16 DcmSCU::nextMessageID()
490 {
491     if (!isConnected())
492         return 0;
493     else
494         return m_assoc->nextMsgID++;
495 }
496 
closeAssociation(const DcmCloseAssociationType closeType)497 void DcmSCU::closeAssociation(const DcmCloseAssociationType closeType)
498 {
499     if (!isConnected())
500     {
501         DCMNET_WARN("Closing of association requested but no association active (ignored)");
502         return;
503     }
504 
505     OFCondition cond;
506     OFString tempStr;
507 
508     /* tear down association, i.e. terminate network connection to SCP */
509     switch (closeType)
510     {
511         case DCMSCU_RELEASE_ASSOCIATION:
512             /* release association */
513             DCMNET_INFO("Releasing Association");
514             cond = ASC_releaseAssociation(m_assoc);
515             if (cond.bad())
516             {
517                 DCMNET_ERROR("Association Release Failed: " << DimseCondition::dump(tempStr, cond));
518                 return; // TODO: do we really need this?
519             }
520             break;
521         case DCMSCU_ABORT_ASSOCIATION:
522             /* abort association */
523             DCMNET_INFO("Aborting Association");
524             cond = ASC_abortAssociation(m_assoc);
525             if (cond.bad())
526             {
527                 DCMNET_ERROR("Association Abort Failed: " << DimseCondition::dump(tempStr, cond));
528             }
529             break;
530         case DCMSCU_PEER_REQUESTED_RELEASE:
531             /* peer requested release */
532             DCMNET_ERROR("Protocol Error: Peer requested release (Aborting)");
533             DCMNET_INFO("Aborting Association");
534             cond = ASC_abortAssociation(m_assoc);
535             if (cond.bad())
536             {
537                 DCMNET_ERROR("Association Abort Failed: " << DimseCondition::dump(tempStr, cond));
538             }
539             break;
540         case DCMSCU_PEER_ABORTED_ASSOCIATION:
541             /* peer aborted association */
542             DCMNET_INFO("Peer Aborted Association");
543             break;
544     }
545 
546     // destroy and free memory of internal association and network structures
547     freeNetwork();
548 }
549 
releaseAssociation()550 OFCondition DcmSCU::releaseAssociation()
551 {
552     OFCondition status = DIMSE_ILLEGALASSOCIATION;
553     // check whether there is an active association
554     if (isConnected())
555     {
556         closeAssociation(DCMSCU_RELEASE_ASSOCIATION);
557         status = EC_Normal;
558     }
559     return status;
560 }
561 
abortAssociation()562 OFCondition DcmSCU::abortAssociation()
563 {
564     OFCondition status = DIMSE_ILLEGALASSOCIATION;
565     // check whether there is an active association
566     if (isConnected())
567     {
568         closeAssociation(DCMSCU_ABORT_ASSOCIATION);
569         status = EC_Normal;
570     }
571     return status;
572 }
573 
574 /* ************************************************************************* */
575 /*                            C-ECHO functionality                           */
576 /* ************************************************************************* */
577 
578 // Sends C-ECHO request to another DICOM application
sendECHORequest(const T_ASC_PresentationContextID presID)579 OFCondition DcmSCU::sendECHORequest(const T_ASC_PresentationContextID presID)
580 {
581     if (!isConnected())
582         return DIMSE_ILLEGALASSOCIATION;
583 
584     OFCondition cond;
585     T_ASC_PresentationContextID pcid = presID;
586 
587     /* If necessary, find appropriate presentation context */
588     if (pcid == 0)
589         pcid = findPresentationContextID(UID_VerificationSOPClass, UID_LittleEndianExplicitTransferSyntax);
590     if (pcid == 0)
591         pcid = findPresentationContextID(UID_VerificationSOPClass, UID_BigEndianExplicitTransferSyntax);
592     if (pcid == 0)
593         pcid = findPresentationContextID(UID_VerificationSOPClass, UID_LittleEndianImplicitTransferSyntax);
594     if (pcid == 0)
595     {
596         DCMNET_ERROR("No valid presentation context found for sending C-ECHO using SOP Class: "
597                      << dcmFindNameOfUID(UID_VerificationSOPClass, ""));
598         return DIMSE_NOVALIDPRESENTATIONCONTEXTID;
599     }
600 
601     /* Now, assemble DIMSE message */
602     T_DIMSE_Message msg;
603     // Make sure everything is zeroed (especially options)
604     bzero((char*)&msg, sizeof(msg));
605     T_DIMSE_C_EchoRQ* req = &(msg.msg.CEchoRQ);
606     // Set type of message
607     msg.CommandField = DIMSE_C_ECHO_RQ;
608     // Set message ID
609     req->MessageID = nextMessageID();
610     // Announce no dataset
611     req->DataSetType = DIMSE_DATASET_NULL;
612     // Set affected SOP Class UID (always Verification SOP Class)
613     OFStandard::strlcpy(req->AffectedSOPClassUID, UID_VerificationSOPClass, sizeof(req->AffectedSOPClassUID));
614 
615     /* Send request */
616     OFString tempStr;
617     if (DCM_dcmnetLogger.isEnabledFor(OFLogger::DEBUG_LOG_LEVEL))
618     {
619         DCMNET_INFO("Sending C-ECHO Request");
620         DCMNET_DEBUG(DIMSE_dumpMessage(tempStr, msg, DIMSE_OUTGOING, NULL, pcid));
621     }
622     else
623     {
624         DCMNET_INFO("Sending C-ECHO Request (MsgID " << req->MessageID << ")");
625     }
626     cond = sendDIMSEMessage(pcid, &msg, NULL /*dataObject*/);
627     if (cond.bad())
628     {
629         DCMNET_ERROR("Failed sending C-ECHO request: " << DimseCondition::dump(tempStr, cond));
630         return cond;
631     }
632 
633     /* Receive response */
634     T_DIMSE_Message rsp;
635     // Make sure everything is zeroed (especially options)
636     bzero((char*)&rsp, sizeof(rsp));
637 
638     DcmDataset* statusDetail = NULL;
639     cond = receiveDIMSECommand(&pcid, &rsp, &statusDetail, NULL /* not interested in the command set */);
640     if (cond.bad())
641     {
642         DCMNET_ERROR("Failed receiving DIMSE response: " << DimseCondition::dump(tempStr, cond));
643         return cond;
644     }
645     /* Check whether we received C-ECHO response, otherwise print error */
646     if (rsp.CommandField == DIMSE_C_ECHO_RSP)
647     {
648         if (DCM_dcmnetLogger.isEnabledFor(OFLogger::DEBUG_LOG_LEVEL))
649         {
650             DCMNET_INFO("Received C-ECHO Response");
651             DCMNET_DEBUG(DIMSE_dumpMessage(tempStr, rsp, DIMSE_INCOMING, NULL, pcid));
652         }
653         else
654         {
655             DCMNET_INFO("Received C-ECHO Response (" << DU_cechoStatusString(rsp.msg.CEchoRSP.DimseStatus) << ")");
656         }
657     }
658     else
659     {
660         DCMNET_ERROR("Expected C-ECHO response but received DIMSE command 0x"
661                      << STD_NAMESPACE hex << STD_NAMESPACE setfill('0') << STD_NAMESPACE setw(4)
662                      << OFstatic_cast(unsigned int, rsp.CommandField));
663         DCMNET_DEBUG(DIMSE_dumpMessage(tempStr, rsp, DIMSE_INCOMING, NULL, pcid));
664         delete statusDetail;
665         return DIMSE_BADCOMMANDTYPE;
666     }
667     /* Print status detail if it was received */
668     if (statusDetail != NULL)
669     {
670         DCMNET_DEBUG("Response has status detail:" << OFendl << DcmObject::PrintHelper(*statusDetail));
671         delete statusDetail;
672     }
673     return EC_Normal;
674 }
675 
676 /* ************************************************************************* */
677 /*                            C-STORE functionality                          */
678 /* ************************************************************************* */
679 
680 // Sends C-STORE request to another DICOM application
sendSTORERequest(const T_ASC_PresentationContextID presID,const OFFilename & dicomFile,DcmDataset * dataset,Uint16 & rspStatusCode,const OFString & moveOriginatorAETitle,const Uint16 moveOriginatorMsgID)681 OFCondition DcmSCU::sendSTORERequest(const T_ASC_PresentationContextID presID,
682                                      const OFFilename& dicomFile,
683                                      DcmDataset* dataset,
684                                      Uint16& rspStatusCode,
685                                      const OFString& moveOriginatorAETitle,
686                                      const Uint16 moveOriginatorMsgID)
687 {
688     // Do some basic validity checks
689     if (!isConnected())
690         return DIMSE_ILLEGALASSOCIATION;
691 
692     OFCondition cond;
693     OFString tempStr;
694     T_ASC_PresentationContextID pcid = presID;
695     DcmDataset* statusDetail         = NULL;
696     T_DIMSE_Message msg;
697     // Make sure everything is zeroed (especially options)
698     bzero((char*)&msg, sizeof(msg));
699     T_DIMSE_C_StoreRQ* req = &(msg.msg.CStoreRQ);
700 
701     // Set type of message
702     msg.CommandField = DIMSE_C_STORE_RQ;
703     /* Set message ID */
704     req->MessageID = nextMessageID();
705     /* Load file if necessary */
706     DcmFileFormat* fileformat = NULL;
707     if (!dicomFile.isEmpty())
708     {
709         fileformat = new DcmFileFormat();
710         if (fileformat == NULL)
711             return EC_MemoryExhausted;
712         cond = fileformat->loadFile(dicomFile);
713         if (cond.bad())
714         {
715             delete fileformat;
716             return cond;
717         }
718         dataset = fileformat->getDataset();
719     }
720 
721     /* Fill message according to dataset to be sent */
722     OFString sopClassUID;
723     OFString sopInstanceUID;
724     E_TransferSyntax xferSyntax = EXS_Unknown;
725     cond                        = getDatasetInfo(dataset, sopClassUID, sopInstanceUID, xferSyntax);
726     DcmXfer xfer(xferSyntax);
727     /* Check whether the information is sufficient */
728     if (sopClassUID.empty() || sopInstanceUID.empty() || ((pcid == 0) && (xferSyntax == EXS_Unknown)))
729     {
730         DCMNET_ERROR("Cannot send SOP instance, missing information:");
731         if (!dicomFile.isEmpty())
732             DCMNET_ERROR("  DICOM Filename   : " << dicomFile);
733         DCMNET_ERROR("  SOP Class UID    : " << sopClassUID);
734         DCMNET_ERROR("  SOP Instance UID : " << sopInstanceUID);
735         DCMNET_ERROR("  Transfer Syntax  : " << xfer.getXferName());
736         if (pcid == 0)
737             DCMNET_ERROR("  Pres. Context ID : 0 (find via SOP Class and Transfer Syntax)");
738         else
739             DCMNET_ERROR("  Pres. Context ID : " << OFstatic_cast(unsigned int, pcid));
740         delete fileformat;
741         return cond;
742     }
743     OFStandard::strlcpy(req->AffectedSOPClassUID, sopClassUID.c_str(), sizeof(req->AffectedSOPClassUID));
744     OFStandard::strlcpy(req->AffectedSOPInstanceUID, sopInstanceUID.c_str(), sizeof(req->AffectedSOPInstanceUID));
745     req->DataSetType = DIMSE_DATASET_PRESENT;
746     req->Priority    = DIMSE_PRIORITY_MEDIUM;
747 
748     /* If desired (optional), insert MOVE originator information if this C-STORE
749        was initiated through a C-MOVE request.
750      */
751     if (!moveOriginatorAETitle.empty())
752     {
753         OFStandard::strlcpy(req->MoveOriginatorApplicationEntityTitle,
754                             moveOriginatorAETitle.c_str(),
755                             sizeof(req->MoveOriginatorApplicationEntityTitle));
756         req->opts |= O_STORE_MOVEORIGINATORAETITLE;
757     }
758     if (moveOriginatorMsgID != 0)
759     {
760         req->MoveOriginatorID = moveOriginatorMsgID;
761         req->opts |= O_STORE_MOVEORIGINATORID;
762     }
763 
764     /* If no presentation context is specified by the caller ... */
765     if (pcid == 0)
766     {
767         /* ... try to find an appropriate presentation context automatically */
768         pcid = findPresentationContextID(sopClassUID, xfer.getXferID());
769     }
770     else if (m_datasetConversionMode)
771     {
772         /* Convert dataset to network transfer syntax (if required) */
773         OFString abstractSyntax, transferSyntax;
774         findPresentationContext(pcid, abstractSyntax, transferSyntax);
775         /* Check whether given presentation context was accepted by the peer */
776         if (abstractSyntax.empty() || transferSyntax.empty())
777         {
778             /* Mark presentation context as invalid */
779             pcid = 0;
780         }
781         else
782         {
783             if (abstractSyntax != sopClassUID)
784             {
785                 DCMNET_WARN("Inappropriate presentation context with ID "
786                             << OFstatic_cast(unsigned int, pcid) << ": abstract syntax does not match SOP class UID");
787             }
788             /* Try to convert to the negotiated transfer syntax */
789             DcmXfer netXfer = DcmXfer(transferSyntax.c_str()).getXfer();
790             if (netXfer.getXfer() != xferSyntax)
791             {
792                 DCMNET_INFO("Converting transfer syntax: " << xfer.getXferName() << " -> " << netXfer.getXferName());
793                 cond = dataset->chooseRepresentation(netXfer.getXfer(), NULL);
794                 if (cond.bad())
795                 {
796                     DCMNET_ERROR("No conversion to transfer syntax " << netXfer.getXferName() << " possible!");
797                     delete fileformat;
798                     return cond;
799                 }
800             }
801         }
802     }
803     /* No appropriate presentation context for sending */
804     if (pcid == 0)
805     {
806         OFString sopClassName = dcmFindNameOfUID(sopClassUID.c_str(), sopClassUID.c_str());
807         OFString xferName     = xfer.getXferName();
808         DCMNET_ERROR("No presentation context found for sending C-STORE with SOP Class / Transfer Syntax: "
809                      << sopClassName << " / " << xferName);
810         delete fileformat;
811         return DIMSE_NOVALIDPRESENTATIONCONTEXTID;
812     }
813 
814     /* Send request */
815     if (DCM_dcmnetLogger.isEnabledFor(OFLogger::DEBUG_LOG_LEVEL))
816     {
817         DCMNET_INFO("Sending C-STORE Request");
818         DCMNET_DEBUG(DIMSE_dumpMessage(tempStr, msg, DIMSE_OUTGOING, NULL, pcid));
819     }
820     else
821     {
822         DCMNET_INFO("Sending C-STORE Request (MsgID " << req->MessageID << ", "
823                                                       << dcmSOPClassUIDToModality(sopClassUID.c_str(), "OT") << ")");
824     }
825     cond = sendDIMSEMessage(pcid, &msg, dataset);
826     delete fileformat;
827     fileformat = NULL;
828     if (cond.bad())
829     {
830         DCMNET_ERROR("Failed sending C-STORE request: " << DimseCondition::dump(tempStr, cond));
831         return cond;
832     }
833 
834     /* Receive response */
835     T_DIMSE_Message rsp;
836     // Make sure everything is zeroed (especially options)
837     bzero((char*)&rsp, sizeof(rsp));
838     cond = receiveDIMSECommand(&pcid, &rsp, &statusDetail, NULL /* not interested in the command set */);
839     if (cond.bad())
840     {
841         DCMNET_ERROR("Failed receiving DIMSE response: " << DimseCondition::dump(tempStr, cond));
842         return cond;
843     }
844 
845     if (rsp.CommandField == DIMSE_C_STORE_RSP)
846     {
847         if (DCM_dcmnetLogger.isEnabledFor(OFLogger::DEBUG_LOG_LEVEL))
848         {
849             DCMNET_INFO("Received C-STORE Response");
850             DCMNET_DEBUG(DIMSE_dumpMessage(tempStr, rsp, DIMSE_INCOMING, NULL, pcid));
851         }
852         else
853         {
854             DCMNET_INFO("Received C-STORE Response (" << DU_cstoreStatusString(rsp.msg.CStoreRSP.DimseStatus) << ")");
855         }
856     }
857     else
858     {
859         DCMNET_ERROR("Expected C-STORE response but received DIMSE command 0x"
860                      << STD_NAMESPACE hex << STD_NAMESPACE setfill('0') << STD_NAMESPACE setw(4)
861                      << OFstatic_cast(unsigned int, rsp.CommandField));
862         DCMNET_DEBUG(DIMSE_dumpMessage(tempStr, rsp, DIMSE_INCOMING, NULL, pcid));
863         delete statusDetail;
864         return DIMSE_BADCOMMANDTYPE;
865     }
866     T_DIMSE_C_StoreRSP storeRsp = rsp.msg.CStoreRSP;
867     rspStatusCode               = storeRsp.DimseStatus;
868     if (statusDetail != NULL)
869     {
870         DCMNET_DEBUG("Response has status detail:" << OFendl << DcmObject::PrintHelper(*statusDetail));
871         delete statusDetail;
872     }
873 
874     return cond;
875 }
876 
877 /* ************************************************************************* */
878 /*                            C-MOVE functionality                           */
879 /* ************************************************************************* */
880 
881 // Sends a C-MOVE Request on given presentation context
sendMOVERequest(const T_ASC_PresentationContextID presID,const OFString & moveDestinationAETitle,DcmDataset * dataset,OFList<RetrieveResponse * > * responses)882 OFCondition DcmSCU::sendMOVERequest(const T_ASC_PresentationContextID presID,
883                                     const OFString& moveDestinationAETitle,
884                                     DcmDataset* dataset,
885                                     OFList<RetrieveResponse*>* responses)
886 {
887     // Do some basic validity checks
888     if (!isConnected())
889         return DIMSE_ILLEGALASSOCIATION;
890     if (dataset == NULL)
891         return DIMSE_NULLKEY;
892 
893     /* Prepare DIMSE data structures for issuing request */
894     OFCondition cond;
895     OFString tempStr;
896     T_ASC_PresentationContextID pcid = presID;
897     T_DIMSE_Message msg;
898     // make sure everything is zeroed (especially options)
899     bzero((char*)&msg, sizeof(msg));
900     DcmDataset* statusDetail = NULL;
901     T_DIMSE_C_MoveRQ* req    = &(msg.msg.CMoveRQ);
902     // Set type of message
903     msg.CommandField = DIMSE_C_MOVE_RQ;
904     // Set message ID
905     req->MessageID = nextMessageID();
906     // Announce dataset
907     req->DataSetType = DIMSE_DATASET_PRESENT;
908     // Set target for embedded C-Store's
909     OFStandard::strlcpy(req->MoveDestination, moveDestinationAETitle.c_str(), sizeof(req->MoveDestination));
910     // Set priority (mandatory)
911     req->Priority = DIMSE_PRIORITY_MEDIUM;
912 
913     /* Determine SOP Class from presentation context */
914     OFString abstractSyntax, transferSyntax;
915     findPresentationContext(pcid, abstractSyntax, transferSyntax);
916     if (abstractSyntax.empty() || transferSyntax.empty())
917         return DIMSE_NOVALIDPRESENTATIONCONTEXTID;
918     OFStandard::strlcpy(req->AffectedSOPClassUID, abstractSyntax.c_str(), sizeof(req->AffectedSOPClassUID));
919 
920     /* Send request */
921     if (DCM_dcmnetLogger.isEnabledFor(OFLogger::DEBUG_LOG_LEVEL))
922     {
923         DCMNET_INFO("Sending C-MOVE Request");
924         DCMNET_DEBUG(DIMSE_dumpMessage(tempStr, msg, DIMSE_OUTGOING, dataset, pcid));
925     }
926     else
927     {
928         DCMNET_INFO("Sending C-MOVE Request (MsgID " << req->MessageID << ")");
929     }
930     cond = sendDIMSEMessage(pcid, &msg, dataset);
931     if (cond.bad())
932     {
933         DCMNET_ERROR("Failed sending C-MOVE request: " << DimseCondition::dump(tempStr, cond));
934         return cond;
935     }
936 
937     /* Receive and handle C-MOVE response messages */
938     OFBool waitForNextResponse = OFTrue;
939     while (waitForNextResponse)
940     {
941         T_DIMSE_Message rsp;
942         // Make sure everything is zeroed, especially options
943         bzero((char*)&rsp, sizeof(rsp));
944         statusDetail = NULL;
945 
946         // Receive command set
947         cond = receiveDIMSECommand(&pcid, &rsp, &statusDetail, NULL /* not interested in the command set */);
948         if (cond.bad())
949         {
950             DCMNET_ERROR("Failed receiving DIMSE response: " << DimseCondition::dump(tempStr, cond));
951             delete statusDetail;
952             break;
953         }
954 
955         if (rsp.CommandField == DIMSE_C_MOVE_RSP)
956         {
957             if (DCM_dcmnetLogger.isEnabledFor(OFLogger::DEBUG_LOG_LEVEL))
958             {
959                 DCMNET_INFO("Received C-MOVE Response");
960                 DCMNET_DEBUG(DIMSE_dumpMessage(tempStr, rsp, DIMSE_INCOMING, NULL, pcid));
961             }
962             else
963             {
964                 DCMNET_INFO("Received C-MOVE Response (" << DU_cmoveStatusString(rsp.msg.CMoveRSP.DimseStatus) << ")");
965             }
966         }
967         else
968         {
969             DCMNET_ERROR("Expected C-MOVE response but received DIMSE command 0x"
970                          << STD_NAMESPACE hex << STD_NAMESPACE setfill('0') << STD_NAMESPACE setw(4)
971                          << OFstatic_cast(unsigned int, rsp.CommandField));
972             DCMNET_DEBUG(DIMSE_dumpMessage(tempStr, rsp, DIMSE_INCOMING, NULL, pcid));
973             delete statusDetail;
974             cond = DIMSE_BADCOMMANDTYPE;
975             break;
976         }
977 
978         // Prepare response package for response handler
979         OFunique_ptr<RetrieveResponse> moveRSP(new RetrieveResponse);
980         moveRSP->m_affectedSOPClassUID     = rsp.msg.CMoveRSP.AffectedSOPClassUID;
981         moveRSP->m_messageIDRespondedTo    = rsp.msg.CMoveRSP.MessageIDBeingRespondedTo;
982         moveRSP->m_status                  = rsp.msg.CMoveRSP.DimseStatus;
983         moveRSP->m_numberOfRemainingSubops = rsp.msg.CMoveRSP.NumberOfRemainingSubOperations;
984         moveRSP->m_numberOfCompletedSubops = rsp.msg.CMoveRSP.NumberOfCompletedSubOperations;
985         moveRSP->m_numberOfFailedSubops    = rsp.msg.CMoveRSP.NumberOfFailedSubOperations;
986         moveRSP->m_numberOfWarningSubops   = rsp.msg.CMoveRSP.NumberOfWarningSubOperations;
987         moveRSP->m_statusDetail            = statusDetail;
988         // DCMNET_DEBUG("C-MOVE response has status 0x"
989         //  << STD_NAMESPACE hex << STD_NAMESPACE setfill('0') << STD_NAMESPACE setw(4)
990         //  << moveRSP->m_status);
991         if (statusDetail != NULL)
992         {
993             DCMNET_DEBUG("Response has status detail:" << OFendl << DcmObject::PrintHelper(*statusDetail));
994         }
995 
996         // Receive dataset if there is one (status PENDING)
997         DcmDataset* rspDataset = NULL;
998         // Check if dataset is announced correctly
999         if (rsp.msg.CMoveRSP.DataSetType != DIMSE_DATASET_NULL) // Some of the sub operations have failed, thus a
1000                                                                 // dataset with a list of them is attached
1001         {
1002             // Receive dataset
1003             cond = receiveDIMSEDataset(&pcid, &rspDataset);
1004             if (cond.bad())
1005             {
1006                 DCMNET_ERROR("Unable to receive C-MOVE dataset on presentation context "
1007                              << OFstatic_cast(unsigned int, pcid) << ": " << DimseCondition::dump(tempStr, cond));
1008                 break;
1009             }
1010             moveRSP->m_dataset = rspDataset;
1011         }
1012 
1013         // Handle C-MOVE response (has to handle all possible status flags)
1014         cond = handleMOVEResponse(pcid, moveRSP.get(), waitForNextResponse);
1015         if (cond.bad())
1016         {
1017             DCMNET_WARN("Unable to handle C-MOVE response correctly: " << cond.text() << " (ignored)");
1018             // don't return here but trust the "waitForNextResponse" variable
1019         }
1020         // if response could be handled successfully, add it to response list
1021         else
1022         {
1023             if (responses != NULL) // only add if desired by caller
1024                 responses->push_back(moveRSP.release());
1025         }
1026     }
1027     /* All responses received or break signal occurred */
1028     return cond;
1029 }
1030 
1031 // Standard handler for C-MOVE message responses
handleMOVEResponse(const T_ASC_PresentationContextID,RetrieveResponse * response,OFBool & waitForNextResponse)1032 OFCondition DcmSCU::handleMOVEResponse(const T_ASC_PresentationContextID /* presID */,
1033                                        RetrieveResponse* response,
1034                                        OFBool& waitForNextResponse)
1035 {
1036     waitForNextResponse = OFFalse;
1037     if (response == NULL)
1038         return DIMSE_NULLKEY;
1039 
1040     DCMNET_DEBUG("Handling C-MOVE Response");
1041     OFString s;
1042     s = DU_cmoveStatusString(response->m_status);
1043     return handleSessionResponseDefault(response->m_status, s, waitForNextResponse);
1044 }
1045 
1046 /* ************************************************************************* */
1047 /*               C-GET and accompanying C-STORE functionality                */
1048 /* ************************************************************************* */
1049 
1050 // Sends a C-GET Request on given presentation context
sendCGETRequest(const T_ASC_PresentationContextID presID,DcmDataset * dataset,OFList<RetrieveResponse * > * responses)1051 OFCondition DcmSCU::sendCGETRequest(const T_ASC_PresentationContextID presID,
1052                                     DcmDataset* dataset,
1053                                     OFList<RetrieveResponse*>* responses)
1054 {
1055     // Do some basic validity checks
1056     if (!isConnected())
1057         return DIMSE_ILLEGALASSOCIATION;
1058     if (dataset == NULL)
1059         return DIMSE_NULLKEY;
1060 
1061     /* Prepare DIMSE data structures for issuing request */
1062     OFCondition cond;
1063     OFString tempStr;
1064     T_ASC_PresentationContextID pcid = presID;
1065     T_DIMSE_Message msg;
1066     // Make sure everything is zeroed (especially options)
1067     bzero((char*)&msg, sizeof(msg));
1068     T_DIMSE_C_GetRQ* req = &(msg.msg.CGetRQ);
1069     // Set type of message
1070     msg.CommandField = DIMSE_C_GET_RQ;
1071     // Set message ID
1072     req->MessageID = nextMessageID();
1073     // Announce dataset
1074     req->DataSetType = DIMSE_DATASET_PRESENT;
1075     // Specify priority
1076     req->Priority = DIMSE_PRIORITY_MEDIUM;
1077 
1078     // Determine SOP Class from presentation context
1079     OFString abstractSyntax, transferSyntax;
1080     findPresentationContext(pcid, abstractSyntax, transferSyntax);
1081     if (abstractSyntax.empty() || transferSyntax.empty())
1082         return DIMSE_NOVALIDPRESENTATIONCONTEXTID;
1083     OFStandard::strlcpy(req->AffectedSOPClassUID, abstractSyntax.c_str(), sizeof(req->AffectedSOPClassUID));
1084 
1085     /* Send request */
1086     if (DCM_dcmnetLogger.isEnabledFor(OFLogger::DEBUG_LOG_LEVEL))
1087     {
1088         DCMNET_INFO("Sending C-GET Request");
1089         DCMNET_DEBUG(DIMSE_dumpMessage(tempStr, msg, DIMSE_OUTGOING, dataset, pcid));
1090     }
1091     else
1092     {
1093         DCMNET_INFO("Sending C-GET Request (MsgID " << req->MessageID << ")");
1094     }
1095     cond = sendDIMSEMessage(pcid, &msg, dataset);
1096     if (cond.bad())
1097     {
1098         DCMNET_ERROR("Failed sending C-GET request: " << DimseCondition::dump(tempStr, cond));
1099         return cond;
1100     }
1101 
1102     cond = handleCGETSession(pcid, dataset, responses);
1103     return cond;
1104 }
1105 
1106 // Does the logic for switching between C-GET Response and C-STORE Requests
handleCGETSession(const T_ASC_PresentationContextID,DcmDataset *,OFList<RetrieveResponse * > * responses)1107 OFCondition DcmSCU::handleCGETSession(const T_ASC_PresentationContextID /* presID */,
1108                                       DcmDataset* /* dataset */,
1109                                       OFList<RetrieveResponse*>* responses)
1110 {
1111     OFCondition result;
1112     OFBool continueSession = OFTrue;
1113     OFString tempStr;
1114 
1115     // As long we want to continue (usually, as long as we receive more objects,
1116     // i.e. the final C-GET response has not arrived yet)
1117     while (continueSession)
1118     {
1119         T_DIMSE_Message rsp;
1120         // Make sure everything is zeroed (especially options)
1121         bzero((char*)&rsp, sizeof(rsp));
1122 
1123         DcmDataset* statusDetail         = NULL;
1124         T_ASC_PresentationContextID pcid = 0;
1125 
1126         // Receive command set
1127         result = receiveDIMSECommand(&pcid, &rsp, &statusDetail, NULL /* not interested in the command set */);
1128         if (result.bad())
1129         {
1130             DCMNET_ERROR("Failed receiving DIMSE command: " << DimseCondition::dump(tempStr, result));
1131             delete statusDetail;
1132             break;
1133         }
1134         // Handle C-GET Response
1135         if (rsp.CommandField == DIMSE_C_GET_RSP)
1136         {
1137             if (DCM_dcmnetLogger.isEnabledFor(OFLogger::DEBUG_LOG_LEVEL))
1138             {
1139                 DCMNET_INFO("Received C-GET Response");
1140                 DCMNET_DEBUG(DIMSE_dumpMessage(tempStr, rsp, DIMSE_INCOMING, NULL, pcid));
1141             }
1142             else
1143             {
1144                 DCMNET_INFO("Received C-GET Response (" << DU_cgetStatusString(rsp.msg.CGetRSP.DimseStatus) << ")");
1145             }
1146             // Prepare response package for response handler
1147             OFunique_ptr<RetrieveResponse> getRSP(new RetrieveResponse());
1148             getRSP->m_affectedSOPClassUID     = rsp.msg.CGetRSP.AffectedSOPClassUID;
1149             getRSP->m_messageIDRespondedTo    = rsp.msg.CGetRSP.MessageIDBeingRespondedTo;
1150             getRSP->m_status                  = rsp.msg.CGetRSP.DimseStatus;
1151             getRSP->m_numberOfRemainingSubops = rsp.msg.CGetRSP.NumberOfRemainingSubOperations;
1152             getRSP->m_numberOfCompletedSubops = rsp.msg.CGetRSP.NumberOfCompletedSubOperations;
1153             getRSP->m_numberOfFailedSubops    = rsp.msg.CGetRSP.NumberOfFailedSubOperations;
1154             getRSP->m_numberOfWarningSubops   = rsp.msg.CGetRSP.NumberOfWarningSubOperations;
1155             getRSP->m_statusDetail            = statusDetail;
1156             if (statusDetail != NULL)
1157             {
1158                 DCMNET_DEBUG("Response has status detail:" << OFendl << DcmObject::PrintHelper(*statusDetail));
1159                 statusDetail = NULL; // forget reference to status detail, will be deleted with getRSP
1160             }
1161             result = handleCGETResponse(pcid, getRSP.get(), continueSession);
1162             if (result.bad())
1163             {
1164                 DCMNET_WARN("Unable to handle C-GET response correctly: " << result.text() << " (ignored)");
1165                 // don't return here but trust the "continueSession" variable
1166             }
1167             // if response could be handled successfully, add it to response list
1168             else
1169             {
1170                 if (responses != NULL) // only add if desired by caller
1171                     responses->push_back(getRSP.release());
1172             }
1173         }
1174 
1175         // Handle C-STORE Request
1176         else if (rsp.CommandField == DIMSE_C_STORE_RQ)
1177         {
1178             if (DCM_dcmnetLogger.isEnabledFor(OFLogger::DEBUG_LOG_LEVEL))
1179             {
1180                 DCMNET_INFO("Received C-STORE Request");
1181                 DCMNET_DEBUG(DIMSE_dumpMessage(tempStr, rsp, DIMSE_INCOMING, NULL, pcid));
1182             }
1183             else
1184             {
1185                 DCMNET_INFO("Received C-STORE Request (MsgID " << rsp.msg.CStoreRQ.MessageID << ")");
1186             }
1187             // Receive dataset if there is one (status PENDING)
1188             DcmDataset* rspDataset = NULL;
1189             // Check if dataset is announced correctly
1190             if (rsp.msg.CStoreRQ.DataSetType == DIMSE_DATASET_NULL)
1191             {
1192                 DCMNET_WARN("Incoming C-STORE with no dataset, trying to receive one anyway");
1193             }
1194             Uint16 desiredCStoreReturnStatus = 0;
1195             // handle normal storage mode, i.e. receive in memory and store to disk
1196             if (m_storageMode == DCMSCU_STORAGE_DISK)
1197             {
1198                 // Receive dataset
1199                 result = receiveDIMSEDataset(&pcid, &rspDataset);
1200                 if (result.bad())
1201                 {
1202                     result                    = DIMSE_NULLKEY;
1203                     desiredCStoreReturnStatus = STATUS_STORE_Error_CannotUnderstand;
1204                 }
1205                 else
1206                 {
1207                     result = handleSTORERequest(pcid, rspDataset, continueSession, desiredCStoreReturnStatus);
1208                 }
1209             }
1210             // handle bit preserving storage mode, i.e. receive directly to disk
1211             else if (m_storageMode == DCMSCU_STORAGE_BIT_PRESERVING)
1212             {
1213                 OFString storageFilename;
1214                 OFStandard::combineDirAndFilename(
1215                     storageFilename, m_storageDir, rsp.msg.CStoreRQ.AffectedSOPInstanceUID, OFTrue);
1216                 result = handleSTORERequestFile(&pcid, storageFilename, &(rsp.msg.CStoreRQ));
1217                 if (result.good())
1218                 {
1219                     notifyInstanceStored(
1220                         storageFilename, rsp.msg.CStoreRQ.AffectedSOPClassUID, rsp.msg.CStoreRQ.AffectedSOPInstanceUID);
1221                 }
1222             }
1223             // handle ignore storage mode, i.e. ignore received dataset and do not store at all
1224             else
1225             {
1226                 result = ignoreSTORERequest(pcid, rsp.msg.CStoreRQ);
1227             }
1228 
1229             // Evaluate result from C-STORE request handling and send response
1230             if (result.bad())
1231             {
1232                 desiredCStoreReturnStatus = STATUS_STORE_Error_CannotUnderstand;
1233                 continueSession           = OFFalse;
1234             }
1235             result = sendSTOREResponse(pcid, desiredCStoreReturnStatus, rsp.msg.CStoreRQ);
1236             if (result.bad())
1237             {
1238                 continueSession = OFFalse;
1239             }
1240         }
1241 
1242         // Handle other DIMSE command (error since other command than GET/STORE not expected)
1243         else
1244         {
1245             DCMNET_ERROR("Expected C-GET response or C-STORE request but received DIMSE command 0x"
1246                          << STD_NAMESPACE hex << STD_NAMESPACE setfill('0') << STD_NAMESPACE setw(4)
1247                          << OFstatic_cast(unsigned int, rsp.CommandField));
1248             DCMNET_DEBUG(DIMSE_dumpMessage(tempStr, rsp, DIMSE_INCOMING, NULL, pcid));
1249             result          = DIMSE_BADCOMMANDTYPE;
1250             continueSession = OFFalse;
1251         }
1252 
1253         delete statusDetail; // should be NULL if not existing or added to response list
1254         statusDetail = NULL;
1255     }
1256     /* All responses received or break signal occurred */
1257 
1258     return result;
1259 }
1260 
1261 // Handles single C-GET Response
handleCGETResponse(const T_ASC_PresentationContextID,RetrieveResponse * response,OFBool & continueCGETSession)1262 OFCondition DcmSCU::handleCGETResponse(const T_ASC_PresentationContextID /* presID */,
1263                                        RetrieveResponse* response,
1264                                        OFBool& continueCGETSession)
1265 {
1266     continueCGETSession = OFFalse;
1267     if (response == NULL)
1268         return DIMSE_NULLKEY;
1269 
1270     DCMNET_DEBUG("Handling C-GET Response");
1271     OFString s;
1272     s = DU_cgetStatusString(response->m_status);
1273     return handleSessionResponseDefault(response->m_status, s, continueCGETSession);
1274 }
1275 
1276 // Handles single C-STORE Request received during C-GET session
handleSTORERequest(const T_ASC_PresentationContextID,DcmDataset * incomingObject,OFBool &,Uint16 & cStoreReturnStatus)1277 OFCondition DcmSCU::handleSTORERequest(const T_ASC_PresentationContextID /* presID */,
1278                                        DcmDataset* incomingObject,
1279                                        OFBool& /* continueCGETSession */,
1280                                        Uint16& cStoreReturnStatus)
1281 {
1282     if (incomingObject == NULL)
1283         return DIMSE_NULLKEY;
1284 
1285     OFString sopClassUID;
1286     OFString sopInstanceUID;
1287     OFCondition result = incomingObject->findAndGetOFString(DCM_SOPClassUID, sopClassUID);
1288     if (result.good())
1289         result = incomingObject->findAndGetOFString(DCM_SOPInstanceUID, sopInstanceUID);
1290     if (result.bad())
1291     {
1292         DCMNET_ERROR("Cannot store received object: either SOP Instance or SOP Class UID not present");
1293         cStoreReturnStatus = STATUS_STORE_Error_DataSetDoesNotMatchSOPClass;
1294         delete incomingObject;
1295         return EC_TagNotFound;
1296     }
1297 
1298     OFString filename = createStorageFilename(incomingObject);
1299     if (OFStandard::fileExists(filename))
1300     {
1301         DCMNET_WARN("DICOM file already exists, overwriting: " << filename);
1302     }
1303     DcmFileFormat dcmff(incomingObject, OFFalse /* do not copy but take ownership */);
1304     result = dcmff.saveFile(filename);
1305     if (result.good())
1306     {
1307         E_TransferSyntax xferSyntax;
1308         getDatasetInfo(incomingObject, sopClassUID, sopInstanceUID, xferSyntax);
1309         notifyInstanceStored(filename, sopClassUID, sopInstanceUID);
1310         cStoreReturnStatus = STATUS_Success;
1311     }
1312     else
1313     {
1314         DCMNET_ERROR("cannot write DICOM file: " << filename);
1315         cStoreReturnStatus = STATUS_STORE_Refused_OutOfResources;
1316 
1317         // delete incomplete file
1318         OFStandard::deleteFile(filename);
1319     }
1320 
1321     return result;
1322 }
1323 
handleSTORERequestFile(T_ASC_PresentationContextID * presID,const OFString & filename,T_DIMSE_C_StoreRQ * request)1324 OFCondition DcmSCU::handleSTORERequestFile(T_ASC_PresentationContextID* presID,
1325                                            const OFString& filename,
1326                                            T_DIMSE_C_StoreRQ* request)
1327 {
1328     if (filename.empty())
1329         return EC_IllegalParameter;
1330 
1331     /* in the following, we want to receive data over the network and write it to a file */
1332     /* exactly the way it was received over the network. Hence, a filestream will be created and the data */
1333     /* set will be received and written to the file through the call to DIMSE_receiveDataSetInFile(...).*/
1334     /* create filestream */
1335     DcmOutputFileStream* filestream = NULL;
1336     OFCondition cond                = DIMSE_createFilestream(filename, request, m_assoc, *presID, OFTrue, &filestream);
1337     if (cond.good())
1338     {
1339         if (m_progressNotificationMode)
1340         {
1341             cond = DIMSE_receiveDataSetInFile(m_assoc,
1342                                               m_blockMode,
1343                                               m_dimseTimeout,
1344                                               presID,
1345                                               filestream,
1346                                               callbackRECEIVEProgress,
1347                                               this /*callbackData*/);
1348         }
1349         else
1350         {
1351             cond = DIMSE_receiveDataSetInFile(
1352                 m_assoc, m_blockMode, m_dimseTimeout, presID, filestream, NULL /*callback*/, NULL /*callbackData*/);
1353         }
1354         delete filestream;
1355         if (cond != EC_Normal)
1356         {
1357             OFStandard::deleteFile(filename);
1358         }
1359         DCMNET_DEBUG("Received dataset on presentation context " << OFstatic_cast(unsigned int, *presID));
1360     }
1361     else
1362     {
1363         OFString tempStr;
1364         DCMNET_ERROR("Unable to receive and store dataset on presentation context "
1365                      << OFstatic_cast(unsigned int, *presID) << ": " << DimseCondition::dump(tempStr, cond));
1366     }
1367     return cond;
1368 }
1369 
1370 OFCondition
sendSTOREResponse(T_ASC_PresentationContextID presID,Uint16 status,const T_DIMSE_C_StoreRQ & request)1371 DcmSCU::sendSTOREResponse(T_ASC_PresentationContextID presID, Uint16 status, const T_DIMSE_C_StoreRQ& request)
1372 {
1373     // Send back response
1374     T_DIMSE_Message response;
1375     // Make sure everything is zeroed (especially options)
1376     bzero((char*)&response, sizeof(response));
1377     T_DIMSE_C_StoreRSP& storeRsp       = response.msg.CStoreRSP;
1378     response.CommandField              = DIMSE_C_STORE_RSP;
1379     storeRsp.MessageIDBeingRespondedTo = request.MessageID;
1380     storeRsp.DimseStatus               = status;
1381     storeRsp.DataSetType               = DIMSE_DATASET_NULL;
1382     /* Following information is optional and normally not sent by the underlying
1383      * dcmnet routines. However, maybe this could be changed later, so insert it.
1384      */
1385     OFStandard::strlcpy(
1386         storeRsp.AffectedSOPClassUID, request.AffectedSOPClassUID, sizeof(storeRsp.AffectedSOPClassUID));
1387     OFStandard::strlcpy(
1388         storeRsp.AffectedSOPInstanceUID, request.AffectedSOPInstanceUID, sizeof(storeRsp.AffectedSOPInstanceUID));
1389     storeRsp.opts = O_STORE_AFFECTEDSOPCLASSUID | O_STORE_AFFECTEDSOPINSTANCEUID;
1390 
1391     OFString tempStr;
1392     if (DCM_dcmnetLogger.isEnabledFor(OFLogger::DEBUG_LOG_LEVEL))
1393     {
1394         DCMNET_INFO("Sending C-STORE Response");
1395         DCMNET_DEBUG(DIMSE_dumpMessage(tempStr, response, DIMSE_OUTGOING, NULL, presID));
1396     }
1397     else
1398     {
1399         DCMNET_INFO("Sending C-STORE Response (" << DU_cstoreStatusString(status) << ")");
1400     }
1401     OFCondition cond = sendDIMSEMessage(presID, &response, NULL /*dataObject*/);
1402     if (cond.bad())
1403     {
1404         DCMNET_ERROR("Failed sending C-STORE response: " << DimseCondition::dump(tempStr, cond));
1405     }
1406     return cond;
1407 }
1408 
createStorageFilename(DcmDataset * dataset)1409 OFString DcmSCU::createStorageFilename(DcmDataset* dataset)
1410 {
1411     OFString sopClassUID, sopInstanceUID;
1412     E_TransferSyntax dummy;
1413     getDatasetInfo(dataset, sopClassUID, sopInstanceUID, dummy);
1414     // Create unique filename
1415     if (sopClassUID.empty() || sopInstanceUID.empty())
1416         return "";
1417     OFString name = dcmSOPClassUIDToModality(sopClassUID.c_str(), "UNKNOWN");
1418     name += ".";
1419     name += sopInstanceUID;
1420     OFString returnStr;
1421     OFStandard::combineDirAndFilename(returnStr, m_storageDir, name, OFTrue);
1422     return returnStr;
1423 }
1424 
ignoreSTORERequest(T_ASC_PresentationContextID presID,const T_DIMSE_C_StoreRQ & request)1425 OFCondition DcmSCU::ignoreSTORERequest(T_ASC_PresentationContextID presID, const T_DIMSE_C_StoreRQ& request)
1426 {
1427 
1428     /* We cannot create the filestream, so ignore the incoming dataset and return an out-of-resources error to the SCU
1429      */
1430     DIC_UL bytesRead = 0;
1431     DIC_UL pdvCount  = 0;
1432     DCMNET_DEBUG("Ignoring incoming C-STORE dataset on presentation context "
1433                  << OFstatic_cast(unsigned int, presID)
1434                  << " with Affected SOP Instance UID: " << request.AffectedSOPInstanceUID);
1435     OFCondition result = DIMSE_ignoreDataSet(m_assoc, m_blockMode, m_dimseTimeout, &bytesRead, &pdvCount);
1436     if (result.good())
1437     {
1438         DCMNET_TRACE("Successfully skipped " << bytesRead << " bytes in " << pdvCount << " PDVs");
1439     }
1440     return result;
1441 }
1442 
notifyInstanceStored(const OFString & filename,const OFString & sopClassUID,const OFString & sopInstanceUID) const1443 void DcmSCU::notifyInstanceStored(const OFString& filename,
1444                                   const OFString& sopClassUID,
1445                                   const OFString& sopInstanceUID) const
1446 {
1447     DCMNET_DEBUG("Stored instance to disk:");
1448     DCMNET_DEBUG("  Filename: " << filename);
1449     DCMNET_DEBUG("  SOP Class UID: " << sopClassUID);
1450     DCMNET_DEBUG("  SOP Instance UID: " << sopInstanceUID);
1451 }
1452 
1453 /* ************************************************************************* */
1454 /*                            C-FIND functionality                           */
1455 /* ************************************************************************* */
1456 
1457 // Sends a C-FIND Request on given presentation context
1458 OFCondition
sendFINDRequest(const T_ASC_PresentationContextID presID,DcmDataset * queryKeys,OFList<QRResponse * > * responses)1459 DcmSCU::sendFINDRequest(const T_ASC_PresentationContextID presID, DcmDataset* queryKeys, OFList<QRResponse*>* responses)
1460 {
1461     // Do some basic validity checks
1462     if (!isConnected())
1463         return DIMSE_ILLEGALASSOCIATION;
1464     if (queryKeys == NULL)
1465         return DIMSE_NULLKEY;
1466 
1467     /* Prepare DIMSE data structures for issuing request */
1468     OFCondition cond;
1469     OFString tempStr;
1470     T_ASC_PresentationContextID pcid = presID;
1471     T_DIMSE_Message msg;
1472     // Make sure everything is zeroed (especially options)
1473     bzero((char*)&msg, sizeof(msg));
1474 
1475     DcmDataset* statusDetail = NULL;
1476     T_DIMSE_C_FindRQ* req    = &(msg.msg.CFindRQ);
1477     // Set type of message
1478     msg.CommandField = DIMSE_C_FIND_RQ;
1479     // Set message ID
1480     req->MessageID = nextMessageID();
1481     // Announce dataset
1482     req->DataSetType = DIMSE_DATASET_PRESENT;
1483     // Specify priority
1484     req->Priority = DIMSE_PRIORITY_MEDIUM;
1485 
1486     // Determine SOP Class from presentation context
1487     OFString abstractSyntax, transferSyntax;
1488     findPresentationContext(pcid, abstractSyntax, transferSyntax);
1489     if (abstractSyntax.empty() || transferSyntax.empty())
1490         return DIMSE_NOVALIDPRESENTATIONCONTEXTID;
1491     OFStandard::strlcpy(req->AffectedSOPClassUID, abstractSyntax.c_str(), sizeof(req->AffectedSOPClassUID));
1492 
1493     /* Send request */
1494     if (DCM_dcmnetLogger.isEnabledFor(OFLogger::DEBUG_LOG_LEVEL))
1495     {
1496         DCMNET_INFO("Sending C-FIND Request");
1497         DCMNET_DEBUG(DIMSE_dumpMessage(tempStr, msg, DIMSE_OUTGOING, queryKeys, pcid));
1498     }
1499     else
1500     {
1501         DCMNET_INFO("Sending C-FIND Request (MsgID " << req->MessageID << ")");
1502     }
1503     cond = sendDIMSEMessage(pcid, &msg, queryKeys);
1504     if (cond.bad())
1505     {
1506         DCMNET_ERROR("Failed sending C-FIND request: " << DimseCondition::dump(tempStr, cond));
1507         return cond;
1508     }
1509 
1510     /* Receive and handle response */
1511     OFBool waitForNextResponse = OFTrue;
1512     while (waitForNextResponse)
1513     {
1514         T_DIMSE_Message rsp;
1515         // Make sure everything is zeroed (especially options)
1516         bzero((char*)&rsp, sizeof(rsp));
1517 
1518         statusDetail = NULL;
1519 
1520         // Receive command set
1521         cond = receiveDIMSECommand(&pcid, &rsp, &statusDetail, NULL /* not interested in the command set */);
1522         if (cond.bad())
1523         {
1524             DCMNET_ERROR("Failed receiving DIMSE response: " << DimseCondition::dump(tempStr, cond));
1525             return cond;
1526         }
1527 
1528         if (rsp.CommandField == DIMSE_C_FIND_RSP)
1529         {
1530             if (DCM_dcmnetLogger.isEnabledFor(OFLogger::DEBUG_LOG_LEVEL))
1531             {
1532                 DCMNET_INFO("Received C-FIND Response");
1533                 DCMNET_DEBUG(DIMSE_dumpMessage(tempStr, rsp, DIMSE_INCOMING, NULL, pcid));
1534             }
1535             else
1536             {
1537                 DCMNET_INFO("Received C-FIND Response (" << DU_cfindStatusString(rsp.msg.CFindRSP.DimseStatus) << ")");
1538             }
1539         }
1540         else
1541         {
1542             DCMNET_ERROR("Expected C-FIND response but received DIMSE command 0x"
1543                          << STD_NAMESPACE hex << STD_NAMESPACE setfill('0') << STD_NAMESPACE setw(4)
1544                          << OFstatic_cast(unsigned int, rsp.CommandField));
1545             DCMNET_DEBUG(DIMSE_dumpMessage(tempStr, rsp, DIMSE_INCOMING, NULL, pcid));
1546             delete statusDetail;
1547             return DIMSE_BADCOMMANDTYPE;
1548         }
1549 
1550         // Prepare response package for response handler
1551         OFunique_ptr<QRResponse> findRSP(new QRResponse);
1552         findRSP->m_affectedSOPClassUID  = rsp.msg.CFindRSP.AffectedSOPClassUID;
1553         findRSP->m_messageIDRespondedTo = rsp.msg.CFindRSP.MessageIDBeingRespondedTo;
1554         findRSP->m_status               = rsp.msg.CFindRSP.DimseStatus;
1555         findRSP->m_statusDetail         = statusDetail;
1556 
1557         // Receive dataset if there is one (status PENDING)
1558         DcmDataset* rspDataset = NULL;
1559         if (DICOM_PENDING_STATUS(findRSP->m_status))
1560         {
1561             // Check if dataset is announced correctly
1562             if (rsp.msg.CFindRSP.DataSetType == DIMSE_DATASET_NULL)
1563             {
1564                 DCMNET_ERROR("Received C-FIND response with PENDING status but no dataset announced, aborting");
1565                 return DIMSE_BADMESSAGE;
1566             }
1567 
1568             // Receive dataset
1569             cond = receiveDIMSEDataset(&pcid, &rspDataset);
1570             if (cond.bad())
1571                 return DIMSE_BADDATA;
1572             findRSP->m_dataset = rspDataset;
1573         }
1574 
1575         // Handle C-FIND response (has to handle all possible status flags)
1576         cond = handleFINDResponse(pcid, findRSP.get(), waitForNextResponse);
1577         if (cond.bad())
1578         {
1579             DCMNET_WARN("Unable to handle C-FIND response correctly: " << cond.text() << " (ignored)");
1580             // don't return here but trust the "waitForNextResponse" variable
1581         }
1582         // if response could be handled successfully, add it to response list
1583         else
1584         {
1585             if (responses != NULL) // only add if desired by caller
1586                 responses->push_back(findRSP.release());
1587         }
1588     }
1589     /* All responses received or break signal occurred */
1590     return EC_Normal;
1591 }
1592 
1593 // Standard handler for C-FIND message responses
handleFINDResponse(const T_ASC_PresentationContextID,QRResponse * response,OFBool & waitForNextResponse)1594 OFCondition DcmSCU::handleFINDResponse(const T_ASC_PresentationContextID /* presID */,
1595                                        QRResponse* response,
1596                                        OFBool& waitForNextResponse)
1597 {
1598     waitForNextResponse = OFFalse;
1599     if (response == NULL)
1600         return DIMSE_NULLKEY;
1601 
1602     DCMNET_DEBUG("Handling C-FIND Response");
1603     OFString s;
1604     s = DU_cfindStatusString(response->m_status);
1605     return handleSessionResponseDefault(response->m_status, s, waitForNextResponse);
1606 }
1607 
1608 /* ************************************************************************* */
1609 /*                            C-CANCEL functionality                         */
1610 /* ************************************************************************* */
1611 
1612 // Send C-CANCEL-REQ and, therefore, ends current C-FIND, -MOVE or -GET session
sendCANCELRequest(const T_ASC_PresentationContextID presID)1613 OFCondition DcmSCU::sendCANCELRequest(const T_ASC_PresentationContextID presID)
1614 {
1615     if (!isConnected())
1616         return DIMSE_ILLEGALASSOCIATION;
1617 
1618     /* Prepare DIMSE data structures for issuing request */
1619     OFCondition cond;
1620     OFString tempStr;
1621     T_ASC_PresentationContextID pcid = presID;
1622     T_DIMSE_Message msg;
1623     // Make sure everything is zeroed (especially options)
1624     bzero((char*)&msg, sizeof(msg));
1625     T_DIMSE_C_CancelRQ* req = &(msg.msg.CCancelRQ);
1626     // Set type of message
1627     msg.CommandField = DIMSE_C_CANCEL_RQ;
1628     /* Set message ID responded to. A new message ID is _not_ needed so
1629        we do not increment the message ID here but instead have to give the
1630        message ID that was used last.
1631        Note that that it is required to actually use the message ID of the last
1632        C-FIND/GET/MOVE that was issued on this presentation context channel.
1633        However, since we only support synchronous association mode so far,
1634        it is enough to take the last message ID used at all.
1635        For asynchronous operation, we would have to lookup the message ID
1636        of the last C-FIND/GET/MOVE request issued and thus, store this
1637        information after sending it.
1638      */
1639     req->MessageIDBeingRespondedTo = m_assoc->nextMsgID - 1;
1640     // Announce dataset
1641     req->DataSetType = DIMSE_DATASET_NULL;
1642 
1643     /* We do not care about the transfer syntax since no
1644        dataset is transported at all, i.e. we trust that the user provided
1645        the correct presentation context ID (could be private one).
1646      */
1647     if (DCM_dcmnetLogger.isEnabledFor(OFLogger::DEBUG_LOG_LEVEL))
1648     {
1649         DCMNET_INFO("Sending C-CANCEL Request");
1650         DCMNET_DEBUG(DIMSE_dumpMessage(tempStr, msg, DIMSE_OUTGOING, NULL, pcid));
1651     }
1652     else
1653     {
1654         DCMNET_INFO("Sending C-CANCEL Request (MsgID " << req->MessageIDBeingRespondedTo << ", PresID "
1655                                                        << OFstatic_cast(unsigned int, pcid) << ")");
1656     }
1657     cond = sendDIMSEMessage(pcid, &msg, NULL /*dataObject*/);
1658     if (cond.bad())
1659     {
1660         DCMNET_ERROR("Failed sending C-CANCEL request: " << DimseCondition::dump(tempStr, cond));
1661     }
1662 
1663     DCMNET_TRACE("There is no C-CANCEL response in DICOM, so none expected");
1664     return cond;
1665 }
1666 
1667 /* ************************************************************************* */
1668 /*                            N-ACTION functionality                         */
1669 /* ************************************************************************* */
1670 
1671 // Sends N-ACTION request to another DICOM application
sendACTIONRequest(const T_ASC_PresentationContextID presID,const OFString & sopInstanceUID,const Uint16 actionTypeID,DcmDataset * reqDataset,Uint16 & rspStatusCode)1672 OFCondition DcmSCU::sendACTIONRequest(const T_ASC_PresentationContextID presID,
1673                                       const OFString& sopInstanceUID,
1674                                       const Uint16 actionTypeID,
1675                                       DcmDataset* reqDataset,
1676                                       Uint16& rspStatusCode)
1677 {
1678     // Do some basic validity checks
1679     if (!isConnected())
1680         return DIMSE_ILLEGALASSOCIATION;
1681     if (sopInstanceUID.empty() || (reqDataset == NULL))
1682         return DIMSE_NULLKEY;
1683 
1684     // Prepare DIMSE data structures for issuing request
1685     OFCondition cond;
1686     OFString tempStr;
1687     T_ASC_PresentationContextID pcid = presID;
1688     T_DIMSE_Message request;
1689     // Make sure everything is zeroed (especially options)
1690     bzero((char*)&request, sizeof(request));
1691     T_DIMSE_N_ActionRQ& actionReq = request.msg.NActionRQ;
1692     DcmDataset* statusDetail      = NULL;
1693 
1694     request.CommandField   = DIMSE_N_ACTION_RQ;
1695     actionReq.MessageID    = nextMessageID();
1696     actionReq.DataSetType  = DIMSE_DATASET_PRESENT;
1697     actionReq.ActionTypeID = actionTypeID;
1698 
1699     // Determine SOP Class from presentation context
1700     OFString abstractSyntax, transferSyntax;
1701     findPresentationContext(pcid, abstractSyntax, transferSyntax);
1702     if (abstractSyntax.empty() || transferSyntax.empty())
1703         return DIMSE_NOVALIDPRESENTATIONCONTEXTID;
1704     OFStandard::strlcpy(actionReq.RequestedSOPClassUID, abstractSyntax.c_str(), sizeof(actionReq.RequestedSOPClassUID));
1705     OFStandard::strlcpy(
1706         actionReq.RequestedSOPInstanceUID, sopInstanceUID.c_str(), sizeof(actionReq.RequestedSOPInstanceUID));
1707 
1708     // Send request
1709     if (DCM_dcmnetLogger.isEnabledFor(OFLogger::DEBUG_LOG_LEVEL))
1710     {
1711         DCMNET_INFO("Sending N-ACTION Request");
1712         // Output dataset only if trace level is enabled
1713         if (DCM_dcmnetLogger.isEnabledFor(OFLogger::TRACE_LOG_LEVEL))
1714             DCMNET_DEBUG(DIMSE_dumpMessage(tempStr, request, DIMSE_OUTGOING, reqDataset, pcid));
1715         else
1716             DCMNET_DEBUG(DIMSE_dumpMessage(tempStr, request, DIMSE_OUTGOING, NULL, pcid));
1717     }
1718     else
1719     {
1720         DCMNET_INFO("Sending N-ACTION Request (MsgID " << actionReq.MessageID << ")");
1721     }
1722     cond = sendDIMSEMessage(pcid, &request, reqDataset);
1723     if (cond.bad())
1724     {
1725         DCMNET_ERROR("Failed sending N-ACTION request: " << DimseCondition::dump(tempStr, cond));
1726         return cond;
1727     }
1728 
1729     // Receive response
1730     T_DIMSE_Message response;
1731     bzero((char*)&response, sizeof(response));
1732     cond = receiveDIMSECommand(&pcid, &response, &statusDetail, NULL /* commandSet */);
1733     if (cond.bad())
1734     {
1735         DCMNET_ERROR("Failed receiving DIMSE response: " << DimseCondition::dump(tempStr, cond));
1736         return cond;
1737     }
1738 
1739     // Check command set
1740     if (response.CommandField == DIMSE_N_ACTION_RSP)
1741     {
1742         if (DCM_dcmnetLogger.isEnabledFor(OFLogger::DEBUG_LOG_LEVEL))
1743         {
1744             DCMNET_INFO("Received N-ACTION Response");
1745             DCMNET_DEBUG(DIMSE_dumpMessage(tempStr, response, DIMSE_INCOMING, NULL, pcid));
1746         }
1747         else
1748         {
1749             DCMNET_INFO("Received N-ACTION Response (" << DU_nactionStatusString(response.msg.NActionRSP.DimseStatus)
1750                                                        << ")");
1751         }
1752     }
1753     else
1754     {
1755         DCMNET_ERROR("Expected N-ACTION response but received DIMSE command 0x"
1756                      << STD_NAMESPACE hex << STD_NAMESPACE setfill('0') << STD_NAMESPACE setw(4)
1757                      << OFstatic_cast(unsigned int, response.CommandField));
1758         DCMNET_DEBUG(DIMSE_dumpMessage(tempStr, response, DIMSE_INCOMING, NULL, pcid));
1759         delete statusDetail;
1760         return DIMSE_BADCOMMANDTYPE;
1761     }
1762     if (statusDetail != NULL)
1763     {
1764         DCMNET_DEBUG("Response has status detail:" << OFendl << DcmObject::PrintHelper(*statusDetail));
1765         delete statusDetail;
1766     }
1767 
1768     // Set return value
1769     T_DIMSE_N_ActionRSP& actionRsp = response.msg.NActionRSP;
1770     rspStatusCode                  = actionRsp.DimseStatus;
1771 
1772     // Check whether there is a dataset to be received
1773     if (actionRsp.DataSetType == DIMSE_DATASET_PRESENT)
1774     {
1775         // this should never happen
1776         DcmDataset* tempDataset = NULL;
1777         T_ASC_PresentationContextID tempID;
1778         DCMNET_WARN("Trying to retrieve unexpected dataset in N-ACTION response");
1779         cond = receiveDIMSEDataset(&tempID, &tempDataset);
1780         if (cond.good())
1781         {
1782             DCMNET_WARN("Received unexpected dataset after N-ACTION response, ignoring");
1783             delete tempDataset;
1784         }
1785         else
1786         {
1787             return DIMSE_BADDATA;
1788         }
1789     }
1790     if (actionRsp.MessageIDBeingRespondedTo != actionReq.MessageID)
1791     {
1792         // since we only support synchronous communication, the message ID in the response
1793         // should be identical to the one in the request
1794         DCMNET_ERROR("Received response with wrong message ID (" << actionRsp.MessageIDBeingRespondedTo
1795                                                                  << " instead of " << actionReq.MessageID << ")");
1796         return DIMSE_BADMESSAGE;
1797     }
1798 
1799     return cond;
1800 }
1801 
1802 /* ************************************************************************* */
1803 /*                         N-EVENT REPORT functionality                      */
1804 /* ************************************************************************* */
1805 
1806 // Sends N-EVENT-REPORT request and receives N-EVENT-REPORT response
sendEVENTREPORTRequest(const T_ASC_PresentationContextID presID,const OFString & sopInstanceUID,const Uint16 eventTypeID,DcmDataset * reqDataset,Uint16 & rspStatusCode)1807 OFCondition DcmSCU::sendEVENTREPORTRequest(const T_ASC_PresentationContextID presID,
1808                                            const OFString& sopInstanceUID,
1809                                            const Uint16 eventTypeID,
1810                                            DcmDataset* reqDataset,
1811                                            Uint16& rspStatusCode)
1812 {
1813     // Do some basic validity checks
1814     if (!isConnected())
1815         return DIMSE_ILLEGALASSOCIATION;
1816     if (sopInstanceUID.empty() || (reqDataset == NULL))
1817         return DIMSE_NULLKEY;
1818 
1819     // Prepare DIMSE data structures for issuing request
1820     OFCondition cond;
1821     OFString tempStr;
1822     T_ASC_PresentationContextID pcid = presID;
1823     T_DIMSE_Message request;
1824     // Make sure everything is zeroed (especially options)
1825     bzero((char*)&request, sizeof(request));
1826 
1827     T_DIMSE_N_EventReportRQ& eventReportReq = request.msg.NEventReportRQ;
1828     DcmDataset* statusDetail                = NULL;
1829 
1830     request.CommandField = DIMSE_N_EVENT_REPORT_RQ;
1831 
1832     // Generate a new message ID
1833     eventReportReq.MessageID   = nextMessageID();
1834     eventReportReq.DataSetType = DIMSE_DATASET_PRESENT;
1835     eventReportReq.EventTypeID = eventTypeID;
1836 
1837     // Determine SOP Class from presentation context
1838     OFString abstractSyntax, transferSyntax;
1839     findPresentationContext(pcid, abstractSyntax, transferSyntax);
1840     if (abstractSyntax.empty() || transferSyntax.empty())
1841         return DIMSE_NOVALIDPRESENTATIONCONTEXTID;
1842     OFStandard::strlcpy(
1843         eventReportReq.AffectedSOPClassUID, abstractSyntax.c_str(), sizeof(eventReportReq.AffectedSOPClassUID));
1844     OFStandard::strlcpy(
1845         eventReportReq.AffectedSOPInstanceUID, sopInstanceUID.c_str(), sizeof(eventReportReq.AffectedSOPInstanceUID));
1846 
1847     // Send request
1848     if (DCM_dcmnetLogger.isEnabledFor(OFLogger::DEBUG_LOG_LEVEL))
1849     {
1850         DCMNET_INFO("Sending N-EVENT-REPORT Request");
1851         // Output dataset only if trace level is enabled
1852         if (DCM_dcmnetLogger.isEnabledFor(OFLogger::TRACE_LOG_LEVEL))
1853             DCMNET_DEBUG(DIMSE_dumpMessage(tempStr, request, DIMSE_OUTGOING, reqDataset, pcid));
1854         else
1855             DCMNET_DEBUG(DIMSE_dumpMessage(tempStr, request, DIMSE_OUTGOING, NULL, pcid));
1856     }
1857     else
1858     {
1859         DCMNET_INFO("Sending N-EVENT-REPORT Request (MsgID " << eventReportReq.MessageID << ")");
1860     }
1861     cond = sendDIMSEMessage(pcid, &request, reqDataset);
1862     if (cond.bad())
1863     {
1864         DCMNET_ERROR("Failed sending N-EVENT-REPORT request: " << DimseCondition::dump(tempStr, cond));
1865         return cond;
1866     }
1867     // Receive response
1868     T_DIMSE_Message response;
1869     // Make sure everything is zeroed (especially options)
1870     bzero((char*)&response, sizeof(response));
1871 
1872     cond = receiveDIMSECommand(&pcid, &response, &statusDetail, NULL /* commandSet */);
1873     if (cond.bad())
1874     {
1875         DCMNET_ERROR("Failed receiving DIMSE response: " << DimseCondition::dump(tempStr, cond));
1876         return cond;
1877     }
1878 
1879     // Check command set
1880     if (response.CommandField == DIMSE_N_EVENT_REPORT_RSP)
1881     {
1882         if (DCM_dcmnetLogger.isEnabledFor(OFLogger::DEBUG_LOG_LEVEL))
1883         {
1884             DCMNET_INFO("Received N-EVENT-REPORT Response");
1885             DCMNET_DEBUG(DIMSE_dumpMessage(tempStr, response, DIMSE_INCOMING, NULL, pcid));
1886         }
1887         else
1888         {
1889             DCMNET_INFO("Received N-EVENT-REPORT Response ("
1890                         << DU_neventReportStatusString(response.msg.NEventReportRSP.DimseStatus) << ")");
1891         }
1892     }
1893     else
1894     {
1895         DCMNET_ERROR("Expected N-EVENT-REPORT response but received DIMSE command 0x"
1896                      << STD_NAMESPACE hex << STD_NAMESPACE setfill('0') << STD_NAMESPACE setw(4)
1897                      << OFstatic_cast(unsigned int, response.CommandField));
1898         DCMNET_DEBUG(DIMSE_dumpMessage(tempStr, response, DIMSE_INCOMING, NULL, pcid));
1899         delete statusDetail;
1900         return DIMSE_BADCOMMANDTYPE;
1901     }
1902     if (statusDetail != NULL)
1903     {
1904         DCMNET_DEBUG("Response has status detail:" << OFendl << DcmObject::PrintHelper(*statusDetail));
1905         delete statusDetail;
1906     }
1907 
1908     // Set return value
1909     T_DIMSE_N_EventReportRSP& eventReportRsp = response.msg.NEventReportRSP;
1910     rspStatusCode                            = eventReportRsp.DimseStatus;
1911 
1912     // Check whether there is a dataset to be received
1913     if (eventReportRsp.DataSetType == DIMSE_DATASET_PRESENT)
1914     {
1915         // this should never happen
1916         DcmDataset* tempDataset = NULL;
1917         T_ASC_PresentationContextID tempID;
1918         cond = receiveDIMSEDataset(&tempID, &tempDataset);
1919         if (cond.good())
1920         {
1921             DCMNET_WARN("Received unexpected dataset after N-EVENT-REPORT response, ignoring");
1922             delete tempDataset;
1923         }
1924         else
1925         {
1926             DCMNET_ERROR("Failed receiving unexpected dataset after N-EVENT-REPORT response: "
1927                          << DimseCondition::dump(tempStr, cond));
1928             return DIMSE_BADDATA;
1929         }
1930     }
1931 
1932     // Check whether the message ID being responded to is equal to the message ID of the request
1933     if (eventReportRsp.MessageIDBeingRespondedTo != eventReportReq.MessageID)
1934     {
1935         DCMNET_ERROR("Received response with wrong message ID (" << eventReportRsp.MessageIDBeingRespondedTo
1936                                                                  << " instead of " << eventReportReq.MessageID << ")");
1937         return DIMSE_BADMESSAGE;
1938     }
1939     return cond;
1940 }
1941 
1942 // Receives N-EVENT-REPORT request
handleEVENTREPORTRequest(DcmDataset * & reqDataset,Uint16 & eventTypeID,const int timeout)1943 OFCondition DcmSCU::handleEVENTREPORTRequest(DcmDataset*& reqDataset, Uint16& eventTypeID, const int timeout)
1944 {
1945     // Do some basic validity checks
1946     if (!isConnected())
1947         return DIMSE_ILLEGALASSOCIATION;
1948 
1949     OFCondition cond;
1950     OFString tempStr;
1951     T_ASC_PresentationContextID presID;
1952     T_ASC_PresentationContextID presIDdset;
1953     T_DIMSE_Message request;
1954     // Make sure everything is zeroed (especially options)
1955     bzero((char*)&request, sizeof(request));
1956     T_DIMSE_N_EventReportRQ& eventReportReq = request.msg.NEventReportRQ;
1957     DcmDataset* dataset                     = NULL;
1958     DcmDataset* statusDetail                = NULL;
1959     Uint16 statusCode                       = 0;
1960 
1961     if (timeout > 0)
1962         DCMNET_DEBUG("Handle N-EVENT-REPORT request, waiting up to " << timeout
1963                                                                      << " seconds (only for N-EVENT-REPORT message)");
1964     else if ((m_dimseTimeout > 0) && (m_blockMode == DIMSE_NONBLOCKING))
1965         DCMNET_DEBUG("Handle N-EVENT-REPORT request, waiting up to " << m_dimseTimeout
1966                                                                      << " seconds (default for all DIMSE messages)");
1967     else
1968         DCMNET_DEBUG("Handle N-EVENT-REPORT request, waiting an unlimited period of time");
1969 
1970     // Receive request, use specific timeout (if defined)
1971     cond = receiveDIMSECommand(&presID, &request, &statusDetail, NULL /* commandSet */, timeout);
1972     if (cond.bad())
1973     {
1974         if (cond != DIMSE_NODATAAVAILABLE)
1975             DCMNET_ERROR("Failed receiving DIMSE request: " << DimseCondition::dump(tempStr, cond));
1976         return cond;
1977     }
1978 
1979     // Check command set
1980     if (request.CommandField == DIMSE_N_EVENT_REPORT_RQ)
1981     {
1982         if (DCM_dcmnetLogger.isEnabledFor(OFLogger::DEBUG_LOG_LEVEL))
1983             DCMNET_INFO("Received N-EVENT-REPORT Request");
1984         else
1985             DCMNET_INFO("Received N-EVENT-REPORT Request (MsgID " << eventReportReq.MessageID << ")");
1986     }
1987     else
1988     {
1989         DCMNET_DEBUG(DIMSE_dumpMessage(tempStr, request, DIMSE_INCOMING, NULL, presID));
1990         DCMNET_ERROR("Expected N-EVENT-REPORT request but received DIMSE command 0x"
1991                      << STD_NAMESPACE hex << STD_NAMESPACE setfill('0') << STD_NAMESPACE setw(4)
1992                      << OFstatic_cast(unsigned int, request.CommandField));
1993         delete statusDetail;
1994         return DIMSE_BADCOMMANDTYPE;
1995     }
1996     if (statusDetail != NULL)
1997     {
1998         DCMNET_DEBUG("Request has status detail:" << OFendl << DcmObject::PrintHelper(*statusDetail));
1999         delete statusDetail;
2000     }
2001 
2002     // Check if dataset is announced correctly
2003     if (eventReportReq.DataSetType == DIMSE_DATASET_NULL)
2004     {
2005         DCMNET_DEBUG(DIMSE_dumpMessage(tempStr, request, DIMSE_INCOMING, NULL, presID));
2006         DCMNET_ERROR("Received N-EVENT-REPORT request but no dataset announced, aborting");
2007         return DIMSE_BADMESSAGE;
2008     }
2009 
2010     // Receive dataset
2011     cond = receiveDIMSEDataset(&presIDdset, &dataset);
2012     if (cond.bad())
2013     {
2014         DCMNET_DEBUG(DIMSE_dumpMessage(tempStr, request, DIMSE_INCOMING, NULL, presID));
2015         return DIMSE_BADDATA;
2016     }
2017 
2018     // Output dataset only if trace level is enabled
2019     if (DCM_dcmnetLogger.isEnabledFor(OFLogger::TRACE_LOG_LEVEL))
2020         DCMNET_DEBUG(DIMSE_dumpMessage(tempStr, request, DIMSE_INCOMING, dataset, presID));
2021     else
2022         DCMNET_DEBUG(DIMSE_dumpMessage(tempStr, request, DIMSE_INCOMING, NULL, presID));
2023 
2024     // Compare presentation context ID of command and data set
2025     if (presIDdset != presID)
2026     {
2027         DCMNET_ERROR("Presentation Context ID of command (" << OFstatic_cast(unsigned int, presID) << ") and data set ("
2028                                                             << OFstatic_cast(unsigned int, presIDdset) << ") differ");
2029         delete dataset;
2030         return makeDcmnetCondition(DIMSEC_INVALIDPRESENTATIONCONTEXTID,
2031                                    OF_error,
2032                                    "DIMSE: Presentation Contexts of Command and Data Set differ");
2033     }
2034 
2035     // Check the request dataset and return the DIMSE status code to be used
2036     statusCode = checkEVENTREPORTRequest(eventReportReq, dataset);
2037 
2038     // Send back response
2039     T_DIMSE_Message response;
2040     // Make sure everything is zeroed (especially options)
2041     bzero((char*)&response, sizeof(response));
2042 
2043     T_DIMSE_N_EventReportRSP& eventReportRsp = response.msg.NEventReportRSP;
2044     response.CommandField                    = DIMSE_N_EVENT_REPORT_RSP;
2045     eventReportRsp.MessageIDBeingRespondedTo = eventReportReq.MessageID;
2046     eventReportRsp.DimseStatus               = statusCode;
2047     eventReportRsp.DataSetType               = DIMSE_DATASET_NULL;
2048     eventReportRsp.opts                      = 0;
2049     eventReportRsp.AffectedSOPClassUID[0]    = 0;
2050     eventReportRsp.AffectedSOPInstanceUID[0] = 0;
2051 
2052     if (DCM_dcmnetLogger.isEnabledFor(OFLogger::DEBUG_LOG_LEVEL))
2053     {
2054         DCMNET_INFO("Sending N-EVENT-REPORT Response");
2055         DCMNET_DEBUG(DIMSE_dumpMessage(tempStr, response, DIMSE_OUTGOING, NULL, presID));
2056     }
2057     else
2058     {
2059         DCMNET_INFO("Sending N-EVENT-REPORT Response (" << DU_neventReportStatusString(statusCode) << ")");
2060     }
2061     cond = sendDIMSEMessage(presID, &response, NULL /*dataObject*/);
2062     if (cond.bad())
2063     {
2064         DCMNET_ERROR("Failed sending N-EVENT-REPORT response: " << DimseCondition::dump(tempStr, cond));
2065         delete dataset;
2066         return cond;
2067     }
2068 
2069     // Set return values
2070     reqDataset  = dataset;
2071     eventTypeID = eventReportReq.EventTypeID;
2072 
2073     return cond;
2074 }
2075 
checkEVENTREPORTRequest(T_DIMSE_N_EventReportRQ &,DcmDataset *)2076 Uint16 DcmSCU::checkEVENTREPORTRequest(T_DIMSE_N_EventReportRQ& /*eventReportReq*/, DcmDataset* /*reqDataset*/)
2077 {
2078     // we default to success
2079     return STATUS_Success;
2080 }
2081 
2082 OFCondition
handleSessionResponseDefault(const Uint16 dimseStatus,const OFString & message,OFBool & waitForNextResponse)2083 DcmSCU::handleSessionResponseDefault(const Uint16 dimseStatus, const OFString& message, OFBool& waitForNextResponse)
2084 {
2085     waitForNextResponse = OFFalse;
2086 
2087     // Do some basic validity checks
2088     if (!isConnected())
2089         return DIMSE_ILLEGALASSOCIATION;
2090 
2091     if (DICOM_WARNING_STATUS(dimseStatus))
2092     {
2093         DCMNET_WARN("DIMSE status is: " << message);
2094     }
2095     else if (DICOM_PENDING_STATUS(dimseStatus))
2096     {
2097         waitForNextResponse = OFTrue;
2098         DCMNET_DEBUG("DIMSE status is: " << message);
2099     }
2100     else if (dimseStatus == STATUS_Success)
2101     {
2102         DCMNET_DEBUG("DIMSE status is: " << message);
2103     }
2104     else
2105     {
2106         DCMNET_ERROR("DIMSE status is: " << message);
2107     }
2108 
2109     return EC_Normal;
2110 }
2111 
2112 /* ************************************************************************* */
2113 /*                         General message handling                          */
2114 /* ************************************************************************* */
2115 
notifySENDProgress(const unsigned long byteCount)2116 void DcmSCU::notifySENDProgress(const unsigned long byteCount)
2117 {
2118     DCMNET_TRACE("Bytes sent: " << byteCount);
2119 }
2120 
notifyRECEIVEProgress(const unsigned long byteCount)2121 void DcmSCU::notifyRECEIVEProgress(const unsigned long byteCount)
2122 {
2123     DCMNET_TRACE("Bytes received: " << byteCount);
2124 }
2125 
2126 /* ************************************************************************* */
2127 /*                            Various helpers                                */
2128 /* ************************************************************************* */
2129 
2130 // Sends a DIMSE command and possibly also instance data to the configured peer DICOM application
sendDIMSEMessage(const T_ASC_PresentationContextID presID,T_DIMSE_Message * msg,DcmDataset * dataObject,DcmDataset ** commandSet)2131 OFCondition DcmSCU::sendDIMSEMessage(const T_ASC_PresentationContextID presID,
2132                                      T_DIMSE_Message* msg,
2133                                      DcmDataset* dataObject,
2134                                      DcmDataset** commandSet)
2135 {
2136     if (!isConnected())
2137         return DIMSE_ILLEGALASSOCIATION;
2138     if (msg == NULL)
2139         return DIMSE_NULLKEY;
2140 
2141     OFCondition cond;
2142     /* call the corresponding DIMSE function to send the message */
2143     if (m_progressNotificationMode)
2144     {
2145         cond = DIMSE_sendMessageUsingMemoryData(m_assoc,
2146                                                 presID,
2147                                                 msg,
2148                                                 NULL /*statusDetail*/,
2149                                                 dataObject,
2150                                                 callbackSENDProgress,
2151                                                 this /*callbackData*/,
2152                                                 commandSet);
2153     }
2154     else
2155     {
2156         cond = DIMSE_sendMessageUsingMemoryData(m_assoc,
2157                                                 presID,
2158                                                 msg,
2159                                                 NULL /*statusDetail*/,
2160                                                 dataObject,
2161                                                 NULL /*callback*/,
2162                                                 NULL /*callbackData*/,
2163                                                 commandSet);
2164     }
2165 
2166 #if 0
2167   // currently disabled because it is not (yet) needed
2168   if (cond.good())
2169   {
2170     /* create a copy of the current DIMSE command message */
2171     delete m_openDIMSERequest;
2172     m_openDIMSERequest = new T_DIMSE_Message;
2173     // Make sure everyhting is zeroed (especially options)
2174     bzero((char*)&m_openDIMSERequest, sizeof(m_openDimseRequest));
2175     memcpy((char*)m_openDIMSERequest, msg, sizeof(*m_openDIMSERequest));
2176   }
2177 #endif
2178 
2179     return cond;
2180 }
2181 
2182 // Receive DIMSE command (excluding dataset!) over the currently open association
receiveDIMSECommand(T_ASC_PresentationContextID * presID,T_DIMSE_Message * msg,DcmDataset ** statusDetail,DcmDataset ** commandSet,const Uint32 timeout)2183 OFCondition DcmSCU::receiveDIMSECommand(T_ASC_PresentationContextID* presID,
2184                                         T_DIMSE_Message* msg,
2185                                         DcmDataset** statusDetail,
2186                                         DcmDataset** commandSet,
2187                                         const Uint32 timeout)
2188 {
2189     if (!isConnected())
2190         return DIMSE_ILLEGALASSOCIATION;
2191 
2192     OFCondition cond;
2193     if (timeout > 0)
2194     {
2195         /* call the corresponding DIMSE function to receive the command (use specified timeout)*/
2196         cond = DIMSE_receiveCommand(m_assoc, DIMSE_NONBLOCKING, timeout, presID, msg, statusDetail, commandSet);
2197     }
2198     else
2199     {
2200         /* call the corresponding DIMSE function to receive the command (use default timeout) */
2201         cond = DIMSE_receiveCommand(m_assoc, m_blockMode, m_dimseTimeout, presID, msg, statusDetail, commandSet);
2202     }
2203     return cond;
2204 }
2205 
2206 // Receives one dataset (of instance data) via network from another DICOM application
receiveDIMSEDataset(T_ASC_PresentationContextID * presID,DcmDataset ** dataObject)2207 OFCondition DcmSCU::receiveDIMSEDataset(T_ASC_PresentationContextID* presID, DcmDataset** dataObject)
2208 {
2209     if (!isConnected())
2210         return DIMSE_ILLEGALASSOCIATION;
2211 
2212     OFCondition cond;
2213     /* call the corresponding DIMSE function to receive the dataset */
2214     if (m_progressNotificationMode)
2215     {
2216         cond = DIMSE_receiveDataSetInMemory(
2217             m_assoc, m_blockMode, m_dimseTimeout, presID, dataObject, callbackRECEIVEProgress, this /*callbackData*/);
2218     }
2219     else
2220     {
2221         cond = DIMSE_receiveDataSetInMemory(
2222             m_assoc, m_blockMode, m_dimseTimeout, presID, dataObject, NULL /*callback*/, NULL /*callbackData*/);
2223     }
2224 
2225     if (cond.good())
2226     {
2227         DCMNET_DEBUG("Received dataset on presentation context " << OFstatic_cast(unsigned int, *presID));
2228     }
2229     else
2230     {
2231         OFString tempStr;
2232         DCMNET_ERROR("Unable to receive dataset on presentation context "
2233                      << OFstatic_cast(unsigned int, *presID) << ": " << DimseCondition::dump(tempStr, cond));
2234     }
2235     return cond;
2236 }
2237 
setMaxReceivePDULength(const Uint32 maxRecPDU)2238 void DcmSCU::setMaxReceivePDULength(const Uint32 maxRecPDU)
2239 {
2240     m_maxReceivePDULength = maxRecPDU;
2241 }
2242 
setDIMSEBlockingMode(const T_DIMSE_BlockingMode blockingMode)2243 void DcmSCU::setDIMSEBlockingMode(const T_DIMSE_BlockingMode blockingMode)
2244 {
2245     m_blockMode = blockingMode;
2246 }
2247 
setAETitle(const OFString & myAETtitle)2248 void DcmSCU::setAETitle(const OFString& myAETtitle)
2249 {
2250     m_ourAETitle = myAETtitle;
2251 }
2252 
setPeerHostName(const OFString & peerHostName)2253 void DcmSCU::setPeerHostName(const OFString& peerHostName)
2254 {
2255     m_peer = peerHostName;
2256 }
2257 
setPeerAETitle(const OFString & peerAETitle)2258 void DcmSCU::setPeerAETitle(const OFString& peerAETitle)
2259 {
2260     m_peerAETitle = peerAETitle;
2261 }
2262 
setPeerPort(const Uint16 peerPort)2263 void DcmSCU::setPeerPort(const Uint16 peerPort)
2264 {
2265     m_peerPort = peerPort;
2266 }
2267 
setDIMSETimeout(const Uint32 dimseTimeout)2268 void DcmSCU::setDIMSETimeout(const Uint32 dimseTimeout)
2269 {
2270     m_dimseTimeout = dimseTimeout;
2271 }
2272 
setACSETimeout(const Uint32 acseTimeout)2273 void DcmSCU::setACSETimeout(const Uint32 acseTimeout)
2274 {
2275     m_acseTimeout = acseTimeout;
2276 }
2277 
setConnectionTimeout(const Sint32 connectionTimeout)2278 void DcmSCU::setConnectionTimeout(const Sint32 connectionTimeout)
2279 {
2280     dcmConnectionTimeout.set(connectionTimeout);
2281 }
2282 
setAssocConfigFileAndProfile(const OFString & filename,const OFString & profile)2283 void DcmSCU::setAssocConfigFileAndProfile(const OFString& filename, const OFString& profile)
2284 {
2285     m_assocConfigFilename = filename;
2286     m_assocConfigProfile  = profile;
2287 }
2288 
setStorageDir(const OFString & storeDir)2289 void DcmSCU::setStorageDir(const OFString& storeDir)
2290 {
2291     m_storageDir = storeDir;
2292 }
2293 
setStorageMode(const DcmStorageMode storageMode)2294 void DcmSCU::setStorageMode(const DcmStorageMode storageMode)
2295 {
2296     m_storageMode = storageMode;
2297 }
2298 
setVerbosePCMode(const OFBool mode)2299 void DcmSCU::setVerbosePCMode(const OFBool mode)
2300 {
2301     m_verbosePCMode = mode;
2302 }
2303 
setDatasetConversionMode(const OFBool mode)2304 void DcmSCU::setDatasetConversionMode(const OFBool mode)
2305 {
2306     m_datasetConversionMode = mode;
2307 }
2308 
setProgressNotificationMode(const OFBool mode)2309 void DcmSCU::setProgressNotificationMode(const OFBool mode)
2310 {
2311     m_progressNotificationMode = mode;
2312 }
2313 
2314 /* Get methods */
2315 
isConnected() const2316 OFBool DcmSCU::isConnected() const
2317 {
2318     return (m_assoc != NULL) && (m_assoc->DULassociation != NULL);
2319 }
2320 
getMaxReceivePDULength() const2321 Uint32 DcmSCU::getMaxReceivePDULength() const
2322 {
2323     return m_maxReceivePDULength;
2324 }
2325 
getTLSEnabled() const2326 OFBool DcmSCU::getTLSEnabled() const
2327 {
2328     return OFFalse;
2329 }
2330 
getDIMSEBlockingMode() const2331 T_DIMSE_BlockingMode DcmSCU::getDIMSEBlockingMode() const
2332 {
2333     return m_blockMode;
2334 }
2335 
getAETitle() const2336 const OFString& DcmSCU::getAETitle() const
2337 {
2338     return m_ourAETitle;
2339 }
2340 
getPeerHostName() const2341 const OFString& DcmSCU::getPeerHostName() const
2342 {
2343     return m_peer;
2344 }
2345 
getPeerAETitle() const2346 const OFString& DcmSCU::getPeerAETitle() const
2347 {
2348     return m_peerAETitle;
2349 }
2350 
getPeerPort() const2351 Uint16 DcmSCU::getPeerPort() const
2352 {
2353     return m_peerPort;
2354 }
2355 
getDIMSETimeout() const2356 Uint32 DcmSCU::getDIMSETimeout() const
2357 {
2358     return m_dimseTimeout;
2359 }
2360 
getACSETimeout() const2361 Uint32 DcmSCU::getACSETimeout() const
2362 {
2363     return m_acseTimeout;
2364 }
2365 
getConnectionTimeout() const2366 Sint32 DcmSCU::getConnectionTimeout() const
2367 {
2368     return dcmConnectionTimeout.get();
2369 }
2370 
getStorageDir() const2371 OFString DcmSCU::getStorageDir() const
2372 {
2373     return m_storageDir;
2374 }
2375 
getStorageMode() const2376 DcmStorageMode DcmSCU::getStorageMode() const
2377 {
2378     return m_storageMode;
2379 }
2380 
getVerbosePCMode() const2381 OFBool DcmSCU::getVerbosePCMode() const
2382 {
2383     return m_verbosePCMode;
2384 }
2385 
getDatasetConversionMode() const2386 OFBool DcmSCU::getDatasetConversionMode() const
2387 {
2388     return m_datasetConversionMode;
2389 }
2390 
getProgressNotificationMode() const2391 OFBool DcmSCU::getProgressNotificationMode() const
2392 {
2393     return m_progressNotificationMode;
2394 }
2395 
getDatasetInfo(DcmDataset * dataset,OFString & sopClassUID,OFString & sopInstanceUID,E_TransferSyntax & transferSyntax)2396 OFCondition DcmSCU::getDatasetInfo(DcmDataset* dataset,
2397                                    OFString& sopClassUID,
2398                                    OFString& sopInstanceUID,
2399                                    E_TransferSyntax& transferSyntax)
2400 {
2401     OFCondition status = EC_IllegalParameter;
2402     sopClassUID.clear();
2403     sopInstanceUID.clear();
2404     transferSyntax = EXS_Unknown;
2405     if (dataset != NULL)
2406     {
2407         // ignore returned condition codes (e.g. EC_TagNotFound)
2408         dataset->findAndGetOFString(DCM_SOPClassUID, sopClassUID);
2409         dataset->findAndGetOFString(DCM_SOPInstanceUID, sopInstanceUID);
2410         transferSyntax = dataset->getOriginalXfer();
2411         // check return values for validity
2412         if (sopClassUID.empty())
2413             status = NET_EC_InvalidSOPClassUID;
2414         else if (sopInstanceUID.empty())
2415             status = NET_EC_InvalidSOPInstanceUID;
2416         else if (transferSyntax == EXS_Unknown)
2417             status = NET_EC_UnknownTransferSyntax;
2418         else
2419             status = EC_Normal;
2420     }
2421     return status;
2422 }
2423 
2424 /* ************************************************************************* */
2425 /*                            Callback functions                             */
2426 /* ************************************************************************* */
2427 
callbackSENDProgress(void * callbackContext,const unsigned long byteCount)2428 void DcmSCU::callbackSENDProgress(void* callbackContext, const unsigned long byteCount)
2429 {
2430     if (callbackContext != NULL)
2431         OFreinterpret_cast(DcmSCU*, callbackContext)->notifySENDProgress(byteCount);
2432 }
2433 
callbackRECEIVEProgress(void * callbackContext,const unsigned long byteCount)2434 void DcmSCU::callbackRECEIVEProgress(void* callbackContext, const unsigned long byteCount)
2435 {
2436     if (callbackContext != NULL)
2437         OFreinterpret_cast(DcmSCU*, callbackContext)->notifyRECEIVEProgress(byteCount);
2438 }
2439 
2440 /* ************************************************************************* */
2441 /*                          class RetrieveResponse                           */
2442 /* ************************************************************************* */
2443 
print()2444 void RetrieveResponse::print()
2445 {
2446     DCMNET_INFO("  Number of Remaining Suboperations : " << m_numberOfRemainingSubops);
2447     DCMNET_INFO("  Number of Completed Suboperations : " << m_numberOfCompletedSubops);
2448     DCMNET_INFO("  Number of Failed Suboperations    : " << m_numberOfFailedSubops);
2449     DCMNET_INFO("  Number of Warning Suboperations   : " << m_numberOfWarningSubops);
2450 }
2451