1 /*
2 *
3 * Copyright (C) 1994-2019, OFFIS e.V.
4 * All rights reserved. See COPYRIGHT file for details.
5 *
6 * This software and supporting documentation were partly developed by
7 *
8 * OFFIS e.V.
9 * R&D Division Health
10 * Escherweg 2
11 * D-26121 Oldenburg, Germany
12 *
13 * For further copyrights, see the following paragraphs.
14 *
15 */
16
17 /*
18 ** Copyright (C) 1993/1994, OFFIS, Oldenburg University and CERIUM
19 **
20 ** This software and supporting documentation were
21 ** developed by
22 **
23 ** Institut OFFIS
24 ** Bereich Kommunikationssysteme
25 ** Westerstr. 10-12
26 ** 26121 Oldenburg, Germany
27 **
28 ** Fachbereich Informatik
29 ** Abteilung Prozessinformatik
30 ** Carl von Ossietzky Universitaet Oldenburg
31 ** Ammerlaender Heerstr. 114-118
32 ** 26111 Oldenburg, Germany
33 **
34 ** CERIUM
35 ** Laboratoire SIM
36 ** Faculte de Medecine
37 ** 2 Avenue du Pr. Leon Bernard
38 ** 35043 Rennes Cedex, France
39 **
40 ** for CEN/TC251/WG4 as a contribution to the Radiological
41 ** Society of North America (RSNA) 1993 Digital Imaging and
42 ** Communications in Medicine (DICOM) Demonstration.
43 **
44 ** THIS SOFTWARE IS MADE AVAILABLE, AS IS, AND NEITHER OFFIS,
45 ** OLDENBURG UNIVERSITY NOR CERIUM MAKE ANY WARRANTY REGARDING
46 ** THE SOFTWARE, ITS PERFORMANCE, ITS MERCHANTABILITY OR
47 ** FITNESS FOR ANY PARTICULAR USE, FREEDOM FROM ANY COMPUTER
48 ** DISEASES OR ITS CONFORMITY TO ANY SPECIFICATION. THE
49 ** ENTIRE RISK AS TO QUALITY AND PERFORMANCE OF THE SOFTWARE
50 ** IS WITH THE USER.
51 **
52 ** Copyright of the software and supporting documentation
53 ** is, unless otherwise stated, jointly owned by OFFIS,
54 ** Oldenburg University and CERIUM and free access is hereby
55 ** granted as a license to use this software, copy this
56 ** software and prepare derivative works based upon this
57 ** software. However, any distribution of this software
58 ** source code or supporting documentation or derivative
59 ** works (source code and supporting documentation) must
60 ** include the three paragraphs of this copyright notice.
61 **
62 */
63
64 /*
65 **
66 ** Author: Andrew Hewett Created: 03-06-93
67 **
68 ** Module: dimse
69 **
70 ** Purpose:
71 ** This file contains the routines which provide dimse layer services
72 ** for DICOM V.3 applications.
73 **
74 ** Module Prefix: DIMSE_
75 */
76
77 /*
78 ** Include Files
79 */
80
81 #include "dcmtk/config/osconfig.h" /* make sure OS specific configuration is included first */
82
83 #define INCLUDE_CSTDLIB
84 #define INCLUDE_CSTDIO
85 #define INCLUDE_CSTRING
86 #define INCLUDE_CSTDARG
87 #define INCLUDE_UNISTD
88 #include "dcmtk/ofstd/ofstdinc.h"
89
90 #ifdef HAVE_UNIX_H
91 #if defined(macintosh) && defined (HAVE_WINSOCK_H)
92 /* unix.h defines timeval incompatible with winsock.h */
93 #define timeval _UNWANTED_timeval
94 #endif
95 #include <unix.h> /* for unlink() under Metrowerks C++ (Macintosh) */
96 #undef timeval
97 #endif
98 #ifdef HAVE_FCNTL_H
99 #include <fcntl.h>
100 #endif
101
102 #include "dcmtk/dcmnet/diutil.h"
103 #include "dcmtk/dcmnet/dimse.h" /* always include the module header */
104 #include "dcmtk/dcmnet/cond.h"
105 #include "dimcmd.h"
106 #include "dcmtk/dcmdata/dcdeftag.h" /* for tag names */
107 #include "dcmtk/dcmdata/dcdict.h" /* for dcmDataDict */
108 #include "dcmtk/dcmdata/dcfilefo.h" /* for class DcmFileFormat */
109 #include "dcmtk/dcmdata/dcmetinf.h" /* for class DcmMetaInfo */
110 #include "dcmtk/dcmdata/dcistrmb.h" /* for class DcmInputBufferStream */
111 #include "dcmtk/dcmdata/dcostrmb.h" /* for class DcmOutputBufferStream */
112 #include "dcmtk/dcmdata/dcostrmf.h" /* for class DcmOutputFileStream */
113 #include "dcmtk/dcmdata/dcvrul.h" /* for class DcmUnsignedLong */
114 #include "dcmtk/dcmdata/dcvrobow.h" /* for class DcmOtherByteOtherWord */
115 #include "dcmtk/dcmdata/dcvrsh.h" /* for class DcmShortString */
116 #include "dcmtk/dcmdata/dcvrae.h" /* for class DcmApplicationEntity */
117 #include "dcmtk/dcmdata/dcdicent.h" /* for class DcmDictEntry, needed for MSVC5 */
118 #include "dcmtk/dcmdata/dcwcache.h" /* for class DcmWriteCache */
119 #include "dcmtk/dcmdata/dcvrui.h" /* for class DcmUniqueIdentifier */
120
121
122 /*
123 * Global variables, mutex protected
124 */
125
126 /* global flag allowing to restrict the maximum size of outgoing
127 * P-DATA PDUs to a value less than the maximum supported by the
128 * remote application entity or this library. May be useful
129 * if there is an interaction between PDU size and other network
130 * layers, e. g. TLS, IP or below.
131 */
132 OFGlobal<Uint32> dcmMaxOutgoingPDUSize((Uint32) -1);
133
134 /*
135 * Other global variables (should be used very, very rarely).
136 * Modification of this variables is THREAD UNSAFE.
137 */
138
139 /*
140 * Define global defaults for data encoding when sending out data-sets.
141 * These can be adjusted to allow variants to be tested.
142 */
143
144 E_GrpLenEncoding g_dimse_send_groupLength_encoding = EGL_recalcGL;
145 E_EncodingType g_dimse_send_sequenceType_encoding = EET_ExplicitLength;
146
147 /*
148 * These globals control the options for saving all DIMSE messages
149 */
150
151 OFBool g_dimse_save_dimse_data = OFFalse;
152 static unsigned long g_dimse_commandCounter = 0;
153 static unsigned long g_dimse_dataCounter = 0;
154
155
156 /*
157 ** Private Functions Bodies
158 */
159
saveDimseFragment(DcmDataset * dset,OFBool isCommand,OFBool isReceive)160 static void saveDimseFragment(
161 DcmDataset *dset,
162 OFBool isCommand,
163 OFBool isReceive)
164 /*
165 * This function saves the information which is contained in dset to a file
166 *
167 * Parameters:
168 * dset - [in] Contains the information which shall be written to a file.
169 * isCommand - [in] Specifies if dset's information refers to a DIMSE command (as for
170 * example C-STORE) (OFTrue) or if it refers to instance data (OFFalse).
171 * isReceive - [in] Specifies if this application received (OFTrue) or sent (OFFalse)
172 * the information in dset.
173 */
174 {
175 /* depending on if the information in dset refers to a DIMSE command or not, set some global variables */
176 if (isCommand)
177 {
178 g_dimse_commandCounter++;
179 g_dimse_dataCounter = 0;
180 } else g_dimse_dataCounter++;
181
182 /* if there is no information exit this function */
183 if (dset==NULL) return;
184
185 /* create a string which will contain the name of the file we are about to write to */
186 /* filenames are supposed to look like "dimse-cmd-[rcv|snd]-[counter].dcm" */
187 const char *transmission = (isReceive ? "rcv" : "snd");
188 char filename[2048];
189 if (isCommand)
190 {
191 sprintf(filename, "dimse-cmd-%s-%04lu.dcm", transmission, g_dimse_commandCounter);
192 } else {
193 if (g_dimse_dataCounter < 2)
194 {
195 sprintf(filename, "dimse-dat-%s-%04lu.dcm", transmission, g_dimse_commandCounter);
196 } else {
197 sprintf(filename, "dimse-dat-%s-%04lu-%02lu.dcm", transmission, g_dimse_commandCounter, g_dimse_dataCounter);
198 }
199 }
200
201 /* write data to file */
202 dset->saveFile(filename, EXS_LittleEndianImplicit, EET_ExplicitLength, EGL_recalcGL, EPD_withoutPadding);
203 return;
204 }
205
206 static OFBool
isDataDictPresent()207 isDataDictPresent()
208 {
209 /* is a data dictionary present */
210 return dcmDataDict.isDictionaryLoaded();
211 }
212
213 /*
214 * PDV Reading
215 */
216
217 static OFCondition
DIMSE_readNextPDV(T_ASC_Association * assoc,T_DIMSE_BlockingMode blocking,int timeout,DUL_PDV * pdv)218 DIMSE_readNextPDV(
219 T_ASC_Association *assoc,
220 T_DIMSE_BlockingMode blocking,
221 int timeout,
222 DUL_PDV *pdv)
223 /*
224 * This function returns the next PDV which was (earlier or just now) received on the incoming
225 * socket stream. If there are no PDVs (which were already received earlier) waiting to be picked
226 * up, this function will go ahead and read a new PDU (containing one or more new PDVs) from the
227 * incoming socket stream.
228 *
229 * Parameters:
230 * assoc - [in] The association (network connection to another DICOM application).
231 * blocking - [in] The blocking mode for reading data (either DIMSE_BLOCKING or DIMSE_NONBLOCKING)
232 * timeout - [in] Timeout interval for receiving data. If the blocking mode is DIMSE_NONBLOCKING
233 * and we are trying to read data from the incoming socket stream and no data has
234 * been received after timeout seconds, an error will be reported.
235 * pdv - [out] Contains in the end the next PDV which was (earlier or just now) received on
236 * the incoming socket stream.
237 *
238 */
239 {
240 DUL_BLOCKOPTIONS blk;
241
242 /*
243 * NOTE: DUL currently ignores blocking and timeout so do it here!
244 */
245
246 /* determine the DUL blocking option */
247 blk = (blocking == DIMSE_BLOCKING) ? (DUL_BLOCK) : (DUL_NOBLOCK);
248
249 /* get the next PDV from the association, in case there are still some PDVs waiting to be picked up */
250 OFCondition cond = DUL_NextPDV(&assoc->DULassociation, pdv);
251
252 if (cond.bad())
253 {
254 /* in case DUL_NextPDV(...) did not return DUL_NORMAL, the association */
255 /* did not contain any more PDVs that are waiting to be picked up. Hence, */
256 /* we need to read new PDVs from the incoming socket stream. */
257
258 /* if the blocking mode is DIMSE_NONBLOCKING and there is no data waiting after timeout seconds, report an error */
259 if (blocking == DIMSE_NONBLOCKING)
260 {
261 if (!ASC_dataWaiting(assoc, timeout)) return DIMSE_NODATAAVAILABLE;
262 }
263
264 /* try to receive new PDVs on the incoming socket stream (in detail, try to receive one PDU) */
265 cond = DUL_ReadPDVs(&assoc->DULassociation, NULL, blk, timeout);
266
267 /* check return value, if it is different from DUL_PDATAPDUARRIVED, an error occurred */
268 if (cond != DUL_PDATAPDUARRIVED)
269 {
270 if (cond == DUL_NULLKEY || cond == DUL_ILLEGALKEY) return DIMSE_ILLEGALASSOCIATION;
271 else if (cond == DUL_PEERREQUESTEDRELEASE ||
272 cond == DUL_PEERABORTEDASSOCIATION) return cond;
273 else return makeDcmnetSubCondition(DIMSEC_READPDVFAILED, OF_error, "DIMSE Read PDV failed", cond);
274 }
275
276 /* get the next PDV, assign it to pdv */
277 cond = DUL_NextPDV(&assoc->DULassociation, pdv);
278 if (cond.bad())
279 {
280 return makeDcmnetSubCondition(DIMSEC_READPDVFAILED, OF_error, "DIMSE Read PDV failed", cond);
281 }
282 }
283
284 /* return ok */
285 return EC_Normal;
286 }
287
288 /*
289 * Checking and Validation
290 */
291
292
293 static OFCondition
getTransferSyntax(T_ASC_Association * assoc,T_ASC_PresentationContextID pid,E_TransferSyntax * xferSyntax)294 getTransferSyntax(
295 T_ASC_Association *assoc,
296 T_ASC_PresentationContextID pid,
297 E_TransferSyntax *xferSyntax)
298 /*
299 * This function checks if the presentation context id which was passed refers to a valid presentation
300 * context. If this is the case, this function determines the transfer syntax the presentation context ID
301 * refers to (will be returned to the user) and also checks if dcmtk supports this transfer syntax.
302 *
303 * Parameters:
304 * assoc - [in] The association (network connection to another DICOM application).
305 * pid - [in] The id of the presentation context which shall be checked regarding validity.
306 * xferSyntax - [out] If pid refers to a valid presentation context, this variable contains in the
307 * end the transfer syntax which is specified in the presentation context.
308 */
309 {
310 T_ASC_PresentationContext pc;
311 char *ts = NULL;
312
313 /* figure out if is this a valid presentation context */
314 OFCondition cond = ASC_findAcceptedPresentationContext(assoc->params, pid, &pc);
315 if (cond.bad())
316 {
317 return makeDcmnetSubCondition(DIMSEC_RECEIVEFAILED, OF_error, "DIMSE Failed to receive message", cond);
318 }
319
320 /* determine the transfer syntax which is specified in the presentation context */
321 ts = pc.acceptedTransferSyntax;
322
323 /* create a DcmXfer object on the basis of the transfer syntax which was determined above */
324 DcmXfer xfer(ts);
325
326 /* check if the transfer syntax is supported by dcmtk */
327 *xferSyntax = xfer.getXfer();
328 switch (*xferSyntax)
329 {
330 case EXS_LittleEndianImplicit:
331 case EXS_LittleEndianExplicit:
332 case EXS_BigEndianExplicit:
333 case EXS_JPEGProcess1:
334 case EXS_JPEGProcess2_4:
335 case EXS_JPEGProcess3_5:
336 case EXS_JPEGProcess6_8:
337 case EXS_JPEGProcess7_9:
338 case EXS_JPEGProcess10_12:
339 case EXS_JPEGProcess11_13:
340 case EXS_JPEGProcess14:
341 case EXS_JPEGProcess15:
342 case EXS_JPEGProcess16_18:
343 case EXS_JPEGProcess17_19:
344 case EXS_JPEGProcess20_22:
345 case EXS_JPEGProcess21_23:
346 case EXS_JPEGProcess24_26:
347 case EXS_JPEGProcess25_27:
348 case EXS_JPEGProcess28:
349 case EXS_JPEGProcess29:
350 case EXS_JPEGProcess14SV1:
351 case EXS_RLELossless:
352 case EXS_JPEGLSLossless:
353 case EXS_JPEGLSLossy:
354 case EXS_JPEG2000LosslessOnly:
355 case EXS_JPEG2000:
356 case EXS_JPEG2000MulticomponentLosslessOnly:
357 case EXS_JPEG2000Multicomponent:
358 case EXS_MPEG2MainProfileAtMainLevel:
359 case EXS_MPEG2MainProfileAtHighLevel:
360 case EXS_MPEG4HighProfileLevel4_1:
361 case EXS_MPEG4BDcompatibleHighProfileLevel4_1:
362 case EXS_MPEG4HighProfileLevel4_2_For2DVideo:
363 case EXS_MPEG4HighProfileLevel4_2_For3DVideo:
364 case EXS_MPEG4StereoHighProfileLevel4_2:
365 case EXS_HEVCMainProfileLevel5_1:
366 case EXS_HEVCMain10ProfileLevel5_1:
367 #ifdef WITH_ZLIB
368 case EXS_DeflatedLittleEndianExplicit:
369 #endif
370 /* OK, these can be supported */
371 break;
372 default:
373 /* all other transfer syntaxes are not supported; hence, set the error indicator variable */
374 {
375 char buf[256];
376 sprintf(buf, "DIMSE Unsupported transfer syntax: %s", ts);
377 OFCondition subCond = makeDcmnetCondition(DIMSEC_UNSUPPORTEDTRANSFERSYNTAX, OF_error, buf);
378 cond = makeDcmnetSubCondition(DIMSEC_RECEIVEFAILED, OF_error, "DIMSE Failed to receive message", subCond);
379 }
380 break;
381 }
382
383 /* return result value */
384 return cond;
385 }
386
387
388 static OFCondition
checkPresentationContextForMessage(T_ASC_Association * assoc,T_DIMSE_Message * msg,T_ASC_PresentationContextID presID,E_TransferSyntax * xferSyntax)389 checkPresentationContextForMessage(
390 T_ASC_Association *assoc,
391 T_DIMSE_Message *msg,
392 T_ASC_PresentationContextID presID,
393 E_TransferSyntax *xferSyntax)
394 /*
395 * This function checks if the presentation context id refers to a valid presentation context and
396 * determines the transfer syntax which is specified for this presentation context. Additionally,
397 * the function checks if the specified transfer syntax is supported at all. If any of the checks
398 * returns an error, return a corresponding error..
399 *
400 * Parameters:
401 * assoc - [in] The association (network connection to another DICOM application).
402 * msg - [in] Structure that represents a certain DIMSE message.
403 * presID - [in] The ID of the presentation context which shall be used.
404 * xferSyntax - [out] Contains in the end the transfer syntax which is specified
405 * in the presentation context with the corresponding ID.
406 */
407 {
408 /* char *as; */ /* abstract syntax */
409 /* char *ts; */ /* transfer syntax */
410 T_ASC_PresentationContext pc;
411
412 /* try to find the accepted presentation context on the basis of the ID which was passed */
413 OFCondition cond = ASC_findAcceptedPresentationContext(assoc->params, presID, &pc);
414
415 /*
416 * as = pc.abstractSyntax;
417 * ts = pc.acceptedTransferSyntax;
418 */
419
420 /* if the accepted presentation context was found, check if the message type is supported */
421 if (cond.good())
422 {
423 switch (msg->CommandField)
424 {
425 case DIMSE_C_ECHO_RQ:
426 case DIMSE_C_ECHO_RSP:
427 case DIMSE_C_STORE_RQ:
428 case DIMSE_C_STORE_RSP:
429 case DIMSE_C_GET_RQ:
430 case DIMSE_C_GET_RSP:
431 case DIMSE_C_FIND_RQ:
432 case DIMSE_C_FIND_RSP:
433 case DIMSE_C_MOVE_RQ:
434 case DIMSE_C_MOVE_RSP:
435 case DIMSE_C_CANCEL_RQ:
436 case DIMSE_N_EVENT_REPORT_RQ:
437 case DIMSE_N_EVENT_REPORT_RSP:
438 case DIMSE_N_GET_RQ:
439 case DIMSE_N_GET_RSP:
440 case DIMSE_N_SET_RQ:
441 case DIMSE_N_SET_RSP:
442 case DIMSE_N_ACTION_RQ:
443 case DIMSE_N_ACTION_RSP:
444 case DIMSE_N_CREATE_RQ:
445 case DIMSE_N_CREATE_RSP:
446 case DIMSE_N_DELETE_RQ:
447 case DIMSE_N_DELETE_RSP:
448 break;
449
450 default:
451 cond = DIMSE_BADCOMMANDTYPE;
452 break;
453 }
454 }
455
456 /* if everything was ok so far determine the transfer syntax */
457 /* which is specified in the accepted presentation context */
458 if (cond.good()) cond = getTransferSyntax(assoc, presID, xferSyntax);
459
460 /* otherwise return ok */
461 return cond;
462 }
463
464 static OFCondition
validateMessage(T_ASC_Association * assoc,T_DIMSE_Message * msg)465 validateMessage(
466 T_ASC_Association *assoc,
467 T_DIMSE_Message *msg)
468 /*
469 * This function checks if the information which is contained in msg meets certain conditions.
470 * For example, if msg represents a C-ECHO-RQ, then there is not supposed to be a corresponding
471 * data set. If the specified conditions are not met, return an error.
472 *
473 * Parameters:
474 * assoc - [in] The association (network connection to another DICOM application).
475 * msg - [in] Structure that represents a certain DIMSE message.
476 */
477 {
478 OFCondition cond = EC_Normal;
479
480 /* check certain conditions depending on the message type */
481 switch (msg->CommandField) {
482 case DIMSE_C_ECHO_RQ:
483 if (msg->msg.CEchoRQ.DataSetType != DIMSE_DATASET_NULL) {
484 DCMNET_WARN(DIMSE_warn_str(assoc) << "C-Echo RQ: DataSetType != NULL");
485 cond = DIMSE_BADMESSAGE;
486 }
487 break;
488 case DIMSE_C_ECHO_RSP:
489 if (msg->msg.CEchoRSP.DataSetType != DIMSE_DATASET_NULL) {
490 DCMNET_WARN(DIMSE_warn_str(assoc) << "C-Echo RSP: DataSetType != NULL");
491 cond = DIMSE_BADMESSAGE;
492 }
493 break;
494 case DIMSE_C_STORE_RQ:
495 if (msg->msg.CStoreRQ.DataSetType == DIMSE_DATASET_NULL) {
496 DCMNET_WARN(DIMSE_warn_str(assoc) << "C-Store RQ: DataSetType == NULL");
497 cond = DIMSE_BADMESSAGE;
498 }
499 if (! IN_RANGE(strlen(msg->msg.CStoreRQ.AffectedSOPInstanceUID), 1, DIC_UI_LEN)) {
500 DCMNET_WARN(DIMSE_warn_str(assoc) << "C-Store RQ: AffectedSOPInstanceUID: bad size");
501 cond = DIMSE_BADMESSAGE;
502 }
503 break;
504 case DIMSE_C_STORE_RSP:
505 if (msg->msg.CStoreRSP.DataSetType != DIMSE_DATASET_NULL) {
506 DCMNET_WARN(DIMSE_warn_str(assoc) << "C-Store RSP: DataSetType != NULL");
507 cond = DIMSE_BADMESSAGE;
508 }
509 if ((msg->msg.CStoreRSP.opts & O_STORE_AFFECTEDSOPINSTANCEUID) &&
510 (! IN_RANGE(strlen(msg->msg.CStoreRSP.AffectedSOPInstanceUID), 1, DIC_UI_LEN))) {
511 DCMNET_WARN(DIMSE_warn_str(assoc) << "C-Store RSP: AffectedSOPInstanceUID: bad size");
512 cond = DIMSE_BADMESSAGE;
513 }
514 break;
515 case DIMSE_C_GET_RQ:
516 if (msg->msg.CGetRQ.DataSetType == DIMSE_DATASET_NULL) {
517 DCMNET_WARN(DIMSE_warn_str(assoc) << "C-Get RQ: DataSetType == NULL");
518 cond = DIMSE_BADMESSAGE;
519 }
520 break;
521 case DIMSE_C_GET_RSP:
522 /* data set can be empty or present */
523 break;
524 case DIMSE_C_FIND_RQ:
525 if (msg->msg.CFindRQ.DataSetType == DIMSE_DATASET_NULL) {
526 DCMNET_WARN(DIMSE_warn_str(assoc) << "C-Find RQ: DataSetType == NULL");
527 cond = DIMSE_BADMESSAGE;
528 }
529 break;
530 case DIMSE_C_FIND_RSP:
531 /* data set can be empty or present */
532 break;
533 case DIMSE_C_MOVE_RQ:
534 if (msg->msg.CMoveRQ.DataSetType == DIMSE_DATASET_NULL) {
535 DCMNET_WARN(DIMSE_warn_str(assoc) << "C-Move RQ: DataSetType == NULL");
536 cond = DIMSE_BADMESSAGE;
537 }
538 break;
539 case DIMSE_C_MOVE_RSP:
540 /* data set can be empty or present */
541 break;
542 case DIMSE_C_CANCEL_RQ:
543 if (msg->msg.CCancelRQ.DataSetType != DIMSE_DATASET_NULL) {
544 DCMNET_WARN(DIMSE_warn_str(assoc) << "C-Cancel RQ: DataSetType != NULL");
545 cond = DIMSE_BADMESSAGE;
546 }
547 break;
548
549 case DIMSE_N_EVENT_REPORT_RQ:
550 case DIMSE_N_EVENT_REPORT_RSP:
551 case DIMSE_N_GET_RQ:
552 case DIMSE_N_GET_RSP:
553 case DIMSE_N_SET_RQ:
554 case DIMSE_N_SET_RSP:
555 case DIMSE_N_ACTION_RQ:
556 case DIMSE_N_ACTION_RSP:
557 case DIMSE_N_CREATE_RQ:
558 case DIMSE_N_CREATE_RSP:
559 case DIMSE_N_DELETE_RQ:
560 case DIMSE_N_DELETE_RSP:
561 /* No checking on the normalized messages yet, assume ok */
562 break;
563
564 default:
565 cond = DIMSE_BADCOMMANDTYPE;
566 break;
567 }
568
569 return cond;
570 }
571
572
573 /*
574 * Message sending support routines
575 */
576
577 #if 0
578 /*** Currently Unused */
579 /*
580 ** SendStraightFileData does not work for files with a Metaheader.
581 ** It would need to be rewritten to read data after the Metaheader.
582 ** However, finding out where the Metaheader ends means parsing the
583 ** whole file in the current implementation. It is thus not worth
584 ** the effort!
585 */
586 static OFCondition
587 sendStraightFileData(
588 T_ASC_Association *assoc,
589 const char *dataFileName,
590 T_ASC_PresentationContextID presID,
591 E_TransferSyntax /* xferSyntax */,
592 DIMSE_ProgressCallback callback,
593 void *callbackContext)
594
595 {
596 /* we assume that the file contains transfer syntax compatible data */
597 unsigned char *buf;
598 unsigned long bufLen;
599 FILE *f;
600 long nbytes;
601 OFBool last;
602 unsigned long bytesTransmitted = 0;
603 OFCondition dulCond = EC_Normal;
604 DUL_PDVLIST pdvList;
605 DUL_PDV pdv;
606 /* the following variable is currently unused, leave it for future use */
607 unsigned long pdvCount = 0;
608
609 buf = assoc->sendPDVBuffer;
610 bufLen = assoc->sendPDVLength;
611
612 OFCondition cond = EC_Normal;
613
614 f = fopen(dataFileName, "rb");
615 if (f == NULL) {
616 char buf[256];
617 DCMNET_WARN(DIMSE_warn_str(assoc) << "sendStraightFileData: cannot open DICOM file ("
618 << dataFileName << "): " << OFStandard::strerror(errno, buf, sizeof(buf)));
619 cond = DIMSE_SENDFAILED;
620 }
621
622 while (cond.good() && ((nbytes = fread(buf, 1, bufLen, f)) > 0)) {
623 last = ((unsigned long)nbytes != bufLen);
624 pdv.fragmentLength = nbytes;
625 pdv.presentationContextID = presID;
626 pdv.pdvType = DUL_DATASETPDV;
627 pdv.lastPDV = last;
628 pdv.data = buf;
629
630 pdvList.count = 1;
631 pdvList.pdv = &pdv;
632
633 DCMNET_TRACE("DIMSE sendStraightFileData: sending " << pdv.fragmentLength << " bytes (last: "
634 << ((last)?("YES"):("NO")) << ")");
635
636 dulCond = DUL_WritePDVs(&assoc->DULassociation, &pdvList);
637 if (dulCond.bad())
638 {
639 cond = makeDcmnetSubCondition(DIMSEC_SENDFAILED, OF_error, "DIMSE Failed to send message", dulCond);
640 }
641
642 bytesTransmitted += nbytes;
643 pdvCount += pdvList.count;
644
645 if (callback) { /* execute callback function */
646 callback(callbackContext, bytesTransmitted);
647 }
648 }
649
650 fclose(f);
651
652 return cond;
653 }
654 #endif
655
656 static OFCondition
sendDcmDataset(T_ASC_Association * assoc,DcmDataset * obj,T_ASC_PresentationContextID presID,E_TransferSyntax xferSyntax,DUL_DATAPDV pdvType,DIMSE_ProgressCallback callback,void * callbackContext)657 sendDcmDataset(
658 T_ASC_Association *assoc,
659 DcmDataset *obj,
660 T_ASC_PresentationContextID presID,
661 E_TransferSyntax xferSyntax,
662 DUL_DATAPDV pdvType,
663 DIMSE_ProgressCallback callback,
664 void *callbackContext)
665 /*
666 * This function sends all information which is included in a DcmDataset object over
667 * the network which is provided in assoc.
668 *
669 * Parameters:
670 * assoc - [in] The association (network connection to another DICOM application).
671 * obj - [in] Contains the information which shall be sent over the network.
672 * presId - [in] The ID of the presentation context which shall be used
673 * xferSyntax - [in] The transfer syntax which shall be used.
674 * pdvType - [in] Specifies if the information in this DcmDataset object belongs to
675 * a DIMSE command (as for example C-STORE) (DUL_COMMANDPDV) or if
676 * the information is actual instance information (DUL_DATASETPDV).
677 * callback - [in] Pointer to a function which shall be called to indicate progress.
678 * callbackContext - []
679 */
680 {
681 OFCondition dulCond = EC_Normal;
682 OFCondition econd = EC_Normal;
683 unsigned char *buf;
684 unsigned long bufLen;
685 OFBool last = OFFalse;
686 OFBool written = OFFalse;
687 offile_off_t rtnLength;
688 Uint32 bytesTransmitted = 0;
689 DUL_PDVLIST pdvList;
690 DUL_PDV pdv;
691 /* the following variable is currently unused, leave it for future use */
692 unsigned long pdvCount = 0;
693 DcmWriteCache wcache;
694
695 /* initialize some local variables (we want to use the association's send buffer */
696 /* to store data) this buffer can only take a certain number of elements */
697 buf = assoc->sendPDVBuffer;
698 bufLen = assoc->sendPDVLength;
699
700 /* we may wish to restrict output PDU size */
701 Uint32 maxpdulen = dcmMaxOutgoingPDUSize.get();
702
703 /* max PDV size is max PDU size minus 12 bytes PDU/PDV header */
704 if (bufLen + 12 > maxpdulen)
705 {
706 bufLen = maxpdulen - 12;
707 }
708
709 /* on the basis of the association's buffer, create a buffer variable that we can write to */
710 DcmOutputBufferStream outBuf(buf, bufLen);
711
712 /* prepare all elements in the DcmDataset variable for transfer */
713 obj->transferInit();
714
715 /* initialize two more variables: groupLength_encoding specifies what will be done concerning */
716 /* group length tags, sequenceType_encoding specifies how sequences will be handled */
717 E_GrpLenEncoding groupLength_encoding = g_dimse_send_groupLength_encoding;
718 E_EncodingType sequenceType_encoding = g_dimse_send_sequenceType_encoding;
719
720 /* Mind that commands must always include group length (0000,0000) and */
721 /* that commands do not contain sequences, yet */
722 if (pdvType == DUL_COMMANDPDV)
723 groupLength_encoding = EGL_withGL;
724
725 /* start a loop: in each iteration information from the DcmDataset object (i.e. infor- */
726 /* mation which shall be sent) will be set in the buffer (we need more than one itera- */
727 /* tion if there is more information than the buffer can take at a time), a PDV object */
728 /* with the buffer's data will be created and assigned to a list, and finally the */
729 /* list's information will be sent over the network to the other DICOM application. */
730 while (!last)
731 {
732 /* write data values which are contained in the DcmDataSet variable to the above created */
733 /* buffer. Mind the transfer syntax, the sequence type encoding, the group length encoding */
734 /* and remove all padding data elements. Depending on whether all information has been written */
735 /* to the buffer, update the variable that determines the end of the while loop. (Note that */
736 /* DcmDataset stores information about what of its content has already been sent to the buffer.) */
737 if (! written)
738 {
739 econd = obj->write(outBuf, xferSyntax, sequenceType_encoding, &wcache,
740 groupLength_encoding, EPD_withoutPadding);
741 if (econd == EC_Normal) /* all contents have been written to the buffer */
742 {
743 written = OFTrue;
744 }
745 else if (econd == EC_StreamNotifyClient) /* no more space in buffer, _not_ all elements have been written to it */
746 {
747 // nothing to do
748 }
749 else /* some error has occurred */
750 {
751 DCMNET_WARN(DIMSE_warn_str(assoc) << "writeBlock Failed (" << econd.text() << ")");
752 return DIMSE_SENDFAILED;
753 }
754 }
755
756 if (written) outBuf.flush(); // flush stream including embedded compression codec.
757
758 /* get buffer and its length, assign to local variable */
759 void *fullBuf = NULL;
760 outBuf.flushBuffer(fullBuf, rtnLength);
761
762 last = written && outBuf.isFlushed();
763
764 /* if the buffer is not empty, do something with its contents */
765 if (rtnLength > 0)
766 {
767 /* rtnLength could be odd */
768 if (rtnLength & 1)
769 {
770 /* this should only happen if we use a stream compressed transfer
771 * syntax and then only at the very end of the stream. Everything
772 * else is a failure.
773 */
774 if (!last)
775 {
776 return makeDcmnetCondition(DIMSEC_SENDFAILED, OF_error,
777 "DIMSE Failed to send message: odd block length encountered");
778 }
779
780 /* since the block size is always even, block size must be larger
781 * than rtnLength, so we can safely add a pad byte (and hope that
782 * the pad byte will not confuse the receiver's decompressor).
783 */
784 unsigned char *cbuf = (unsigned char *)fullBuf;
785 cbuf[rtnLength++] = 0; // add zero pad byte
786 }
787
788 /* initialize a DUL_PDV variable with the buffer's data */
789 pdv.fragmentLength = OFstatic_cast(unsigned long, rtnLength);
790 pdv.presentationContextID = presID;
791 pdv.pdvType = pdvType;
792 pdv.lastPDV = last;
793 pdv.data = fullBuf;
794
795 /* append this PDV to a PDV list structure, set the counter variable */
796 /* to 1 since this structure contains only 1 element */
797 pdvList.count = 1;
798 pdvList.pdv = &pdv;
799
800 /* dump some information if required */
801 DCMNET_TRACE("DIMSE sendDcmDataset: sending " << pdv.fragmentLength << " bytes");
802
803 /* send information over the network to the other DICOM application */
804 dulCond = DUL_WritePDVs(&assoc->DULassociation, &pdvList);
805 if (dulCond.bad())
806 return makeDcmnetSubCondition(DIMSEC_SENDFAILED, OF_error, "DIMSE Failed to send message", dulCond);
807
808 /* count the bytes and the amount of PDVs which were transmitted */
809 bytesTransmitted += OFstatic_cast(Uint32, rtnLength);
810 pdvCount += pdvList.count;
811
812 /* execute callback function to indicate progress */
813 if (callback) {
814 callback(callbackContext, bytesTransmitted);
815 }
816 }
817 }
818
819 /* indicate the end of the transfer */
820 obj->transferEnd();
821
822 return EC_Normal;
823 }
824
825 /*
826 ** Public Functions Bodies
827 */
828
829 /*
830 * DIMSE Messaging
831 */
832
833 /*
834 * Message Send
835 */
836
837 static OFCondition
DIMSE_sendMessage(T_ASC_Association * assoc,T_ASC_PresentationContextID presID,T_DIMSE_Message * msg,DcmDataset * statusDetail,DcmDataset * dataObject,const char * dataFileName,DIMSE_ProgressCallback callback,void * callbackContext,DcmDataset ** commandSet)838 DIMSE_sendMessage(
839 T_ASC_Association *assoc,
840 T_ASC_PresentationContextID presID,
841 T_DIMSE_Message *msg,
842 DcmDataset *statusDetail,
843 DcmDataset *dataObject,
844 const char *dataFileName,
845 DIMSE_ProgressCallback callback,
846 void *callbackContext,
847 DcmDataset **commandSet)
848 /*
849 * This function sends a DIMSE command and possibly also instance data from a file or from a given
850 * data object via network to another DICOM application.
851 *
852 * Parameters:
853 * assoc - [in] The association (network connection to another DICOM application).
854 * presId - [in] The ID of the presentation context which shall be used
855 * msg - [in] Structure that represents a certain DIMSE command which shall be sent.
856 * statusDetail - [in] Detailed information with regard to the status information which is captured
857 * in the status element (0000,0900). Note that the value for element (0000,0900)
858 * is contained in this variable.
859 * dataObject - [in] The instance data which shall be sent to the other DICOM application,
860 * NULL, if there is none
861 * dataFileName - [in] The name of the file that contains the instance data which shall be sent to
862 * the other DICOM application, NULL; if there is none.
863 * callback - [in] Pointer to a function which shall be called to indicate progress.
864 * callbackContext - []
865 * commandSet - [out] [optional parameter, default = NULL] If this parameter is not NULL
866 * it will return a copy of the DIMSE command which is sent to the other
867 * DICOM application.
868 */
869 {
870 E_TransferSyntax xferSyntax;
871 DcmDataset *cmdObj = NULL;
872 DcmFileFormat dcmff;
873 int fromFile = 0;
874 OFCondition cond = EC_Normal;
875
876 if (commandSet) *commandSet = NULL;
877
878 /* check if the data dictionary is available. If not return an error */
879 if (!isDataDictPresent()) return DIMSE_NODATADICT;
880
881 /* validate DIMSE command information, i.e. check if the information which is */
882 /* contained in msg meets certain conditions. For example, if msg represents a */
883 /* C-ECHO-RQ, then there is not supposed to be a corresponding data set. If the */
884 /* specified conditions are not met, return an error. */
885 if (EC_Normal != (cond = validateMessage(assoc, msg))) return cond;
886
887 /* check if the presentation context id refers to a valid presentation context and determine the */
888 /* transfer syntax which is specified for this presentation context. Additionally, check if the specified */
889 /* transfer syntax is supported at all. If any of the checks returns an error, return this error. */
890 if (EC_Normal != (cond = checkPresentationContextForMessage(assoc, msg, presID, &xferSyntax))) return cond;
891
892 /* create a DcmDataset object ("command object") based on the information in the DIMSE command */
893 /* variable (remember that all DICOM commands are - in the end - particular data sets). The */
894 /* information which will shortly be set in this object will be sent over the network later. */
895 cond = DIMSE_buildCmdObject(msg, &cmdObj);
896
897 /* if the command object has been created successfully and there is status detail */
898 /* information move the status detail information to the command object. */
899 if (cond.good() && statusDetail != NULL)
900 {
901 /* move the status detail to the command */
902 DcmElement* e;
903 while ((e = statusDetail->remove((unsigned long)0)) != NULL) cmdObj->insert(e, OFTrue);
904 }
905
906 /* if the command object has been created successfully and the data set is present */
907 /* according to the information in the DIMSE command information variable, do something */
908 if (cond.good() && DIMSE_isDataSetPresent(msg))
909 {
910 /* if a data object and a file name were passed, something is fishy */
911 if ((dataObject != NULL)&&(dataFileName != NULL))
912 {
913 DCMNET_WARN(DIMSE_warn_str(assoc) << "sendData: both object and file specified (sending object only)");
914 }
915 /* if there is no data object but a file name, we need to read data from the specified file */
916 /* to create a data object with the actual instance data that shall be sent */
917 else if ((dataObject == NULL)&&(dataFileName != NULL))
918 {
919 if (! dcmff.loadFile(dataFileName, EXS_Unknown).good())
920 {
921 DCMNET_WARN(DIMSE_warn_str(assoc) << "sendMessage: cannot open DICOM file ("
922 << dataFileName << "): " << OFStandard::getLastSystemErrorCode().message());
923 cond = DIMSE_SENDFAILED;
924 } else {
925 dataObject = dcmff.getDataset();
926 fromFile = 1;
927 }
928 }
929
930 /* if we have a data object now, check if we can write the data object's elements in the required */
931 /* transfer syntax. In detail, every single item of the data object will be checked. */
932 if (dataObject)
933 {
934 if (dataObject->isEmpty())
935 {
936 /* if dataset is empty, i.e. it contains no data elements, create a warning. */
937 DCMNET_WARN(DIMSE_warn_str(assoc) << "sendMessage: dataset is empty");
938 cond = DIMSE_SENDFAILED;
939 }
940 else if (! dataObject->canWriteXfer(xferSyntax))
941 {
942 /* if we cannot write all elements in the required transfer syntax, create a warning. */
943 DcmXfer writeXferSyntax(xferSyntax);
944 DcmXfer originalXferSyntax(dataObject->getOriginalXfer());
945 if (fromFile && dataFileName)
946 {
947 DCMNET_WARN(DIMSE_warn_str(assoc) << "sendMessage: unable to convert DICOM file '"
948 << dataFileName << "' from '" << originalXferSyntax.getXferName()
949 << "' transfer syntax to '" << writeXferSyntax.getXferName() << "'");
950 } else {
951 DCMNET_WARN(DIMSE_warn_str(assoc) << "sendMessage: unable to convert dataset from '"
952 << originalXferSyntax.getXferName() << "' transfer syntax to '"
953 << writeXferSyntax.getXferName() << "'");
954 }
955 cond = DIMSE_SENDFAILED;
956 }
957 } else {
958 /* if there is neither a data object nor a file name, create a warning, since */
959 /* the information in msg specified that instance data should be present. */
960 DCMNET_WARN(DIMSE_warn_str(assoc) << "sendMessage: no dataset to send");
961 cond = DIMSE_SENDFAILED;
962 }
963 }
964
965 /* if all previous calls were successful, go ahead and send the */
966 /* specified DIMSE command to the other DICOM application */
967 if (cond.good())
968 {
969 /* if the global variable says so, we want to save the */
970 /* DIMSE command's information to a file */
971 if (g_dimse_save_dimse_data) saveDimseFragment(cmdObj, OFTrue, OFFalse);
972
973 /* return a copy of the DIMSE command if required */
974 if (commandSet) *commandSet = new DcmDataset(*cmdObj);
975
976 /* dump information if required */
977 DCMNET_TRACE("DIMSE Command to be sent on Presentation Context ID: " << OFstatic_cast(Uint16, presID));
978 DCMNET_TRACE("DIMSE Command to send:" << OFendl << DcmObject::PrintHelper(*cmdObj));
979
980 /* Send the DIMSE command. DIMSE commands are always little endian implicit. */
981 cond = sendDcmDataset(assoc, cmdObj, presID, EXS_LittleEndianImplicit, DUL_COMMANDPDV, NULL, NULL);
982 }
983
984 /* Then we still have to send the actual instance data if the DIMSE command information variable */
985 /* says that instance data is present and there actually is a corresponding data object */
986 if (cond.good() && DIMSE_isDataSetPresent(msg) && (dataObject))
987 {
988 /* again, if the global variable says so, we want to save the instance data to a file */
989 if (g_dimse_save_dimse_data) saveDimseFragment(dataObject, OFFalse, OFFalse);
990
991 /* Send the instance data set using the corresponding transfer syntax */
992 cond = sendDcmDataset(assoc, dataObject, presID, xferSyntax,
993 DUL_DATASETPDV, callback, callbackContext);
994 }
995
996 /* clean up some memory */
997 delete cmdObj;
998
999 /* return result value */
1000 return cond;
1001 }
1002
1003 OFCondition
DIMSE_sendMessageUsingFileData(T_ASC_Association * assoc,T_ASC_PresentationContextID presID,T_DIMSE_Message * msg,DcmDataset * statusDetail,const char * dataFileName,DIMSE_ProgressCallback callback,void * callbackContext,DcmDataset ** commandSet)1004 DIMSE_sendMessageUsingFileData(
1005 T_ASC_Association *assoc,
1006 T_ASC_PresentationContextID presID,
1007 T_DIMSE_Message *msg,
1008 DcmDataset *statusDetail,
1009 const char *dataFileName,
1010 DIMSE_ProgressCallback callback,
1011 void *callbackContext,
1012 DcmDataset **commandSet)
1013 {
1014 /* simply call DIMSE_sendMessage to accomplish this task */
1015 return DIMSE_sendMessage(assoc, presID, msg, statusDetail, NULL, dataFileName, callback, callbackContext, commandSet);
1016 }
1017
1018 OFCondition
DIMSE_sendMessageUsingMemoryData(T_ASC_Association * assoc,T_ASC_PresentationContextID presID,T_DIMSE_Message * msg,DcmDataset * statusDetail,DcmDataset * dataObject,DIMSE_ProgressCallback callback,void * callbackContext,DcmDataset ** commandSet)1019 DIMSE_sendMessageUsingMemoryData(
1020 T_ASC_Association *assoc,
1021 T_ASC_PresentationContextID presID,
1022 T_DIMSE_Message *msg,
1023 DcmDataset *statusDetail,
1024 DcmDataset *dataObject,
1025 DIMSE_ProgressCallback callback,
1026 void *callbackContext,
1027 DcmDataset **commandSet)
1028 {
1029 /* simply call DIMSE_sendMessage to accomplish this task */
1030 return DIMSE_sendMessage(assoc, presID, msg, statusDetail, dataObject, NULL, callback, callbackContext, commandSet);
1031 }
1032
1033 /*
1034 * Message Receive
1035 */
1036
DIMSE_ignoreDataSet(T_ASC_Association * assoc,T_DIMSE_BlockingMode blocking,int timeout,DIC_UL * bytesRead,DIC_UL * pdvCount)1037 OFCondition DIMSE_ignoreDataSet(
1038 T_ASC_Association *assoc,
1039 T_DIMSE_BlockingMode blocking,
1040 int timeout,
1041 DIC_UL *bytesRead,
1042 DIC_UL *pdvCount)
1043 {
1044 OFCondition cond = EC_Normal;
1045 DUL_PDV pdv;
1046 OFBool last = OFFalse;
1047
1048 while (!last) {
1049 cond = DIMSE_readNextPDV(assoc, blocking, timeout, &pdv);
1050 if (cond.bad()) {
1051 break;
1052 }
1053 if (pdv.pdvType != DUL_DATASETPDV) {
1054 cond = DIMSE_UNEXPECTEDPDVTYPE;
1055 break;
1056 }
1057 *bytesRead += pdv.fragmentLength;
1058 (*pdvCount)++;
1059 last = pdv.lastPDV;
1060 }
1061 return cond;
1062 }
1063
1064
1065 OFCondition
DIMSE_receiveCommand(T_ASC_Association * assoc,T_DIMSE_BlockingMode blocking,int timeout,T_ASC_PresentationContextID * presID,T_DIMSE_Message * msg,DcmDataset ** statusDetail,DcmDataset ** commandSet)1066 DIMSE_receiveCommand(
1067 T_ASC_Association *assoc,
1068 T_DIMSE_BlockingMode blocking,
1069 int timeout,
1070 T_ASC_PresentationContextID*presID,
1071 T_DIMSE_Message *msg,
1072 DcmDataset **statusDetail,
1073 DcmDataset **commandSet)
1074 {
1075 OFCondition cond = EC_Normal;
1076 unsigned long bytesRead;
1077 unsigned long pdvCount;
1078
1079 DUL_DATAPDV type;
1080 OFBool last;
1081 DUL_PDV pdv;
1082
1083 DIC_UL elemsLeft;
1084 T_ASC_PresentationContextID pid = 0;
1085 E_TransferSyntax xferSyntax;
1086 DcmDataset *cmdSet;
1087 OFCondition econd = EC_Normal;
1088
1089 if (statusDetail) *statusDetail = NULL;
1090 if (commandSet) *commandSet = NULL;
1091
1092 /* dump some information if required */
1093 DCMNET_TRACE("DIMSE receiveCommand");
1094
1095 /* check if the data dictionary is available. If not return an error */
1096 if (!isDataDictPresent()) return DIMSE_NODATADICT;
1097
1098 /* set PDV counter to 0 */
1099 pdvCount = 0;
1100
1101 /* create a new DcmDataset variable to capture the DIMSE command which we are about to receive */
1102 cmdSet = new DcmDataset();
1103 if (cmdSet == NULL) return EC_MemoryExhausted;
1104
1105 /* prepare the DcmDataset variable for transfer of data */
1106 cmdSet->transferInit();
1107
1108 /* create a buffer variable which can be used to store the received information */
1109 DcmInputBufferStream cmdBuf;
1110 if (! cmdBuf.good())
1111 {
1112 delete cmdSet;
1113 return makeDcmnetCondition(DIMSEC_PARSEFAILED, OF_error, "DIMSE: receiveCommand: Failed to initialize cmdBuf");
1114 }
1115
1116 /* start a loop in which we want to read a DIMSE command from the incoming socket stream. */
1117 /* Since the command could stretch over more than one PDU, the use of a loop is mandatory. */
1118 for (last = OFFalse, bytesRead = 0, type = DUL_COMMANDPDV;
1119 type == DUL_COMMANDPDV && !last;)
1120 {
1121 /* make the stream remember any unread bytes */
1122 cmdBuf.releaseBuffer();
1123
1124 /* get next PDV (in detail, in order to get this PDV, a */
1125 /* PDU has to be read from the incoming socket stream) */
1126 cond = DIMSE_readNextPDV(assoc, blocking, timeout, &pdv);
1127 if (cond.bad() || (cond == DUL_PEERREQUESTEDRELEASE))
1128 {
1129 delete cmdSet;
1130 if (cond == DIMSE_READPDVFAILED)
1131 return makeDcmnetSubCondition(DIMSEC_RECEIVEFAILED, OF_error, "DIMSE Failed to receive message", cond);
1132 else return cond; /* it was an abort or release request */
1133 }
1134
1135 /* if this is the first loop iteration, get the presentation context ID which is captured in the */
1136 /* current PDV. If this is not the first loop iteration, check if the presentation context IDs in */
1137 /* the current PDV and in the last PDV are identical. If they are not, return an error. */
1138 if (pdvCount == 0)
1139 {
1140 pid = pdv.presentationContextID;
1141 }
1142 else if (pdv.presentationContextID != pid)
1143 {
1144 delete cmdSet;
1145 char buf1[256];
1146 sprintf(buf1, "DIMSE: Different PresIDs inside Command Set: %d != %d", pid, pdv.presentationContextID);
1147 OFCondition subCond = makeDcmnetCondition(DIMSEC_INVALIDPRESENTATIONCONTEXTID, OF_error, buf1);
1148 return makeDcmnetSubCondition(DIMSEC_RECEIVEFAILED, OF_error, "DIMSE Failed to receive message", subCond);
1149 }
1150
1151 /* check if the fragment length of the current PDV is odd. This should */
1152 /* never happen (see DICOM standard (year 2000) part 7, annex F) (or */
1153 /* the corresponding section in a later version of the standard.) */
1154 if ((pdv.fragmentLength % 2) != 0)
1155 {
1156 /* This should NEVER happen. See Part 7, Annex F. */
1157 char buf2[256];
1158 sprintf(buf2, "DIMSE: Odd Fragment Length: %lu", pdv.fragmentLength);
1159 cond = makeDcmnetCondition(DIMSEC_RECEIVEFAILED, OF_error, buf2);
1160 break;
1161 }
1162
1163 /* if information is contained the PDVs fragment, we want to insert this information into the buffer */
1164 if (pdv.fragmentLength > 0) {
1165 cmdBuf.setBuffer(pdv.data, pdv.fragmentLength);
1166 }
1167
1168 /* if this fragment contains the last fragment of the DIMSE command, set the end of the stream */
1169 if (pdv.lastPDV) {
1170 cmdBuf.setEos();
1171 }
1172
1173 /* insert the information which is contained in the buffer into the DcmDataset */
1174 /* variable. Mind that DIMSE commands are always specified in the little endian */
1175 /* implicit transfer syntax. Additionally, we want to remove group length tags. */
1176 econd = cmdSet->read(cmdBuf, EXS_LittleEndianImplicit, EGL_withoutGL);
1177 if (econd != EC_Normal && econd != EC_StreamNotifyClient)
1178 {
1179 delete cmdSet;
1180 return makeDcmnetSubCondition(DIMSEC_RECEIVEFAILED, OF_error, "DIMSE: receiveCommand: cmdSet->read() Failed", econd);
1181 }
1182
1183 /* update the counter that counts how many bytes were read from the incoming socket */
1184 /* stream. This variable will only be used for dumping general information. */
1185 bytesRead += pdv.fragmentLength;
1186
1187 /* update the following variables which will be evaluated at the beginning of each loop iteration. */
1188 last = pdv.lastPDV;
1189 type = pdv.pdvType;
1190
1191 /* update the counter that counts how many PDVs were received on the incoming */
1192 /* socket stream. This variable will be used for determining the first */
1193 /* loop iteration and dumping general information. */
1194 pdvCount++;
1195 }
1196
1197 /* indicate the end of the transfer */
1198 cmdSet->transferEnd();
1199
1200 /* dump information if required */
1201 DCMNET_TRACE("DIMSE receiveCommand: " << pdvCount << " PDVs (" << bytesRead << " bytes), PresID=" << (int) pid);
1202
1203 /* check if this is a valid presentation context */
1204 cond = getTransferSyntax(assoc, pid, &xferSyntax);
1205 if (cond.bad())
1206 {
1207 delete cmdSet;
1208 return cond;
1209 }
1210
1211 /* check if the PDVs which were read actually do belong */
1212 /* to a DIMSE command. If not, return an error. */
1213 if (type != DUL_COMMANDPDV)
1214 {
1215 delete cmdSet;
1216 OFCondition subCond = makeDcmnetCondition(DIMSEC_UNEXPECTEDPDVTYPE, OF_error, "DIMSE: Command PDV Expected");
1217 return makeDcmnetSubCondition(DIMSEC_RECEIVEFAILED, OF_error, "DIMSE Failed to receive message", subCond);
1218 }
1219
1220 /* if the global variable says so, we want to save the */
1221 /* DIMSE command's information to a file */
1222 if (g_dimse_save_dimse_data) saveDimseFragment(cmdSet, OFTrue, OFTrue);
1223
1224 /* return a copy of the DIMSE command if required */
1225 if (commandSet) *commandSet = new DcmDataset(*cmdSet);
1226
1227 /* dump some more information if required */
1228 DCMNET_TRACE("DIMSE Command Received:" << OFendl << DcmObject::PrintHelper(*cmdSet));
1229
1230 /* parse the information in cmdSet and create a corresponding T_DIMSE_Message */
1231 /* structure which represents the the DIMSE message which was received */
1232 cond = DIMSE_parseCmdObject(msg, cmdSet);
1233
1234 /* if the T_DIMSE_Message structure was created successfully, validate the message, i.e. */
1235 /* check if the information which is contained in msg meets certain conditions */
1236 if (cond == EC_Normal) {
1237 cond = validateMessage(assoc, msg);
1238 }
1239
1240 /* Whatever is left in the cmdSet object should be status detail */
1241 /* information. Return this information to the caller if required. */
1242 if (cond == EC_Normal) {
1243 /* Count the elements that are still existent in cmdSet */
1244 elemsLeft = DIMSE_countElements(cmdSet);
1245
1246 /* if the caller requires status Detail information and there is some */
1247 if ((statusDetail != NULL) && (elemsLeft > 0)) {
1248 /* return it to the caller in the reference parameter */
1249 *statusDetail = cmdSet;
1250 } else {
1251 /* either nothing is left or the caller does not want status */
1252 /* detail information; hence, delete the cmdSet object */
1253 delete cmdSet;
1254 }
1255 }
1256 /* if some error occurred before, delete cmdSet */
1257 else
1258 delete cmdSet;
1259
1260 /* set the Presentation Context ID we received (out parameter) */
1261 *presID = pid;
1262
1263 /* return result value */
1264 return cond;
1265 }
1266
1267
DIMSE_createFilestream(const OFFilename & filename,const T_DIMSE_C_StoreRQ * request,const T_ASC_Association * assoc,T_ASC_PresentationContextID presIdCmd,int writeMetaheader,DcmOutputFileStream ** filestream)1268 OFCondition DIMSE_createFilestream(
1269 const OFFilename &filename,
1270 const T_DIMSE_C_StoreRQ *request,
1271 const T_ASC_Association *assoc,
1272 T_ASC_PresentationContextID presIdCmd,
1273 int writeMetaheader,
1274 DcmOutputFileStream **filestream)
1275 {
1276 OFCondition cond = EC_Normal;
1277 DcmElement *elem=NULL;
1278 DcmMetaInfo *metainfo=NULL;
1279 DcmTag metaElementGroupLength(DCM_FileMetaInformationGroupLength);
1280 DcmTag fileMetaInformationVersion(DCM_FileMetaInformationVersion);
1281 DcmTag mediaStorageSOPClassUID(DCM_MediaStorageSOPClassUID);
1282 DcmTag mediaStorageSOPInstanceUID(DCM_MediaStorageSOPInstanceUID);
1283 DcmTag transferSyntaxUID(DCM_TransferSyntaxUID);
1284 DcmTag implementationClassUID(DCM_ImplementationClassUID);
1285 DcmTag implementationVersionName(DCM_ImplementationVersionName);
1286 DcmTag sourceApplicationEntityTitle(DCM_SourceApplicationEntityTitle);
1287 T_ASC_PresentationContext presentationContext;
1288
1289 if (filename.isEmpty() || (request==NULL) || (assoc==NULL) ||
1290 (assoc->params==NULL) || (filestream==NULL))
1291 {
1292 return DIMSE_NULLKEY;
1293 }
1294
1295 cond = ASC_findAcceptedPresentationContext(assoc->params, presIdCmd, &presentationContext);
1296 if (cond.bad()) return cond;
1297
1298 if (writeMetaheader)
1299 {
1300 if (NULL == (metainfo = new DcmMetaInfo())) return EC_MemoryExhausted;
1301 if (NULL != (elem = new DcmUnsignedLong(metaElementGroupLength)))
1302 {
1303 metainfo->insert(elem, OFTrue);
1304 Uint32 temp = 0;
1305 ((DcmUnsignedLong*)elem)->putUint32Array(&temp, 1);
1306 } else cond = EC_MemoryExhausted;
1307 if (NULL != (elem = new DcmOtherByteOtherWord(fileMetaInformationVersion)))
1308 {
1309 metainfo->insert(elem, OFTrue);
1310 Uint8 version[2] = {0,1};
1311 ((DcmOtherByteOtherWord*)elem)->putUint8Array(version, 2);
1312 } else cond = EC_MemoryExhausted;
1313 if (NULL != (elem = new DcmUniqueIdentifier(mediaStorageSOPClassUID)))
1314 {
1315 metainfo->insert(elem, OFTrue);
1316 ((DcmUniqueIdentifier*)elem)->putString(request->AffectedSOPClassUID);
1317 } else cond = EC_MemoryExhausted;
1318 if (NULL != (elem = new DcmUniqueIdentifier(mediaStorageSOPInstanceUID)))
1319 {
1320 metainfo->insert(elem, OFTrue);
1321 ((DcmUniqueIdentifier*)elem)->putString(request->AffectedSOPInstanceUID);
1322 } else cond = EC_MemoryExhausted;
1323 if (NULL != (elem = new DcmUniqueIdentifier(transferSyntaxUID)))
1324 {
1325 metainfo->insert(elem, OFTrue);
1326 elem->putString(presentationContext.acceptedTransferSyntax);
1327 } else cond = EC_MemoryExhausted;
1328 if (NULL != (elem = new DcmUniqueIdentifier(implementationClassUID)))
1329 {
1330 metainfo->insert(elem, OFTrue);
1331 const char *uid = OFFIS_IMPLEMENTATION_CLASS_UID;
1332 ((DcmUniqueIdentifier*)elem)->putString(uid);
1333 } else cond = EC_MemoryExhausted;
1334 if (NULL != (elem = new DcmShortString(implementationVersionName)))
1335 {
1336 metainfo->insert(elem, OFTrue);
1337 const char *version = OFFIS_DTK_IMPLEMENTATION_VERSION_NAME2;
1338 ((DcmShortString*)elem)->putString(version);
1339
1340 if (strlen(OFFIS_DTK_IMPLEMENTATION_VERSION_NAME2) > 16)
1341 {
1342 DCMNET_WARN("DICOM implementation version name too long: " << OFFIS_DTK_IMPLEMENTATION_VERSION_NAME2);
1343 }
1344 } else cond = EC_MemoryExhausted;
1345 if (NULL != (elem = new DcmApplicationEntity(sourceApplicationEntityTitle)))
1346 {
1347 metainfo->insert(elem, OFTrue);
1348 const char *aet = assoc->params->DULparams.callingAPTitle;
1349 if (aet) ((DcmApplicationEntity*)elem)->putString(aet);
1350 } else cond = EC_MemoryExhausted;
1351
1352 if (cond == EC_MemoryExhausted)
1353 {
1354 delete metainfo;
1355 return cond;
1356 }
1357
1358 cond = metainfo->computeGroupLengthAndPadding(EGL_withGL, EPD_noChange,
1359 META_HEADER_DEFAULT_TRANSFERSYNTAX, EET_UndefinedLength);
1360 if (cond.bad())
1361 {
1362 delete metainfo;
1363 return cond;
1364 }
1365 }
1366
1367 *filestream = new DcmOutputFileStream(filename);
1368 if ((*filestream == NULL)||(! (*filestream)->good()))
1369 {
1370 if (metainfo) delete metainfo;
1371 if (*filestream)
1372 {
1373 delete *filestream;
1374 *filestream = NULL;
1375 }
1376 OFOStringStream stream;
1377 stream << "DIMSE createFilestream: cannot create file '" << filename << "'" << OFStringStream_ends;
1378 OFSTRINGSTREAM_GETOFSTRING(stream, msg)
1379 return makeDcmnetCondition(DIMSEC_OUTOFRESOURCES, OF_error, msg.c_str());
1380 }
1381
1382 if (metainfo)
1383 {
1384 metainfo->transferInit();
1385 if (EC_Normal != metainfo->write(**filestream, META_HEADER_DEFAULT_TRANSFERSYNTAX, EET_ExplicitLength, NULL))
1386 {
1387 OFOStringStream stream;
1388 stream << "DIMSE createFilestream: cannot write metaheader to file '" << filename << "'" << OFStringStream_ends;
1389 OFSTRINGSTREAM_GETOFSTRING(stream, msg)
1390 cond = makeDcmnetCondition(DIMSEC_OUTOFRESOURCES, OF_error, msg.c_str());
1391 }
1392 metainfo->transferEnd();
1393 delete metainfo;
1394 }
1395
1396 return cond;
1397 }
1398
1399
1400 OFCondition
DIMSE_receiveDataSetInFile(T_ASC_Association * assoc,T_DIMSE_BlockingMode blocking,int timeout,T_ASC_PresentationContextID * presID,DcmOutputStream * filestream,DIMSE_ProgressCallback callback,void * callbackData)1401 DIMSE_receiveDataSetInFile(
1402 T_ASC_Association *assoc,
1403 T_DIMSE_BlockingMode blocking,
1404 int timeout,
1405 T_ASC_PresentationContextID *presID,
1406 DcmOutputStream *filestream,
1407 DIMSE_ProgressCallback callback,
1408 void *callbackData)
1409 {
1410 OFCondition cond = EC_Normal;
1411 DUL_PDV pdv;
1412 T_ASC_PresentationContextID pid = 0;
1413 E_TransferSyntax xferSyntax;
1414 OFBool last = OFFalse;
1415 DIC_UL pdvCount = 0;
1416 DIC_UL bytesRead = 0;
1417
1418 if ((assoc == NULL) || (presID==NULL) || (filestream==NULL)) return DIMSE_NULLKEY;
1419
1420 *presID = 0; /* invalid value */
1421 offile_off_t written = 0;
1422 while (!last)
1423 {
1424 cond = DIMSE_readNextPDV(assoc, blocking, timeout, &pdv);
1425 if (cond != EC_Normal) last = OFTrue; // terminate loop
1426
1427 if (!last)
1428 {
1429 if (pdv.pdvType != DUL_DATASETPDV)
1430 {
1431 cond = DIMSE_UNEXPECTEDPDVTYPE;
1432 last = OFTrue;
1433 }
1434 }
1435
1436 if (!last)
1437 {
1438 if (pdvCount == 0)
1439 {
1440 pid = pdv.presentationContextID;
1441 /* is this a valid presentation context ? */
1442 cond = getTransferSyntax(assoc, pid, &xferSyntax);
1443 if (cond.bad()) last = OFTrue; // terminate loop
1444 }
1445 else if (pdv.presentationContextID != pid)
1446 {
1447 char buf1[256];
1448 sprintf(buf1, "DIMSE: Different PresIDs inside data set: %d != %d", pid, pdv.presentationContextID);
1449 OFCondition subCond = makeDcmnetCondition(DIMSEC_INVALIDPRESENTATIONCONTEXTID, OF_error, buf1);
1450 cond = makeDcmnetSubCondition(DIMSEC_RECEIVEFAILED, OF_error, "DIMSE Failed to receive message", subCond);
1451 last = OFTrue; // terminate loop
1452 }
1453 }
1454
1455 if (!last)
1456 {
1457 if ((pdv.fragmentLength % 2) != 0)
1458 {
1459 /* This should NEVER happen. See Part 7, Annex F. */
1460 char buf2[256];
1461 sprintf(buf2, "DIMSE: Odd Fragment Length: %lu", pdv.fragmentLength);
1462 cond = makeDcmnetCondition(DIMSEC_RECEIVEFAILED, OF_error, buf2);
1463 last = OFTrue; // terminate loop
1464 }
1465 }
1466
1467 if (!last)
1468 {
1469 written = filestream->write((void *)(pdv.data), (Uint32)(pdv.fragmentLength));
1470 if ((! filestream->good()) || (written != (Uint32)(pdv.fragmentLength)))
1471 {
1472 cond = DIMSE_ignoreDataSet(assoc, blocking, timeout, &bytesRead, &pdvCount);
1473 if (cond == EC_Normal)
1474 {
1475 cond = makeDcmnetCondition(DIMSEC_OUTOFRESOURCES, OF_error, "DIMSE receiveDataSetInFile: Cannot write to file");
1476 }
1477 last = OFTrue; // terminate loop
1478 }
1479 }
1480
1481 if (!last)
1482 {
1483 bytesRead += pdv.fragmentLength;
1484 pdvCount++;
1485 last = pdv.lastPDV;
1486
1487 DCMNET_TRACE("DIMSE receiveDataSetInFile: " << pdv.fragmentLength
1488 << " bytes read (last: " << ((last)?("YES"):("NO")) << ")");
1489
1490 if (callback)
1491 { /* execute callback function */
1492 callback(callbackData, bytesRead);
1493 }
1494 }
1495 }
1496
1497 /* set the Presentation Context ID we received */
1498 *presID = pid;
1499 return cond;
1500 }
1501
1502
1503 OFCondition
DIMSE_receiveDataSetInMemory(T_ASC_Association * assoc,T_DIMSE_BlockingMode blocking,int timeout,T_ASC_PresentationContextID * presID,DcmDataset ** dataObject,DIMSE_ProgressCallback callback,void * callbackData)1504 DIMSE_receiveDataSetInMemory(
1505 T_ASC_Association *assoc,
1506 T_DIMSE_BlockingMode blocking,
1507 int timeout,
1508 T_ASC_PresentationContextID *presID,
1509 DcmDataset **dataObject,
1510 DIMSE_ProgressCallback callback,
1511 void *callbackData)
1512 {
1513 OFCondition cond = EC_Normal;
1514 OFCondition econd = EC_Normal;
1515 DcmDataset *dset = NULL;
1516 DUL_PDV pdv;
1517 T_ASC_PresentationContextID pid = 0;
1518 E_TransferSyntax xferSyntax;
1519 OFBool last = OFFalse;
1520 DIC_UL pdvCount = 0;
1521 DIC_UL bytesRead = 0;
1522
1523 /* check if the caller provided an address where the data set can be stored. If not return an error */
1524 if (dataObject == NULL) return DIMSE_NULLKEY;
1525
1526 /* check if the data dictionary is available. If not return an error */
1527 if (!isDataDictPresent()) return DIMSE_NODATADICT;
1528
1529 /* if we need to create a DcmDataset object at the given address, do so */
1530 if (*dataObject == NULL) {
1531 dset = new DcmDataset();
1532 } else {
1533 /* if not, use the one which was passed */
1534 dset = *dataObject;
1535 }
1536
1537 /* check if there is still no DcmDataset object which can be used to store the data set. */
1538 if (dset == NULL)
1539 {
1540 /* if this is the case, just go ahead an receive data, but do not store it anywhere */
1541 cond = DIMSE_ignoreDataSet(assoc, blocking, timeout, &bytesRead, &pdvCount);
1542
1543 /* if receiving was successful, let the caller know though that no DcmDataset variable could be created */
1544 if (cond == EC_Normal)
1545 {
1546 return makeDcmnetCondition(DIMSEC_OUTOFRESOURCES, OF_error, "DIMSE receiveDataSetInMemory: Cannot create DcmDataset");
1547 }
1548
1549 /* if we get to here, receiving was not successful; there must have */
1550 /* been a network problem. Just pass the result on to the caller. */
1551 return cond;
1552 }
1553
1554 /* create a buffer variable which can be used to store the received information */
1555 DcmInputBufferStream dataBuf;
1556
1557 /* prepare the DcmDataset variable for transfer of data */
1558 dset->transferInit();
1559
1560 /* start a loop in which we want to read the data set from the incoming socket stream. */
1561 /* Since the data set could stretch over more than one PDU, the use of a loop is mandatory. */
1562 while (!last && cond == EC_Normal)
1563 {
1564
1565 /* make the stream remember any unread bytes */
1566 dataBuf.releaseBuffer();
1567
1568 /* get next PDV (in detail, in order to get this PDV, a */
1569 /* PDU has to be read from the incoming socket stream) */
1570 cond = DIMSE_readNextPDV(assoc, blocking, timeout, &pdv);
1571
1572 /* if some error occurred, end the loop */
1573 if (cond != EC_Normal) last = OFTrue;
1574
1575 /* we are expecting to see a data set PDV; if the PDV which was received does not refer */
1576 /* to a data set, set the error indicating variable correspondingly and end the loop. */
1577 if (!last)
1578 {
1579 if (pdv.pdvType != DUL_DATASETPDV)
1580 {
1581 cond = DIMSE_UNEXPECTEDPDVTYPE;
1582 last = OFTrue;
1583 }
1584 }
1585
1586 /* if this is the first loop iteration, get the presentation context ID which is captured */
1587 /* in the current PDV and determine what transfer syntax the presentation context ID refers */
1588 /* to (this is also a check concerning the question if the presentation context ID is valid). */
1589 /* If this is not the first loop iteration, check if the presentation context IDs in the */
1590 /* current PDV and in the last PDV are identical. If they are not, return an error. */
1591 if (!last)
1592 {
1593 if (pdvCount == 0)
1594 {
1595 pid = pdv.presentationContextID;
1596
1597 cond = getTransferSyntax(assoc, pid, &xferSyntax);
1598 if (cond.bad()) last = OFTrue;
1599 }
1600 else if (pdv.presentationContextID != pid)
1601 {
1602 char buf1[256];
1603 sprintf(buf1, "DIMSE: Different PresIDs inside data set: %d != %d", pid, pdv.presentationContextID);
1604 OFCondition subCond = makeDcmnetCondition(DIMSEC_INVALIDPRESENTATIONCONTEXTID, OF_error, buf1);
1605 cond = makeDcmnetSubCondition(DIMSEC_RECEIVEFAILED, OF_error, "DIMSE Failed to receive message", subCond);
1606 last = OFTrue;
1607 }
1608 }
1609
1610 if (!last)
1611 {
1612 /* check if the fragment length of the current PDV is odd. This should */
1613 /* never happen (see DICOM standard (year 2000) part 7, annex F) (or */
1614 /* the corresponding section in a later version of the standard.) */
1615 if ((pdv.fragmentLength % 2) != 0)
1616 {
1617 char buf2[256];
1618 sprintf(buf2, "DIMSE: Odd Fragment Length: %lu", pdv.fragmentLength);
1619 cond = makeDcmnetCondition(DIMSEC_RECEIVEFAILED, OF_error, buf2);
1620 last = OFTrue;
1621 }
1622 }
1623
1624 if (!last)
1625 {
1626 /* if information is contained the PDVs fragment, we want to insert this information into the buffer */
1627 if (pdv.fragmentLength > 0)
1628 {
1629 dataBuf.setBuffer(pdv.data, pdv.fragmentLength);
1630 }
1631
1632 /* if this fragment contains the last fragment of the data set, set the end of the stream */
1633 if (pdv.lastPDV)
1634 {
1635 dataBuf.setEos();
1636 }
1637
1638 /* insert the information which is contained in the buffer into the DcmDataset variable. Mind the */
1639 /* transfer syntax which was specified through the presentation context ID of the first PDV. */
1640 econd = dset->read(dataBuf, xferSyntax);
1641 if (econd != EC_Normal && econd != EC_StreamNotifyClient)
1642 {
1643 DCMNET_WARN(DIMSE_warn_str(assoc) << "DIMSE receiveDataSetInMemory: dset->read() Failed ("
1644 << econd.text() << ")");
1645 cond = DIMSE_RECEIVEFAILED;
1646 last = OFTrue;
1647 }
1648 }
1649
1650 if (!last)
1651 {
1652 /* update the counter that counts how many bytes were read from the incoming socket */
1653 /* stream. This variable will only be used for dumping general information. */
1654 bytesRead += pdv.fragmentLength;
1655
1656 /* update the counter that counts how many PDVs were received on the incoming */
1657 /* socket stream. This variable will be used for determining the first */
1658 /* loop iteration and dumping general information. */
1659 pdvCount++;
1660
1661 /* update the variable which will be evaluated at the beginning of each loop iteration. */
1662 last = pdv.lastPDV;
1663
1664 /* dump information if required */
1665 DCMNET_TRACE("DIMSE receiveDataSetInMemory: " << pdv.fragmentLength
1666 << " bytes read (last: " << ((last)?("YES"):("NO")) << ")");
1667
1668 /* execute callback function after each received PDV */
1669 if (callback)
1670 {
1671 callback(callbackData, bytesRead);
1672 }
1673 }
1674 }
1675
1676 /* indicate the end of the transfer */
1677 dset->transferEnd();
1678
1679 /* in case an error occurred, return this error */
1680 if (cond.bad())
1681 {
1682 if (*dataObject == NULL)
1683 {
1684 delete dset;
1685 }
1686 return cond;
1687 }
1688
1689 /* if the global variable says so, we want to save the */
1690 /* DIMSE command's information to a file */
1691 if (g_dimse_save_dimse_data) saveDimseFragment(dset, OFFalse, OFTrue);
1692
1693 /* set the Presentation Context ID we received */
1694 *presID = pid;
1695
1696 /* set the object we received */
1697 *dataObject = dset;
1698
1699 /* return result value */
1700 return cond;
1701 }
1702
1703
1704 /*
1705 * Misc functions
1706 */
1707
DIMSE_warn_str(T_ASC_Association * assoc)1708 OFString DIMSE_warn_str(T_ASC_Association *assoc)
1709 {
1710 return OFString("DIMSE Warning: (") + assoc->params->DULparams.callingAPTitle
1711 + "," + assoc->params->DULparams.calledAPTitle + "): ";
1712 }
1713