Mercurial > hg > orthanc
comparison Core/DicomNetworking/Internals/CommandDispatcher.cpp @ 3761:3b5feb2bbd4b transcoding
integration mainline->transcoding
author | Sebastien Jodogne <s.jodogne@gmail.com> |
---|---|
date | Mon, 16 Mar 2020 12:17:43 +0100 |
parents | 0540b54324f1 |
children | c6658187e4b1 |
comparison
equal
deleted
inserted
replaced
3748:ca36e3f1112c | 3761:3b5feb2bbd4b |
---|---|
91 #include "StoreScp.h" | 91 #include "StoreScp.h" |
92 #include "MoveScp.h" | 92 #include "MoveScp.h" |
93 #include "../../Compatibility.h" | 93 #include "../../Compatibility.h" |
94 #include "../../Toolbox.h" | 94 #include "../../Toolbox.h" |
95 #include "../../Logging.h" | 95 #include "../../Logging.h" |
96 | 96 #include "../../OrthancException.h" |
97 | |
98 #include <dcmtk/dcmdata/dcdeftag.h> /* for storage commitment */ | |
99 #include <dcmtk/dcmdata/dcsequen.h> /* for class DcmSequenceOfItems */ | |
100 #include <dcmtk/dcmdata/dcuid.h> /* for variable dcmAllStorageSOPClassUIDs */ | |
97 #include <dcmtk/dcmnet/dcasccfg.h> /* for class DcmAssociationConfiguration */ | 101 #include <dcmtk/dcmnet/dcasccfg.h> /* for class DcmAssociationConfiguration */ |
98 #include <dcmtk/dcmdata/dcuid.h> /* for variable dcmAllStorageSOPClassUIDs */ | |
99 | 102 |
100 #include <boost/lexical_cast.hpp> | 103 #include <boost/lexical_cast.hpp> |
101 | 104 |
102 static OFBool opt_rejectWithoutImplementationUID = OFFalse; | 105 static OFBool opt_rejectWithoutImplementationUID = OFFalse; |
103 | 106 |
270 T_ASC_Association *assoc; | 273 T_ASC_Association *assoc; |
271 OFCondition cond; | 274 OFCondition cond; |
272 OFString sprofile; | 275 OFString sprofile; |
273 OFString temp_str; | 276 OFString temp_str; |
274 | 277 |
275 std::vector<const char*> knownAbstractSyntaxes; | |
276 | |
277 // For C-STORE | |
278 if (server.HasStoreRequestHandlerFactory()) | |
279 { | |
280 knownAbstractSyntaxes.push_back(UID_VerificationSOPClass); | |
281 } | |
282 | |
283 // For C-FIND | |
284 if (server.HasFindRequestHandlerFactory()) | |
285 { | |
286 knownAbstractSyntaxes.push_back(UID_FINDPatientRootQueryRetrieveInformationModel); | |
287 knownAbstractSyntaxes.push_back(UID_FINDStudyRootQueryRetrieveInformationModel); | |
288 } | |
289 | |
290 if (server.HasWorklistRequestHandlerFactory()) | |
291 { | |
292 knownAbstractSyntaxes.push_back(UID_FINDModalityWorklistInformationModel); | |
293 } | |
294 | |
295 // For C-MOVE | |
296 if (server.HasMoveRequestHandlerFactory()) | |
297 { | |
298 knownAbstractSyntaxes.push_back(UID_MOVEStudyRootQueryRetrieveInformationModel); | |
299 knownAbstractSyntaxes.push_back(UID_MOVEPatientRootQueryRetrieveInformationModel); | |
300 } | |
301 | |
302 cond = ASC_receiveAssociation(net, &assoc, | 278 cond = ASC_receiveAssociation(net, &assoc, |
303 /*opt_maxPDU*/ ASC_DEFAULTMAXPDU, | 279 /*opt_maxPDU*/ ASC_DEFAULTMAXPDU, |
304 NULL, NULL, | 280 NULL, NULL, |
305 /*opt_secureConnection*/ OFFalse, | 281 /*opt_secureConnection*/ OFFalse, |
306 DUL_NOBLOCK, 1); | 282 DUL_NOBLOCK, 1); |
360 | 336 |
361 LOG(INFO) << "Association Received from AET " << remoteAet | 337 LOG(INFO) << "Association Received from AET " << remoteAet |
362 << " on IP " << remoteIp; | 338 << " on IP " << remoteIp; |
363 | 339 |
364 | 340 |
365 std::vector<const char*> transferSyntaxes; | 341 { |
366 | 342 /* accept the abstract syntaxes for C-ECHO, C-FIND, C-MOVE, |
367 // This is the list of the transfer syntaxes that were supported up to Orthanc 0.7.1 | 343 and storage commitment, if presented */ |
368 transferSyntaxes.push_back(UID_LittleEndianExplicitTransferSyntax); | 344 |
369 transferSyntaxes.push_back(UID_BigEndianExplicitTransferSyntax); | 345 std::vector<const char*> genericTransferSyntaxes; |
370 transferSyntaxes.push_back(UID_LittleEndianImplicitTransferSyntax); | 346 genericTransferSyntaxes.push_back(UID_LittleEndianExplicitTransferSyntax); |
371 | 347 genericTransferSyntaxes.push_back(UID_BigEndianExplicitTransferSyntax); |
372 // New transfer syntaxes supported since Orthanc 0.7.2 | 348 genericTransferSyntaxes.push_back(UID_LittleEndianImplicitTransferSyntax); |
373 if (!server.HasApplicationEntityFilter() || | 349 |
374 server.GetApplicationEntityFilter().IsAllowedTransferSyntax(remoteIp, remoteAet, calledAet, TransferSyntax_Deflated)) | 350 std::vector<const char*> knownAbstractSyntaxes; |
375 { | 351 |
376 transferSyntaxes.push_back(UID_DeflatedExplicitVRLittleEndianTransferSyntax); | 352 // For C-ECHO (always enabled since Orthanc 1.6.0; in earlier |
377 } | 353 // versions, only enabled if C-STORE was also enabled) |
378 | 354 knownAbstractSyntaxes.push_back(UID_VerificationSOPClass); |
379 if (!server.HasApplicationEntityFilter() || | 355 |
380 server.GetApplicationEntityFilter().IsAllowedTransferSyntax(remoteIp, remoteAet, calledAet, TransferSyntax_Jpeg)) | 356 // For C-FIND |
381 { | 357 if (server.HasFindRequestHandlerFactory()) |
382 transferSyntaxes.push_back(UID_JPEGProcess1TransferSyntax); | 358 { |
383 transferSyntaxes.push_back(UID_JPEGProcess2_4TransferSyntax); | 359 knownAbstractSyntaxes.push_back(UID_FINDPatientRootQueryRetrieveInformationModel); |
384 transferSyntaxes.push_back(UID_JPEGProcess3_5TransferSyntax); | 360 knownAbstractSyntaxes.push_back(UID_FINDStudyRootQueryRetrieveInformationModel); |
385 transferSyntaxes.push_back(UID_JPEGProcess6_8TransferSyntax); | 361 } |
386 transferSyntaxes.push_back(UID_JPEGProcess7_9TransferSyntax); | 362 |
387 transferSyntaxes.push_back(UID_JPEGProcess10_12TransferSyntax); | 363 if (server.HasWorklistRequestHandlerFactory()) |
388 transferSyntaxes.push_back(UID_JPEGProcess11_13TransferSyntax); | 364 { |
389 transferSyntaxes.push_back(UID_JPEGProcess14TransferSyntax); | 365 knownAbstractSyntaxes.push_back(UID_FINDModalityWorklistInformationModel); |
390 transferSyntaxes.push_back(UID_JPEGProcess15TransferSyntax); | 366 } |
391 transferSyntaxes.push_back(UID_JPEGProcess16_18TransferSyntax); | 367 |
392 transferSyntaxes.push_back(UID_JPEGProcess17_19TransferSyntax); | 368 // For C-MOVE |
393 transferSyntaxes.push_back(UID_JPEGProcess20_22TransferSyntax); | 369 if (server.HasMoveRequestHandlerFactory()) |
394 transferSyntaxes.push_back(UID_JPEGProcess21_23TransferSyntax); | 370 { |
395 transferSyntaxes.push_back(UID_JPEGProcess24_26TransferSyntax); | 371 knownAbstractSyntaxes.push_back(UID_MOVEStudyRootQueryRetrieveInformationModel); |
396 transferSyntaxes.push_back(UID_JPEGProcess25_27TransferSyntax); | 372 knownAbstractSyntaxes.push_back(UID_MOVEPatientRootQueryRetrieveInformationModel); |
397 transferSyntaxes.push_back(UID_JPEGProcess28TransferSyntax); | 373 } |
398 transferSyntaxes.push_back(UID_JPEGProcess29TransferSyntax); | 374 |
399 transferSyntaxes.push_back(UID_JPEGProcess14SV1TransferSyntax); | 375 cond = ASC_acceptContextsWithPreferredTransferSyntaxes( |
400 } | 376 assoc->params, |
401 | 377 &knownAbstractSyntaxes[0], knownAbstractSyntaxes.size(), |
402 if (!server.HasApplicationEntityFilter() || | 378 &genericTransferSyntaxes[0], genericTransferSyntaxes.size()); |
403 server.GetApplicationEntityFilter().IsAllowedTransferSyntax(remoteIp, remoteAet, calledAet, TransferSyntax_Jpeg2000)) | |
404 { | |
405 transferSyntaxes.push_back(UID_JPEG2000LosslessOnlyTransferSyntax); | |
406 transferSyntaxes.push_back(UID_JPEG2000TransferSyntax); | |
407 transferSyntaxes.push_back(UID_JPEG2000LosslessOnlyTransferSyntax); | |
408 transferSyntaxes.push_back(UID_JPEG2000TransferSyntax); | |
409 transferSyntaxes.push_back(UID_JPEG2000Part2MulticomponentImageCompressionLosslessOnlyTransferSyntax); | |
410 transferSyntaxes.push_back(UID_JPEG2000Part2MulticomponentImageCompressionTransferSyntax); | |
411 } | |
412 | |
413 if (!server.HasApplicationEntityFilter() || | |
414 server.GetApplicationEntityFilter().IsAllowedTransferSyntax(remoteIp, remoteAet, calledAet, TransferSyntax_JpegLossless)) | |
415 { | |
416 transferSyntaxes.push_back(UID_JPEGLSLosslessTransferSyntax); | |
417 transferSyntaxes.push_back(UID_JPEGLSLossyTransferSyntax); | |
418 } | |
419 | |
420 if (!server.HasApplicationEntityFilter() || | |
421 server.GetApplicationEntityFilter().IsAllowedTransferSyntax(remoteIp, remoteAet, calledAet, TransferSyntax_Jpip)) | |
422 { | |
423 transferSyntaxes.push_back(UID_JPIPReferencedTransferSyntax); | |
424 transferSyntaxes.push_back(UID_JPIPReferencedDeflateTransferSyntax); | |
425 } | |
426 | |
427 if (!server.HasApplicationEntityFilter() || | |
428 server.GetApplicationEntityFilter().IsAllowedTransferSyntax(remoteIp, remoteAet, calledAet, TransferSyntax_Mpeg2)) | |
429 { | |
430 transferSyntaxes.push_back(UID_MPEG2MainProfileAtMainLevelTransferSyntax); | |
431 transferSyntaxes.push_back(UID_MPEG2MainProfileAtHighLevelTransferSyntax); | |
432 } | |
433 | |
434 #if DCMTK_VERSION_NUMBER >= 361 | |
435 // New in Orthanc 1.6.0 | |
436 if (!server.HasApplicationEntityFilter() || | |
437 server.GetApplicationEntityFilter().IsAllowedTransferSyntax(remoteIp, remoteAet, calledAet, TransferSyntax_Mpeg4)) | |
438 { | |
439 transferSyntaxes.push_back(UID_MPEG4BDcompatibleHighProfileLevel4_1TransferSyntax); | |
440 transferSyntaxes.push_back(UID_MPEG4HighProfileLevel4_1TransferSyntax); | |
441 transferSyntaxes.push_back(UID_MPEG4HighProfileLevel4_2_For2DVideoTransferSyntax); | |
442 transferSyntaxes.push_back(UID_MPEG4HighProfileLevel4_2_For3DVideoTransferSyntax); | |
443 transferSyntaxes.push_back(UID_MPEG4StereoHighProfileLevel4_2TransferSyntax); | |
444 } | |
445 #endif | |
446 | |
447 if (!server.HasApplicationEntityFilter() || | |
448 server.GetApplicationEntityFilter().IsAllowedTransferSyntax(remoteIp, remoteAet, calledAet, TransferSyntax_Rle)) | |
449 { | |
450 transferSyntaxes.push_back(UID_RLELosslessTransferSyntax); | |
451 } | |
452 | |
453 /* accept the Verification SOP Class if presented */ | |
454 cond = ASC_acceptContextsWithPreferredTransferSyntaxes( | |
455 assoc->params, | |
456 &knownAbstractSyntaxes[0], knownAbstractSyntaxes.size(), | |
457 &transferSyntaxes[0], transferSyntaxes.size()); | |
458 if (cond.bad()) | |
459 { | |
460 LOG(INFO) << cond.text(); | |
461 AssociationCleanup(assoc); | |
462 return NULL; | |
463 } | |
464 | |
465 /* the array of Storage SOP Class UIDs that is defined within "dcmdata/libsrc/dcuid.cc" */ | |
466 size_t count = 0; | |
467 while (dcmAllStorageSOPClassUIDs[count] != NULL) | |
468 { | |
469 count++; | |
470 } | |
471 | |
472 #if DCMTK_VERSION_NUMBER >= 362 | |
473 // The global variable "numberOfDcmAllStorageSOPClassUIDs" is | |
474 // only published if DCMTK >= 3.6.2: | |
475 // https://bitbucket.org/sjodogne/orthanc/issues/137 | |
476 assert(static_cast<int>(count) == numberOfDcmAllStorageSOPClassUIDs); | |
477 #endif | |
478 | |
479 cond = ASC_acceptContextsWithPreferredTransferSyntaxes( | |
480 assoc->params, | |
481 dcmAllStorageSOPClassUIDs, count, | |
482 &transferSyntaxes[0], transferSyntaxes.size()); | |
483 if (cond.bad()) | |
484 { | |
485 LOG(INFO) << cond.text(); | |
486 AssociationCleanup(assoc); | |
487 return NULL; | |
488 } | |
489 | |
490 if (!server.HasApplicationEntityFilter() || | |
491 server.GetApplicationEntityFilter().IsUnknownSopClassAccepted(remoteIp, remoteAet, calledAet)) | |
492 { | |
493 /* | |
494 * Promiscous mode is enabled: Accept everything not known not | |
495 * to be a storage SOP class. | |
496 **/ | |
497 cond = acceptUnknownContextsWithPreferredTransferSyntaxes( | |
498 assoc->params, &transferSyntaxes[0], transferSyntaxes.size(), ASC_SC_ROLE_DEFAULT); | |
499 if (cond.bad()) | 379 if (cond.bad()) |
500 { | 380 { |
501 LOG(INFO) << cond.text(); | 381 LOG(INFO) << cond.text(); |
502 AssociationCleanup(assoc); | 382 AssociationCleanup(assoc); |
503 return NULL; | 383 return NULL; |
384 } | |
385 | |
386 | |
387 /* storage commitment support, new in Orthanc 1.6.0 */ | |
388 if (server.HasStorageCommitmentRequestHandlerFactory()) | |
389 { | |
390 /** | |
391 * "ASC_SC_ROLE_SCUSCP": The "SCU" role is needed to accept | |
392 * remote storage commitment requests, and the "SCP" role is | |
393 * needed to receive storage commitments answers. | |
394 **/ | |
395 const char* as[1] = { UID_StorageCommitmentPushModelSOPClass }; | |
396 cond = ASC_acceptContextsWithPreferredTransferSyntaxes( | |
397 assoc->params, as, 1, | |
398 &genericTransferSyntaxes[0], genericTransferSyntaxes.size(), ASC_SC_ROLE_SCUSCP); | |
399 if (cond.bad()) | |
400 { | |
401 LOG(INFO) << cond.text(); | |
402 AssociationCleanup(assoc); | |
403 return NULL; | |
404 } | |
405 } | |
406 } | |
407 | |
408 | |
409 { | |
410 /* accept the abstract syntaxes for C-STORE, if presented */ | |
411 | |
412 std::vector<const char*> storageTransferSyntaxes; | |
413 | |
414 // This is the list of the transfer syntaxes that were supported up to Orthanc 0.7.1 | |
415 storageTransferSyntaxes.push_back(UID_LittleEndianExplicitTransferSyntax); | |
416 storageTransferSyntaxes.push_back(UID_BigEndianExplicitTransferSyntax); | |
417 storageTransferSyntaxes.push_back(UID_LittleEndianImplicitTransferSyntax); | |
418 | |
419 // New transfer syntaxes supported since Orthanc 0.7.2 | |
420 if (!server.HasApplicationEntityFilter() || | |
421 server.GetApplicationEntityFilter().IsAllowedTransferSyntax(remoteIp, remoteAet, calledAet, TransferSyntax_Deflated)) | |
422 { | |
423 storageTransferSyntaxes.push_back(UID_DeflatedExplicitVRLittleEndianTransferSyntax); | |
424 } | |
425 | |
426 if (!server.HasApplicationEntityFilter() || | |
427 server.GetApplicationEntityFilter().IsAllowedTransferSyntax(remoteIp, remoteAet, calledAet, TransferSyntax_Jpeg)) | |
428 { | |
429 storageTransferSyntaxes.push_back(UID_JPEGProcess1TransferSyntax); | |
430 storageTransferSyntaxes.push_back(UID_JPEGProcess2_4TransferSyntax); | |
431 storageTransferSyntaxes.push_back(UID_JPEGProcess3_5TransferSyntax); | |
432 storageTransferSyntaxes.push_back(UID_JPEGProcess6_8TransferSyntax); | |
433 storageTransferSyntaxes.push_back(UID_JPEGProcess7_9TransferSyntax); | |
434 storageTransferSyntaxes.push_back(UID_JPEGProcess10_12TransferSyntax); | |
435 storageTransferSyntaxes.push_back(UID_JPEGProcess11_13TransferSyntax); | |
436 storageTransferSyntaxes.push_back(UID_JPEGProcess14TransferSyntax); | |
437 storageTransferSyntaxes.push_back(UID_JPEGProcess15TransferSyntax); | |
438 storageTransferSyntaxes.push_back(UID_JPEGProcess16_18TransferSyntax); | |
439 storageTransferSyntaxes.push_back(UID_JPEGProcess17_19TransferSyntax); | |
440 storageTransferSyntaxes.push_back(UID_JPEGProcess20_22TransferSyntax); | |
441 storageTransferSyntaxes.push_back(UID_JPEGProcess21_23TransferSyntax); | |
442 storageTransferSyntaxes.push_back(UID_JPEGProcess24_26TransferSyntax); | |
443 storageTransferSyntaxes.push_back(UID_JPEGProcess25_27TransferSyntax); | |
444 storageTransferSyntaxes.push_back(UID_JPEGProcess28TransferSyntax); | |
445 storageTransferSyntaxes.push_back(UID_JPEGProcess29TransferSyntax); | |
446 storageTransferSyntaxes.push_back(UID_JPEGProcess14SV1TransferSyntax); | |
447 } | |
448 | |
449 if (!server.HasApplicationEntityFilter() || | |
450 server.GetApplicationEntityFilter().IsAllowedTransferSyntax(remoteIp, remoteAet, calledAet, TransferSyntax_Jpeg2000)) | |
451 { | |
452 storageTransferSyntaxes.push_back(UID_JPEG2000LosslessOnlyTransferSyntax); | |
453 storageTransferSyntaxes.push_back(UID_JPEG2000TransferSyntax); | |
454 storageTransferSyntaxes.push_back(UID_JPEG2000LosslessOnlyTransferSyntax); | |
455 storageTransferSyntaxes.push_back(UID_JPEG2000TransferSyntax); | |
456 storageTransferSyntaxes.push_back(UID_JPEG2000Part2MulticomponentImageCompressionLosslessOnlyTransferSyntax); | |
457 storageTransferSyntaxes.push_back(UID_JPEG2000Part2MulticomponentImageCompressionTransferSyntax); | |
458 } | |
459 | |
460 if (!server.HasApplicationEntityFilter() || | |
461 server.GetApplicationEntityFilter().IsAllowedTransferSyntax(remoteIp, remoteAet, calledAet, TransferSyntax_JpegLossless)) | |
462 { | |
463 storageTransferSyntaxes.push_back(UID_JPEGLSLosslessTransferSyntax); | |
464 storageTransferSyntaxes.push_back(UID_JPEGLSLossyTransferSyntax); | |
465 } | |
466 | |
467 if (!server.HasApplicationEntityFilter() || | |
468 server.GetApplicationEntityFilter().IsAllowedTransferSyntax(remoteIp, remoteAet, calledAet, TransferSyntax_Jpip)) | |
469 { | |
470 storageTransferSyntaxes.push_back(UID_JPIPReferencedTransferSyntax); | |
471 storageTransferSyntaxes.push_back(UID_JPIPReferencedDeflateTransferSyntax); | |
472 } | |
473 | |
474 if (!server.HasApplicationEntityFilter() || | |
475 server.GetApplicationEntityFilter().IsAllowedTransferSyntax(remoteIp, remoteAet, calledAet, TransferSyntax_Mpeg2)) | |
476 { | |
477 storageTransferSyntaxes.push_back(UID_MPEG2MainProfileAtMainLevelTransferSyntax); | |
478 storageTransferSyntaxes.push_back(UID_MPEG2MainProfileAtHighLevelTransferSyntax); | |
479 } | |
480 | |
481 // New in Orthanc 1.6.0 | |
482 if (!server.HasApplicationEntityFilter() || | |
483 server.GetApplicationEntityFilter().IsAllowedTransferSyntax(remoteIp, remoteAet, calledAet, TransferSyntax_Mpeg4)) | |
484 { | |
485 storageTransferSyntaxes.push_back(UID_MPEG4BDcompatibleHighProfileLevel4_1TransferSyntax); | |
486 storageTransferSyntaxes.push_back(UID_MPEG4HighProfileLevel4_1TransferSyntax); | |
487 storageTransferSyntaxes.push_back(UID_MPEG4HighProfileLevel4_2_For2DVideoTransferSyntax); | |
488 storageTransferSyntaxes.push_back(UID_MPEG4HighProfileLevel4_2_For3DVideoTransferSyntax); | |
489 storageTransferSyntaxes.push_back(UID_MPEG4StereoHighProfileLevel4_2TransferSyntax); | |
490 } | |
491 | |
492 if (!server.HasApplicationEntityFilter() || | |
493 server.GetApplicationEntityFilter().IsAllowedTransferSyntax(remoteIp, remoteAet, calledAet, TransferSyntax_Rle)) | |
494 { | |
495 storageTransferSyntaxes.push_back(UID_RLELosslessTransferSyntax); | |
496 } | |
497 | |
498 /* the array of Storage SOP Class UIDs that is defined within "dcmdata/libsrc/dcuid.cc" */ | |
499 size_t count = 0; | |
500 while (dcmAllStorageSOPClassUIDs[count] != NULL) | |
501 { | |
502 count++; | |
503 } | |
504 | |
505 #if DCMTK_VERSION_NUMBER >= 362 | |
506 // The global variable "numberOfDcmAllStorageSOPClassUIDs" is | |
507 // only published if DCMTK >= 3.6.2: | |
508 // https://bitbucket.org/sjodogne/orthanc/issues/137 | |
509 assert(static_cast<int>(count) == numberOfDcmAllStorageSOPClassUIDs); | |
510 #endif | |
511 | |
512 cond = ASC_acceptContextsWithPreferredTransferSyntaxes( | |
513 assoc->params, | |
514 dcmAllStorageSOPClassUIDs, count, | |
515 &storageTransferSyntaxes[0], storageTransferSyntaxes.size()); | |
516 if (cond.bad()) | |
517 { | |
518 LOG(INFO) << cond.text(); | |
519 AssociationCleanup(assoc); | |
520 return NULL; | |
521 } | |
522 | |
523 if (!server.HasApplicationEntityFilter() || | |
524 server.GetApplicationEntityFilter().IsUnknownSopClassAccepted(remoteIp, remoteAet, calledAet)) | |
525 { | |
526 /* | |
527 * Promiscous mode is enabled: Accept everything not known not | |
528 * to be a storage SOP class. | |
529 **/ | |
530 cond = acceptUnknownContextsWithPreferredTransferSyntaxes( | |
531 assoc->params, &storageTransferSyntaxes[0], storageTransferSyntaxes.size(), ASC_SC_ROLE_DEFAULT); | |
532 if (cond.bad()) | |
533 { | |
534 LOG(INFO) << cond.text(); | |
535 AssociationCleanup(assoc); | |
536 return NULL; | |
537 } | |
504 } | 538 } |
505 } | 539 } |
506 | 540 |
507 /* set our app title */ | 541 /* set our app title */ |
508 ASC_setAPTitles(assoc->params, NULL, NULL, server.GetApplicationEntityTitle().c_str()); | 542 ASC_setAPTitles(assoc->params, NULL, NULL, server.GetApplicationEntityTitle().c_str()); |
701 case DIMSE_C_FIND_RQ: | 735 case DIMSE_C_FIND_RQ: |
702 request = DicomRequestType_Find; | 736 request = DicomRequestType_Find; |
703 supported = true; | 737 supported = true; |
704 break; | 738 break; |
705 | 739 |
740 case DIMSE_N_ACTION_RQ: | |
741 request = DicomRequestType_NAction; | |
742 supported = true; | |
743 break; | |
744 | |
745 case DIMSE_N_EVENT_REPORT_RQ: | |
746 request = DicomRequestType_NEventReport; | |
747 supported = true; | |
748 break; | |
749 | |
706 default: | 750 default: |
707 // we cannot handle this kind of message | 751 // we cannot handle this kind of message |
708 cond = DIMSE_BADCOMMANDTYPE; | 752 cond = DIMSE_BADCOMMANDTYPE; |
709 LOG(ERROR) << "cannot handle command: 0x" << std::hex << msg.CommandField; | 753 LOG(ERROR) << "cannot handle command: 0x" << std::hex << msg.CommandField; |
710 break; | 754 break; |
782 findHandler.get(), worklistHandler.get(), | 826 findHandler.get(), worklistHandler.get(), |
783 remoteIp_, remoteAet_, calledAet_, associationTimeout_); | 827 remoteIp_, remoteAet_, calledAet_, associationTimeout_); |
784 } | 828 } |
785 break; | 829 break; |
786 | 830 |
831 case DicomRequestType_NAction: | |
832 cond = NActionScp(&msg, presID); | |
833 break; | |
834 | |
835 case DicomRequestType_NEventReport: | |
836 cond = NEventReportScp(&msg, presID); | |
837 break; | |
838 | |
787 default: | 839 default: |
788 // Should never happen | 840 // Should never happen |
789 break; | 841 break; |
790 } | 842 } |
791 } | 843 } |
835 { | 887 { |
836 LOG(ERROR) << "Echo SCP Failed: " << cond.text(); | 888 LOG(ERROR) << "Echo SCP Failed: " << cond.text(); |
837 } | 889 } |
838 return cond; | 890 return cond; |
839 } | 891 } |
892 | |
893 | |
894 static DcmDataset* ReadDataset(T_ASC_Association* assoc, | |
895 const char* errorMessage, | |
896 int timeout) | |
897 { | |
898 DcmDataset *tmp = NULL; | |
899 T_ASC_PresentationContextID presIdData; | |
900 | |
901 OFCondition cond = DIMSE_receiveDataSetInMemory( | |
902 assoc, (timeout ? DIMSE_NONBLOCKING : DIMSE_BLOCKING), timeout, | |
903 &presIdData, &tmp, NULL, NULL); | |
904 if (!cond.good() || | |
905 tmp == NULL) | |
906 { | |
907 throw OrthancException(ErrorCode_NetworkProtocol, errorMessage); | |
908 } | |
909 | |
910 return tmp; | |
911 } | |
912 | |
913 | |
914 static std::string ReadString(DcmDataset& dataset, | |
915 const DcmTagKey& tag) | |
916 { | |
917 const char* s = NULL; | |
918 if (!dataset.findAndGetString(tag, s).good() || | |
919 s == NULL) | |
920 { | |
921 char buf[64]; | |
922 sprintf(buf, "Missing mandatory tag in dataset: (%04X,%04X)", | |
923 tag.getGroup(), tag.getElement()); | |
924 throw OrthancException(ErrorCode_NetworkProtocol, buf); | |
925 } | |
926 | |
927 return std::string(s); | |
928 } | |
929 | |
930 | |
931 static void ReadSopSequence( | |
932 std::vector<std::string>& sopClassUids, | |
933 std::vector<std::string>& sopInstanceUids, | |
934 std::vector<StorageCommitmentFailureReason>* failureReasons, // Can be NULL | |
935 DcmDataset& dataset, | |
936 const DcmTagKey& tag, | |
937 bool mandatory) | |
938 { | |
939 sopClassUids.clear(); | |
940 sopInstanceUids.clear(); | |
941 | |
942 if (failureReasons) | |
943 { | |
944 failureReasons->clear(); | |
945 } | |
946 | |
947 DcmSequenceOfItems* sequence = NULL; | |
948 if (!dataset.findAndGetSequence(tag, sequence).good() || | |
949 sequence == NULL) | |
950 { | |
951 if (mandatory) | |
952 { | |
953 char buf[64]; | |
954 sprintf(buf, "Missing mandatory sequence in dataset: (%04X,%04X)", | |
955 tag.getGroup(), tag.getElement()); | |
956 throw OrthancException(ErrorCode_NetworkProtocol, buf); | |
957 } | |
958 else | |
959 { | |
960 return; | |
961 } | |
962 } | |
963 | |
964 sopClassUids.reserve(sequence->card()); | |
965 sopInstanceUids.reserve(sequence->card()); | |
966 | |
967 if (failureReasons) | |
968 { | |
969 failureReasons->reserve(sequence->card()); | |
970 } | |
971 | |
972 for (unsigned long i = 0; i < sequence->card(); i++) | |
973 { | |
974 const char* a = NULL; | |
975 const char* b = NULL; | |
976 if (!sequence->getItem(i)->findAndGetString(DCM_ReferencedSOPClassUID, a).good() || | |
977 !sequence->getItem(i)->findAndGetString(DCM_ReferencedSOPInstanceUID, b).good() || | |
978 a == NULL || | |
979 b == NULL) | |
980 { | |
981 throw OrthancException(ErrorCode_NetworkProtocol, | |
982 "Missing Referenced SOP Class/Instance UID " | |
983 "in storage commitment dataset"); | |
984 } | |
985 | |
986 sopClassUids.push_back(a); | |
987 sopInstanceUids.push_back(b); | |
988 | |
989 if (failureReasons != NULL) | |
990 { | |
991 Uint16 reason; | |
992 if (!sequence->getItem(i)->findAndGetUint16(DCM_FailureReason, reason).good()) | |
993 { | |
994 throw OrthancException(ErrorCode_NetworkProtocol, | |
995 "Missing Failure Reason (0008,1197) " | |
996 "in storage commitment dataset"); | |
997 } | |
998 | |
999 failureReasons->push_back(static_cast<StorageCommitmentFailureReason>(reason)); | |
1000 } | |
1001 } | |
1002 } | |
1003 | |
1004 | |
1005 OFCondition CommandDispatcher::NActionScp(T_DIMSE_Message* msg, | |
1006 T_ASC_PresentationContextID presID) | |
1007 { | |
1008 /** | |
1009 * Starting with Orthanc 1.6.0, only storage commitment is | |
1010 * supported with DICOM N-ACTION. This corresponds to the case | |
1011 * where "Action Type ID" equals "1". | |
1012 * http://dicom.nema.org/medical/dicom/2019a/output/chtml/part04/sect_J.3.2.html | |
1013 * http://dicom.nema.org/medical/dicom/2019a/output/chtml/part07/chapter_10.html#table_10.1-4 | |
1014 **/ | |
1015 | |
1016 if (msg->CommandField != DIMSE_N_ACTION_RQ /* value == 304 == 0x0130 */ || | |
1017 !server_.HasStorageCommitmentRequestHandlerFactory()) | |
1018 { | |
1019 throw OrthancException(ErrorCode_InternalError); | |
1020 } | |
1021 | |
1022 | |
1023 /** | |
1024 * Check that the storage commitment request is correctly formatted. | |
1025 **/ | |
1026 | |
1027 const T_DIMSE_N_ActionRQ& request = msg->msg.NActionRQ; | |
1028 | |
1029 if (request.ActionTypeID != 1) | |
1030 { | |
1031 throw OrthancException(ErrorCode_NotImplemented, | |
1032 "Only storage commitment is implemented for DICOM N-ACTION SCP"); | |
1033 } | |
1034 | |
1035 if (std::string(request.RequestedSOPClassUID) != UID_StorageCommitmentPushModelSOPClass || | |
1036 std::string(request.RequestedSOPInstanceUID) != UID_StorageCommitmentPushModelSOPInstance) | |
1037 { | |
1038 throw OrthancException(ErrorCode_NetworkProtocol, | |
1039 "Unexpected incoming SOP class or instance UID for storage commitment"); | |
1040 } | |
1041 | |
1042 if (request.DataSetType != DIMSE_DATASET_PRESENT) | |
1043 { | |
1044 throw OrthancException(ErrorCode_NetworkProtocol, | |
1045 "Incoming storage commitment request without a dataset"); | |
1046 } | |
1047 | |
1048 | |
1049 /** | |
1050 * Extract the DICOM dataset that is associated with the DIMSE | |
1051 * message. The content of this dataset is documented in "Table | |
1052 * J.3-1. Storage Commitment Request - Action Information": | |
1053 * http://dicom.nema.org/medical/dicom/2019a/output/chtml/part04/sect_J.3.2.html#table_J.3-1 | |
1054 **/ | |
1055 | |
1056 std::auto_ptr<DcmDataset> dataset( | |
1057 ReadDataset(assoc_, "Cannot read the dataset in N-ACTION SCP", associationTimeout_)); | |
1058 | |
1059 std::string transactionUid = ReadString(*dataset, DCM_TransactionUID); | |
1060 | |
1061 std::vector<std::string> sopClassUid, sopInstanceUid; | |
1062 ReadSopSequence(sopClassUid, sopInstanceUid, NULL, | |
1063 *dataset, DCM_ReferencedSOPSequence, true /* mandatory */); | |
1064 | |
1065 LOG(INFO) << "Incoming storage commitment request, with transaction UID: " << transactionUid; | |
1066 | |
1067 for (size_t i = 0; i < sopClassUid.size(); i++) | |
1068 { | |
1069 LOG(INFO) << " (" << (i + 1) << "/" << sopClassUid.size() | |
1070 << ") queried SOP Class/Instance UID: " | |
1071 << sopClassUid[i] << " / " << sopInstanceUid[i]; | |
1072 } | |
1073 | |
1074 | |
1075 /** | |
1076 * Call the Orthanc handler. The list of available DIMSE status | |
1077 * codes can be found at: | |
1078 * http://dicom.nema.org/medical/dicom/2019a/output/chtml/part07/chapter_10.html#sect_10.1.4.1.10 | |
1079 **/ | |
1080 | |
1081 DIC_US dimseStatus; | |
1082 | |
1083 try | |
1084 { | |
1085 std::auto_ptr<IStorageCommitmentRequestHandler> handler | |
1086 (server_.GetStorageCommitmentRequestHandlerFactory(). | |
1087 ConstructStorageCommitmentRequestHandler()); | |
1088 | |
1089 handler->HandleRequest(transactionUid, sopClassUid, sopInstanceUid, | |
1090 remoteIp_, remoteAet_, calledAet_); | |
1091 | |
1092 dimseStatus = 0; // Success | |
1093 } | |
1094 catch (OrthancException& e) | |
1095 { | |
1096 LOG(ERROR) << "Error while processing an incoming storage commitment request: " << e.What(); | |
1097 | |
1098 // Code 0x0110 - "General failure in processing the operation was encountered" | |
1099 dimseStatus = STATUS_N_ProcessingFailure; | |
1100 } | |
1101 | |
1102 | |
1103 /** | |
1104 * Send the DIMSE status back to the SCU. | |
1105 **/ | |
1106 | |
1107 { | |
1108 T_DIMSE_Message response; | |
1109 memset(&response, 0, sizeof(response)); | |
1110 response.CommandField = DIMSE_N_ACTION_RSP; | |
1111 | |
1112 T_DIMSE_N_ActionRSP& content = response.msg.NActionRSP; | |
1113 content.MessageIDBeingRespondedTo = request.MessageID; | |
1114 strncpy(content.AffectedSOPClassUID, UID_StorageCommitmentPushModelSOPClass, DIC_UI_LEN); | |
1115 content.DimseStatus = dimseStatus; | |
1116 strncpy(content.AffectedSOPInstanceUID, UID_StorageCommitmentPushModelSOPInstance, DIC_UI_LEN); | |
1117 content.ActionTypeID = 0; // Not present, as "O_NACTION_ACTIONTYPEID" not set in "opts" | |
1118 content.DataSetType = DIMSE_DATASET_NULL; // Dataset is absent in storage commitment response | |
1119 content.opts = O_NACTION_AFFECTEDSOPCLASSUID | O_NACTION_AFFECTEDSOPINSTANCEUID; | |
1120 | |
1121 return DIMSE_sendMessageUsingMemoryData( | |
1122 assoc_, presID, &response, NULL /* no dataset */, NULL /* dataObject */, | |
1123 NULL /* callback */, NULL /* callback context */, NULL /* commandSet */); | |
1124 } | |
1125 } | |
1126 | |
1127 | |
1128 OFCondition CommandDispatcher::NEventReportScp(T_DIMSE_Message* msg, | |
1129 T_ASC_PresentationContextID presID) | |
1130 { | |
1131 /** | |
1132 * Starting with Orthanc 1.6.0, handling N-EVENT-REPORT for | |
1133 * storage commitment. | |
1134 * http://dicom.nema.org/medical/dicom/2019a/output/chtml/part04/sect_J.3.3.html | |
1135 * http://dicom.nema.org/medical/dicom/2019a/output/chtml/part07/chapter_10.html#table_10.1-1 | |
1136 **/ | |
1137 | |
1138 if (msg->CommandField != DIMSE_N_EVENT_REPORT_RQ /* value == 256 == 0x0100 */ || | |
1139 !server_.HasStorageCommitmentRequestHandlerFactory()) | |
1140 { | |
1141 throw OrthancException(ErrorCode_InternalError); | |
1142 } | |
1143 | |
1144 | |
1145 /** | |
1146 * Check that the storage commitment report is correctly formatted. | |
1147 **/ | |
1148 | |
1149 const T_DIMSE_N_EventReportRQ& report = msg->msg.NEventReportRQ; | |
1150 | |
1151 if (report.EventTypeID != 1 /* successful */ && | |
1152 report.EventTypeID != 2 /* failures exist */) | |
1153 { | |
1154 throw OrthancException(ErrorCode_NotImplemented, | |
1155 "Unknown event for DICOM N-EVENT-REPORT SCP"); | |
1156 } | |
1157 | |
1158 if (std::string(report.AffectedSOPClassUID) != UID_StorageCommitmentPushModelSOPClass || | |
1159 std::string(report.AffectedSOPInstanceUID) != UID_StorageCommitmentPushModelSOPInstance) | |
1160 { | |
1161 throw OrthancException(ErrorCode_NetworkProtocol, | |
1162 "Unexpected incoming SOP class or instance UID for storage commitment"); | |
1163 } | |
1164 | |
1165 if (report.DataSetType != DIMSE_DATASET_PRESENT) | |
1166 { | |
1167 throw OrthancException(ErrorCode_NetworkProtocol, | |
1168 "Incoming storage commitment report without a dataset"); | |
1169 } | |
1170 | |
1171 | |
1172 /** | |
1173 * Extract the DICOM dataset that is associated with the DIMSE | |
1174 * message. The content of this dataset is documented in "Table | |
1175 * J.3-2. Storage Commitment Result - Event Information": | |
1176 * http://dicom.nema.org/medical/dicom/2019a/output/chtml/part04/sect_J.3.3.html#table_J.3-2 | |
1177 **/ | |
1178 | |
1179 std::auto_ptr<DcmDataset> dataset( | |
1180 ReadDataset(assoc_, "Cannot read the dataset in N-EVENT-REPORT SCP", associationTimeout_)); | |
1181 | |
1182 std::string transactionUid = ReadString(*dataset, DCM_TransactionUID); | |
1183 | |
1184 std::vector<std::string> successSopClassUid, successSopInstanceUid; | |
1185 ReadSopSequence(successSopClassUid, successSopInstanceUid, NULL, | |
1186 *dataset, DCM_ReferencedSOPSequence, | |
1187 (report.EventTypeID == 1) /* mandatory in the case of success */); | |
1188 | |
1189 std::vector<std::string> failedSopClassUid, failedSopInstanceUid; | |
1190 std::vector<StorageCommitmentFailureReason> failureReasons; | |
1191 | |
1192 if (report.EventTypeID == 2 /* failures exist */) | |
1193 { | |
1194 ReadSopSequence(failedSopClassUid, failedSopInstanceUid, &failureReasons, | |
1195 *dataset, DCM_FailedSOPSequence, true); | |
1196 } | |
1197 | |
1198 LOG(INFO) << "Incoming storage commitment report, with transaction UID: " << transactionUid; | |
1199 | |
1200 for (size_t i = 0; i < successSopClassUid.size(); i++) | |
1201 { | |
1202 LOG(INFO) << " (success " << (i + 1) << "/" << successSopClassUid.size() | |
1203 << ") SOP Class/Instance UID: " | |
1204 << successSopClassUid[i] << " / " << successSopInstanceUid[i]; | |
1205 } | |
1206 | |
1207 for (size_t i = 0; i < failedSopClassUid.size(); i++) | |
1208 { | |
1209 LOG(INFO) << " (failure " << (i + 1) << "/" << failedSopClassUid.size() | |
1210 << ") SOP Class/Instance UID: " | |
1211 << failedSopClassUid[i] << " / " << failedSopInstanceUid[i]; | |
1212 } | |
1213 | |
1214 /** | |
1215 * Call the Orthanc handler. The list of available DIMSE status | |
1216 * codes can be found at: | |
1217 * http://dicom.nema.org/medical/dicom/2019a/output/chtml/part07/chapter_10.html#sect_10.1.4.1.10 | |
1218 **/ | |
1219 | |
1220 DIC_US dimseStatus; | |
1221 | |
1222 try | |
1223 { | |
1224 std::auto_ptr<IStorageCommitmentRequestHandler> handler | |
1225 (server_.GetStorageCommitmentRequestHandlerFactory(). | |
1226 ConstructStorageCommitmentRequestHandler()); | |
1227 | |
1228 handler->HandleReport(transactionUid, successSopClassUid, successSopInstanceUid, | |
1229 failedSopClassUid, failedSopInstanceUid, failureReasons, | |
1230 remoteIp_, remoteAet_, calledAet_); | |
1231 | |
1232 dimseStatus = 0; // Success | |
1233 } | |
1234 catch (OrthancException& e) | |
1235 { | |
1236 LOG(ERROR) << "Error while processing an incoming storage commitment report: " << e.What(); | |
1237 | |
1238 // Code 0x0110 - "General failure in processing the operation was encountered" | |
1239 dimseStatus = STATUS_N_ProcessingFailure; | |
1240 } | |
1241 | |
1242 | |
1243 /** | |
1244 * Send the DIMSE status back to the SCU. | |
1245 **/ | |
1246 | |
1247 { | |
1248 T_DIMSE_Message response; | |
1249 memset(&response, 0, sizeof(response)); | |
1250 response.CommandField = DIMSE_N_EVENT_REPORT_RSP; | |
1251 | |
1252 T_DIMSE_N_EventReportRSP& content = response.msg.NEventReportRSP; | |
1253 content.MessageIDBeingRespondedTo = report.MessageID; | |
1254 strncpy(content.AffectedSOPClassUID, UID_StorageCommitmentPushModelSOPClass, DIC_UI_LEN); | |
1255 content.DimseStatus = dimseStatus; | |
1256 strncpy(content.AffectedSOPInstanceUID, UID_StorageCommitmentPushModelSOPInstance, DIC_UI_LEN); | |
1257 content.EventTypeID = 0; // Not present, as "O_NEVENTREPORT_EVENTTYPEID" not set in "opts" | |
1258 content.DataSetType = DIMSE_DATASET_NULL; // Dataset is absent in storage commitment response | |
1259 content.opts = O_NEVENTREPORT_AFFECTEDSOPCLASSUID | O_NEVENTREPORT_AFFECTEDSOPINSTANCEUID; | |
1260 | |
1261 return DIMSE_sendMessageUsingMemoryData( | |
1262 assoc_, presID, &response, NULL /* no dataset */, NULL /* dataObject */, | |
1263 NULL /* callback */, NULL /* callback context */, NULL /* commandSet */); | |
1264 } | |
1265 } | |
840 } | 1266 } |
841 } | 1267 } |