1 /*
2 *
3 * Copyright (C) 1996-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: Andrew Hewett
17 *
18 * Purpose: Storage Service Class User (C-STORE operation)
19 *
20 */
21
22 #include "dcmtk/config/osconfig.h" /* make sure OS specific configuration is included first */
23
24 #define INCLUDE_CSTDLIB
25 #define INCLUDE_CSTDIO
26 #define INCLUDE_CSTRING
27 #define INCLUDE_CCTYPE
28 #include "dcmtk/ofstd/ofstdinc.h"
29
30 BEGIN_EXTERN_C
31 #ifdef HAVE_SYS_FILE_H
32 #include <sys/file.h>
33 #endif
34 END_EXTERN_C
35
36 #include "dcmtk/ofstd/ofstd.h"
37 #include "dcmtk/ofstd/ofconapp.h"
38 #include "dcmtk/ofstd/ofstring.h"
39 #include "dcmtk/ofstd/ofstream.h"
40 #include "dcmtk/dcmnet/dicom.h" /* for DICOM_APPLICATION_REQUESTOR */
41 #include "dcmtk/dcmnet/dimse.h"
42 #include "dcmtk/dcmnet/diutil.h"
43 #include "dcmtk/dcmnet/dcmtrans.h" /* for dcmSocketSend/ReceiveTimeout */
44 #include "dcmtk/dcmnet/dcasccfg.h" /* for class DcmAssociationConfiguration */
45 #include "dcmtk/dcmnet/dcasccff.h" /* for class DcmAssociationConfigurationFile */
46 #include "dcmtk/dcmdata/dcdatset.h"
47 #include "dcmtk/dcmdata/dcmetinf.h"
48 #include "dcmtk/dcmdata/dcfilefo.h"
49 #include "dcmtk/dcmdata/dcuid.h"
50 #include "dcmtk/dcmdata/dcdict.h"
51 #include "dcmtk/dcmdata/dcdeftag.h"
52 #include "dcmtk/dcmdata/cmdlnarg.h"
53 #include "dcmtk/dcmdata/dcuid.h" /* for dcmtk version name */
54 #include "dcmtk/dcmdata/dcostrmz.h" /* for dcmZlibCompressionLevel */
55 #include "dcmtk/dcmtls/tlsopt.h" /* for DcmTLSOptions */
56
57 #ifdef ON_THE_FLY_COMPRESSION
58 #include "dcmtk/dcmjpeg/djdecode.h" /* for JPEG decoders */
59 #include "dcmtk/dcmjpeg/djencode.h" /* for JPEG encoders */
60 #include "dcmtk/dcmjpls/djdecode.h" /* for JPEG-LS decoders */
61 #include "dcmtk/dcmjpls/djencode.h" /* for JPEG-LS encoders */
62 #include "dcmtk/dcmdata/dcrledrg.h" /* for RLE decoder */
63 #include "dcmtk/dcmdata/dcrleerg.h" /* for RLE encoder */
64 #include "dcmtk/dcmjpeg/dipijpeg.h" /* for dcmimage JPEG plugin */
65 #endif
66
67 #ifdef WITH_ZLIB
68 #include <zlib.h> /* for zlibVersion() */
69 #endif
70
71 #if defined (HAVE_WINDOWS_H) || defined(HAVE_FNMATCH_H)
72 #define PATTERN_MATCHING_AVAILABLE
73 #endif
74
75 #define OFFIS_CONSOLE_APPLICATION "storescu"
76
77 static OFLogger storescuLogger = OFLog::getLogger("dcmtk.apps." OFFIS_CONSOLE_APPLICATION);
78
79 static char rcsid[] = "$dcmtk: " OFFIS_CONSOLE_APPLICATION " v"
80 OFFIS_DCMTK_VERSION " " OFFIS_DCMTK_RELEASEDATE " $";
81
82 /* default application titles */
83 #define APPLICATIONTITLE "STORESCU"
84 #define PEERAPPLICATIONTITLE "ANY-SCP"
85
86 static OFBool opt_showPresentationContexts = OFFalse;
87 static OFBool opt_abortAssociation = OFFalse;
88 static OFCmdUnsignedInt opt_maxReceivePDULength = ASC_DEFAULTMAXPDU;
89 static OFCmdUnsignedInt opt_maxSendPDULength = 0;
90 static E_TransferSyntax opt_networkTransferSyntax = EXS_Unknown;
91 static E_FileReadMode opt_readMode = ERM_autoDetect;
92
93 static OFBool opt_scanDir = OFFalse;
94 static OFBool opt_recurse = OFFalse;
95 static OFBool opt_renameFile = OFFalse;
96 static const char *opt_scanPattern = "";
97
98 static OFBool opt_haltOnUnsuccessfulStore = OFTrue;
99 static OFBool unsuccessfulStoreEncountered = OFFalse;
100 static int lastStatusCode = STATUS_Success;
101
102 static OFBool opt_proposeOnlyRequiredPresentationContexts = OFFalse;
103 static OFBool opt_combineProposedTransferSyntaxes = OFFalse;
104
105 static OFCmdUnsignedInt opt_repeatCount = 1;
106 static OFCmdUnsignedInt opt_inventPatientCount = 25;
107 static OFCmdUnsignedInt opt_inventStudyCount = 50;
108 static OFCmdUnsignedInt opt_inventSeriesCount = 100;
109 static OFBool opt_inventSOPInstanceInformation = OFFalse;
110 static OFBool opt_correctUIDPadding = OFFalse;
111 static OFString patientNamePrefix("OFFIS^TEST_PN_"); // PatientName is PN (maximum 16 chars)
112 static OFString patientIDPrefix("PID_"); // PatientID is LO (maximum 64 chars)
113 static OFString studyIDPrefix("SID_"); // StudyID is SH (maximum 16 chars)
114 static OFString accessionNumberPrefix; // AccessionNumber is SH (maximum 16 chars)
115 static const char *opt_configFile = NULL;
116 static const char *opt_profileName = NULL;
117 T_DIMSE_BlockingMode opt_blockMode = DIMSE_BLOCKING;
118 int opt_dimse_timeout = 0;
119 int opt_acse_timeout = 30;
120 OFCmdSignedInt opt_socket_timeout = 60;
121
122 #ifdef WITH_ZLIB
123 static OFCmdUnsignedInt opt_compressionLevel = 0;
124 #endif
125
126 // User Identity Negotiation
127 static T_ASC_UserIdentityNegotiationMode opt_identMode = ASC_USER_IDENTITY_NONE;
128 static OFString opt_user;
129 static OFString opt_password;
130 static OFString opt_identFile;
131 static OFBool opt_identResponse = OFFalse;
132
133 static OFCondition
134 addStoragePresentationContexts(T_ASC_Parameters *params, OFList<OFString> &sopClasses);
135
136 static OFCondition
137 cstore(T_ASC_Association *assoc, const OFString &fname);
138
139 static OFBool
140 findSOPClassAndInstanceInFile(
141 const char *fname,
142 char *sopClass,
143 size_t sopClassSize,
144 char *sopInstance,
145 size_t sopInstanceSize);
146
147 static OFCondition
148 configureUserIdentityRequest(T_ASC_Parameters *params);
149
150 static OFCondition
151 checkUserIdentityResponse(T_ASC_Parameters *params);
152
153 /* helper macro for converting stream output to a string */
154 #define CONVERT_TO_STRING(output, string) \
155 optStream.str(""); \
156 optStream.clear(); \
157 optStream << output << OFStringStream_ends; \
158 OFSTRINGSTREAM_GETOFSTRING(optStream, string)
159
160 #define SHORTCOL 4
161 #define LONGCOL 19
162
main(int argc,char * argv[])163 int main(int argc, char *argv[])
164 {
165 OFOStringStream optStream;
166
167 const char *opt_peer = NULL;
168 OFCmdUnsignedInt opt_port = 104;
169 const char *opt_peerTitle = PEERAPPLICATIONTITLE;
170 const char *opt_ourTitle = APPLICATIONTITLE;
171
172 OFList<OFString> fileNameList; // list of files to transfer to SCP
173 OFList<OFString> sopClassUIDList; // the list of SOP classes
174 OFList<OFString> sopInstanceUIDList; // the list of SOP instances
175
176 T_ASC_Network *net;
177 T_ASC_Parameters *params;
178 DIC_NODENAME peerHost;
179 T_ASC_Association *assoc;
180 DcmAssociationConfiguration asccfg; // handler for association configuration profiles
181 DcmTLSOptions tlsOptions(NET_REQUESTOR);
182
183 OFStandard::initializeNetwork();
184 #ifdef WITH_OPENSSL
185 DcmTLSTransportLayer::initializeOpenSSL();
186 #endif
187
188 OFString temp_str;
189 OFConsoleApplication app(OFFIS_CONSOLE_APPLICATION , "DICOM storage (C-STORE) SCU", rcsid);
190 OFCommandLine cmd;
191
192 cmd.setParamColumn(LONGCOL + SHORTCOL + 4);
193 cmd.addParam("peer", "hostname of DICOM peer");
194 cmd.addParam("port", "tcp/ip port number of peer");
195 cmd.addParam("dcmfile-in", "DICOM file or directory to be transmitted", OFCmdParam::PM_MultiMandatory);
196
197 cmd.setOptionColumns(LONGCOL, SHORTCOL);
198 cmd.addGroup("general options:", LONGCOL, SHORTCOL + 2);
199 cmd.addOption("--help", "-h", "print this help text and exit", OFCommandLine::AF_Exclusive);
200 cmd.addOption("--version", "print version information and exit", OFCommandLine::AF_Exclusive);
201 OFLog::addOptions(cmd);
202 cmd.addOption("--verbose-pc", "+v", "show presentation contexts in verbose mode");
203
204 cmd.addGroup("input options:");
205 cmd.addSubGroup("input file format:");
206 cmd.addOption("--read-file", "+f", "read file format or data set (default)");
207 cmd.addOption("--read-file-only", "+fo", "read file format only");
208 cmd.addOption("--read-dataset", "-f", "read data set without file meta information");
209 cmd.addSubGroup("input files:");
210 cmd.addOption("--scan-directories", "+sd", "scan directories for input files (dcmfile-in)");
211 #ifdef PATTERN_MATCHING_AVAILABLE
212 cmd.addOption("--scan-pattern", "+sp", 1, "[p]attern: string (only with --scan-directories)",
213 "pattern for filename matching (wildcards)");
214 #endif
215 cmd.addOption("--no-recurse", "-r", "do not recurse within directories (default)");
216 cmd.addOption("--recurse", "+r", "recurse within specified directories");
217 cmd.addOption("--no-rename", "-rn", "do not rename processed files (default)");
218 cmd.addOption("--rename", "+rn", "append .done/.bad to processed files");
219 cmd.addGroup("network options:");
220 cmd.addSubGroup("application entity titles:");
221 cmd.addOption("--aetitle", "-aet", 1, "[a]etitle: string", "set my calling AE title (default: " APPLICATIONTITLE ")");
222 cmd.addOption("--call", "-aec", 1, "[a]etitle: string", "set called AE title of peer (default: " PEERAPPLICATIONTITLE ")");
223 cmd.addSubGroup("association negotiation profile from configuration file:");
224 cmd.addOption("--config-file", "-xf", 2, "[f]ilename, [p]rofile: string",
225 "use profile p from config file f");
226 cmd.addSubGroup("proposed transmission transfer syntaxes (not with --config-file):");
227 cmd.addOption("--propose-uncompr", "-x=", "propose all uncompressed TS, explicit VR\nwith local byte ordering first (default)");
228 cmd.addOption("--propose-little", "-xe", "propose all uncompressed TS, explicit VR\nlittle endian first");
229 cmd.addOption("--propose-big", "-xb", "propose all uncompressed TS, explicit VR\nbig endian first");
230 cmd.addOption("--propose-implicit", "-xi", "propose implicit VR little endian TS only");
231 cmd.addOption("--propose-lossless", "-xs", "propose default JPEG lossless TS\nand all uncompressed transfer syntaxes");
232 cmd.addOption("--propose-jpeg8", "-xy", "propose default JPEG lossy TS for 8 bit data\nand all uncompressed transfer syntaxes");
233 cmd.addOption("--propose-jpeg12", "-xx", "propose default JPEG lossy TS for 12 bit data\nand all uncompressed transfer syntaxes");
234 cmd.addOption("--propose-j2k-lossless", "-xv", "propose JPEG 2000 lossless TS\nand all uncompressed transfer syntaxes");
235 cmd.addOption("--propose-j2k-lossy", "-xw", "propose JPEG 2000 lossy TS\nand all uncompressed transfer syntaxes");
236 cmd.addOption("--propose-jls-lossless", "-xt", "propose JPEG-LS lossless TS\nand all uncompressed transfer syntaxes");
237 cmd.addOption("--propose-jls-lossy", "-xu", "propose JPEG-LS lossy TS\nand all uncompressed transfer syntaxes");
238 cmd.addOption("--propose-mpeg2", "-xm", "propose MPEG2 Main Profile @ Main Level TS");
239 cmd.addOption("--propose-mpeg2-high", "-xh", "propose MPEG2 Main Profile @ High Level TS");
240 cmd.addOption("--propose-mpeg4", "-xn", "propose MPEG4 AVC/H.264 HP / Level 4.1 TS");
241 cmd.addOption("--propose-mpeg4-bd", "-xl", "propose MPEG4 AVC/H.264 BD-compatible TS");
242 cmd.addOption("--propose-mpeg4-2-2d", "-x2", "propose MPEG4 AVC/H.264 HP / Level 4.2 TS (2D)");
243 cmd.addOption("--propose-mpeg4-2-3d", "-x3", "propose MPEG4 AVC/H.264 HP / Level 4.2 TS (3D)");
244 cmd.addOption("--propose-mpeg4-2-st", "-xo", "propose MPEG4 AVC/H.264 Stereo / Level 4.2 TS");
245 cmd.addOption("--propose-hevc", "-x4", "propose HEVC/H.265 Main Profile / Level 5.1 TS");
246 cmd.addOption("--propose-hevc10", "-x5", "propose HEVC/H.265 Main 10 Profile / Level 5.1 TS");
247 cmd.addOption("--propose-rle", "-xr", "propose RLE lossless TS\nand all uncompressed transfer syntaxes");
248 #ifdef WITH_ZLIB
249 cmd.addOption("--propose-deflated", "-xd", "propose deflated expl. VR little endian TS\nand all uncompressed transfer syntaxes");
250 #endif
251 cmd.addOption("--required", "-R", "propose only required presentation contexts\n(default: propose all supported)");
252 cmd.addOption("--combine", "+C", "combine proposed transfer syntaxes\n(default: separate pres. context for each TS)");
253 cmd.addSubGroup("post-1993 value representations:");
254 cmd.addOption("--enable-new-vr", "+u", "enable support for new VRs (UN/UT) (default)");
255 cmd.addOption("--disable-new-vr", "-u", "disable support for new VRs, convert to OB");
256 #ifdef WITH_ZLIB
257 cmd.addSubGroup("deflate compression level (only with --propose-deflated or --config-file):");
258 cmd.addOption("--compression-level", "+cl", 1, "[l]evel: integer (default: 6)",
259 "0=uncompressed, 1=fastest, 9=best compression");
260 #endif
261 cmd.addSubGroup("user identity negotiation:");
262 cmd.addOption("--user", "-usr", 1, "[u]ser name: string",
263 "authenticate using user name u");
264 cmd.addOption("--password", "-pwd", 1, "[p]assword: string (only with --user)",
265 "authenticate using password p");
266 cmd.addOption("--empty-password", "-epw", "send empty password (only with --user)");
267 cmd.addOption("--kerberos", "-kt", 1, "[f]ilename: string",
268 "read kerberos ticket from file f");
269 cmd.addOption("--saml", 1, "[f]ilename: string",
270 "read SAML request from file f");
271 cmd.addOption("--jwt", 1, "[f]ilename: string",
272 "read JWT data from file f");
273 cmd.addOption("--pos-response", "-rsp", "expect positive response");
274 cmd.addSubGroup("other network options:");
275 cmd.addOption("--timeout", "-to", 1, "[s]econds: integer (default: unlimited)", "timeout for connection requests");
276 CONVERT_TO_STRING("[s]econds: integer (default: " << opt_socket_timeout << ")", optString1);
277 cmd.addOption("--socket-timeout", "-ts", 1, optString1.c_str(), "timeout for network socket (0 for none)");
278 CONVERT_TO_STRING("[s]econds: integer (default: " << opt_acse_timeout << ")", optString2);
279 cmd.addOption("--acse-timeout", "-ta", 1, optString2.c_str(), "timeout for ACSE messages");
280 cmd.addOption("--dimse-timeout", "-td", 1, "[s]econds: integer (default: unlimited)", "timeout for DIMSE messages");
281 CONVERT_TO_STRING("[n]umber of bytes: integer (" << ASC_MINIMUMPDUSIZE << ".." << ASC_MAXIMUMPDUSIZE << ")", optString3);
282 CONVERT_TO_STRING("set max receive pdu to n bytes (default: " << opt_maxReceivePDULength << ")", optString4);
283 cmd.addOption("--max-pdu", "-pdu", 1, optString3.c_str(), optString4.c_str());
284 cmd.addOption("--max-send-pdu", 1, optString3.c_str(), "restrict max send pdu to n bytes");
285 cmd.addOption("--repeat", 1, "[n]umber: integer", "repeat n times");
286 cmd.addOption("--abort", "abort association instead of releasing it");
287 cmd.addOption("--no-halt", "-nh", "do not halt if unsuccessful store encountered\n(default: do halt)");
288 cmd.addOption("--uid-padding", "-up", "silently correct space-padded UIDs");
289
290 cmd.addOption("--invent-instance", "+II", "invent a new SOP instance UID for every image\nsent");
291 CONVERT_TO_STRING("invent a new series UID after n images" << OFendl << "have been sent (default: " << opt_inventSeriesCount << ")", optString5);
292 cmd.addOption("--invent-series", "+IR", 1, "[n]umber: integer (implies --invent-instance)", optString5.c_str());
293 CONVERT_TO_STRING("invent a new study UID after n series" << OFendl << "have been sent (default: " << opt_inventStudyCount << ")", optString6);
294 cmd.addOption("--invent-study", "+IS", 1, "[n]umber: integer (implies --invent-instance)", optString6.c_str());
295 CONVERT_TO_STRING("invent a new patient ID and name after n studies" << OFendl << "have been sent (default: " << opt_inventPatientCount << ")", optString7);
296 cmd.addOption("--invent-patient", "+IP", 1, "[n]umber: integer (implies --invent-instance)", optString7.c_str());
297
298 // add TLS specific command line options if (and only if) we are compiling with OpenSSL
299 tlsOptions.addTLSCommandlineOptions(cmd);
300
301 /* evaluate command line */
302 prepareCmdLineArgs(argc, argv, OFFIS_CONSOLE_APPLICATION);
303 if (app.parseCommandLine(cmd, argc, argv))
304 {
305 /* check exclusive options first */
306 if (cmd.hasExclusiveOption())
307 {
308 if (cmd.findOption("--version"))
309 {
310 app.printHeader(OFTrue /*print host identifier*/);
311 COUT << OFendl << "External libraries used:";
312 #if !defined(WITH_ZLIB) && !(ON_THE_FLY_COMPRESSION) && !defined(WITH_OPENSSL)
313 COUT << " none" << OFendl;
314 #else
315 COUT << OFendl;
316 #endif
317 #ifdef WITH_ZLIB
318 COUT << "- ZLIB, Version " << zlibVersion() << OFendl;
319 #endif
320 #ifdef ON_THE_FLY_COMPRESSION
321 COUT << "- " << DiJPEGPlugin::getLibraryVersionString() << OFendl;
322 COUT << "- " << DJLSDecoderRegistration::getLibraryVersionString() << OFendl;
323 #endif
324 // print OpenSSL version if (and only if) we are compiling with OpenSSL
325 tlsOptions.printLibraryVersion();
326 return 0;
327 }
328
329 // check if the command line contains the --list-ciphers option
330 if (tlsOptions.listOfCiphersRequested(cmd))
331 {
332 tlsOptions.printSupportedCiphersuites(app, COUT);
333 return 0;
334 }
335 }
336
337 /* command line parameters */
338
339 cmd.getParam(1, opt_peer);
340 app.checkParam(cmd.getParamAndCheckMinMax(2, opt_port, 1, 65535));
341
342 OFLog::configureFromCommandLine(cmd, app);
343 if (cmd.findOption("--verbose-pc"))
344 {
345 app.checkDependence("--verbose-pc", "verbose mode", storescuLogger.isEnabledFor(OFLogger::INFO_LOG_LEVEL));
346 opt_showPresentationContexts = OFTrue;
347 }
348
349 cmd.beginOptionBlock();
350 if (cmd.findOption("--read-file")) opt_readMode = ERM_autoDetect;
351 if (cmd.findOption("--read-file-only")) opt_readMode = ERM_fileOnly;
352 if (cmd.findOption("--read-dataset")) opt_readMode = ERM_dataset;
353 cmd.endOptionBlock();
354
355 if (cmd.findOption("--scan-directories")) opt_scanDir = OFTrue;
356 #ifdef PATTERN_MATCHING_AVAILABLE
357 if (cmd.findOption("--scan-pattern"))
358 {
359 app.checkDependence("--scan-pattern", "--scan-directories", opt_scanDir);
360 app.checkValue(cmd.getValue(opt_scanPattern));
361 }
362 #endif
363 cmd.beginOptionBlock();
364 if (cmd.findOption("--no-recurse")) opt_recurse = OFFalse;
365 if (cmd.findOption("--recurse"))
366 {
367 app.checkDependence("--recurse", "--scan-directories", opt_scanDir);
368 opt_recurse = OFTrue;
369 }
370 cmd.endOptionBlock();
371
372 cmd.beginOptionBlock();
373 if (cmd.findOption("--no-rename")) opt_renameFile = OFFalse;
374 if (cmd.findOption("--rename")) opt_renameFile = OFTrue;
375 cmd.endOptionBlock();
376
377 if (cmd.findOption("--aetitle")) app.checkValue(cmd.getValue(opt_ourTitle));
378 if (cmd.findOption("--call")) app.checkValue(cmd.getValue(opt_peerTitle));
379
380 cmd.beginOptionBlock();
381 if (cmd.findOption("--propose-uncompr")) opt_networkTransferSyntax = EXS_Unknown;
382 if (cmd.findOption("--propose-little")) opt_networkTransferSyntax = EXS_LittleEndianExplicit;
383 if (cmd.findOption("--propose-big")) opt_networkTransferSyntax = EXS_BigEndianExplicit;
384 if (cmd.findOption("--propose-implicit")) opt_networkTransferSyntax = EXS_LittleEndianImplicit;
385 if (cmd.findOption("--propose-lossless")) opt_networkTransferSyntax = EXS_JPEGProcess14SV1;
386 if (cmd.findOption("--propose-jpeg8")) opt_networkTransferSyntax = EXS_JPEGProcess1;
387 if (cmd.findOption("--propose-jpeg12")) opt_networkTransferSyntax = EXS_JPEGProcess2_4;
388 if (cmd.findOption("--propose-j2k-lossless")) opt_networkTransferSyntax = EXS_JPEG2000LosslessOnly;
389 if (cmd.findOption("--propose-j2k-lossy")) opt_networkTransferSyntax = EXS_JPEG2000;
390 if (cmd.findOption("--propose-jls-lossless")) opt_networkTransferSyntax = EXS_JPEGLSLossless;
391 if (cmd.findOption("--propose-jls-lossy")) opt_networkTransferSyntax = EXS_JPEGLSLossy;
392 if (cmd.findOption("--propose-mpeg2")) opt_networkTransferSyntax = EXS_MPEG2MainProfileAtMainLevel;
393 if (cmd.findOption("--propose-mpeg2-high")) opt_networkTransferSyntax = EXS_MPEG2MainProfileAtHighLevel;
394 if (cmd.findOption("--propose-mpeg4")) opt_networkTransferSyntax = EXS_MPEG4HighProfileLevel4_1;
395 if (cmd.findOption("--propose-mpeg4-bd")) opt_networkTransferSyntax = EXS_MPEG4BDcompatibleHighProfileLevel4_1;
396 if (cmd.findOption("--propose-mpeg4-2-2d")) opt_networkTransferSyntax = EXS_MPEG4HighProfileLevel4_2_For2DVideo;
397 if (cmd.findOption("--propose-mpeg4-2-3d")) opt_networkTransferSyntax = EXS_MPEG4HighProfileLevel4_2_For3DVideo;
398 if (cmd.findOption("--propose-mpeg4-2-st")) opt_networkTransferSyntax = EXS_MPEG4StereoHighProfileLevel4_2;
399 if (cmd.findOption("--propose-hevc")) opt_networkTransferSyntax = EXS_HEVCMainProfileLevel5_1;
400 if (cmd.findOption("--propose-hevc10")) opt_networkTransferSyntax = EXS_HEVCMain10ProfileLevel5_1;
401 if (cmd.findOption("--propose-rle")) opt_networkTransferSyntax = EXS_RLELossless;
402 #ifdef WITH_ZLIB
403 if (cmd.findOption("--propose-deflated")) opt_networkTransferSyntax = EXS_DeflatedLittleEndianExplicit;
404 #endif
405 cmd.endOptionBlock();
406
407 if (cmd.findOption("--required")) opt_proposeOnlyRequiredPresentationContexts = OFTrue;
408 if (cmd.findOption("--combine")) opt_combineProposedTransferSyntaxes = OFTrue;
409
410 if (cmd.findOption("--config-file"))
411 {
412 // check conflicts with other command line options
413 app.checkConflict("--config-file", "--propose-little", opt_networkTransferSyntax == EXS_LittleEndianExplicit);
414 app.checkConflict("--config-file", "--propose-big", opt_networkTransferSyntax == EXS_BigEndianExplicit);
415 app.checkConflict("--config-file", "--propose-implicit", opt_networkTransferSyntax == EXS_LittleEndianImplicit);
416 app.checkConflict("--config-file", "--propose-lossless", opt_networkTransferSyntax == EXS_JPEGProcess14SV1);
417 app.checkConflict("--config-file", "--propose-jpeg8", opt_networkTransferSyntax == EXS_JPEGProcess1);
418 app.checkConflict("--config-file", "--propose-jpeg12", opt_networkTransferSyntax == EXS_JPEGProcess2_4);
419 app.checkConflict("--config-file", "--propose-j2k-lossless", opt_networkTransferSyntax == EXS_JPEG2000LosslessOnly);
420 app.checkConflict("--config-file", "--propose-j2k-lossy", opt_networkTransferSyntax == EXS_JPEG2000);
421 app.checkConflict("--config-file", "--propose-jls-lossless", opt_networkTransferSyntax == EXS_JPEGLSLossless);
422 app.checkConflict("--config-file", "--propose-jls-lossy", opt_networkTransferSyntax == EXS_JPEGLSLossy);
423 app.checkConflict("--config-file", "--propose-mpeg2", opt_networkTransferSyntax == EXS_MPEG2MainProfileAtMainLevel);
424 app.checkConflict("--config-file", "--propose-mpeg2-high", opt_networkTransferSyntax == EXS_MPEG2MainProfileAtHighLevel);
425 app.checkConflict("--config-file", "--propose-mpeg4", opt_networkTransferSyntax == EXS_MPEG4HighProfileLevel4_1);
426 app.checkConflict("--config-file", "--propose-mpeg4-bd", opt_networkTransferSyntax == EXS_MPEG4BDcompatibleHighProfileLevel4_1);
427 app.checkConflict("--config-file", "--propose-mpeg4-2-2d", opt_networkTransferSyntax == EXS_MPEG4HighProfileLevel4_2_For2DVideo);
428 app.checkConflict("--config-file", "--propose-mpeg4-2-3d", opt_networkTransferSyntax == EXS_MPEG4HighProfileLevel4_2_For3DVideo);
429 app.checkConflict("--config-file", "--propose-mpeg4-2-st", opt_networkTransferSyntax == EXS_MPEG4StereoHighProfileLevel4_2);
430 app.checkConflict("--config-file", "--propose-hevc", opt_networkTransferSyntax == EXS_HEVCMainProfileLevel5_1);
431 app.checkConflict("--config-file", "--propose-hevc10", opt_networkTransferSyntax == EXS_HEVCMain10ProfileLevel5_1);
432 app.checkConflict("--config-file", "--propose-rle", opt_networkTransferSyntax == EXS_RLELossless);
433 #ifdef WITH_ZLIB
434 app.checkConflict("--config-file", "--propose-deflated", opt_networkTransferSyntax == EXS_DeflatedLittleEndianExplicit);
435 #endif
436 app.checkConflict("--config-file", "--required", opt_proposeOnlyRequiredPresentationContexts);
437 app.checkConflict("--config-file", "--combine", opt_combineProposedTransferSyntaxes);
438
439 app.checkValue(cmd.getValue(opt_configFile));
440 app.checkValue(cmd.getValue(opt_profileName));
441
442 // read configuration file. The profile name is checked later.
443 OFCondition cond = DcmAssociationConfigurationFile::initialize(asccfg, opt_configFile);
444 if (cond.bad())
445 {
446 OFLOG_ERROR(storescuLogger, "reading config file: " << cond.text());
447 return 1;
448 }
449 }
450
451 #ifdef WITH_ZLIB
452 if (cmd.findOption("--compression-level"))
453 {
454 app.checkDependence("--compression-level", "--propose-deflated or --config-file",
455 (opt_networkTransferSyntax == EXS_DeflatedLittleEndianExplicit) || (opt_configFile != NULL));
456 app.checkValue(cmd.getValueAndCheckMinMax(opt_compressionLevel, 0, 9));
457 dcmZlibCompressionLevel.set(OFstatic_cast(int, opt_compressionLevel));
458 }
459 #endif
460
461 cmd.beginOptionBlock();
462 if (cmd.findOption("--enable-new-vr")) dcmEnableGenerationOfNewVRs();
463 if (cmd.findOption("--disable-new-vr")) dcmDisableGenerationOfNewVRs();
464 cmd.endOptionBlock();
465
466 if (cmd.findOption("--timeout"))
467 {
468 OFCmdSignedInt opt_timeout = 0;
469 app.checkValue(cmd.getValueAndCheckMin(opt_timeout, 1));
470 dcmConnectionTimeout.set(OFstatic_cast(Sint32, opt_timeout));
471 }
472
473 if (cmd.findOption("--socket-timeout"))
474 app.checkValue(cmd.getValueAndCheckMin(opt_socket_timeout, -1));
475 // always set the timeout values since the global default might be different
476 dcmSocketSendTimeout.set(OFstatic_cast(Sint32, opt_socket_timeout));
477 dcmSocketReceiveTimeout.set(OFstatic_cast(Sint32, opt_socket_timeout));
478
479 if (cmd.findOption("--acse-timeout"))
480 {
481 OFCmdSignedInt opt_timeout = 0;
482 app.checkValue(cmd.getValueAndCheckMin(opt_timeout, 1));
483 opt_acse_timeout = OFstatic_cast(int, opt_timeout);
484 }
485
486 if (cmd.findOption("--dimse-timeout"))
487 {
488 OFCmdSignedInt opt_timeout = 0;
489 app.checkValue(cmd.getValueAndCheckMin(opt_timeout, 1));
490 opt_dimse_timeout = OFstatic_cast(int, opt_timeout);
491 opt_blockMode = DIMSE_NONBLOCKING;
492 }
493
494 if (cmd.findOption("--max-pdu"))
495 app.checkValue(cmd.getValueAndCheckMinMax(opt_maxReceivePDULength, ASC_MINIMUMPDUSIZE, ASC_MAXIMUMPDUSIZE));
496
497 if (cmd.findOption("--max-send-pdu"))
498 {
499 app.checkValue(cmd.getValueAndCheckMinMax(opt_maxSendPDULength, ASC_MINIMUMPDUSIZE, ASC_MAXIMUMPDUSIZE));
500 dcmMaxOutgoingPDUSize.set(OFstatic_cast(Uint32, opt_maxSendPDULength));
501 }
502
503 if (cmd.findOption("--repeat")) app.checkValue(cmd.getValueAndCheckMin(opt_repeatCount, 1));
504 if (cmd.findOption("--abort")) opt_abortAssociation = OFTrue;
505 if (cmd.findOption("--no-halt")) opt_haltOnUnsuccessfulStore = OFFalse;
506 if (cmd.findOption("--uid-padding")) opt_correctUIDPadding = OFTrue;
507
508 if (cmd.findOption("--invent-instance")) opt_inventSOPInstanceInformation = OFTrue;
509 if (cmd.findOption("--invent-series"))
510 {
511 opt_inventSOPInstanceInformation = OFTrue;
512 app.checkValue(cmd.getValueAndCheckMin(opt_inventSeriesCount, 1));
513 }
514 if (cmd.findOption("--invent-study"))
515 {
516 opt_inventSOPInstanceInformation = OFTrue;
517 app.checkValue(cmd.getValueAndCheckMin(opt_inventStudyCount, 1));
518 }
519 if (cmd.findOption("--invent-patient"))
520 {
521 opt_inventSOPInstanceInformation = OFTrue;
522 app.checkValue(cmd.getValueAndCheckMin(opt_inventPatientCount, 1));
523 }
524
525 // evaluate (most of) the TLS command line options (if we are compiling with OpenSSL)
526 tlsOptions.parseArguments(app, cmd);
527
528 // User Identity Negotiation
529 cmd.beginOptionBlock();
530 if (cmd.findOption("--user"))
531 {
532 app.checkValue(cmd.getValue(opt_user));
533 opt_identMode = ASC_USER_IDENTITY_USER;
534 }
535 if (cmd.findOption("--kerberos"))
536 {
537 app.checkValue(cmd.getValue(opt_identFile));
538 opt_identMode = ASC_USER_IDENTITY_KERBEROS;
539 }
540 if (cmd.findOption("--saml"))
541 {
542 app.checkValue(cmd.getValue(opt_identFile));
543 opt_identMode = ASC_USER_IDENTITY_SAML;
544 }
545 if (cmd.findOption("--jwt"))
546 {
547 app.checkValue(cmd.getValue(opt_identFile));
548 opt_identMode = ASC_USER_IDENTITY_JWT;
549 }
550 cmd.endOptionBlock();
551 cmd.beginOptionBlock();
552 if (cmd.findOption("--password"))
553 {
554 app.checkDependence("--password", "--user", opt_identMode == ASC_USER_IDENTITY_USER);
555 app.checkValue(cmd.getValue(opt_password));
556 opt_identMode = ASC_USER_IDENTITY_USER_PASSWORD;
557 }
558 if (cmd.findOption("--empty-password"))
559 {
560 app.checkDependence("--empty-password", "--user", opt_identMode == ASC_USER_IDENTITY_USER);
561 opt_password= "";
562 opt_identMode = ASC_USER_IDENTITY_USER_PASSWORD;
563 }
564 cmd.endOptionBlock();
565 if (cmd.findOption("--pos-response"))
566 {
567 app.checkDependence("--pos-response", "--user, --kerberos or --saml", opt_identMode != ASC_USER_IDENTITY_NONE);
568 opt_identResponse = OFTrue;
569 }
570 }
571
572 /* print resource identifier */
573 OFLOG_DEBUG(storescuLogger, rcsid << OFendl);
574
575 /* make sure data dictionary is loaded */
576 if (!dcmDataDict.isDictionaryLoaded())
577 {
578 OFLOG_WARN(storescuLogger, "no data dictionary loaded, check environment variable: "
579 << DCM_DICT_ENVIRONMENT_VARIABLE);
580 }
581
582 /* finally, create list of input files */
583 const char *paramString = NULL;
584 const int paramCount = cmd.getParamCount();
585 OFList<OFString> inputFiles;
586 if (opt_scanDir)
587 OFLOG_INFO(storescuLogger, "determining input files ...");
588 /* iterate over all input filenames/directories */
589 for (int i = 3; i <= paramCount; i++)
590 {
591 cmd.getParam(i, paramString);
592 /* search directory recursively (if required) */
593 if (OFStandard::dirExists(paramString))
594 {
595 if (opt_scanDir)
596 OFStandard::searchDirectoryRecursively(paramString, inputFiles, opt_scanPattern, "" /*dirPrefix*/, opt_recurse);
597 else
598 OFLOG_WARN(storescuLogger, "ignoring directory because option --scan-directories is not set: " << paramString);
599 } else
600 inputFiles.push_back(paramString);
601 }
602 /* check whether there are any input files at all */
603 if (inputFiles.empty())
604 {
605 OFLOG_FATAL(storescuLogger, "no input files to be sent");
606 exit(1);
607 }
608
609 /* check input files */
610 OFString errormsg;
611 DcmFileFormat dfile;
612 char sopClassUID[128];
613 char sopInstanceUID[128];
614 OFBool ignoreName;
615 const char *currentFilename = NULL;
616 OFListIterator(OFString) if_iter = inputFiles.begin();
617 OFListIterator(OFString) if_last = inputFiles.end();
618 OFLOG_INFO(storescuLogger, "checking input files ...");
619 /* iterate over all input filenames */
620 while (if_iter != if_last)
621 {
622 ignoreName = OFFalse;
623 currentFilename = (*if_iter).c_str();
624 if (OFStandard::fileExists(currentFilename))
625 {
626 if (opt_proposeOnlyRequiredPresentationContexts)
627 {
628 if (!findSOPClassAndInstanceInFile(currentFilename, sopClassUID, sizeof(sopClassUID), sopInstanceUID, sizeof(sopInstanceUID)))
629 {
630 ignoreName = OFTrue;
631 errormsg = "missing SOP class (or instance) in file: ";
632 errormsg += currentFilename;
633 if (opt_haltOnUnsuccessfulStore)
634 {
635 OFLOG_FATAL(storescuLogger, errormsg);
636 exit(1);
637 }
638 else
639 OFLOG_WARN(storescuLogger, errormsg << ", ignoring file");
640 }
641 else if (!dcmIsaStorageSOPClassUID(sopClassUID, ESSC_All))
642 {
643 ignoreName = OFTrue;
644 errormsg = "unknown storage SOP class in file: ";
645 errormsg += currentFilename;
646 errormsg += ": ";
647 errormsg += sopClassUID;
648 if (opt_haltOnUnsuccessfulStore)
649 {
650 OFLOG_FATAL(storescuLogger, errormsg);
651 exit(1);
652 }
653 else
654 OFLOG_WARN(storescuLogger, errormsg << ", ignoring file");
655 }
656 else
657 {
658 sopClassUIDList.push_back(sopClassUID);
659 sopInstanceUIDList.push_back(sopInstanceUID);
660 }
661 }
662 if (!ignoreName) fileNameList.push_back(currentFilename);
663 }
664 else
665 {
666 errormsg = "cannot access file: ";
667 errormsg += currentFilename;
668 if (opt_haltOnUnsuccessfulStore)
669 {
670 OFLOG_FATAL(storescuLogger, errormsg);
671 exit(1);
672 }
673 else
674 OFLOG_WARN(storescuLogger, errormsg << ", ignoring file");
675 }
676 ++if_iter;
677 }
678
679 #ifdef ON_THE_FLY_COMPRESSION
680 // register global JPEG decompression codecs
681 DJDecoderRegistration::registerCodecs();
682
683 // register global JPEG compression codecs
684 DJEncoderRegistration::registerCodecs();
685
686 // register JPEG-LS decompression codecs
687 DJLSDecoderRegistration::registerCodecs();
688
689 // register JPEG-LS compression codecs
690 DJLSEncoderRegistration::registerCodecs();
691
692 // register RLE compression codec
693 DcmRLEEncoderRegistration::registerCodecs();
694
695 // register RLE decompression codec
696 DcmRLEDecoderRegistration::registerCodecs();
697 #endif
698
699 /* initialize network, i.e. create an instance of T_ASC_Network*. */
700 OFCondition cond = ASC_initializeNetwork(NET_REQUESTOR, 0, opt_acse_timeout, &net);
701 if (cond.bad()) {
702 OFLOG_FATAL(storescuLogger, DimseCondition::dump(temp_str, cond));
703 return 1;
704 }
705
706 /* initialize association parameters, i.e. create an instance of T_ASC_Parameters*. */
707 cond = ASC_createAssociationParameters(¶ms, opt_maxReceivePDULength);
708 if (cond.bad()) {
709 OFLOG_FATAL(storescuLogger, DimseCondition::dump(temp_str, cond));
710 return 1;
711 }
712
713 /* create a secure transport layer if requested and OpenSSL is available */
714 cond = tlsOptions.createTransportLayer(net, params, app, cmd);
715 if (cond.bad()) {
716 OFLOG_FATAL(storescuLogger, DimseCondition::dump(temp_str, cond));
717 return 1;
718 }
719
720 /* sets this application's title and the called application's title in the params */
721 /* structure. The default values to be set here are "STORESCU" and "ANY-SCP". */
722 ASC_setAPTitles(params, opt_ourTitle, opt_peerTitle, NULL);
723
724 /* Figure out the presentation addresses and copy the */
725 /* corresponding values into the association parameters.*/
726 sprintf(peerHost, "%s:%d", opt_peer, OFstatic_cast(int, opt_port));
727 ASC_setPresentationAddresses(params, OFStandard::getHostName().c_str(), peerHost);
728
729 /* Configure User Identity Negotiation*/
730 if (opt_identMode != ASC_USER_IDENTITY_NONE)
731 {
732 cond = configureUserIdentityRequest(params);
733 if (cond.bad())
734 return 1;
735 }
736
737 if (opt_profileName)
738 {
739 /* perform name mangling for config file key */
740 OFString sprofile;
741 const unsigned char *c = OFreinterpret_cast(const unsigned char *, opt_profileName);
742 while (*c)
743 {
744 if (!isspace(*c)) sprofile += OFstatic_cast(char, toupper(*c));
745 ++c;
746 }
747
748 /* set presentation contexts as defined in config file */
749 cond = asccfg.setAssociationParameters(sprofile.c_str(), *params);
750 }
751 else
752 {
753 /* Set the presentation contexts which will be negotiated */
754 /* when the network connection will be established */
755 cond = addStoragePresentationContexts(params, sopClassUIDList);
756 }
757
758 if (cond.bad()) {
759 OFLOG_FATAL(storescuLogger, DimseCondition::dump(temp_str, cond));
760 return 1;
761 }
762
763 /* dump presentation contexts if required */
764 if (opt_showPresentationContexts)
765 OFLOG_INFO(storescuLogger, "Request Parameters:" << OFendl << ASC_dumpParameters(temp_str, params, ASC_ASSOC_RQ));
766 else
767 OFLOG_DEBUG(storescuLogger, "Request Parameters:" << OFendl << ASC_dumpParameters(temp_str, params, ASC_ASSOC_RQ));
768
769 /* create association, i.e. try to establish a network connection to another */
770 /* DICOM application. This call creates an instance of T_ASC_Association*. */
771 OFLOG_INFO(storescuLogger, "Requesting Association");
772 cond = ASC_requestAssociation(net, params, &assoc);
773 if (cond.bad()) {
774 if (cond == DUL_ASSOCIATIONREJECTED) {
775 T_ASC_RejectParameters rej;
776
777 ASC_getRejectParameters(params, &rej);
778 OFLOG_FATAL(storescuLogger, "Association Rejected:" << OFendl << ASC_printRejectParameters(temp_str, &rej));
779 return 1;
780 } else {
781 OFLOG_FATAL(storescuLogger, "Association Request Failed: " << DimseCondition::dump(temp_str, cond));
782 return 1;
783 }
784 }
785
786 /* dump the connection parameters if in debug mode*/
787 OFLOG_DEBUG(storescuLogger, ASC_dumpConnectionParameters(temp_str, assoc));
788
789 /* dump the presentation contexts which have been accepted/refused */
790 if (opt_showPresentationContexts)
791 OFLOG_INFO(storescuLogger, "Association Parameters Negotiated:" << OFendl << ASC_dumpParameters(temp_str, params, ASC_ASSOC_AC));
792 else
793 OFLOG_DEBUG(storescuLogger, "Association Parameters Negotiated:" << OFendl << ASC_dumpParameters(temp_str, params, ASC_ASSOC_AC));
794
795 /* count the presentation contexts which have been accepted by the SCP */
796 /* If there are none, finish the execution */
797 if (ASC_countAcceptedPresentationContexts(params) == 0) {
798 OFLOG_FATAL(storescuLogger, "No Acceptable Presentation Contexts");
799 return 1;
800 }
801
802 /* check user authentication response (if applicable) */
803 cond = checkUserIdentityResponse(params);
804 if (cond.bad())
805 {
806 OFLOG_FATAL(storescuLogger, DimseCondition::dump(temp_str, cond));
807 return 1;
808 }
809 /* dump general information concerning the establishment of the network connection if required */
810 OFLOG_INFO(storescuLogger, "Association Accepted (Max Send PDV: " << assoc->sendPDVLength << ")");
811
812 /* do the real work, i.e. for all files which were specified in the */
813 /* command line, transmit the encapsulated DICOM objects to the SCP. */
814 cond = EC_Normal;
815 OFListIterator(OFString) iter = fileNameList.begin();
816 OFListIterator(OFString) enditer = fileNameList.end();
817
818 while ((iter != enditer) && cond.good())
819 {
820 cond = cstore(assoc, *iter);
821 ++iter;
822 }
823
824 /* tear down association, i.e. terminate network connection to SCP */
825 if (cond == EC_Normal)
826 {
827 if (opt_abortAssociation) {
828 OFLOG_INFO(storescuLogger, "Aborting Association");
829 cond = ASC_abortAssociation(assoc);
830 if (cond.bad()) {
831 OFLOG_ERROR(storescuLogger, "Association Abort Failed: " << DimseCondition::dump(temp_str, cond));
832 return 1;
833 }
834 } else {
835 /* release association */
836 OFLOG_INFO(storescuLogger, "Releasing Association");
837 cond = ASC_releaseAssociation(assoc);
838 if (cond.bad())
839 {
840 OFLOG_ERROR(storescuLogger, "Association Release Failed: " << DimseCondition::dump(temp_str, cond));
841 return 1;
842 }
843 }
844 }
845 else if (cond == DUL_PEERREQUESTEDRELEASE)
846 {
847 OFLOG_ERROR(storescuLogger, "Protocol Error: Peer requested release (Aborting)");
848 OFLOG_INFO(storescuLogger, "Aborting Association");
849 cond = ASC_abortAssociation(assoc);
850 if (cond.bad()) {
851 OFLOG_ERROR(storescuLogger, "Association Abort Failed: " << DimseCondition::dump(temp_str, cond));
852 return 1;
853 }
854 }
855 else if (cond == DUL_PEERABORTEDASSOCIATION)
856 {
857 OFLOG_INFO(storescuLogger, "Peer Aborted Association");
858 }
859 else
860 {
861 OFLOG_ERROR(storescuLogger, "Store SCU Failed: " << DimseCondition::dump(temp_str, cond));
862 OFLOG_INFO(storescuLogger, "Aborting Association");
863 cond = ASC_abortAssociation(assoc);
864 if (cond.bad()) {
865 OFLOG_ERROR(storescuLogger, "Association Abort Failed: " << DimseCondition::dump(temp_str, cond));
866 return 1;
867 }
868 }
869
870 /* destroy the association, i.e. free memory of T_ASC_Association* structure. This */
871 /* call is the counterpart of ASC_requestAssociation(...) which was called above. */
872 cond = ASC_destroyAssociation(&assoc);
873 if (cond.bad()) {
874 OFLOG_FATAL(storescuLogger, DimseCondition::dump(temp_str, cond));
875 return 1;
876 }
877 /* drop the network, i.e. free memory of T_ASC_Network* structure. This call */
878 /* is the counterpart of ASC_initializeNetwork(...) which was called above. */
879 cond = ASC_dropNetwork(&net);
880 if (cond.bad()) {
881 OFLOG_FATAL(storescuLogger, DimseCondition::dump(temp_str, cond));
882 return 1;
883 }
884
885 OFStandard::shutdownNetwork();
886
887 cond = tlsOptions.writeRandomSeed();
888 if (cond.bad()) {
889 // failure to write back the random seed is a warning, not an error
890 OFLOG_WARN(storescuLogger, DimseCondition::dump(temp_str, cond));
891 }
892
893 int exitCode = 0;
894 if (opt_haltOnUnsuccessfulStore && unsuccessfulStoreEncountered) {
895 if (lastStatusCode == STATUS_Success) {
896 // there must have been some kind of general network error
897 exitCode = 0xff;
898 } else {
899 exitCode = (lastStatusCode >> 8); // only the least significant byte is relevant as exit code
900 }
901 }
902
903 #ifdef ON_THE_FLY_COMPRESSION
904 // deregister JPEG codecs
905 DJDecoderRegistration::cleanup();
906 DJEncoderRegistration::cleanup();
907
908 // deregister JPEG-LS codecs
909 DJLSDecoderRegistration::cleanup();
910 DJLSEncoderRegistration::cleanup();
911
912 // deregister RLE codecs
913 DcmRLEDecoderRegistration::cleanup();
914 DcmRLEEncoderRegistration::cleanup();
915 #endif
916
917 #ifdef DEBUG
918 dcmDataDict.clear(); /* useful for debugging with dmalloc */
919 #endif
920 return exitCode;
921 }
922
923
924 static OFBool
isaListMember(OFList<OFString> & lst,OFString & s)925 isaListMember(OFList<OFString> &lst, OFString &s)
926 {
927 OFListIterator(OFString) cur = lst.begin();
928 OFListIterator(OFString) end = lst.end();
929
930 OFBool found = OFFalse;
931 while (cur != end && !found) {
932 found = (s == *cur);
933 ++cur;
934 }
935
936 return found;
937 }
938
939 static OFCondition
addPresentationContext(T_ASC_Parameters * params,int presentationContextId,const OFString & abstractSyntax,const OFString & transferSyntax,T_ASC_SC_ROLE proposedRole=ASC_SC_ROLE_DEFAULT)940 addPresentationContext(T_ASC_Parameters *params,
941 int presentationContextId,
942 const OFString &abstractSyntax,
943 const OFString &transferSyntax,
944 T_ASC_SC_ROLE proposedRole = ASC_SC_ROLE_DEFAULT)
945 {
946 const char *c_p = transferSyntax.c_str();
947 OFCondition cond = ASC_addPresentationContext(params, presentationContextId,
948 abstractSyntax.c_str(), &c_p, 1, proposedRole);
949 return cond;
950 }
951
952 static OFCondition
addPresentationContext(T_ASC_Parameters * params,int presentationContextId,const OFString & abstractSyntax,const OFList<OFString> & transferSyntaxList,T_ASC_SC_ROLE proposedRole=ASC_SC_ROLE_DEFAULT)953 addPresentationContext(T_ASC_Parameters *params,
954 int presentationContextId,
955 const OFString &abstractSyntax,
956 const OFList<OFString> &transferSyntaxList,
957 T_ASC_SC_ROLE proposedRole = ASC_SC_ROLE_DEFAULT)
958 {
959 // create an array of supported/possible transfer syntaxes
960 const char **transferSyntaxes = new const char*[transferSyntaxList.size()];
961 int transferSyntaxCount = 0;
962 OFListConstIterator(OFString) s_cur = transferSyntaxList.begin();
963 OFListConstIterator(OFString) s_end = transferSyntaxList.end();
964 while (s_cur != s_end) {
965 transferSyntaxes[transferSyntaxCount++] = (*s_cur).c_str();
966 ++s_cur;
967 }
968
969 OFCondition cond = ASC_addPresentationContext(params, presentationContextId,
970 abstractSyntax.c_str(), transferSyntaxes, transferSyntaxCount, proposedRole);
971
972 delete[] transferSyntaxes;
973 return cond;
974 }
975
976 static OFCondition
addStoragePresentationContexts(T_ASC_Parameters * params,OFList<OFString> & sopClasses)977 addStoragePresentationContexts(T_ASC_Parameters *params,
978 OFList<OFString> &sopClasses)
979 {
980 /*
981 * Each SOP Class will be proposed in two presentation contexts (unless
982 * the opt_combineProposedTransferSyntaxes global variable is true).
983 * The command line specified a preferred transfer syntax to use.
984 * This preferred transfer syntax will be proposed in one
985 * presentation context and a set of alternative (fallback) transfer
986 * syntaxes will be proposed in a different presentation context.
987 *
988 * Generally, we prefer to use Explicitly encoded transfer syntaxes
989 * and if running on a Little Endian machine we prefer
990 * LittleEndianExplicitTransferSyntax to BigEndianTransferSyntax.
991 * Some SCP implementations will just select the first transfer
992 * syntax they support (this is not part of the standard) so
993 * organise the proposed transfer syntaxes to take advantage
994 * of such behaviour.
995 */
996
997 // Which transfer syntax was preferred on the command line
998 OFString preferredTransferSyntax;
999 if (opt_networkTransferSyntax == EXS_Unknown) {
1000 /* gLocalByteOrder is defined in dcxfer.h */
1001 if (gLocalByteOrder == EBO_LittleEndian) {
1002 /* we are on a little endian machine */
1003 preferredTransferSyntax = UID_LittleEndianExplicitTransferSyntax;
1004 } else {
1005 /* we are on a big endian machine */
1006 preferredTransferSyntax = UID_BigEndianExplicitTransferSyntax;
1007 }
1008 } else {
1009 DcmXfer xfer(opt_networkTransferSyntax);
1010 preferredTransferSyntax = xfer.getXferID();
1011 }
1012
1013 OFListIterator(OFString) s_cur;
1014 OFListIterator(OFString) s_end;
1015
1016 OFList<OFString> fallbackSyntaxes;
1017 // - If little endian implicit is preferred, we don't need any fallback syntaxes
1018 // because it is the default transfer syntax and all applications must support it.
1019 // - If MPEG2, MPEG4 or HEVC is preferred, we don't want to propose any fallback solution
1020 // because this is not required and we cannot decompress the movie anyway.
1021 if ((opt_networkTransferSyntax != EXS_LittleEndianImplicit) &&
1022 (opt_networkTransferSyntax != EXS_MPEG2MainProfileAtMainLevel) &&
1023 (opt_networkTransferSyntax != EXS_MPEG2MainProfileAtHighLevel) &&
1024 (opt_networkTransferSyntax != EXS_MPEG4HighProfileLevel4_1) &&
1025 (opt_networkTransferSyntax != EXS_MPEG4BDcompatibleHighProfileLevel4_1) &&
1026 (opt_networkTransferSyntax != EXS_MPEG4HighProfileLevel4_2_For2DVideo) &&
1027 (opt_networkTransferSyntax != EXS_MPEG4HighProfileLevel4_2_For3DVideo) &&
1028 (opt_networkTransferSyntax != EXS_MPEG4StereoHighProfileLevel4_2) &&
1029 (opt_networkTransferSyntax != EXS_HEVCMainProfileLevel5_1) &&
1030 (opt_networkTransferSyntax != EXS_HEVCMain10ProfileLevel5_1))
1031 {
1032 fallbackSyntaxes.push_back(UID_LittleEndianExplicitTransferSyntax);
1033 fallbackSyntaxes.push_back(UID_BigEndianExplicitTransferSyntax);
1034 fallbackSyntaxes.push_back(UID_LittleEndianImplicitTransferSyntax);
1035 // Remove the preferred syntax from the fallback list
1036 fallbackSyntaxes.remove(preferredTransferSyntax);
1037 }
1038
1039 // create a list of transfer syntaxes combined from the preferred and fallback syntaxes
1040 OFList<OFString> combinedSyntaxes;
1041 s_cur = fallbackSyntaxes.begin();
1042 s_end = fallbackSyntaxes.end();
1043 combinedSyntaxes.push_back(preferredTransferSyntax);
1044 while (s_cur != s_end)
1045 {
1046 if (!isaListMember(combinedSyntaxes, *s_cur)) combinedSyntaxes.push_back(*s_cur);
1047 ++s_cur;
1048 }
1049
1050 if (!opt_proposeOnlyRequiredPresentationContexts) {
1051 // add the (short list of) known storage SOP classes to the list
1052 // the array of Storage SOP Class UIDs comes from dcuid.h
1053 for (int i = 0; i < numberOfDcmShortSCUStorageSOPClassUIDs; i++)
1054 sopClasses.push_back(dcmShortSCUStorageSOPClassUIDs[i]);
1055 }
1056
1057 // thin out the SOP classes to remove any duplicates
1058 OFList<OFString> sops;
1059 s_cur = sopClasses.begin();
1060 s_end = sopClasses.end();
1061 while (s_cur != s_end) {
1062 if (!isaListMember(sops, *s_cur)) {
1063 sops.push_back(*s_cur);
1064 }
1065 ++s_cur;
1066 }
1067
1068 // add a presentations context for each SOP class / transfer syntax pair
1069 OFCondition cond = EC_Normal;
1070 int pid = 1; // presentation context id
1071 s_cur = sops.begin();
1072 s_end = sops.end();
1073 while (s_cur != s_end && cond.good()) {
1074
1075 if (pid > 255) {
1076 OFLOG_ERROR(storescuLogger, "Too many presentation contexts");
1077 return ASC_BADPRESENTATIONCONTEXTID;
1078 }
1079
1080 if (opt_combineProposedTransferSyntaxes) {
1081 cond = addPresentationContext(params, pid, *s_cur, combinedSyntaxes);
1082 pid += 2; /* only odd presentation context id's */
1083 } else {
1084
1085 // SOP class with preferred transfer syntax
1086 cond = addPresentationContext(params, pid, *s_cur, preferredTransferSyntax);
1087 pid += 2; /* only odd presentation context id's */
1088
1089 if (fallbackSyntaxes.size() > 0) {
1090 if (pid > 255) {
1091 OFLOG_ERROR(storescuLogger, "Too many presentation contexts");
1092 return ASC_BADPRESENTATIONCONTEXTID;
1093 }
1094
1095 // SOP class with fallback transfer syntax
1096 cond = addPresentationContext(params, pid, *s_cur, fallbackSyntaxes);
1097 pid += 2; /* only odd presentation context id's */
1098 }
1099 }
1100 ++s_cur;
1101 }
1102
1103 return cond;
1104 }
1105
1106 static int
secondsSince1970()1107 secondsSince1970()
1108 {
1109 time_t t = time(NULL);
1110 return OFstatic_cast(int, t);
1111 }
1112
1113 static OFString
intToString(int i)1114 intToString(int i)
1115 {
1116 char numbuf[32];
1117 sprintf(numbuf, "%d", i);
1118 return numbuf;
1119 }
1120
1121 static OFString
makeUID(OFString basePrefix,int counter)1122 makeUID(OFString basePrefix, int counter)
1123 {
1124 OFString prefix = basePrefix + "." + intToString(counter);
1125 char uidbuf[65];
1126 OFString uid = dcmGenerateUniqueIdentifier(uidbuf, prefix.c_str());
1127 return uid;
1128 }
1129
1130 static OFBool
updateStringAttributeValue(DcmItem * dataset,const DcmTagKey & key,OFString & value)1131 updateStringAttributeValue(DcmItem *dataset, const DcmTagKey &key, OFString &value)
1132 {
1133 DcmStack stack;
1134 DcmTag tag(key);
1135
1136 OFCondition cond = EC_Normal;
1137 cond = dataset->search(key, stack, ESM_fromHere, OFFalse);
1138 if (cond != EC_Normal) {
1139 OFLOG_ERROR(storescuLogger, "updateStringAttributeValue: cannot find: " << tag.getTagName()
1140 << " " << key << ": " << cond.text());
1141 return OFFalse;
1142 }
1143
1144 DcmElement *elem = OFstatic_cast(DcmElement *, stack.top());
1145
1146 DcmVR vr(elem->ident());
1147 if (elem->getLength() > vr.getMaxValueLength()) {
1148 OFLOG_ERROR(storescuLogger, "updateStringAttributeValue: INTERNAL ERROR: " << tag.getTagName()
1149 << " " << key << ": value too large (max " << vr.getMaxValueLength()
1150 << ") for " << vr.getVRName() << " value: " << value);
1151 return OFFalse;
1152 }
1153
1154 cond = elem->putOFStringArray(value);
1155 if (cond != EC_Normal) {
1156 OFLOG_ERROR(storescuLogger, "updateStringAttributeValue: cannot put string in attribute: " << tag.getTagName()
1157 << " " << key << ": " << cond.text());
1158 return OFFalse;
1159 }
1160
1161 return OFTrue;
1162 }
1163
1164 static void
replaceSOPInstanceInformation(DcmDataset * dataset)1165 replaceSOPInstanceInformation(DcmDataset *dataset)
1166 {
1167 static OFCmdUnsignedInt patientCounter = 0;
1168 static OFCmdUnsignedInt studyCounter = 0;
1169 static OFCmdUnsignedInt seriesCounter = 0;
1170 static OFCmdUnsignedInt imageCounter = 0;
1171 static OFString seriesInstanceUID;
1172 static OFString seriesNumber;
1173 static OFString studyInstanceUID;
1174 static OFString studyID;
1175 static OFString accessionNumber;
1176 static OFString patientID;
1177 static OFString patientName;
1178
1179 if (seriesInstanceUID.empty()) seriesInstanceUID=makeUID(SITE_SERIES_UID_ROOT, OFstatic_cast(int, seriesCounter));
1180 if (seriesNumber.empty()) seriesNumber = intToString(OFstatic_cast(int, seriesCounter));
1181 if (studyInstanceUID.empty()) studyInstanceUID = makeUID(SITE_STUDY_UID_ROOT, OFstatic_cast(int, studyCounter));
1182 if (studyID.empty()) studyID = studyIDPrefix + intToString(OFstatic_cast(int, secondsSince1970())) + intToString(OFstatic_cast(int, studyCounter));
1183 if (accessionNumber.empty()) accessionNumber = accessionNumberPrefix + intToString(secondsSince1970()) + intToString(OFstatic_cast(int, studyCounter));
1184 if (patientID.empty()) patientID = patientIDPrefix + intToString(secondsSince1970()) + intToString(OFstatic_cast(int, patientCounter));
1185 if (patientName.empty()) patientName = patientNamePrefix + intToString(secondsSince1970()) + intToString(OFstatic_cast(int, patientCounter));
1186
1187 if (imageCounter >= opt_inventSeriesCount) {
1188 imageCounter = 0;
1189 seriesCounter++;
1190 seriesInstanceUID = makeUID(SITE_SERIES_UID_ROOT, OFstatic_cast(int, seriesCounter));
1191 seriesNumber = intToString(OFstatic_cast(int, seriesCounter));
1192 }
1193 if (seriesCounter >= opt_inventStudyCount) {
1194 seriesCounter = 0;
1195 studyCounter++;
1196 studyInstanceUID = makeUID(SITE_STUDY_UID_ROOT, OFstatic_cast(int, studyCounter));
1197 studyID = studyIDPrefix + intToString(secondsSince1970()) + intToString(OFstatic_cast(int, studyCounter));
1198 accessionNumber = accessionNumberPrefix + intToString(secondsSince1970()) + intToString(OFstatic_cast(int, studyCounter));
1199 }
1200 if (studyCounter >= opt_inventPatientCount) {
1201 // we create as many patients as necessary */
1202 studyCounter = 0;
1203 patientCounter++;
1204 patientID = patientIDPrefix + intToString(secondsSince1970()) + intToString(OFstatic_cast(int, patientCounter));
1205 patientName = patientNamePrefix + intToString(secondsSince1970()) + intToString(OFstatic_cast(int, patientCounter));
1206 }
1207
1208 OFString sopInstanceUID = makeUID(SITE_INSTANCE_UID_ROOT, OFstatic_cast(int, imageCounter));
1209 OFString imageNumber = intToString(OFstatic_cast(int, imageCounter));
1210
1211 OFLOG_INFO(storescuLogger, "Inventing Identifying Information ("
1212 << "pa" << patientCounter << ", st" << studyCounter
1213 << ", se" << seriesCounter << ", im" << imageCounter << "):");
1214 OFLOG_INFO(storescuLogger, " PatientName=" << patientName);
1215 OFLOG_INFO(storescuLogger, " PatientID=" << patientID);
1216 OFLOG_INFO(storescuLogger, " StudyInstanceUID=" << studyInstanceUID);
1217 OFLOG_INFO(storescuLogger, " StudyID=" << studyID);
1218 OFLOG_INFO(storescuLogger, " SeriesInstanceUID=" << seriesInstanceUID);
1219 OFLOG_INFO(storescuLogger, " SeriesNumber=" << seriesNumber);
1220 OFLOG_INFO(storescuLogger, " SOPInstanceUID=" << sopInstanceUID);
1221 OFLOG_INFO(storescuLogger, " ImageNumber=" << imageNumber);
1222
1223 updateStringAttributeValue(dataset, DCM_PatientName, patientName);
1224 updateStringAttributeValue(dataset, DCM_PatientID, patientID);
1225 updateStringAttributeValue(dataset, DCM_StudyInstanceUID, studyInstanceUID);
1226 updateStringAttributeValue(dataset, DCM_StudyID, studyID);
1227 updateStringAttributeValue(dataset, DCM_SeriesInstanceUID, seriesInstanceUID);
1228 updateStringAttributeValue(dataset, DCM_SeriesNumber, seriesNumber);
1229 updateStringAttributeValue(dataset, DCM_SOPInstanceUID, sopInstanceUID);
1230 updateStringAttributeValue(dataset, DCM_InstanceNumber, imageNumber);
1231
1232 imageCounter++;
1233 }
1234
1235 static void
progressCallback(void *,T_DIMSE_StoreProgress * progress,T_DIMSE_C_StoreRQ * req)1236 progressCallback(void * /*callbackData*/,
1237 T_DIMSE_StoreProgress *progress,
1238 T_DIMSE_C_StoreRQ * req)
1239 {
1240 if (progress->state == DIMSE_StoreBegin)
1241 {
1242 OFString str;
1243 OFLOG_DEBUG(storescuLogger, DIMSE_dumpMessage(str, *req, DIMSE_OUTGOING));
1244 }
1245
1246 // We can't use oflog for the pdu output, but we use a special logger for
1247 // generating this output. If it is set to level "INFO" we generate the
1248 // output, if it's set to "DEBUG" then we'll assume that there is debug output
1249 // generated for each PDU elsewhere.
1250 OFLogger progressLogger = OFLog::getLogger("dcmtk.apps." OFFIS_CONSOLE_APPLICATION ".progress");
1251 if (progressLogger.getChainedLogLevel() == OFLogger::INFO_LOG_LEVEL) {
1252 switch (progress->state) {
1253 case DIMSE_StoreBegin:
1254 COUT << "XMIT: "; break;
1255 case DIMSE_StoreEnd:
1256 COUT << OFendl; break;
1257 default:
1258 COUT << "."; break;
1259 }
1260 COUT.flush();
1261 }
1262 }
1263
1264 static void
renameFile(const char * fname,const char * fext)1265 renameFile(const char *fname, const char *fext)
1266 {
1267 if (!opt_renameFile) return;
1268 OFString fnewname(fname);
1269 fnewname += fext;
1270 if (OFStandard::renameFile(fname, fnewname))
1271 OFLOG_DEBUG(storescuLogger, "renamed file '" << fname << "' to '" << fnewname << "'");
1272 else
1273 OFLOG_WARN(storescuLogger, "cannot rename file '" << fname << "' to '" << fnewname << "'");
1274 }
1275
1276 static OFCondition
storeSCU(T_ASC_Association * assoc,const char * fname)1277 storeSCU(T_ASC_Association *assoc, const char *fname)
1278 /*
1279 * This function will read all the information from the given file,
1280 * figure out a corresponding presentation context which will be used
1281 * to transmit the information over the network to the SCP, and it
1282 * will finally initiate the transmission of all data to the SCP.
1283 *
1284 * Parameters:
1285 * assoc - [in] The association (network connection to another DICOM application).
1286 * fname - [in] Name of the file which shall be processed.
1287 */
1288 {
1289 DIC_US msgId = assoc->nextMsgID++;
1290 T_ASC_PresentationContextID presID;
1291 T_DIMSE_C_StoreRQ req;
1292 T_DIMSE_C_StoreRSP rsp;
1293 DIC_UI sopClass;
1294 DIC_UI sopInstance;
1295 DcmDataset *statusDetail = NULL;
1296
1297 unsuccessfulStoreEncountered = OFTrue; // assumption
1298
1299 OFLOG_INFO(storescuLogger, "Sending file: " << fname);
1300
1301 /* read information from file. After the call to DcmFileFormat::loadFile(...) the information */
1302 /* which is encapsulated in the file will be available through the DcmFileFormat object. */
1303 /* In detail, it will be available through calls to DcmFileFormat::getMetaInfo() (for */
1304 /* meta header information) and DcmFileFormat::getDataset() (for data set information). */
1305 DcmFileFormat dcmff;
1306 OFCondition cond = dcmff.loadFile(fname, EXS_Unknown, EGL_noChange, DCM_MaxReadLength, opt_readMode);
1307
1308 /* figure out if an error occurred while the file was read */
1309 if (cond.bad())
1310 {
1311 OFLOG_ERROR(storescuLogger, "Bad DICOM file: " << fname << ": " << cond.text());
1312 renameFile(fname, ".bad");
1313 return cond;
1314 }
1315
1316 /* if required, invent new SOP instance information for the current data set (user option) */
1317 if (opt_inventSOPInstanceInformation) {
1318 replaceSOPInstanceInformation(dcmff.getDataset());
1319 }
1320
1321 /* figure out which SOP class and SOP instance is encapsulated in the file */
1322 if (!DU_findSOPClassAndInstanceInDataSet(dcmff.getDataset(),
1323 sopClass, sizeof(sopClass), sopInstance, sizeof(sopInstance), opt_correctUIDPadding))
1324 {
1325 OFLOG_ERROR(storescuLogger, "No SOP Class or Instance UID in file: " << fname);
1326 renameFile(fname, ".bad");
1327 return DIMSE_BADDATA;
1328 }
1329
1330 /* figure out which of the accepted presentation contexts should be used */
1331 DcmXfer filexfer(dcmff.getDataset()->getOriginalXfer());
1332
1333 /* special case: if the file uses an unencapsulated transfer syntax (uncompressed
1334 * or deflated explicit VR) and we prefer deflated explicit VR, then try
1335 * to find a presentation context for deflated explicit VR first.
1336 */
1337 if (filexfer.isNotEncapsulated() &&
1338 opt_networkTransferSyntax == EXS_DeflatedLittleEndianExplicit)
1339 {
1340 filexfer = EXS_DeflatedLittleEndianExplicit;
1341 }
1342
1343 if (filexfer.getXfer() != EXS_Unknown)
1344 presID = ASC_findAcceptedPresentationContextID(assoc, sopClass, filexfer.getXferID());
1345 else
1346 presID = ASC_findAcceptedPresentationContextID(assoc, sopClass);
1347 if (presID == 0)
1348 {
1349 const char *modalityName = dcmSOPClassUIDToModality(sopClass);
1350 if (!modalityName) modalityName = dcmFindNameOfUID(sopClass);
1351 if (!modalityName) modalityName = "unknown SOP class";
1352 OFLOG_ERROR(storescuLogger, "No presentation context for: (" << modalityName << ") " << sopClass);
1353 renameFile(fname, ".bad");
1354 return DIMSE_NOVALIDPRESENTATIONCONTEXTID;
1355 }
1356
1357 T_ASC_PresentationContext pc;
1358 ASC_findAcceptedPresentationContext(assoc->params, presID, &pc);
1359 DcmXfer netTransfer(pc.acceptedTransferSyntax);
1360
1361 /* if required, dump general information concerning transfer syntaxes */
1362 if (storescuLogger.isEnabledFor(OFLogger::INFO_LOG_LEVEL))
1363 {
1364 DcmXfer fileTransfer(dcmff.getDataset()->getOriginalXfer());
1365 OFLOG_INFO(storescuLogger, "Converting transfer syntax: " << fileTransfer.getXferName()
1366 << " -> " << netTransfer.getXferName());
1367 }
1368
1369 #ifdef ON_THE_FLY_COMPRESSION
1370 cond = dcmff.getDataset()->chooseRepresentation(netTransfer.getXfer(), NULL);
1371 if (cond.bad())
1372 {
1373 OFLOG_ERROR(storescuLogger, "No conversion to transfer syntax " << netTransfer.getXferName() << " possible!");
1374 renameFile(fname, ".bad");
1375 return cond;
1376 }
1377 #endif
1378
1379 /* prepare the transmission of data */
1380 bzero(OFreinterpret_cast(char *, &req), sizeof(req));
1381 req.MessageID = msgId;
1382 OFStandard::strlcpy(req.AffectedSOPClassUID, sopClass, sizeof(req.AffectedSOPClassUID));
1383 OFStandard::strlcpy(req.AffectedSOPInstanceUID, sopInstance, sizeof(req.AffectedSOPInstanceUID));
1384 req.DataSetType = DIMSE_DATASET_PRESENT;
1385 req.Priority = DIMSE_PRIORITY_MEDIUM;
1386
1387 /* if required, dump some more general information */
1388 OFLOG_INFO(storescuLogger, "Sending Store Request (MsgID " << msgId << ", "
1389 << dcmSOPClassUIDToModality(sopClass, "OT") << ")");
1390
1391 /* finally conduct transmission of data */
1392 cond = DIMSE_storeUser(assoc, presID, &req,
1393 NULL, dcmff.getDataset(), progressCallback, NULL,
1394 opt_blockMode, opt_dimse_timeout,
1395 &rsp, &statusDetail, NULL, OFstatic_cast(long, OFStandard::getFileSize(fname)));
1396
1397 /*
1398 * If store command completed normally, with a status
1399 * of success or some warning then the image was accepted.
1400 */
1401 if (cond == EC_Normal && (rsp.DimseStatus == STATUS_Success || DICOM_WARNING_STATUS(rsp.DimseStatus)))
1402 {
1403 unsuccessfulStoreEncountered = OFFalse;
1404 renameFile(fname, ".done");
1405 }
1406 else
1407 {
1408 renameFile(fname, ".bad");
1409 }
1410
1411 /* remember the response's status for later transmissions of data */
1412 lastStatusCode = rsp.DimseStatus;
1413
1414 /* dump some more general information */
1415 if (cond == EC_Normal)
1416 {
1417 OFString temp_str;
1418 if (storescuLogger.isEnabledFor(OFLogger::DEBUG_LOG_LEVEL))
1419 {
1420 OFLOG_INFO(storescuLogger, "Received Store Response");
1421 OFLOG_DEBUG(storescuLogger, DIMSE_dumpMessage(temp_str, rsp, DIMSE_INCOMING, NULL, presID));
1422 } else {
1423 OFLOG_INFO(storescuLogger, "Received Store Response (" << DU_cstoreStatusString(rsp.DimseStatus) << ")");
1424 }
1425 }
1426 else
1427 {
1428 OFString temp_str;
1429 OFLOG_ERROR(storescuLogger, "Store Failed, file: " << fname << ":" << OFendl << DimseCondition::dump(temp_str, cond));
1430 }
1431
1432 /* dump status detail information if there is some */
1433 if (statusDetail != NULL) {
1434 OFLOG_DEBUG(storescuLogger, "Status Detail:" << OFendl << DcmObject::PrintHelper(*statusDetail));
1435 delete statusDetail;
1436 }
1437 /* return */
1438 return cond;
1439 }
1440
1441
1442 static OFCondition
cstore(T_ASC_Association * assoc,const OFString & fname)1443 cstore(T_ASC_Association *assoc, const OFString &fname)
1444 /*
1445 * This function will process the given file as often as is specified by opt_repeatCount.
1446 * "Process" in this case means "read file, send C-STORE-RQ, receive C-STORE-RSP".
1447 *
1448 * Parameters:
1449 * assoc - [in] The association (network connection to another DICOM application).
1450 * fname - [in] Name of the file which shall be processed.
1451 */
1452 {
1453 OFCondition cond = EC_Normal;
1454
1455 /* opt_repeatCount specifies how many times a certain file shall be processed */
1456 int n = OFstatic_cast(int, opt_repeatCount);
1457
1458 /* as long as no error occurred and the counter does not equal 0 */
1459 while ((cond.good()) && n-- && !(opt_haltOnUnsuccessfulStore && unsuccessfulStoreEncountered))
1460 {
1461 /* process file (read file, send C-STORE-RQ, receive C-STORE-RSP) */
1462 cond = storeSCU(assoc, fname.c_str());
1463 }
1464
1465 // we don't want to return an error code if --no-halt was specified.
1466 if (!opt_haltOnUnsuccessfulStore)
1467 {
1468 cond = EC_Normal;
1469 }
1470
1471 /* return result value */
1472 return cond;
1473 }
1474
1475
1476 static OFBool
findSOPClassAndInstanceInFile(const char * fname,char * sopClass,size_t sopClassSize,char * sopInstance,size_t sopInstanceSize)1477 findSOPClassAndInstanceInFile(
1478 const char *fname,
1479 char *sopClass,
1480 size_t sopClassSize,
1481 char *sopInstance,
1482 size_t sopInstanceSize)
1483 {
1484 DcmFileFormat ff;
1485 if (!ff.loadFile(fname, EXS_Unknown, EGL_noChange, DCM_MaxReadLength, opt_readMode).good())
1486 return OFFalse;
1487
1488 /* look in the meta-header first */
1489 OFBool found = DU_findSOPClassAndInstanceInDataSet(ff.getMetaInfo(), sopClass, sopClassSize, sopInstance, sopInstanceSize, opt_correctUIDPadding);
1490
1491 if (!found)
1492 found = DU_findSOPClassAndInstanceInDataSet(ff.getDataset(), sopClass, sopClassSize, sopInstance, sopInstanceSize, opt_correctUIDPadding);
1493
1494 return found;
1495 }
1496
1497
1498 static OFCondition
configureUserIdentityRequest(T_ASC_Parameters * params)1499 configureUserIdentityRequest(T_ASC_Parameters *params)
1500 {
1501 OFCondition cond = EC_Normal;
1502 switch (opt_identMode)
1503 {
1504 case ASC_USER_IDENTITY_USER:
1505 {
1506 cond = ASC_setIdentRQUserOnly(params, opt_user, opt_identResponse);
1507 return cond;
1508 }
1509 case ASC_USER_IDENTITY_USER_PASSWORD:
1510 {
1511 cond = ASC_setIdentRQUserPassword(params, opt_user, opt_password, opt_identResponse);
1512 return cond;
1513 }
1514 case ASC_USER_IDENTITY_KERBEROS:
1515 case ASC_USER_IDENTITY_SAML:
1516 case ASC_USER_IDENTITY_JWT:
1517 {
1518 OFFile identFile;
1519 if (!identFile.fopen(opt_identFile.c_str(), "rb"))
1520 {
1521 OFString openerror;
1522 identFile.getLastErrorString(openerror);
1523 OFLOG_ERROR(storescuLogger, "Unable to open Kerberos, SAML or JWT file: " << openerror);
1524 return EC_IllegalCall;
1525 }
1526 // determine file size
1527 offile_off_t result = identFile.fseek(0, SEEK_END);
1528 if (result != 0)
1529 return EC_IllegalParameter;
1530 offile_off_t filesize = identFile.ftell();
1531 identFile.rewind();
1532 if (filesize > 65535)
1533 {
1534 OFLOG_INFO(storescuLogger, "Kerberos, SAML or JWT file is larger than 65535 bytes, bytes after that position are ignored");
1535 filesize = 65535;
1536 }
1537
1538 char *buf = new char[OFstatic_cast(unsigned int, filesize)];
1539 size_t bytesRead = identFile.fread(buf, 1, OFstatic_cast(size_t, filesize));
1540 identFile.fclose();
1541 if (bytesRead == 0)
1542 {
1543 OFLOG_ERROR(storescuLogger, "Unable to read Kerberos, SAML or JWT info from file: File empty?");
1544 delete[] buf;
1545 return EC_IllegalCall;
1546 }
1547 // Casting to Uint16 should be safe since it is checked above that file
1548 // size does not exceed 65535 bytes.
1549 if (opt_identMode == ASC_USER_IDENTITY_KERBEROS)
1550 cond = ASC_setIdentRQKerberos(params, buf, OFstatic_cast(Uint16,bytesRead), opt_identResponse);
1551 else if (opt_identMode == ASC_USER_IDENTITY_SAML)
1552 cond = ASC_setIdentRQSaml(params, buf, OFstatic_cast(Uint16,bytesRead), opt_identResponse);
1553 else // JWT
1554 cond = ASC_setIdentRQJwt(params, buf, OFstatic_cast(Uint16,bytesRead), opt_identResponse);
1555 delete[] buf;
1556 break;
1557 }
1558 default:
1559 {
1560 cond = EC_IllegalCall;
1561 }
1562 }
1563 if (cond.bad())
1564 {
1565 OFString temp_str;
1566 OFLOG_FATAL(storescuLogger, DimseCondition::dump(temp_str, cond));
1567 }
1568 return cond;
1569 }
1570
1571 static OFCondition
checkUserIdentityResponse(T_ASC_Parameters * params)1572 checkUserIdentityResponse(T_ASC_Parameters *params)
1573 {
1574 if (params == NULL)
1575 return ASC_NULLKEY;
1576
1577 /* So far it is only checked whether a requested, positive response was
1578 actually received */
1579
1580 // In case we sent no user identity request, there are no checks at all
1581 if ((opt_identMode == ASC_USER_IDENTITY_NONE) || (!opt_identResponse))
1582 return EC_Normal;
1583
1584 // If positive response was requested, we expect a corresponding response
1585 if ((opt_identMode == ASC_USER_IDENTITY_USER) || (opt_identMode == ASC_USER_IDENTITY_USER_PASSWORD))
1586 {
1587 UserIdentityNegotiationSubItemAC *rsp = params->DULparams.ackUserIdentNeg;
1588 if (rsp == NULL)
1589 {
1590 OFLOG_ERROR(storescuLogger, "User Identity Negotiation failed: Positive response requested but none received");
1591 return ASC_USERIDENTIFICATIONFAILED;
1592 }
1593 }
1594 return EC_Normal;
1595 }
1596