Mercurial > hg > orthanc
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(¶ms_); | |
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(¶ms_, /*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 = ¶ms_->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(¶ms_); | |
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(¶ms_, /*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 = ¶ms_->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 |