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