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 }