Mercurial > hg > orthanc
comparison UnitTestsSources/FromDcmtkTests.cpp @ 3821:f2488b645f5f transcoding
adding storage commitment to DicomAssociation
author | Sebastien Jodogne <s.jodogne@gmail.com> |
---|---|
date | Thu, 09 Apr 2020 18:29:42 +0200 |
parents | f89eac983c9b |
children | 0d5f3a438e14 |
comparison
equal
deleted
inserted
replaced
3820:f89eac983c9b | 3821:f2488b645f5f |
---|---|
2450 std::string localAet_; | 2450 std::string localAet_; |
2451 std::string remoteAet_; | 2451 std::string remoteAet_; |
2452 std::string remoteHost_; | 2452 std::string remoteHost_; |
2453 uint16_t remotePort_; | 2453 uint16_t remotePort_; |
2454 ModalityManufacturer manufacturer_; | 2454 ModalityManufacturer manufacturer_; |
2455 DicomAssociationRole role_; | |
2456 uint32_t timeout_; | 2455 uint32_t timeout_; |
2457 | 2456 |
2458 void ReadDefaultTimeout() | 2457 void ReadDefaultTimeout() |
2459 { | 2458 { |
2460 boost::mutex::scoped_lock lock(defaultTimeoutMutex_); | 2459 boost::mutex::scoped_lock lock(defaultTimeoutMutex_); |
2465 DicomAssociationParameters() : | 2464 DicomAssociationParameters() : |
2466 localAet_("STORESCU"), | 2465 localAet_("STORESCU"), |
2467 remoteAet_("ANY-SCP"), | 2466 remoteAet_("ANY-SCP"), |
2468 remoteHost_("127.0.0.1"), | 2467 remoteHost_("127.0.0.1"), |
2469 remotePort_(104), | 2468 remotePort_(104), |
2470 manufacturer_(ModalityManufacturer_Generic), | 2469 manufacturer_(ModalityManufacturer_Generic) |
2471 role_(DicomAssociationRole_Default) | |
2472 { | 2470 { |
2473 ReadDefaultTimeout(); | 2471 ReadDefaultTimeout(); |
2474 } | 2472 } |
2475 | 2473 |
2476 DicomAssociationParameters(const std::string& localAet, | 2474 DicomAssociationParameters(const std::string& localAet, |
2478 localAet_(localAet), | 2476 localAet_(localAet), |
2479 remoteAet_(remote.GetApplicationEntityTitle()), | 2477 remoteAet_(remote.GetApplicationEntityTitle()), |
2480 remoteHost_(remote.GetHost()), | 2478 remoteHost_(remote.GetHost()), |
2481 remotePort_(remote.GetPortNumber()), | 2479 remotePort_(remote.GetPortNumber()), |
2482 manufacturer_(remote.GetManufacturer()), | 2480 manufacturer_(remote.GetManufacturer()), |
2483 role_(DicomAssociationRole_Default), | |
2484 timeout_(defaultTimeout_) | 2481 timeout_(defaultTimeout_) |
2485 { | 2482 { |
2486 ReadDefaultTimeout(); | 2483 ReadDefaultTimeout(); |
2487 } | 2484 } |
2488 | 2485 |
2509 ModalityManufacturer GetRemoteManufacturer() const | 2506 ModalityManufacturer GetRemoteManufacturer() const |
2510 { | 2507 { |
2511 return manufacturer_; | 2508 return manufacturer_; |
2512 } | 2509 } |
2513 | 2510 |
2514 DicomAssociationRole GetRole() const | |
2515 { | |
2516 return role_; | |
2517 } | |
2518 | |
2519 void SetLocalApplicationEntityTitle(const std::string& aet) | 2511 void SetLocalApplicationEntityTitle(const std::string& aet) |
2520 { | 2512 { |
2521 localAet_ = aet; | 2513 localAet_ = aet; |
2522 } | 2514 } |
2523 | 2515 |
2543 } | 2535 } |
2544 | 2536 |
2545 void SetRemoteManufacturer(ModalityManufacturer manufacturer) | 2537 void SetRemoteManufacturer(ModalityManufacturer manufacturer) |
2546 { | 2538 { |
2547 manufacturer_ = manufacturer; | 2539 manufacturer_ = manufacturer; |
2548 } | |
2549 | |
2550 void SetRole(DicomAssociationRole role) | |
2551 { | |
2552 role_ = role; | |
2553 } | 2540 } |
2554 | 2541 |
2555 void SetRemoteModality(const RemoteModalityParameters& parameters) | 2542 void SetRemoteModality(const RemoteModalityParameters& parameters) |
2556 { | 2543 { |
2557 SetRemoteApplicationEntityTitle(parameters.GetApplicationEntityTitle()); | 2544 SetRemoteApplicationEntityTitle(parameters.GetApplicationEntityTitle()); |
2564 { | 2551 { |
2565 return (localAet_ == other.localAet_ && | 2552 return (localAet_ == other.localAet_ && |
2566 remoteAet_ == other.remoteAet_ && | 2553 remoteAet_ == other.remoteAet_ && |
2567 remoteHost_ == other.remoteHost_ && | 2554 remoteHost_ == other.remoteHost_ && |
2568 remotePort_ == other.remotePort_ && | 2555 remotePort_ == other.remotePort_ && |
2569 manufacturer_ == other.manufacturer_ && | 2556 manufacturer_ == other.manufacturer_); |
2570 role_ == other.role_); | |
2571 } | 2557 } |
2572 | 2558 |
2573 void SetTimeout(uint32_t seconds) | 2559 void SetTimeout(uint32_t seconds) |
2574 { | 2560 { |
2575 timeout_ = seconds; | 2561 timeout_ = seconds; |
2644 } | 2630 } |
2645 } | 2631 } |
2646 }; | 2632 }; |
2647 | 2633 |
2648 | 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 | |
2649 class DicomAssociation : public boost::noncopyable | 2673 class DicomAssociation : public boost::noncopyable |
2650 { | 2674 { |
2651 private: | 2675 private: |
2652 // This is the maximum number of presentation context IDs (the | 2676 // This is the maximum number of presentation context IDs (the |
2653 // number of odd integers between 1 and 255) | 2677 // number of odd integers between 1 and 255) |
2660 std::set<DicomTransferSyntax> transferSyntaxes_; | 2684 std::set<DicomTransferSyntax> transferSyntaxes_; |
2661 }; | 2685 }; |
2662 | 2686 |
2663 typedef std::map<std::string, std::map<DicomTransferSyntax, uint8_t> > AcceptedPresentationContexts; | 2687 typedef std::map<std::string, std::map<DicomTransferSyntax, uint8_t> > AcceptedPresentationContexts; |
2664 | 2688 |
2689 DicomAssociationRole role_; | |
2665 bool isOpen_; | 2690 bool isOpen_; |
2666 std::vector<ProposedPresentationContext> proposed_; | 2691 std::vector<ProposedPresentationContext> proposed_; |
2667 AcceptedPresentationContexts accepted_; | 2692 AcceptedPresentationContexts accepted_; |
2668 T_ASC_Network* net_; | 2693 T_ASC_Network* net_; |
2669 T_ASC_Parameters* params_; | 2694 T_ASC_Parameters* params_; |
2670 T_ASC_Association* assoc_; | 2695 T_ASC_Association* assoc_; |
2671 | 2696 |
2672 void Initialize() | 2697 void Initialize() |
2673 { | 2698 { |
2699 role_ = DicomAssociationRole_Default; | |
2674 isOpen_ = false; | 2700 isOpen_ = false; |
2675 net_ = NULL; | 2701 net_ = NULL; |
2676 params_ = NULL; | 2702 params_ = NULL; |
2677 assoc_ = NULL; | 2703 assoc_ = NULL; |
2678 | 2704 |
2771 bool IsOpen() const | 2797 bool IsOpen() const |
2772 { | 2798 { |
2773 return isOpen_; | 2799 return isOpen_; |
2774 } | 2800 } |
2775 | 2801 |
2802 void SetRole(DicomAssociationRole role) | |
2803 { | |
2804 if (role_ != role) | |
2805 { | |
2806 Close(); | |
2807 role_ = role; | |
2808 } | |
2809 } | |
2810 | |
2776 void ClearPresentationContexts() | 2811 void ClearPresentationContexts() |
2777 { | 2812 { |
2778 Close(); | 2813 Close(); |
2779 proposed_.clear(); | 2814 proposed_.clear(); |
2780 proposed_.reserve(MAX_PROPOSED_PRESENTATIONS); | 2815 proposed_.reserve(MAX_PROPOSED_PRESENTATIONS); |
2803 { | 2838 { |
2804 dcmConnectionTimeout.set(acseTimeout); | 2839 dcmConnectionTimeout.set(acseTimeout); |
2805 } | 2840 } |
2806 | 2841 |
2807 T_ASC_SC_ROLE dcmtkRole; | 2842 T_ASC_SC_ROLE dcmtkRole; |
2808 switch (parameters.GetRole()) | 2843 switch (role_) |
2809 { | 2844 { |
2810 case DicomAssociationRole_Default: | 2845 case DicomAssociationRole_Default: |
2811 dcmtkRole = ASC_SC_ROLE_DEFAULT; | 2846 dcmtkRole = ASC_SC_ROLE_DEFAULT; |
2812 break; | 2847 break; |
2813 | 2848 |
3030 { | 3065 { |
3031 throw OrthancException(ErrorCode_BadSequenceOfCalls, | 3066 throw OrthancException(ErrorCode_BadSequenceOfCalls, |
3032 "The connection is not open"); | 3067 "The connection is not open"); |
3033 } | 3068 } |
3034 } | 3069 } |
3070 | |
3071 | |
3072 static void ReportStorageCommitment(const DicomAssociationParameters& parameters, | |
3073 const std::string& transactionUid, | |
3074 const std::vector<std::string>& sopClassUids, | |
3075 const std::vector<std::string>& sopInstanceUids, | |
3076 const std::vector<StorageCommitmentFailureReason>& failureReasons) | |
3077 { | |
3078 if (sopClassUids.size() != sopInstanceUids.size() || | |
3079 sopClassUids.size() != failureReasons.size()) | |
3080 { | |
3081 throw OrthancException(ErrorCode_ParameterOutOfRange); | |
3082 } | |
3083 | |
3084 | |
3085 std::vector<std::string> successSopClassUids, successSopInstanceUids, failedSopClassUids, failedSopInstanceUids; | |
3086 std::vector<StorageCommitmentFailureReason> failedReasons; | |
3087 | |
3088 successSopClassUids.reserve(sopClassUids.size()); | |
3089 successSopInstanceUids.reserve(sopClassUids.size()); | |
3090 failedSopClassUids.reserve(sopClassUids.size()); | |
3091 failedSopInstanceUids.reserve(sopClassUids.size()); | |
3092 failedReasons.reserve(sopClassUids.size()); | |
3093 | |
3094 for (size_t i = 0; i < sopClassUids.size(); i++) | |
3095 { | |
3096 switch (failureReasons[i]) | |
3097 { | |
3098 case StorageCommitmentFailureReason_Success: | |
3099 successSopClassUids.push_back(sopClassUids[i]); | |
3100 successSopInstanceUids.push_back(sopInstanceUids[i]); | |
3101 break; | |
3102 | |
3103 case StorageCommitmentFailureReason_ProcessingFailure: | |
3104 case StorageCommitmentFailureReason_NoSuchObjectInstance: | |
3105 case StorageCommitmentFailureReason_ResourceLimitation: | |
3106 case StorageCommitmentFailureReason_ReferencedSOPClassNotSupported: | |
3107 case StorageCommitmentFailureReason_ClassInstanceConflict: | |
3108 case StorageCommitmentFailureReason_DuplicateTransactionUID: | |
3109 failedSopClassUids.push_back(sopClassUids[i]); | |
3110 failedSopInstanceUids.push_back(sopInstanceUids[i]); | |
3111 failedReasons.push_back(failureReasons[i]); | |
3112 break; | |
3113 | |
3114 default: | |
3115 { | |
3116 char buf[16]; | |
3117 sprintf(buf, "%04xH", failureReasons[i]); | |
3118 throw OrthancException(ErrorCode_ParameterOutOfRange, | |
3119 "Unsupported failure reason for storage commitment: " + std::string(buf)); | |
3120 } | |
3121 } | |
3122 } | |
3123 | |
3124 DicomAssociation association; | |
3125 | |
3126 { | |
3127 std::set<DicomTransferSyntax> transferSyntaxes; | |
3128 transferSyntaxes.insert(DicomTransferSyntax_LittleEndianExplicit); | |
3129 transferSyntaxes.insert(DicomTransferSyntax_LittleEndianImplicit); | |
3130 | |
3131 association.SetRole(DicomAssociationRole_Scp); | |
3132 association.ProposePresentationContext(UID_StorageCommitmentPushModelSOPClass, | |
3133 transferSyntaxes); | |
3134 } | |
3135 | |
3136 association.Open(parameters); | |
3137 | |
3138 /** | |
3139 * N-EVENT-REPORT | |
3140 * http://dicom.nema.org/medical/dicom/2019a/output/chtml/part04/sect_J.3.3.html | |
3141 * http://dicom.nema.org/medical/dicom/2019a/output/chtml/part07/chapter_10.html#table_10.1-1 | |
3142 * | |
3143 * Status code: | |
3144 * http://dicom.nema.org/medical/dicom/2019a/output/chtml/part07/chapter_10.html#sect_10.1.1.1.8 | |
3145 **/ | |
3146 | |
3147 /** | |
3148 * Send the "EVENT_REPORT_RQ" request | |
3149 **/ | |
3150 | |
3151 LOG(INFO) << "Reporting modality \"" | |
3152 << parameters.GetRemoteApplicationEntityTitle() | |
3153 << "\" about storage commitment transaction: " << transactionUid | |
3154 << " (" << successSopClassUids.size() << " successes, " | |
3155 << failedSopClassUids.size() << " failures)"; | |
3156 const DIC_US messageId = association.GetDcmtkAssociation().nextMsgID++; | |
3157 | |
3158 { | |
3159 T_DIMSE_Message message; | |
3160 memset(&message, 0, sizeof(message)); | |
3161 message.CommandField = DIMSE_N_EVENT_REPORT_RQ; | |
3162 | |
3163 T_DIMSE_N_EventReportRQ& content = message.msg.NEventReportRQ; | |
3164 content.MessageID = messageId; | |
3165 strncpy(content.AffectedSOPClassUID, UID_StorageCommitmentPushModelSOPClass, DIC_UI_LEN); | |
3166 strncpy(content.AffectedSOPInstanceUID, UID_StorageCommitmentPushModelSOPInstance, DIC_UI_LEN); | |
3167 content.DataSetType = DIMSE_DATASET_PRESENT; | |
3168 | |
3169 DcmDataset dataset; | |
3170 if (!dataset.putAndInsertString(DCM_TransactionUID, transactionUid.c_str()).good()) | |
3171 { | |
3172 throw OrthancException(ErrorCode_InternalError); | |
3173 } | |
3174 | |
3175 { | |
3176 std::vector<StorageCommitmentFailureReason> empty; | |
3177 FillSopSequence(dataset, DCM_ReferencedSOPSequence, successSopClassUids, | |
3178 successSopInstanceUids, empty, false); | |
3179 } | |
3180 | |
3181 // http://dicom.nema.org/medical/dicom/2019a/output/chtml/part04/sect_J.3.3.html | |
3182 if (failedSopClassUids.empty()) | |
3183 { | |
3184 content.EventTypeID = 1; // "Storage Commitment Request Successful" | |
3185 } | |
3186 else | |
3187 { | |
3188 content.EventTypeID = 2; // "Storage Commitment Request Complete - Failures Exist" | |
3189 | |
3190 // Failure reason | |
3191 // http://dicom.nema.org/medical/dicom/2019a/output/chtml/part03/sect_C.14.html#sect_C.14.1.1 | |
3192 FillSopSequence(dataset, DCM_FailedSOPSequence, failedSopClassUids, | |
3193 failedSopInstanceUids, failedReasons, true); | |
3194 } | |
3195 | |
3196 int presID = ASC_findAcceptedPresentationContextID( | |
3197 &association.GetDcmtkAssociation(), UID_StorageCommitmentPushModelSOPClass); | |
3198 if (presID == 0) | |
3199 { | |
3200 throw OrthancException(ErrorCode_NetworkProtocol, "Storage commitment - " | |
3201 "Unable to send N-EVENT-REPORT request to AET: " + | |
3202 parameters.GetRemoteApplicationEntityTitle()); | |
3203 } | |
3204 | |
3205 if (!DIMSE_sendMessageUsingMemoryData( | |
3206 &association.GetDcmtkAssociation(), presID, &message, NULL /* status detail */, | |
3207 &dataset, NULL /* callback */, NULL /* callback context */, | |
3208 NULL /* commandSet */).good()) | |
3209 { | |
3210 throw OrthancException(ErrorCode_NetworkProtocol); | |
3211 } | |
3212 } | |
3213 | |
3214 /** | |
3215 * Read the "EVENT_REPORT_RSP" response | |
3216 **/ | |
3217 | |
3218 { | |
3219 T_ASC_PresentationContextID presID = 0; | |
3220 T_DIMSE_Message message; | |
3221 | |
3222 if (!DIMSE_receiveCommand(&association.GetDcmtkAssociation(), | |
3223 (parameters.HasTimeout() ? DIMSE_NONBLOCKING : DIMSE_BLOCKING), | |
3224 parameters.GetTimeout(), &presID, &message, | |
3225 NULL /* no statusDetail */).good() || | |
3226 message.CommandField != DIMSE_N_EVENT_REPORT_RSP) | |
3227 { | |
3228 throw OrthancException(ErrorCode_NetworkProtocol, "Storage commitment - " | |
3229 "Unable to read N-EVENT-REPORT response from AET: " + | |
3230 parameters.GetRemoteApplicationEntityTitle()); | |
3231 } | |
3232 | |
3233 const T_DIMSE_N_EventReportRSP& content = message.msg.NEventReportRSP; | |
3234 if (content.MessageIDBeingRespondedTo != messageId || | |
3235 !(content.opts & O_NEVENTREPORT_AFFECTEDSOPCLASSUID) || | |
3236 !(content.opts & O_NEVENTREPORT_AFFECTEDSOPINSTANCEUID) || | |
3237 //(content.opts & O_NEVENTREPORT_EVENTTYPEID) || // Pedantic test - The "content.EventTypeID" is not used by Orthanc | |
3238 std::string(content.AffectedSOPClassUID) != UID_StorageCommitmentPushModelSOPClass || | |
3239 std::string(content.AffectedSOPInstanceUID) != UID_StorageCommitmentPushModelSOPInstance || | |
3240 content.DataSetType != DIMSE_DATASET_NULL) | |
3241 { | |
3242 throw OrthancException(ErrorCode_NetworkProtocol, "Storage commitment - " | |
3243 "Badly formatted N-EVENT-REPORT response from AET: " + | |
3244 parameters.GetRemoteApplicationEntityTitle()); | |
3245 } | |
3246 | |
3247 if (content.DimseStatus != 0 /* success */) | |
3248 { | |
3249 throw OrthancException(ErrorCode_NetworkProtocol, "Storage commitment - " | |
3250 "The request cannot be handled by remote AET: " + | |
3251 parameters.GetRemoteApplicationEntityTitle()); | |
3252 } | |
3253 } | |
3254 | |
3255 association.Close(); | |
3256 } | |
3257 | |
3258 static void RequestStorageCommitment(const DicomAssociationParameters& parameters, | |
3259 const std::string& transactionUid, | |
3260 const std::vector<std::string>& sopClassUids, | |
3261 const std::vector<std::string>& sopInstanceUids) | |
3262 { | |
3263 if (sopClassUids.size() != sopInstanceUids.size()) | |
3264 { | |
3265 throw OrthancException(ErrorCode_ParameterOutOfRange); | |
3266 } | |
3267 | |
3268 for (size_t i = 0; i < sopClassUids.size(); i++) | |
3269 { | |
3270 if (sopClassUids[i].empty() || | |
3271 sopInstanceUids[i].empty()) | |
3272 { | |
3273 throw OrthancException(ErrorCode_ParameterOutOfRange, | |
3274 "The SOP class/instance UIDs cannot be empty, found: \"" + | |
3275 sopClassUids[i] + "\" / \"" + sopInstanceUids[i] + "\""); | |
3276 } | |
3277 } | |
3278 | |
3279 if (transactionUid.size() < 5 || | |
3280 transactionUid.substr(0, 5) != "2.25.") | |
3281 { | |
3282 throw OrthancException(ErrorCode_ParameterOutOfRange); | |
3283 } | |
3284 | |
3285 DicomAssociation association; | |
3286 | |
3287 { | |
3288 std::set<DicomTransferSyntax> transferSyntaxes; | |
3289 transferSyntaxes.insert(DicomTransferSyntax_LittleEndianExplicit); | |
3290 transferSyntaxes.insert(DicomTransferSyntax_LittleEndianImplicit); | |
3291 | |
3292 association.SetRole(DicomAssociationRole_Default); | |
3293 association.ProposePresentationContext(UID_StorageCommitmentPushModelSOPClass, | |
3294 transferSyntaxes); | |
3295 } | |
3296 | |
3297 association.Open(parameters); | |
3298 | |
3299 /** | |
3300 * N-ACTION | |
3301 * http://dicom.nema.org/medical/dicom/2019a/output/chtml/part04/sect_J.3.2.html | |
3302 * http://dicom.nema.org/medical/dicom/2019a/output/chtml/part07/chapter_10.html#table_10.1-4 | |
3303 * | |
3304 * Status code: | |
3305 * http://dicom.nema.org/medical/dicom/2019a/output/chtml/part07/chapter_10.html#sect_10.1.1.1.8 | |
3306 **/ | |
3307 | |
3308 /** | |
3309 * Send the "N_ACTION_RQ" request | |
3310 **/ | |
3311 | |
3312 LOG(INFO) << "Request to modality \"" | |
3313 << parameters.GetRemoteApplicationEntityTitle() | |
3314 << "\" about storage commitment for " << sopClassUids.size() | |
3315 << " instances, with transaction UID: " << transactionUid; | |
3316 const DIC_US messageId = association.GetDcmtkAssociation().nextMsgID++; | |
3317 | |
3318 { | |
3319 T_DIMSE_Message message; | |
3320 memset(&message, 0, sizeof(message)); | |
3321 message.CommandField = DIMSE_N_ACTION_RQ; | |
3322 | |
3323 T_DIMSE_N_ActionRQ& content = message.msg.NActionRQ; | |
3324 content.MessageID = messageId; | |
3325 strncpy(content.RequestedSOPClassUID, UID_StorageCommitmentPushModelSOPClass, DIC_UI_LEN); | |
3326 strncpy(content.RequestedSOPInstanceUID, UID_StorageCommitmentPushModelSOPInstance, DIC_UI_LEN); | |
3327 content.ActionTypeID = 1; // "Request Storage Commitment" | |
3328 content.DataSetType = DIMSE_DATASET_PRESENT; | |
3329 | |
3330 DcmDataset dataset; | |
3331 if (!dataset.putAndInsertString(DCM_TransactionUID, transactionUid.c_str()).good()) | |
3332 { | |
3333 throw OrthancException(ErrorCode_InternalError); | |
3334 } | |
3335 | |
3336 { | |
3337 std::vector<StorageCommitmentFailureReason> empty; | |
3338 FillSopSequence(dataset, DCM_ReferencedSOPSequence, sopClassUids, sopInstanceUids, empty, false); | |
3339 } | |
3340 | |
3341 int presID = ASC_findAcceptedPresentationContextID( | |
3342 &association.GetDcmtkAssociation(), UID_StorageCommitmentPushModelSOPClass); | |
3343 if (presID == 0) | |
3344 { | |
3345 throw OrthancException(ErrorCode_NetworkProtocol, "Storage commitment - " | |
3346 "Unable to send N-ACTION request to AET: " + | |
3347 parameters.GetRemoteApplicationEntityTitle()); | |
3348 } | |
3349 | |
3350 if (!DIMSE_sendMessageUsingMemoryData( | |
3351 &association.GetDcmtkAssociation(), presID, &message, NULL /* status detail */, | |
3352 &dataset, NULL /* callback */, NULL /* callback context */, | |
3353 NULL /* commandSet */).good()) | |
3354 { | |
3355 throw OrthancException(ErrorCode_NetworkProtocol); | |
3356 } | |
3357 } | |
3358 | |
3359 /** | |
3360 * Read the "N_ACTION_RSP" response | |
3361 **/ | |
3362 | |
3363 { | |
3364 T_ASC_PresentationContextID presID = 0; | |
3365 T_DIMSE_Message message; | |
3366 | |
3367 if (!DIMSE_receiveCommand(&association.GetDcmtkAssociation(), | |
3368 (parameters.HasTimeout() ? DIMSE_NONBLOCKING : DIMSE_BLOCKING), | |
3369 parameters.GetTimeout(), &presID, &message, | |
3370 NULL /* no statusDetail */).good() || | |
3371 message.CommandField != DIMSE_N_ACTION_RSP) | |
3372 { | |
3373 throw OrthancException(ErrorCode_NetworkProtocol, "Storage commitment - " | |
3374 "Unable to read N-ACTION response from AET: " + | |
3375 parameters.GetRemoteApplicationEntityTitle()); | |
3376 } | |
3377 | |
3378 const T_DIMSE_N_ActionRSP& content = message.msg.NActionRSP; | |
3379 if (content.MessageIDBeingRespondedTo != messageId || | |
3380 !(content.opts & O_NACTION_AFFECTEDSOPCLASSUID) || | |
3381 !(content.opts & O_NACTION_AFFECTEDSOPINSTANCEUID) || | |
3382 //(content.opts & O_NACTION_ACTIONTYPEID) || // Pedantic test - The "content.ActionTypeID" is not used by Orthanc | |
3383 std::string(content.AffectedSOPClassUID) != UID_StorageCommitmentPushModelSOPClass || | |
3384 std::string(content.AffectedSOPInstanceUID) != UID_StorageCommitmentPushModelSOPInstance || | |
3385 content.DataSetType != DIMSE_DATASET_NULL) | |
3386 { | |
3387 throw OrthancException(ErrorCode_NetworkProtocol, "Storage commitment - " | |
3388 "Badly formatted N-ACTION response from AET: " + | |
3389 parameters.GetRemoteApplicationEntityTitle()); | |
3390 } | |
3391 | |
3392 if (content.DimseStatus != 0 /* success */) | |
3393 { | |
3394 throw OrthancException(ErrorCode_NetworkProtocol, "Storage commitment - " | |
3395 "The request cannot be handled by remote AET: " + | |
3396 parameters.GetRemoteApplicationEntityTitle()); | |
3397 } | |
3398 } | |
3399 | |
3400 association.Close(); | |
3401 } | |
3035 }; | 3402 }; |
3036 | 3403 |
3037 | 3404 |
3038 | 3405 |
3039 static void TestAndCopyTag(DicomMap& result, | 3406 static void TestAndCopyTag(DicomMap& result, |
3666 const char* sopClass = UID_FINDModalityWorklistInformationModel; | 4033 const char* sopClass = UID_FINDModalityWorklistInformationModel; |
3667 | 4034 |
3668 FindInternal(result, dataset, sopClass, true, NULL); | 4035 FindInternal(result, dataset, sopClass, true, NULL); |
3669 } | 4036 } |
3670 }; | 4037 }; |
3671 | |
3672 } | 4038 } |
3673 | 4039 |
3674 | 4040 |
3675 TEST(Toto, DISABLED_DicomAssociation) | 4041 TEST(Toto, DISABLED_DicomAssociation) |
3676 { | 4042 { |