comparison UnitTestsSources/FromDcmtkTests.cpp @ 3824:6762506ef4fb transcoding

reorganization of CheckCondition()
author Sebastien Jodogne <s.jodogne@gmail.com>
date Fri, 10 Apr 2020 15:24:02 +0200
parents 897ca3103253
children 638906dcfe32
comparison
equal deleted inserted replaced
3823:897ca3103253 3824:6762506ef4fb
2579 { 2579 {
2580 boost::mutex::scoped_lock lock(defaultTimeoutMutex_); 2580 boost::mutex::scoped_lock lock(defaultTimeoutMutex_);
2581 defaultTimeout_ = seconds; 2581 defaultTimeout_ = seconds;
2582 } 2582 }
2583 } 2583 }
2584 2584 };
2585 void CheckCondition(const OFCondition& cond, 2585
2586 const std::string& command) const 2586
2587 static void FillSopSequence(DcmDataset& dataset,
2588 const DcmTagKey& tag,
2589 const std::vector<std::string>& sopClassUids,
2590 const std::vector<std::string>& sopInstanceUids,
2591 const std::vector<StorageCommitmentFailureReason>& failureReasons,
2592 bool hasFailureReasons)
2593 {
2594 assert(sopClassUids.size() == sopInstanceUids.size() &&
2595 (hasFailureReasons ?
2596 failureReasons.size() == sopClassUids.size() :
2597 failureReasons.empty()));
2598
2599 if (sopInstanceUids.empty())
2600 {
2601 // Add an empty sequence
2602 if (!dataset.insertEmptyElement(tag).good())
2603 {
2604 throw OrthancException(ErrorCode_InternalError);
2605 }
2606 }
2607 else
2608 {
2609 for (size_t i = 0; i < sopClassUids.size(); i++)
2610 {
2611 std::unique_ptr<DcmItem> item(new DcmItem);
2612 if (!item->putAndInsertString(DCM_ReferencedSOPClassUID, sopClassUids[i].c_str()).good() ||
2613 !item->putAndInsertString(DCM_ReferencedSOPInstanceUID, sopInstanceUids[i].c_str()).good() ||
2614 (hasFailureReasons &&
2615 !item->putAndInsertUint16(DCM_FailureReason, failureReasons[i]).good()) ||
2616 !dataset.insertSequenceItem(tag, item.release()).good())
2617 {
2618 throw OrthancException(ErrorCode_InternalError);
2619 }
2620 }
2621 }
2622 }
2623
2624
2625 class DicomAssociation : public boost::noncopyable
2626 {
2627 private:
2628 // This is the maximum number of presentation context IDs (the
2629 // number of odd integers between 1 and 255)
2630 // http://dicom.nema.org/medical/dicom/2019e/output/chtml/part08/sect_9.3.2.2.html
2631 static const size_t MAX_PROPOSED_PRESENTATIONS = 128;
2632
2633 struct ProposedPresentationContext
2634 {
2635 std::string abstractSyntax_;
2636 std::set<DicomTransferSyntax> transferSyntaxes_;
2637 };
2638
2639 typedef std::map<std::string, std::map<DicomTransferSyntax, uint8_t> > AcceptedPresentationContexts;
2640
2641 DicomAssociationRole role_;
2642 bool isOpen_;
2643 std::vector<ProposedPresentationContext> proposed_;
2644 AcceptedPresentationContexts accepted_;
2645 T_ASC_Network* net_;
2646 T_ASC_Parameters* params_;
2647 T_ASC_Association* assoc_;
2648
2649 void Initialize()
2650 {
2651 role_ = DicomAssociationRole_Default;
2652 isOpen_ = false;
2653 net_ = NULL;
2654 params_ = NULL;
2655 assoc_ = NULL;
2656
2657 // Must be after "isOpen_ = false"
2658 ClearPresentationContexts();
2659 }
2660
2661 void CheckConnecting(const DicomAssociationParameters& parameters,
2662 const OFCondition& cond)
2663 {
2664 try
2665 {
2666 CheckCondition(cond, parameters, "connecting");
2667 }
2668 catch (OrthancException&)
2669 {
2670 CloseInternal();
2671 throw;
2672 }
2673 }
2674
2675 void CloseInternal()
2676 {
2677 if (assoc_ != NULL)
2678 {
2679 ASC_releaseAssociation(assoc_);
2680 ASC_destroyAssociation(&assoc_);
2681 assoc_ = NULL;
2682 params_ = NULL;
2683 }
2684 else
2685 {
2686 if (params_ != NULL)
2687 {
2688 ASC_destroyAssociationParameters(&params_);
2689 params_ = NULL;
2690 }
2691 }
2692
2693 if (net_ != NULL)
2694 {
2695 ASC_dropNetwork(&net_);
2696 net_ = NULL;
2697 }
2698
2699 accepted_.clear();
2700 isOpen_ = false;
2701 }
2702
2703 void AddAccepted(const std::string& abstractSyntax,
2704 DicomTransferSyntax syntax,
2705 uint8_t presentationContextId)
2706 {
2707 AcceptedPresentationContexts::iterator found = accepted_.find(abstractSyntax);
2708
2709 if (found == accepted_.end())
2710 {
2711 std::map<DicomTransferSyntax, uint8_t> syntaxes;
2712 syntaxes[syntax] = presentationContextId;
2713 accepted_[abstractSyntax] = syntaxes;
2714 }
2715 else
2716 {
2717 if (found->second.find(syntax) != found->second.end())
2718 {
2719 LOG(WARNING) << "The same transfer syntax ("
2720 << GetTransferSyntaxUid(syntax)
2721 << ") was accepted twice for the same abstract syntax UID ("
2722 << abstractSyntax << ")";
2723 }
2724 else
2725 {
2726 found->second[syntax] = presentationContextId;
2727 }
2728 }
2729 }
2730
2731 public:
2732 DicomAssociation()
2733 {
2734 Initialize();
2735 }
2736
2737 ~DicomAssociation()
2738 {
2739 try
2740 {
2741 Close();
2742 }
2743 catch (OrthancException&)
2744 {
2745 // Don't throw exception in destructors
2746 }
2747 }
2748
2749 bool IsOpen() const
2750 {
2751 return isOpen_;
2752 }
2753
2754 void SetRole(DicomAssociationRole role)
2755 {
2756 if (role_ != role)
2757 {
2758 Close();
2759 role_ = role;
2760 }
2761 }
2762
2763 void ClearPresentationContexts()
2764 {
2765 Close();
2766 proposed_.clear();
2767 proposed_.reserve(MAX_PROPOSED_PRESENTATIONS);
2768 }
2769
2770 void Open(const DicomAssociationParameters& parameters)
2771 {
2772 if (isOpen_)
2773 {
2774 return; // Already open
2775 }
2776
2777 // Timeout used during association negociation and ASC_releaseAssociation()
2778 uint32_t acseTimeout = parameters.GetTimeout();
2779 if (acseTimeout == 0)
2780 {
2781 /**
2782 * Timeout is disabled. Global timeout (seconds) for
2783 * connecting to remote hosts. Default value is -1 which
2784 * selects infinite timeout, i.e. blocking connect().
2785 **/
2786 dcmConnectionTimeout.set(-1);
2787 acseTimeout = 10;
2788 }
2789 else
2790 {
2791 dcmConnectionTimeout.set(acseTimeout);
2792 }
2793
2794 T_ASC_SC_ROLE dcmtkRole;
2795 switch (role_)
2796 {
2797 case DicomAssociationRole_Default:
2798 dcmtkRole = ASC_SC_ROLE_DEFAULT;
2799 break;
2800
2801 case DicomAssociationRole_Scu:
2802 dcmtkRole = ASC_SC_ROLE_SCU;
2803 break;
2804
2805 case DicomAssociationRole_Scp:
2806 dcmtkRole = ASC_SC_ROLE_SCP;
2807 break;
2808
2809 default:
2810 throw OrthancException(ErrorCode_ParameterOutOfRange);
2811 }
2812
2813 assert(net_ == NULL &&
2814 params_ == NULL &&
2815 assoc_ == NULL);
2816
2817 if (proposed_.empty())
2818 {
2819 throw OrthancException(ErrorCode_BadSequenceOfCalls,
2820 "No presentation context was proposed");
2821 }
2822
2823 LOG(INFO) << "Opening a DICOM SCU connection from AET \""
2824 << parameters.GetLocalApplicationEntityTitle()
2825 << "\" to AET \"" << parameters.GetRemoteApplicationEntityTitle()
2826 << "\" on host " << parameters.GetRemoteHost()
2827 << ":" << parameters.GetRemotePort()
2828 << " (manufacturer: " << EnumerationToString(parameters.GetRemoteManufacturer()) << ")";
2829
2830 CheckConnecting(parameters, ASC_initializeNetwork(NET_REQUESTOR, 0, /*opt_acse_timeout*/ acseTimeout, &net_));
2831 CheckConnecting(parameters, ASC_createAssociationParameters(&params_, /*opt_maxReceivePDULength*/ ASC_DEFAULTMAXPDU));
2832
2833 // Set this application's title and the called application's title in the params
2834 CheckConnecting(parameters, ASC_setAPTitles(
2835 params_, parameters.GetLocalApplicationEntityTitle().c_str(),
2836 parameters.GetRemoteApplicationEntityTitle().c_str(), NULL));
2837
2838 // Set the network addresses of the local and remote entities
2839 char localHost[HOST_NAME_MAX];
2840 gethostname(localHost, HOST_NAME_MAX - 1);
2841
2842 char remoteHostAndPort[HOST_NAME_MAX];
2843
2844 #ifdef _MSC_VER
2845 _snprintf
2846 #else
2847 snprintf
2848 #endif
2849 (remoteHostAndPort, HOST_NAME_MAX - 1, "%s:%d",
2850 parameters.GetRemoteHost().c_str(), parameters.GetRemotePort());
2851
2852 CheckConnecting(parameters, ASC_setPresentationAddresses(params_, localHost, remoteHostAndPort));
2853
2854 // Set various options
2855 CheckConnecting(parameters, ASC_setTransportLayerType(params_, /*opt_secureConnection*/ false));
2856
2857 // Setup the list of proposed presentation contexts
2858 unsigned int presentationContextId = 1;
2859 for (size_t i = 0; i < proposed_.size(); i++)
2860 {
2861 assert(presentationContextId <= 255);
2862 const char* abstractSyntax = proposed_[i].abstractSyntax_.c_str();
2863
2864 const std::set<DicomTransferSyntax>& source = proposed_[i].transferSyntaxes_;
2865
2866 std::vector<const char*> transferSyntaxes;
2867 transferSyntaxes.reserve(source.size());
2868
2869 for (std::set<DicomTransferSyntax>::const_iterator
2870 it = source.begin(); it != source.end(); ++it)
2871 {
2872 transferSyntaxes.push_back(GetTransferSyntaxUid(*it));
2873 }
2874
2875 assert(!transferSyntaxes.empty());
2876 CheckConnecting(parameters, ASC_addPresentationContext(
2877 params_, presentationContextId, abstractSyntax,
2878 &transferSyntaxes[0], transferSyntaxes.size(), dcmtkRole));
2879
2880 presentationContextId += 2;
2881 }
2882
2883 // Do the association
2884 CheckConnecting(parameters, ASC_requestAssociation(net_, params_, &assoc_));
2885 isOpen_ = true;
2886
2887 // Inspect the accepted transfer syntaxes
2888 LST_HEAD **l = &params_->DULparams.acceptedPresentationContext;
2889 if (*l != NULL)
2890 {
2891 DUL_PRESENTATIONCONTEXT* pc = (DUL_PRESENTATIONCONTEXT*) LST_Head(l);
2892 LST_Position(l, (LST_NODE*)pc);
2893 while (pc)
2894 {
2895 if (pc->result == ASC_P_ACCEPTANCE)
2896 {
2897 DicomTransferSyntax transferSyntax;
2898 if (LookupTransferSyntax(transferSyntax, pc->acceptedTransferSyntax))
2899 {
2900 AddAccepted(pc->abstractSyntax, transferSyntax, pc->presentationContextID);
2901 }
2902 else
2903 {
2904 LOG(WARNING) << "Unknown transfer syntax received from AET \""
2905 << parameters.GetRemoteApplicationEntityTitle()
2906 << "\": " << pc->acceptedTransferSyntax;
2907 }
2908 }
2909
2910 pc = (DUL_PRESENTATIONCONTEXT*) LST_Next(l);
2911 }
2912 }
2913
2914 if (accepted_.empty())
2915 {
2916 throw OrthancException(ErrorCode_NoPresentationContext,
2917 "Unable to negotiate a presentation context with AET \"" +
2918 parameters.GetRemoteApplicationEntityTitle() + "\"");
2919 }
2920 }
2921
2922 void Close()
2923 {
2924 if (isOpen_)
2925 {
2926 CloseInternal();
2927 }
2928 }
2929
2930 bool LookupAcceptedPresentationContext(std::map<DicomTransferSyntax, uint8_t>& target,
2931 const std::string& abstractSyntax) const
2932 {
2933 if (!IsOpen())
2934 {
2935 throw OrthancException(ErrorCode_BadSequenceOfCalls, "Connection not opened");
2936 }
2937
2938 AcceptedPresentationContexts::const_iterator found = accepted_.find(abstractSyntax);
2939
2940 if (found == accepted_.end())
2941 {
2942 return false;
2943 }
2944 else
2945 {
2946 target = found->second;
2947 return true;
2948 }
2949 }
2950
2951 void ProposeGenericPresentationContext(const std::string& abstractSyntax)
2952 {
2953 std::set<DicomTransferSyntax> ts;
2954 ts.insert(DicomTransferSyntax_LittleEndianImplicit);
2955 ts.insert(DicomTransferSyntax_LittleEndianExplicit);
2956 ts.insert(DicomTransferSyntax_BigEndianExplicit); // Retired
2957 ProposePresentationContext(abstractSyntax, ts);
2958 }
2959
2960 void ProposePresentationContext(const std::string& abstractSyntax,
2961 DicomTransferSyntax transferSyntax)
2962 {
2963 std::set<DicomTransferSyntax> ts;
2964 ts.insert(transferSyntax);
2965 ProposePresentationContext(abstractSyntax, ts);
2966 }
2967
2968 size_t GetRemainingPropositions() const
2969 {
2970 assert(proposed_.size() <= MAX_PROPOSED_PRESENTATIONS);
2971 return MAX_PROPOSED_PRESENTATIONS - proposed_.size();
2972 }
2973
2974 void ProposePresentationContext(const std::string& abstractSyntax,
2975 const std::set<DicomTransferSyntax>& transferSyntaxes)
2976 {
2977 if (transferSyntaxes.empty())
2978 {
2979 throw OrthancException(ErrorCode_ParameterOutOfRange,
2980 "No transfer syntax provided");
2981 }
2982
2983 if (proposed_.size() >= MAX_PROPOSED_PRESENTATIONS)
2984 {
2985 throw OrthancException(ErrorCode_ParameterOutOfRange,
2986 "Too many proposed presentation contexts");
2987 }
2988
2989 if (IsOpen())
2990 {
2991 Close();
2992 }
2993
2994 ProposedPresentationContext context;
2995 context.abstractSyntax_ = abstractSyntax;
2996 context.transferSyntaxes_ = transferSyntaxes;
2997
2998 proposed_.push_back(context);
2999 }
3000
3001 T_ASC_Association& GetDcmtkAssociation() const
3002 {
3003 if (isOpen_)
3004 {
3005 assert(assoc_ != NULL);
3006 return *assoc_;
3007 }
3008 else
3009 {
3010 throw OrthancException(ErrorCode_BadSequenceOfCalls,
3011 "The connection is not open");
3012 }
3013 }
3014
3015 T_ASC_Network& GetDcmtkNetwork() const
3016 {
3017 if (isOpen_)
3018 {
3019 assert(net_ != NULL);
3020 return *net_;
3021 }
3022 else
3023 {
3024 throw OrthancException(ErrorCode_BadSequenceOfCalls,
3025 "The connection is not open");
3026 }
3027 }
3028
3029 static void CheckCondition(const OFCondition& cond,
3030 const DicomAssociationParameters& parameters,
3031 const std::string& command)
2587 { 3032 {
2588 if (cond.bad()) 3033 if (cond.bad())
2589 { 3034 {
2590 // Reformat the error message from DCMTK by turning multiline 3035 // Reformat the error message from DCMTK by turning multiline
2591 // errors into a single line 3036 // errors into a single line
2624 info += ")"; 3069 info += ")";
2625 } 3070 }
2626 3071
2627 throw OrthancException(ErrorCode_NetworkProtocol, 3072 throw OrthancException(ErrorCode_NetworkProtocol,
2628 "DicomUserConnection - " + command + " to AET \"" + 3073 "DicomUserConnection - " + command + " to AET \"" +
2629 GetRemoteApplicationEntityTitle() + "\": " + info); 3074 parameters.GetRemoteApplicationEntityTitle() +
2630 } 3075 "\": " + info);
2631 } 3076 }
2632 }; 3077 }
2633
2634
2635 static void FillSopSequence(DcmDataset& dataset,
2636 const DcmTagKey& tag,
2637 const std::vector<std::string>& sopClassUids,
2638 const std::vector<std::string>& sopInstanceUids,
2639 const std::vector<StorageCommitmentFailureReason>& failureReasons,
2640 bool hasFailureReasons)
2641 {
2642 assert(sopClassUids.size() == sopInstanceUids.size() &&
2643 (hasFailureReasons ?
2644 failureReasons.size() == sopClassUids.size() :
2645 failureReasons.empty()));
2646
2647 if (sopInstanceUids.empty())
2648 {
2649 // Add an empty sequence
2650 if (!dataset.insertEmptyElement(tag).good())
2651 {
2652 throw OrthancException(ErrorCode_InternalError);
2653 }
2654 }
2655 else
2656 {
2657 for (size_t i = 0; i < sopClassUids.size(); i++)
2658 {
2659 std::unique_ptr<DcmItem> item(new DcmItem);
2660 if (!item->putAndInsertString(DCM_ReferencedSOPClassUID, sopClassUids[i].c_str()).good() ||
2661 !item->putAndInsertString(DCM_ReferencedSOPInstanceUID, sopInstanceUids[i].c_str()).good() ||
2662 (hasFailureReasons &&
2663 !item->putAndInsertUint16(DCM_FailureReason, failureReasons[i]).good()) ||
2664 !dataset.insertSequenceItem(tag, item.release()).good())
2665 {
2666 throw OrthancException(ErrorCode_InternalError);
2667 }
2668 }
2669 }
2670 }
2671
2672
2673 class DicomAssociation : public boost::noncopyable
2674 {
2675 private:
2676 // This is the maximum number of presentation context IDs (the
2677 // number of odd integers between 1 and 255)
2678 // http://dicom.nema.org/medical/dicom/2019e/output/chtml/part08/sect_9.3.2.2.html
2679 static const size_t MAX_PROPOSED_PRESENTATIONS = 128;
2680 3078
2681 struct ProposedPresentationContext
2682 {
2683 std::string abstractSyntax_;
2684 std::set<DicomTransferSyntax> transferSyntaxes_;
2685 };
2686
2687 typedef std::map<std::string, std::map<DicomTransferSyntax, uint8_t> > AcceptedPresentationContexts;
2688
2689 DicomAssociationRole role_;
2690 bool isOpen_;
2691 std::vector<ProposedPresentationContext> proposed_;
2692 AcceptedPresentationContexts accepted_;
2693 T_ASC_Network* net_;
2694 T_ASC_Parameters* params_;
2695 T_ASC_Association* assoc_;
2696
2697 void Initialize()
2698 {
2699 role_ = DicomAssociationRole_Default;
2700 isOpen_ = false;
2701 net_ = NULL;
2702 params_ = NULL;
2703 assoc_ = NULL;
2704
2705 // Must be after "isOpen_ = false"
2706 ClearPresentationContexts();
2707 }
2708
2709 void CheckConnecting(const DicomAssociationParameters& parameters,
2710 const OFCondition& cond)
2711 {
2712 try
2713 {
2714 parameters.CheckCondition(cond, "connecting");
2715 }
2716 catch (OrthancException&)
2717 {
2718 CloseInternal();
2719 throw;
2720 }
2721 }
2722
2723 void CloseInternal()
2724 {
2725 if (assoc_ != NULL)
2726 {
2727 ASC_releaseAssociation(assoc_);
2728 ASC_destroyAssociation(&assoc_);
2729 assoc_ = NULL;
2730 params_ = NULL;
2731 }
2732 else
2733 {
2734 if (params_ != NULL)
2735 {
2736 ASC_destroyAssociationParameters(&params_);
2737 params_ = NULL;
2738 }
2739 }
2740
2741 if (net_ != NULL)
2742 {
2743 ASC_dropNetwork(&net_);
2744 net_ = NULL;
2745 }
2746
2747 accepted_.clear();
2748 isOpen_ = false;
2749 }
2750
2751 void AddAccepted(const std::string& abstractSyntax,
2752 DicomTransferSyntax syntax,
2753 uint8_t presentationContextId)
2754 {
2755 AcceptedPresentationContexts::iterator found = accepted_.find(abstractSyntax);
2756
2757 if (found == accepted_.end())
2758 {
2759 std::map<DicomTransferSyntax, uint8_t> syntaxes;
2760 syntaxes[syntax] = presentationContextId;
2761 accepted_[abstractSyntax] = syntaxes;
2762 }
2763 else
2764 {
2765 if (found->second.find(syntax) != found->second.end())
2766 {
2767 LOG(WARNING) << "The same transfer syntax ("
2768 << GetTransferSyntaxUid(syntax)
2769 << ") was accepted twice for the same abstract syntax UID ("
2770 << abstractSyntax << ")";
2771 }
2772 else
2773 {
2774 found->second[syntax] = presentationContextId;
2775 }
2776 }
2777 }
2778
2779 public:
2780 DicomAssociation()
2781 {
2782 Initialize();
2783 }
2784
2785 ~DicomAssociation()
2786 {
2787 try
2788 {
2789 Close();
2790 }
2791 catch (OrthancException&)
2792 {
2793 // Don't throw exception in destructors
2794 }
2795 }
2796
2797 bool IsOpen() const
2798 {
2799 return isOpen_;
2800 }
2801
2802 void SetRole(DicomAssociationRole role)
2803 {
2804 if (role_ != role)
2805 {
2806 Close();
2807 role_ = role;
2808 }
2809 }
2810
2811 void ClearPresentationContexts()
2812 {
2813 Close();
2814 proposed_.clear();
2815 proposed_.reserve(MAX_PROPOSED_PRESENTATIONS);
2816 }
2817
2818 void Open(const DicomAssociationParameters& parameters)
2819 {
2820 if (isOpen_)
2821 {
2822 return; // Already open
2823 }
2824
2825 // Timeout used during association negociation and ASC_releaseAssociation()
2826 uint32_t acseTimeout = parameters.GetTimeout();
2827 if (acseTimeout == 0)
2828 {
2829 /**
2830 * Timeout is disabled. Global timeout (seconds) for
2831 * connecting to remote hosts. Default value is -1 which
2832 * selects infinite timeout, i.e. blocking connect().
2833 **/
2834 dcmConnectionTimeout.set(-1);
2835 acseTimeout = 10;
2836 }
2837 else
2838 {
2839 dcmConnectionTimeout.set(acseTimeout);
2840 }
2841
2842 T_ASC_SC_ROLE dcmtkRole;
2843 switch (role_)
2844 {
2845 case DicomAssociationRole_Default:
2846 dcmtkRole = ASC_SC_ROLE_DEFAULT;
2847 break;
2848
2849 case DicomAssociationRole_Scu:
2850 dcmtkRole = ASC_SC_ROLE_SCU;
2851 break;
2852
2853 case DicomAssociationRole_Scp:
2854 dcmtkRole = ASC_SC_ROLE_SCP;
2855 break;
2856
2857 default:
2858 throw OrthancException(ErrorCode_ParameterOutOfRange);
2859 }
2860
2861 assert(net_ == NULL &&
2862 params_ == NULL &&
2863 assoc_ == NULL);
2864
2865 if (proposed_.empty())
2866 {
2867 throw OrthancException(ErrorCode_BadSequenceOfCalls,
2868 "No presentation context was proposed");
2869 }
2870
2871 LOG(INFO) << "Opening a DICOM SCU connection from AET \""
2872 << parameters.GetLocalApplicationEntityTitle()
2873 << "\" to AET \"" << parameters.GetRemoteApplicationEntityTitle()
2874 << "\" on host " << parameters.GetRemoteHost()
2875 << ":" << parameters.GetRemotePort()
2876 << " (manufacturer: " << EnumerationToString(parameters.GetRemoteManufacturer()) << ")";
2877
2878 CheckConnecting(parameters, ASC_initializeNetwork(NET_REQUESTOR, 0, /*opt_acse_timeout*/ acseTimeout, &net_));
2879 CheckConnecting(parameters, ASC_createAssociationParameters(&params_, /*opt_maxReceivePDULength*/ ASC_DEFAULTMAXPDU));
2880
2881 // Set this application's title and the called application's title in the params
2882 CheckConnecting(parameters, ASC_setAPTitles(
2883 params_, parameters.GetLocalApplicationEntityTitle().c_str(),
2884 parameters.GetRemoteApplicationEntityTitle().c_str(), NULL));
2885
2886 // Set the network addresses of the local and remote entities
2887 char localHost[HOST_NAME_MAX];
2888 gethostname(localHost, HOST_NAME_MAX - 1);
2889
2890 char remoteHostAndPort[HOST_NAME_MAX];
2891
2892 #ifdef _MSC_VER
2893 _snprintf
2894 #else
2895 snprintf
2896 #endif
2897 (remoteHostAndPort, HOST_NAME_MAX - 1, "%s:%d",
2898 parameters.GetRemoteHost().c_str(), parameters.GetRemotePort());
2899
2900 CheckConnecting(parameters, ASC_setPresentationAddresses(params_, localHost, remoteHostAndPort));
2901
2902 // Set various options
2903 CheckConnecting(parameters, ASC_setTransportLayerType(params_, /*opt_secureConnection*/ false));
2904
2905 // Setup the list of proposed presentation contexts
2906 unsigned int presentationContextId = 1;
2907 for (size_t i = 0; i < proposed_.size(); i++)
2908 {
2909 assert(presentationContextId <= 255);
2910 const char* abstractSyntax = proposed_[i].abstractSyntax_.c_str();
2911
2912 const std::set<DicomTransferSyntax>& source = proposed_[i].transferSyntaxes_;
2913
2914 std::vector<const char*> transferSyntaxes;
2915 transferSyntaxes.reserve(source.size());
2916
2917 for (std::set<DicomTransferSyntax>::const_iterator
2918 it = source.begin(); it != source.end(); ++it)
2919 {
2920 transferSyntaxes.push_back(GetTransferSyntaxUid(*it));
2921 }
2922
2923 assert(!transferSyntaxes.empty());
2924 CheckConnecting(parameters, ASC_addPresentationContext(
2925 params_, presentationContextId, abstractSyntax,
2926 &transferSyntaxes[0], transferSyntaxes.size(), dcmtkRole));
2927
2928 presentationContextId += 2;
2929 }
2930
2931 // Do the association
2932 CheckConnecting(parameters, ASC_requestAssociation(net_, params_, &assoc_));
2933 isOpen_ = true;
2934
2935 // Inspect the accepted transfer syntaxes
2936 LST_HEAD **l = &params_->DULparams.acceptedPresentationContext;
2937 if (*l != NULL)
2938 {
2939 DUL_PRESENTATIONCONTEXT* pc = (DUL_PRESENTATIONCONTEXT*) LST_Head(l);
2940 LST_Position(l, (LST_NODE*)pc);
2941 while (pc)
2942 {
2943 if (pc->result == ASC_P_ACCEPTANCE)
2944 {
2945 DicomTransferSyntax transferSyntax;
2946 if (LookupTransferSyntax(transferSyntax, pc->acceptedTransferSyntax))
2947 {
2948 AddAccepted(pc->abstractSyntax, transferSyntax, pc->presentationContextID);
2949 }
2950 else
2951 {
2952 LOG(WARNING) << "Unknown transfer syntax received from AET \""
2953 << parameters.GetRemoteApplicationEntityTitle()
2954 << "\": " << pc->acceptedTransferSyntax;
2955 }
2956 }
2957
2958 pc = (DUL_PRESENTATIONCONTEXT*) LST_Next(l);
2959 }
2960 }
2961
2962 if (accepted_.empty())
2963 {
2964 throw OrthancException(ErrorCode_NoPresentationContext,
2965 "Unable to negotiate a presentation context with AET \"" +
2966 parameters.GetRemoteApplicationEntityTitle() + "\"");
2967 }
2968 }
2969
2970 void Close()
2971 {
2972 if (isOpen_)
2973 {
2974 CloseInternal();
2975 }
2976 }
2977
2978 bool LookupAcceptedPresentationContext(std::map<DicomTransferSyntax, uint8_t>& target,
2979 const std::string& abstractSyntax) const
2980 {
2981 if (!IsOpen())
2982 {
2983 throw OrthancException(ErrorCode_BadSequenceOfCalls, "Connection not opened");
2984 }
2985
2986 AcceptedPresentationContexts::const_iterator found = accepted_.find(abstractSyntax);
2987
2988 if (found == accepted_.end())
2989 {
2990 return false;
2991 }
2992 else
2993 {
2994 target = found->second;
2995 return true;
2996 }
2997 }
2998
2999 void ProposeGenericPresentationContext(const std::string& abstractSyntax)
3000 {
3001 std::set<DicomTransferSyntax> ts;
3002 ts.insert(DicomTransferSyntax_LittleEndianImplicit);
3003 ts.insert(DicomTransferSyntax_LittleEndianExplicit);
3004 ts.insert(DicomTransferSyntax_BigEndianExplicit); // Retired
3005 ProposePresentationContext(abstractSyntax, ts);
3006 }
3007
3008 void ProposePresentationContext(const std::string& abstractSyntax,
3009 DicomTransferSyntax transferSyntax)
3010 {
3011 std::set<DicomTransferSyntax> ts;
3012 ts.insert(transferSyntax);
3013 ProposePresentationContext(abstractSyntax, ts);
3014 }
3015
3016 size_t GetRemainingPropositions() const
3017 {
3018 assert(proposed_.size() <= MAX_PROPOSED_PRESENTATIONS);
3019 return MAX_PROPOSED_PRESENTATIONS - proposed_.size();
3020 }
3021
3022 void ProposePresentationContext(const std::string& abstractSyntax,
3023 const std::set<DicomTransferSyntax>& transferSyntaxes)
3024 {
3025 if (transferSyntaxes.empty())
3026 {
3027 throw OrthancException(ErrorCode_ParameterOutOfRange,
3028 "No transfer syntax provided");
3029 }
3030
3031 if (proposed_.size() >= MAX_PROPOSED_PRESENTATIONS)
3032 {
3033 throw OrthancException(ErrorCode_ParameterOutOfRange,
3034 "Too many proposed presentation contexts");
3035 }
3036
3037 if (IsOpen())
3038 {
3039 Close();
3040 }
3041
3042 ProposedPresentationContext context;
3043 context.abstractSyntax_ = abstractSyntax;
3044 context.transferSyntaxes_ = transferSyntaxes;
3045
3046 proposed_.push_back(context);
3047 }
3048
3049 T_ASC_Association& GetDcmtkAssociation() const
3050 {
3051 if (isOpen_)
3052 {
3053 assert(assoc_ != NULL);
3054 return *assoc_;
3055 }
3056 else
3057 {
3058 throw OrthancException(ErrorCode_BadSequenceOfCalls,
3059 "The connection is not open");
3060 }
3061 }
3062
3063 T_ASC_Network& GetDcmtkNetwork() const
3064 {
3065 if (isOpen_)
3066 {
3067 assert(net_ != NULL);
3068 return *net_;
3069 }
3070 else
3071 {
3072 throw OrthancException(ErrorCode_BadSequenceOfCalls,
3073 "The connection is not open");
3074 }
3075 }
3076
3077 3079
3078 static void ReportStorageCommitment(const DicomAssociationParameters& parameters, 3080 static void ReportStorageCommitment(const DicomAssociationParameters& parameters,
3079 const std::string& transactionUid, 3081 const std::string& transactionUid,
3080 const std::vector<std::string>& sopClassUids, 3082 const std::vector<std::string>& sopClassUids,
3081 const std::vector<std::string>& sopInstanceUids, 3083 const std::vector<std::string>& sopInstanceUids,
3654 if (statusDetail) 3656 if (statusDetail)
3655 { 3657 {
3656 delete statusDetail; 3658 delete statusDetail;
3657 } 3659 }
3658 3660
3659 parameters_.CheckCondition(cond, "C-FIND"); 3661 DicomAssociation::CheckCondition(cond, parameters_, "C-FIND");
3660 3662
3661 3663
3662 /** 3664 /**
3663 * New in Orthanc 1.6.0: Deal with failures during C-FIND. 3665 * New in Orthanc 1.6.0: Deal with failures during C-FIND.
3664 * http://dicom.nema.org/medical/dicom/current/output/chtml/part04/sect_C.4.html#table_C.4-1 3666 * http://dicom.nema.org/medical/dicom/current/output/chtml/part04/sect_C.4.html#table_C.4-1
3756 if (responseIdentifiers) 3758 if (responseIdentifiers)
3757 { 3759 {
3758 delete responseIdentifiers; 3760 delete responseIdentifiers;
3759 } 3761 }
3760 3762
3761 parameters_.CheckCondition(cond, "C-MOVE"); 3763 DicomAssociation::CheckCondition(cond, parameters_, "C-MOVE");
3762 3764
3763 3765
3764 /** 3766 /**
3765 * New in Orthanc 1.6.0: Deal with failures during C-MOVE. 3767 * New in Orthanc 1.6.0: Deal with failures during C-MOVE.
3766 * http://dicom.nema.org/medical/dicom/current/output/chtml/part04/sect_C.4.2.html#table_C.4-2 3768 * http://dicom.nema.org/medical/dicom/current/output/chtml/part04/sect_C.4.2.html#table_C.4-2
3805 bool Echo() 3807 bool Echo()
3806 { 3808 {
3807 association_.Open(parameters_); 3809 association_.Open(parameters_);
3808 3810
3809 DIC_US status; 3811 DIC_US status;
3810 parameters_.CheckCondition( 3812 DicomAssociation::CheckCondition(
3811 DIMSE_echoUser(&association_.GetDcmtkAssociation(), 3813 DIMSE_echoUser(&association_.GetDcmtkAssociation(),
3812 association_.GetDcmtkAssociation().nextMsgID++, 3814 association_.GetDcmtkAssociation().nextMsgID++,
3813 /*opt_blockMode*/ (parameters_.HasTimeout() ? DIMSE_NONBLOCKING : DIMSE_BLOCKING), 3815 /*opt_blockMode*/ (parameters_.HasTimeout() ? DIMSE_NONBLOCKING : DIMSE_BLOCKING),
3814 /*opt_dimse_timeout*/ parameters_.GetTimeout(), 3816 /*opt_dimse_timeout*/ parameters_.GetTimeout(),
3815 &status, NULL), 3817 &status, NULL),
3816 "C-ECHO"); 3818 parameters_, "C-ECHO");
3817 3819
3818 return status == STATUS_Success; 3820 return status == STATUS_Success;
3819 } 3821 }
3820 3822
3821 3823