comparison Core/DicomNetworking/Internals/CommandDispatcher.cpp @ 2382:7284093111b0

big reorganization to cleanly separate framework vs. server
author Sebastien Jodogne <s.jodogne@gmail.com>
date Tue, 29 Aug 2017 21:17:35 +0200
parents OrthancServer/Internals/CommandDispatcher.cpp@b8969010b534
children 878b59270859
comparison
equal deleted inserted replaced
2381:b8969010b534 2382:7284093111b0
1 /**
2 * Orthanc - A Lightweight, RESTful DICOM Store
3 * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
4 * Department, University Hospital of Liege, Belgium
5 * Copyright (C) 2017 Osimis, Belgium
6 *
7 * This program is free software: you can redistribute it and/or
8 * modify it under the terms of the GNU General Public License as
9 * published by the Free Software Foundation, either version 3 of the
10 * License, or (at your option) any later version.
11 *
12 * In addition, as a special exception, the copyright holders of this
13 * program give permission to link the code of its release with the
14 * OpenSSL project's "OpenSSL" library (or with modified versions of it
15 * that use the same license as the "OpenSSL" library), and distribute
16 * the linked executables. You must obey the GNU General Public License
17 * in all respects for all of the code used other than "OpenSSL". If you
18 * modify file(s) with this exception, you may extend this exception to
19 * your version of the file(s), but you are not obligated to do so. If
20 * you do not wish to do so, delete this exception statement from your
21 * version. If you delete this exception statement from all source files
22 * in the program, then also delete it here.
23 *
24 * This program is distributed in the hope that it will be useful, but
25 * WITHOUT ANY WARRANTY; without even the implied warranty of
26 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
27 * General Public License for more details.
28 *
29 * You should have received a copy of the GNU General Public License
30 * along with this program. If not, see <http://www.gnu.org/licenses/>.
31 **/
32
33
34
35
36 /*=========================================================================
37
38 This file is based on portions of the following project:
39
40 Program: DCMTK 3.6.0
41 Module: http://dicom.offis.de/dcmtk.php.en
42
43 Copyright (C) 1994-2011, OFFIS e.V.
44 All rights reserved.
45
46 This software and supporting documentation were developed by
47
48 OFFIS e.V.
49 R&D Division Health
50 Escherweg 2
51 26121 Oldenburg, Germany
52
53 Redistribution and use in source and binary forms, with or without
54 modification, are permitted provided that the following conditions
55 are met:
56
57 - Redistributions of source code must retain the above copyright
58 notice, this list of conditions and the following disclaimer.
59
60 - Redistributions in binary form must reproduce the above copyright
61 notice, this list of conditions and the following disclaimer in the
62 documentation and/or other materials provided with the distribution.
63
64 - Neither the name of OFFIS nor the names of its contributors may be
65 used to endorse or promote products derived from this software
66 without specific prior written permission.
67
68 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
69 "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
70 LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
71 A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
72 HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
73 SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
74 LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
75 DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
76 THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
77 (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
78 OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
79
80 =========================================================================*/
81
82
83 #include "../../PrecompiledHeaders.h"
84 #include "CommandDispatcher.h"
85
86 #include "FindScp.h"
87 #include "StoreScp.h"
88 #include "MoveScp.h"
89 #include "../../Toolbox.h"
90 #include "../../Logging.h"
91
92 #include <dcmtk/dcmnet/dcasccfg.h> /* for class DcmAssociationConfiguration */
93 #include <boost/lexical_cast.hpp>
94
95 static OFBool opt_rejectWithoutImplementationUID = OFFalse;
96
97
98
99 static DUL_PRESENTATIONCONTEXT *
100 findPresentationContextID(LST_HEAD * head,
101 T_ASC_PresentationContextID presentationContextID)
102 {
103 DUL_PRESENTATIONCONTEXT *pc;
104 LST_HEAD **l;
105 OFBool found = OFFalse;
106
107 if (head == NULL)
108 return NULL;
109
110 l = &head;
111 if (*l == NULL)
112 return NULL;
113
114 pc = OFstatic_cast(DUL_PRESENTATIONCONTEXT *, LST_Head(l));
115 (void)LST_Position(l, OFstatic_cast(LST_NODE *, pc));
116
117 while (pc && !found) {
118 if (pc->presentationContextID == presentationContextID) {
119 found = OFTrue;
120 } else {
121 pc = OFstatic_cast(DUL_PRESENTATIONCONTEXT *, LST_Next(l));
122 }
123 }
124 return pc;
125 }
126
127
128 /** accept all presenstation contexts for unknown SOP classes,
129 * i.e. UIDs appearing in the list of abstract syntaxes
130 * where no corresponding name is defined in the UID dictionary.
131 * @param params pointer to association parameters structure
132 * @param transferSyntax transfer syntax to accept
133 * @param acceptedRole SCU/SCP role to accept
134 */
135 static OFCondition acceptUnknownContextsWithTransferSyntax(
136 T_ASC_Parameters * params,
137 const char* transferSyntax,
138 T_ASC_SC_ROLE acceptedRole)
139 {
140 OFCondition cond = EC_Normal;
141 int n, i, k;
142 DUL_PRESENTATIONCONTEXT *dpc;
143 T_ASC_PresentationContext pc;
144 OFBool accepted = OFFalse;
145 OFBool abstractOK = OFFalse;
146
147 n = ASC_countPresentationContexts(params);
148 for (i = 0; i < n; i++)
149 {
150 cond = ASC_getPresentationContext(params, i, &pc);
151 if (cond.bad()) return cond;
152 abstractOK = OFFalse;
153 accepted = OFFalse;
154
155 if (dcmFindNameOfUID(pc.abstractSyntax) == NULL)
156 {
157 abstractOK = OFTrue;
158
159 /* check the transfer syntax */
160 for (k = 0; (k < OFstatic_cast(int, pc.transferSyntaxCount)) && !accepted; k++)
161 {
162 if (strcmp(pc.proposedTransferSyntaxes[k], transferSyntax) == 0)
163 {
164 accepted = OFTrue;
165 }
166 }
167 }
168
169 if (accepted)
170 {
171 cond = ASC_acceptPresentationContext(
172 params, pc.presentationContextID,
173 transferSyntax, acceptedRole);
174 if (cond.bad()) return cond;
175 } else {
176 T_ASC_P_ResultReason reason;
177
178 /* do not refuse if already accepted */
179 dpc = findPresentationContextID(params->DULparams.acceptedPresentationContext,
180 pc.presentationContextID);
181 if ((dpc == NULL) || ((dpc != NULL) && (dpc->result != ASC_P_ACCEPTANCE)))
182 {
183
184 if (abstractOK) {
185 reason = ASC_P_TRANSFERSYNTAXESNOTSUPPORTED;
186 } else {
187 reason = ASC_P_ABSTRACTSYNTAXNOTSUPPORTED;
188 }
189 /*
190 * If previously this presentation context was refused
191 * because of bad transfer syntax let it stay that way.
192 */
193 if ((dpc != NULL) && (dpc->result == ASC_P_TRANSFERSYNTAXESNOTSUPPORTED))
194 reason = ASC_P_TRANSFERSYNTAXESNOTSUPPORTED;
195
196 cond = ASC_refusePresentationContext(params, pc.presentationContextID, reason);
197 if (cond.bad()) return cond;
198 }
199 }
200 }
201 return EC_Normal;
202 }
203
204
205 /** accept all presenstation contexts for unknown SOP classes,
206 * i.e. UIDs appearing in the list of abstract syntaxes
207 * where no corresponding name is defined in the UID dictionary.
208 * This method is passed a list of "preferred" transfer syntaxes.
209 * @param params pointer to association parameters structure
210 * @param transferSyntax transfer syntax to accept
211 * @param acceptedRole SCU/SCP role to accept
212 */
213 static OFCondition acceptUnknownContextsWithPreferredTransferSyntaxes(
214 T_ASC_Parameters * params,
215 const char* transferSyntaxes[], int transferSyntaxCount,
216 T_ASC_SC_ROLE acceptedRole = ASC_SC_ROLE_DEFAULT)
217 {
218 OFCondition cond = EC_Normal;
219 /*
220 ** Accept in the order "least wanted" to "most wanted" transfer
221 ** syntax. Accepting a transfer syntax will override previously
222 ** accepted transfer syntaxes.
223 */
224 for (int i = transferSyntaxCount - 1; i >= 0; i--)
225 {
226 cond = acceptUnknownContextsWithTransferSyntax(params, transferSyntaxes[i], acceptedRole);
227 if (cond.bad()) return cond;
228 }
229 return cond;
230 }
231
232
233
234 namespace Orthanc
235 {
236 namespace Internals
237 {
238 /**
239 * EXTRACT OF FILE "dcmdata/libsrc/dcuid.cc" FROM DCMTK 3.6.0
240 * (dcmAllStorageSOPClassUIDs).
241 *
242 * an array of const strings containing all known Storage SOP
243 * Classes that fit into the conventional
244 * PATIENT-STUDY-SERIES-INSTANCE information model,
245 * i.e. everything a Storage SCP might want to store in a PACS.
246 * Special cases such as hanging protocol storage or the Storage
247 * SOP Class are not included in this list.
248 *
249 * THIS LIST CONTAINS ALL STORAGE SOP CLASSES INCLUDING RETIRED
250 * ONES AND IS LARGER THAN 64 ENTRIES.
251 */
252
253 const char* orthancStorageSOPClassUIDs[] =
254 {
255 UID_AmbulatoryECGWaveformStorage,
256 UID_ArterialPulseWaveformStorage,
257 UID_AutorefractionMeasurementsStorage,
258 UID_BasicStructuredDisplayStorage,
259 UID_BasicTextSRStorage,
260 UID_BasicVoiceAudioWaveformStorage,
261 UID_BlendingSoftcopyPresentationStateStorage,
262 UID_BreastTomosynthesisImageStorage,
263 UID_CardiacElectrophysiologyWaveformStorage,
264 UID_ChestCADSRStorage,
265 UID_ColonCADSRStorage,
266 UID_ColorSoftcopyPresentationStateStorage,
267 UID_ComprehensiveSRStorage,
268 UID_ComputedRadiographyImageStorage,
269 UID_CTImageStorage,
270 UID_DeformableSpatialRegistrationStorage,
271 UID_DigitalIntraOralXRayImageStorageForPresentation,
272 UID_DigitalIntraOralXRayImageStorageForProcessing,
273 UID_DigitalMammographyXRayImageStorageForPresentation,
274 UID_DigitalMammographyXRayImageStorageForProcessing,
275 UID_DigitalXRayImageStorageForPresentation,
276 UID_DigitalXRayImageStorageForProcessing,
277 UID_EncapsulatedCDAStorage,
278 UID_EncapsulatedPDFStorage,
279 UID_EnhancedCTImageStorage,
280 UID_EnhancedMRColorImageStorage,
281 UID_EnhancedMRImageStorage,
282 UID_EnhancedPETImageStorage,
283 UID_EnhancedSRStorage,
284 UID_EnhancedUSVolumeStorage,
285 UID_EnhancedXAImageStorage,
286 UID_EnhancedXRFImageStorage,
287 UID_GeneralAudioWaveformStorage,
288 UID_GeneralECGWaveformStorage,
289 UID_GenericImplantTemplateStorage,
290 UID_GrayscaleSoftcopyPresentationStateStorage,
291 UID_HemodynamicWaveformStorage,
292 UID_ImplantAssemblyTemplateStorage,
293 UID_ImplantationPlanSRDocumentStorage,
294 UID_ImplantTemplateGroupStorage,
295 UID_IntraocularLensCalculationsStorage,
296 UID_KeratometryMeasurementsStorage,
297 UID_KeyObjectSelectionDocumentStorage,
298 UID_LensometryMeasurementsStorage,
299 UID_MacularGridThicknessAndVolumeReportStorage,
300 UID_MammographyCADSRStorage,
301 UID_MRImageStorage,
302 UID_MRSpectroscopyStorage,
303 UID_MultiframeGrayscaleByteSecondaryCaptureImageStorage,
304 UID_MultiframeGrayscaleWordSecondaryCaptureImageStorage,
305 UID_MultiframeSingleBitSecondaryCaptureImageStorage,
306 UID_MultiframeTrueColorSecondaryCaptureImageStorage,
307 UID_NuclearMedicineImageStorage,
308 UID_OphthalmicAxialMeasurementsStorage,
309 UID_OphthalmicPhotography16BitImageStorage,
310 UID_OphthalmicPhotography8BitImageStorage,
311 UID_OphthalmicTomographyImageStorage,
312 UID_OphthalmicVisualFieldStaticPerimetryMeasurementsStorage,
313 UID_PositronEmissionTomographyImageStorage,
314 UID_ProcedureLogStorage,
315 UID_PseudoColorSoftcopyPresentationStateStorage,
316 UID_RawDataStorage,
317 UID_RealWorldValueMappingStorage,
318 UID_RespiratoryWaveformStorage,
319 UID_RTBeamsTreatmentRecordStorage,
320 UID_RTBrachyTreatmentRecordStorage,
321 UID_RTDoseStorage,
322 UID_RTImageStorage,
323 UID_RTIonBeamsTreatmentRecordStorage,
324 UID_RTIonPlanStorage,
325 UID_RTPlanStorage,
326 UID_RTStructureSetStorage,
327 UID_RTTreatmentSummaryRecordStorage,
328 UID_SecondaryCaptureImageStorage,
329 UID_SegmentationStorage,
330 UID_SpatialFiducialsStorage,
331 UID_SpatialRegistrationStorage,
332 UID_SpectaclePrescriptionReportStorage,
333 UID_StereometricRelationshipStorage,
334 UID_SubjectiveRefractionMeasurementsStorage,
335 UID_SurfaceSegmentationStorage,
336 UID_TwelveLeadECGWaveformStorage,
337 UID_UltrasoundImageStorage,
338 UID_UltrasoundMultiframeImageStorage,
339 UID_VideoEndoscopicImageStorage,
340 UID_VideoMicroscopicImageStorage,
341 UID_VideoPhotographicImageStorage,
342 UID_VisualAcuityMeasurementsStorage,
343 UID_VLEndoscopicImageStorage,
344 UID_VLMicroscopicImageStorage,
345 UID_VLPhotographicImageStorage,
346 UID_VLSlideCoordinatesMicroscopicImageStorage,
347 UID_VLWholeSlideMicroscopyImageStorage,
348 UID_XAXRFGrayscaleSoftcopyPresentationStateStorage,
349 UID_XRay3DAngiographicImageStorage,
350 UID_XRay3DCraniofacialImageStorage,
351 UID_XRayAngiographicImageStorage,
352 UID_XRayRadiationDoseSRStorage,
353 UID_XRayRadiofluoroscopicImageStorage,
354 // retired
355 UID_RETIRED_HardcopyColorImageStorage,
356 UID_RETIRED_HardcopyGrayscaleImageStorage,
357 UID_RETIRED_NuclearMedicineImageStorage,
358 UID_RETIRED_StandaloneCurveStorage,
359 UID_RETIRED_StandaloneModalityLUTStorage,
360 UID_RETIRED_StandaloneOverlayStorage,
361 UID_RETIRED_StandalonePETCurveStorage,
362 UID_RETIRED_StandaloneVOILUTStorage,
363 UID_RETIRED_StoredPrintStorage,
364 UID_RETIRED_UltrasoundImageStorage,
365 UID_RETIRED_UltrasoundMultiframeImageStorage,
366 UID_RETIRED_VLImageStorage,
367 UID_RETIRED_VLMultiFrameImageStorage,
368 UID_RETIRED_XRayAngiographicBiPlaneImageStorage,
369 // draft
370 UID_DRAFT_SRAudioStorage,
371 UID_DRAFT_SRComprehensiveStorage,
372 UID_DRAFT_SRDetailStorage,
373 UID_DRAFT_SRTextStorage,
374 UID_DRAFT_WaveformStorage,
375 UID_DRAFT_RTBeamsDeliveryInstructionStorage,
376 NULL
377 };
378
379 const int orthancStorageSOPClassUIDsCount = (sizeof(orthancStorageSOPClassUIDs) / sizeof(const char*)) - 1;
380
381
382
383 OFCondition AssociationCleanup(T_ASC_Association *assoc)
384 {
385 OFCondition cond = ASC_dropSCPAssociation(assoc);
386 if (cond.bad())
387 {
388 LOG(FATAL) << cond.text();
389 return cond;
390 }
391
392 cond = ASC_destroyAssociation(&assoc);
393 if (cond.bad())
394 {
395 LOG(FATAL) << cond.text();
396 return cond;
397 }
398
399 return cond;
400 }
401
402
403
404 CommandDispatcher* AcceptAssociation(const DicomServer& server, T_ASC_Network *net)
405 {
406 DcmAssociationConfiguration asccfg;
407 char buf[BUFSIZ];
408 T_ASC_Association *assoc;
409 OFCondition cond;
410 OFString sprofile;
411 OFString temp_str;
412
413 std::vector<const char*> knownAbstractSyntaxes;
414
415 // For C-STORE
416 if (server.HasStoreRequestHandlerFactory())
417 {
418 knownAbstractSyntaxes.push_back(UID_VerificationSOPClass);
419 }
420
421 // For C-FIND
422 if (server.HasFindRequestHandlerFactory())
423 {
424 knownAbstractSyntaxes.push_back(UID_FINDPatientRootQueryRetrieveInformationModel);
425 knownAbstractSyntaxes.push_back(UID_FINDStudyRootQueryRetrieveInformationModel);
426 }
427
428 if (server.HasWorklistRequestHandlerFactory())
429 {
430 knownAbstractSyntaxes.push_back(UID_FINDModalityWorklistInformationModel);
431 }
432
433 // For C-MOVE
434 if (server.HasMoveRequestHandlerFactory())
435 {
436 knownAbstractSyntaxes.push_back(UID_MOVEStudyRootQueryRetrieveInformationModel);
437 knownAbstractSyntaxes.push_back(UID_MOVEPatientRootQueryRetrieveInformationModel);
438 }
439
440 cond = ASC_receiveAssociation(net, &assoc,
441 /*opt_maxPDU*/ ASC_DEFAULTMAXPDU,
442 NULL, NULL,
443 /*opt_secureConnection*/ OFFalse,
444 DUL_NOBLOCK, 1);
445
446 if (cond == DUL_NOASSOCIATIONREQUEST)
447 {
448 // Timeout
449 AssociationCleanup(assoc);
450 return NULL;
451 }
452
453 // if some kind of error occured, take care of it
454 if (cond.bad())
455 {
456 LOG(ERROR) << "Receiving Association failed: " << cond.text();
457 // no matter what kind of error occurred, we need to do a cleanup
458 AssociationCleanup(assoc);
459 return NULL;
460 }
461
462 // Retrieve the AET and the IP address of the remote modality
463 std::string remoteAet;
464 std::string remoteIp;
465 std::string calledAet;
466
467 {
468 DIC_AE remoteAet_C;
469 DIC_AE calledAet_C;
470 DIC_AE remoteIp_C;
471 DIC_AE calledIP_C;
472 if (ASC_getAPTitles(assoc->params, remoteAet_C, calledAet_C, NULL).bad() ||
473 ASC_getPresentationAddresses(assoc->params, remoteIp_C, calledIP_C).bad())
474 {
475 T_ASC_RejectParameters rej =
476 {
477 ASC_RESULT_REJECTEDPERMANENT,
478 ASC_SOURCE_SERVICEUSER,
479 ASC_REASON_SU_NOREASON
480 };
481 ASC_rejectAssociation(assoc, &rej);
482 AssociationCleanup(assoc);
483 return NULL;
484 }
485
486 remoteIp = std::string(/*OFSTRING_GUARD*/(remoteIp_C));
487 remoteAet = std::string(/*OFSTRING_GUARD*/(remoteAet_C));
488 calledAet = (/*OFSTRING_GUARD*/(calledAet_C));
489 }
490
491 LOG(INFO) << "Association Received from AET " << remoteAet
492 << " on IP " << remoteIp;
493
494
495 std::vector<const char*> transferSyntaxes;
496
497 // This is the list of the transfer syntaxes that were supported up to Orthanc 0.7.1
498 transferSyntaxes.push_back(UID_LittleEndianExplicitTransferSyntax);
499 transferSyntaxes.push_back(UID_BigEndianExplicitTransferSyntax);
500 transferSyntaxes.push_back(UID_LittleEndianImplicitTransferSyntax);
501
502 // New transfer syntaxes supported since Orthanc 0.7.2
503 if (!server.HasApplicationEntityFilter() ||
504 server.GetApplicationEntityFilter().IsAllowedTransferSyntax(remoteIp, remoteAet, calledAet, TransferSyntax_Deflated))
505 {
506 transferSyntaxes.push_back(UID_DeflatedExplicitVRLittleEndianTransferSyntax);
507 }
508
509 if (!server.HasApplicationEntityFilter() ||
510 server.GetApplicationEntityFilter().IsAllowedTransferSyntax(remoteIp, remoteAet, calledAet, TransferSyntax_Jpeg))
511 {
512 transferSyntaxes.push_back(UID_JPEGProcess1TransferSyntax);
513 transferSyntaxes.push_back(UID_JPEGProcess2_4TransferSyntax);
514 transferSyntaxes.push_back(UID_JPEGProcess3_5TransferSyntax);
515 transferSyntaxes.push_back(UID_JPEGProcess6_8TransferSyntax);
516 transferSyntaxes.push_back(UID_JPEGProcess7_9TransferSyntax);
517 transferSyntaxes.push_back(UID_JPEGProcess10_12TransferSyntax);
518 transferSyntaxes.push_back(UID_JPEGProcess11_13TransferSyntax);
519 transferSyntaxes.push_back(UID_JPEGProcess14TransferSyntax);
520 transferSyntaxes.push_back(UID_JPEGProcess15TransferSyntax);
521 transferSyntaxes.push_back(UID_JPEGProcess16_18TransferSyntax);
522 transferSyntaxes.push_back(UID_JPEGProcess17_19TransferSyntax);
523 transferSyntaxes.push_back(UID_JPEGProcess20_22TransferSyntax);
524 transferSyntaxes.push_back(UID_JPEGProcess21_23TransferSyntax);
525 transferSyntaxes.push_back(UID_JPEGProcess24_26TransferSyntax);
526 transferSyntaxes.push_back(UID_JPEGProcess25_27TransferSyntax);
527 transferSyntaxes.push_back(UID_JPEGProcess28TransferSyntax);
528 transferSyntaxes.push_back(UID_JPEGProcess29TransferSyntax);
529 transferSyntaxes.push_back(UID_JPEGProcess14SV1TransferSyntax);
530 transferSyntaxes.push_back(UID_JPEGLSLosslessTransferSyntax);
531 transferSyntaxes.push_back(UID_JPEGLSLossyTransferSyntax);
532 }
533
534 if (!server.HasApplicationEntityFilter() ||
535 server.GetApplicationEntityFilter().IsAllowedTransferSyntax(remoteIp, remoteAet, calledAet, TransferSyntax_Jpeg2000))
536 {
537 transferSyntaxes.push_back(UID_JPEG2000LosslessOnlyTransferSyntax);
538 transferSyntaxes.push_back(UID_JPEG2000TransferSyntax);
539 }
540
541 if (!server.HasApplicationEntityFilter() ||
542 server.GetApplicationEntityFilter().IsAllowedTransferSyntax(remoteIp, remoteAet, calledAet, TransferSyntax_JpegLossless))
543 {
544 transferSyntaxes.push_back(UID_JPEG2000LosslessOnlyTransferSyntax);
545 transferSyntaxes.push_back(UID_JPEG2000TransferSyntax);
546 transferSyntaxes.push_back(UID_JPEG2000Part2MulticomponentImageCompressionLosslessOnlyTransferSyntax);
547 transferSyntaxes.push_back(UID_JPEG2000Part2MulticomponentImageCompressionTransferSyntax);
548 }
549
550 if (!server.HasApplicationEntityFilter() ||
551 server.GetApplicationEntityFilter().IsAllowedTransferSyntax(remoteIp, remoteAet, calledAet, TransferSyntax_Jpip))
552 {
553 transferSyntaxes.push_back(UID_JPIPReferencedTransferSyntax);
554 transferSyntaxes.push_back(UID_JPIPReferencedDeflateTransferSyntax);
555 }
556
557 if (!server.HasApplicationEntityFilter() ||
558 server.GetApplicationEntityFilter().IsAllowedTransferSyntax(remoteIp, remoteAet, calledAet, TransferSyntax_Mpeg2))
559 {
560 transferSyntaxes.push_back(UID_MPEG2MainProfileAtMainLevelTransferSyntax);
561 transferSyntaxes.push_back(UID_MPEG2MainProfileAtHighLevelTransferSyntax);
562 }
563
564 if (!server.HasApplicationEntityFilter() ||
565 server.GetApplicationEntityFilter().IsAllowedTransferSyntax(remoteIp, remoteAet, calledAet, TransferSyntax_Rle))
566 {
567 transferSyntaxes.push_back(UID_RLELosslessTransferSyntax);
568 }
569
570 /* accept the Verification SOP Class if presented */
571 cond = ASC_acceptContextsWithPreferredTransferSyntaxes(assoc->params, &knownAbstractSyntaxes[0], knownAbstractSyntaxes.size(), &transferSyntaxes[0], transferSyntaxes.size());
572 if (cond.bad())
573 {
574 LOG(INFO) << cond.text();
575 AssociationCleanup(assoc);
576 return NULL;
577 }
578
579 /* the array of Storage SOP Class UIDs comes from dcuid.h */
580 cond = ASC_acceptContextsWithPreferredTransferSyntaxes(assoc->params, orthancStorageSOPClassUIDs, orthancStorageSOPClassUIDsCount, &transferSyntaxes[0], transferSyntaxes.size());
581 if (cond.bad())
582 {
583 LOG(INFO) << cond.text();
584 AssociationCleanup(assoc);
585 return NULL;
586 }
587
588 if (!server.HasApplicationEntityFilter() ||
589 server.GetApplicationEntityFilter().IsUnknownSopClassAccepted(remoteIp, remoteAet, calledAet))
590 {
591 /*
592 * Promiscous mode is enabled: Accept everything not known not
593 * to be a storage SOP class.
594 **/
595 cond = acceptUnknownContextsWithPreferredTransferSyntaxes(
596 assoc->params, &transferSyntaxes[0], transferSyntaxes.size());
597 if (cond.bad())
598 {
599 LOG(INFO) << cond.text();
600 AssociationCleanup(assoc);
601 return NULL;
602 }
603 }
604
605 /* set our app title */
606 ASC_setAPTitles(assoc->params, NULL, NULL, server.GetApplicationEntityTitle().c_str());
607
608 /* acknowledge or reject this association */
609 cond = ASC_getApplicationContextName(assoc->params, buf);
610 if ((cond.bad()) || strcmp(buf, UID_StandardApplicationContext) != 0)
611 {
612 /* reject: the application context name is not supported */
613 T_ASC_RejectParameters rej =
614 {
615 ASC_RESULT_REJECTEDPERMANENT,
616 ASC_SOURCE_SERVICEUSER,
617 ASC_REASON_SU_APPCONTEXTNAMENOTSUPPORTED
618 };
619
620 LOG(INFO) << "Association Rejected: Bad Application Context Name: " << buf;
621 cond = ASC_rejectAssociation(assoc, &rej);
622 if (cond.bad())
623 {
624 LOG(INFO) << cond.text();
625 }
626 AssociationCleanup(assoc);
627 return NULL;
628 }
629
630 /* check the AETs */
631 if (!server.IsMyAETitle(calledAet))
632 {
633 LOG(WARNING) << "Rejected association, because of a bad called AET in the request (" << calledAet << ")";
634 T_ASC_RejectParameters rej =
635 {
636 ASC_RESULT_REJECTEDPERMANENT,
637 ASC_SOURCE_SERVICEUSER,
638 ASC_REASON_SU_CALLEDAETITLENOTRECOGNIZED
639 };
640 ASC_rejectAssociation(assoc, &rej);
641 AssociationCleanup(assoc);
642 return NULL;
643 }
644
645 if (server.HasApplicationEntityFilter() &&
646 !server.GetApplicationEntityFilter().IsAllowedConnection(remoteIp, remoteAet, calledAet))
647 {
648 LOG(WARNING) << "Rejected association for remote AET " << remoteAet << " on IP " << remoteIp;
649 T_ASC_RejectParameters rej =
650 {
651 ASC_RESULT_REJECTEDPERMANENT,
652 ASC_SOURCE_SERVICEUSER,
653 ASC_REASON_SU_CALLINGAETITLENOTRECOGNIZED
654 };
655 ASC_rejectAssociation(assoc, &rej);
656 AssociationCleanup(assoc);
657 return NULL;
658 }
659
660 if (opt_rejectWithoutImplementationUID &&
661 strlen(assoc->params->theirImplementationClassUID) == 0)
662 {
663 /* reject: the no implementation Class UID provided */
664 T_ASC_RejectParameters rej =
665 {
666 ASC_RESULT_REJECTEDPERMANENT,
667 ASC_SOURCE_SERVICEUSER,
668 ASC_REASON_SU_NOREASON
669 };
670
671 LOG(INFO) << "Association Rejected: No Implementation Class UID provided";
672 cond = ASC_rejectAssociation(assoc, &rej);
673 if (cond.bad())
674 {
675 LOG(INFO) << cond.text();
676 }
677 AssociationCleanup(assoc);
678 return NULL;
679 }
680
681 {
682 cond = ASC_acknowledgeAssociation(assoc);
683 if (cond.bad())
684 {
685 LOG(ERROR) << cond.text();
686 AssociationCleanup(assoc);
687 return NULL;
688 }
689 LOG(INFO) << "Association Acknowledged (Max Send PDV: " << assoc->sendPDVLength << ")";
690 if (ASC_countAcceptedPresentationContexts(assoc->params) == 0)
691 LOG(INFO) << " (but no valid presentation contexts)";
692 }
693
694 IApplicationEntityFilter* filter = server.HasApplicationEntityFilter() ? &server.GetApplicationEntityFilter() : NULL;
695 return new CommandDispatcher(server, assoc, remoteIp, remoteAet, calledAet, filter);
696 }
697
698
699 CommandDispatcher::CommandDispatcher(const DicomServer& server,
700 T_ASC_Association* assoc,
701 const std::string& remoteIp,
702 const std::string& remoteAet,
703 const std::string& calledAet,
704 IApplicationEntityFilter* filter) :
705 server_(server),
706 assoc_(assoc),
707 remoteIp_(remoteIp),
708 remoteAet_(remoteAet),
709 calledAet_(calledAet),
710 filter_(filter)
711 {
712 associationTimeout_ = server.GetAssociationTimeout();
713 elapsedTimeSinceLastCommand_ = 0;
714 }
715
716
717 CommandDispatcher::~CommandDispatcher()
718 {
719 try
720 {
721 AssociationCleanup(assoc_);
722 }
723 catch (...)
724 {
725 LOG(ERROR) << "Some association was not cleanly aborted";
726 }
727 }
728
729
730 bool CommandDispatcher::Step()
731 /*
732 * This function receives DIMSE commmands over the network connection
733 * and handles these commands correspondingly. Note that in case of
734 * storscp only C-ECHO-RQ and C-STORE-RQ commands can be processed.
735 */
736 {
737 bool finished = false;
738
739 // receive a DIMSE command over the network, with a timeout of 1 second
740 DcmDataset *statusDetail = NULL;
741 T_ASC_PresentationContextID presID = 0;
742 T_DIMSE_Message msg;
743
744 OFCondition cond = DIMSE_receiveCommand(assoc_, DIMSE_NONBLOCKING, 1, &presID, &msg, &statusDetail);
745 elapsedTimeSinceLastCommand_++;
746
747 // if the command which was received has extra status
748 // detail information, dump this information
749 if (statusDetail != NULL)
750 {
751 //LOG4CPP_WARN(Internals::GetLogger(), "Status Detail:" << OFendl << DcmObject::PrintHelper(*statusDetail));
752 delete statusDetail;
753 }
754
755 if (cond == DIMSE_OUTOFRESOURCES)
756 {
757 finished = true;
758 }
759 else if (cond == DIMSE_NODATAAVAILABLE)
760 {
761 // Timeout due to DIMSE_NONBLOCKING
762 if (associationTimeout_ != 0 &&
763 elapsedTimeSinceLastCommand_ >= associationTimeout_)
764 {
765 // This timeout is actually a association timeout
766 finished = true;
767 }
768 }
769 else if (cond == EC_Normal)
770 {
771 // Reset the association timeout counter
772 elapsedTimeSinceLastCommand_ = 0;
773
774 // Convert the type of request to Orthanc's internal type
775 bool supported = false;
776 DicomRequestType request;
777 switch (msg.CommandField)
778 {
779 case DIMSE_C_ECHO_RQ:
780 request = DicomRequestType_Echo;
781 supported = true;
782 break;
783
784 case DIMSE_C_STORE_RQ:
785 request = DicomRequestType_Store;
786 supported = true;
787 break;
788
789 case DIMSE_C_MOVE_RQ:
790 request = DicomRequestType_Move;
791 supported = true;
792 break;
793
794 case DIMSE_C_FIND_RQ:
795 request = DicomRequestType_Find;
796 supported = true;
797 break;
798
799 default:
800 // we cannot handle this kind of message
801 cond = DIMSE_BADCOMMANDTYPE;
802 LOG(ERROR) << "cannot handle command: 0x" << std::hex << msg.CommandField;
803 break;
804 }
805
806
807 // Check whether this request is allowed by the security filter
808 if (supported &&
809 filter_ != NULL &&
810 !filter_->IsAllowedRequest(remoteIp_, remoteAet_, calledAet_, request))
811 {
812 LOG(WARNING) << "Rejected " << EnumerationToString(request)
813 << " request from remote DICOM modality with AET \""
814 << remoteAet_ << "\" and hostname \"" << remoteIp_ << "\"";
815 cond = DIMSE_ILLEGALASSOCIATION;
816 supported = false;
817 finished = true;
818 }
819
820 // in case we received a supported message, process this command
821 if (supported)
822 {
823 // If anything goes wrong, there will be a "BADCOMMANDTYPE" answer
824 cond = DIMSE_BADCOMMANDTYPE;
825
826 switch (request)
827 {
828 case DicomRequestType_Echo:
829 cond = EchoScp(assoc_, &msg, presID);
830 break;
831
832 case DicomRequestType_Store:
833 if (server_.HasStoreRequestHandlerFactory()) // Should always be true
834 {
835 std::auto_ptr<IStoreRequestHandler> handler
836 (server_.GetStoreRequestHandlerFactory().ConstructStoreRequestHandler());
837
838 if (handler.get() != NULL)
839 {
840 cond = Internals::storeScp(assoc_, &msg, presID, *handler, remoteIp_);
841 }
842 }
843 break;
844
845 case DicomRequestType_Move:
846 if (server_.HasMoveRequestHandlerFactory()) // Should always be true
847 {
848 std::auto_ptr<IMoveRequestHandler> handler
849 (server_.GetMoveRequestHandlerFactory().ConstructMoveRequestHandler());
850
851 if (handler.get() != NULL)
852 {
853 cond = Internals::moveScp(assoc_, &msg, presID, *handler, remoteIp_, remoteAet_, calledAet_);
854 }
855 }
856 break;
857
858 case DicomRequestType_Find:
859 if (server_.HasFindRequestHandlerFactory() || // Should always be true
860 server_.HasWorklistRequestHandlerFactory())
861 {
862 std::auto_ptr<IFindRequestHandler> findHandler;
863 if (server_.HasFindRequestHandlerFactory())
864 {
865 findHandler.reset(server_.GetFindRequestHandlerFactory().ConstructFindRequestHandler());
866 }
867
868 std::auto_ptr<IWorklistRequestHandler> worklistHandler;
869 if (server_.HasWorklistRequestHandlerFactory())
870 {
871 worklistHandler.reset(server_.GetWorklistRequestHandlerFactory().ConstructWorklistRequestHandler());
872 }
873
874 cond = Internals::findScp(assoc_, &msg, presID, server_.GetRemoteModalities(),
875 findHandler.get(), worklistHandler.get(),
876 remoteIp_, remoteAet_, calledAet_);
877 }
878 break;
879
880 default:
881 // Should never happen
882 break;
883 }
884 }
885 }
886 else
887 {
888 // Bad status, which indicates the closing of the connection by
889 // the peer or a network error
890 finished = true;
891
892 LOG(INFO) << cond.text();
893 }
894
895 if (finished)
896 {
897 if (cond == DUL_PEERREQUESTEDRELEASE)
898 {
899 LOG(INFO) << "Association Release";
900 ASC_acknowledgeRelease(assoc_);
901 }
902 else if (cond == DUL_PEERABORTEDASSOCIATION)
903 {
904 LOG(INFO) << "Association Aborted";
905 }
906 else
907 {
908 OFString temp_str;
909 LOG(INFO) << "DIMSE failure (aborting association): " << cond.text();
910 /* some kind of error so abort the association */
911 ASC_abortAssociation(assoc_);
912 }
913 }
914
915 return !finished;
916 }
917
918
919 OFCondition EchoScp(T_ASC_Association * assoc, T_DIMSE_Message * msg, T_ASC_PresentationContextID presID)
920 {
921 OFString temp_str;
922 LOG(INFO) << "Received Echo Request";
923 //LOG(DEBUG) << DIMSE_dumpMessage(temp_str, msg->msg.CEchoRQ, DIMSE_INCOMING, NULL, presID));
924
925 /* the echo succeeded !! */
926 OFCondition cond = DIMSE_sendEchoResponse(assoc, presID, &msg->msg.CEchoRQ, STATUS_Success, NULL);
927 if (cond.bad())
928 {
929 LOG(ERROR) << "Echo SCP Failed: " << cond.text();
930 }
931 return cond;
932 }
933 }
934 }