comparison OrthancServer/OrthancGetRequestHandler.cpp @ 3955:66879215cbf3 c-get

C-GET: add timeout, fix uninitalized priority, support multiple resources
author Sebastien Jodogne <s.jodogne@gmail.com>
date Wed, 20 May 2020 16:38:33 +0200
parents 67b457283499
children 76a24be12912
comparison
equal deleted inserted replaced
3954:67b457283499 3955:66879215cbf3
53 { 53 {
54 namespace 54 namespace
55 { 55 {
56 // Anonymous namespace to avoid clashes between compilation modules 56 // Anonymous namespace to avoid clashes between compilation modules
57 57
58 static void getSubOpProgressCallback(void * /* callbackData */, 58 static void GetSubOpProgressCallback(
59 T_DIMSE_StoreProgress *progress, 59 void * /* callbackData == pointer to the "OrthancGetRequestHandler" object */,
60 T_DIMSE_C_StoreRQ * /*req*/) 60 T_DIMSE_StoreProgress *progress,
61 T_DIMSE_C_StoreRQ * /*req*/)
61 { 62 {
62 // SBL - no logging to be done here. 63 // SBL - no logging to be done here.
63 } 64 }
64 } 65 }
65 66
78 if (dicom.size() <= 0) 79 if (dicom.size() <= 0)
79 { 80 {
80 return Status_Failure; 81 return Status_Failure;
81 } 82 }
82 83
83 std::unique_ptr<DcmFileFormat> parsed( 84 ParsedDicomFile parsed(dicom);
84 FromDcmtkBridge::LoadFromMemoryBuffer(dicom.c_str(), dicom.size())); 85
85 86 if (parsed.GetDcmtkObject().getDataset() == NULL)
86 // Determine the storage SOP class UID for this instance 87 {
87 DIC_UI sopClass; 88 throw OrthancException(ErrorCode_InternalError);
88 DIC_UI sopInstance; 89 }
89 90
90 { 91 DcmDataset& dataset = *parsed.GetDcmtkObject().getDataset();
91 bool ok; 92
92 93 OFString a, b;
93 #if DCMTK_VERSION_NUMBER >= 364 94 if (!dataset.findAndGetOFString(DCM_SOPClassUID, a).good() ||
94 ok = DU_findSOPClassAndInstanceInDataSet(static_cast<DcmItem *> (parsed->getDataset()), 95 !dataset.findAndGetOFString(DCM_SOPInstanceUID, b).good())
95 sopClass, sizeof(sopClass), 96 {
96 sopInstance, sizeof(sopInstance)); 97 throw OrthancException(ErrorCode_NoSopClassOrInstance,
97 #else 98 "Unable to determine the SOP class/instance for C-STORE with AET " +
98 ok = DU_findSOPClassAndInstanceInDataSet(parsed->getDataset(), sopClass, sopInstance); 99 originatorAet_);
99 #endif 100 }
100 101
101 if (!ok) 102 std::string sopClassUid(a.c_str());
102 { 103 std::string sopInstanceUid(b.c_str());
103 throw OrthancException(ErrorCode_NoSopClassOrInstance, 104
104 "Unable to determine the SOP class/instance for C-STORE with AET " + 105 OFCondition cond = PerformGetSubOp(assoc, sopClassUid, sopInstanceUid, dataset);
105 originatorAet_);
106 }
107 }
108
109 OFCondition cond = PerformGetSubOp(assoc, sopClass, sopInstance, parsed->getDataset());
110 106
111 if (getCancelled_) 107 if (getCancelled_)
112 { 108 {
113 LOG(INFO) << "Get SCP: Received C-Cancel RQ"; 109 LOG(INFO) << "Get SCP: Received C-Cancel RQ";
114 } 110 }
120 116
121 return Status_Success; 117 return Status_Success;
122 } 118 }
123 119
124 120
125 void OrthancGetRequestHandler::AddFailedUIDInstance(const char *sopInstance) 121 void OrthancGetRequestHandler::AddFailedUIDInstance(const std::string& sopInstance)
126 { 122 {
127 if (failedUIDs_.empty()) 123 if (failedUIDs_.empty())
128 { 124 {
129 failedUIDs_ = sopInstance; 125 failedUIDs_ = sopInstance;
130 } 126 }
131 else 127 else
132 { 128 {
133 failedUIDs_ += "\\" + std::string(sopInstance); 129 failedUIDs_ += "\\" + sopInstance;
134 } 130 }
135 } 131 }
136 132
137 133
138 OFCondition OrthancGetRequestHandler::PerformGetSubOp(T_ASC_Association* assoc, 134 OFCondition OrthancGetRequestHandler::PerformGetSubOp(T_ASC_Association* assoc,
139 DIC_UI sopClass, 135 const std::string& sopClassUid,
140 DIC_UI sopInstance, 136 const std::string& sopInstanceUid,
141 DcmDataset *dataset) 137 DcmDataset& dataset)
142 { 138 {
143 OFCondition cond = EC_Normal;
144 T_DIMSE_C_StoreRQ req;
145 T_DIMSE_C_StoreRSP rsp;
146 DIC_US msgId;
147 T_ASC_PresentationContextID presId; 139 T_ASC_PresentationContextID presId;
148 DcmDataset *stDetail = NULL;
149
150 msgId = assoc->nextMsgID++;
151 140
152 // which presentation context should be used 141 // which presentation context should be used
153 presId = ASC_findAcceptedPresentationContextID(assoc, sopClass); 142 presId = ASC_findAcceptedPresentationContextID(assoc, sopClassUid.c_str());
154 143
155 if (presId == 0) 144 if (presId == 0)
156 { 145 {
157 nFailed_++; 146 nFailed_++;
158 AddFailedUIDInstance(sopInstance); 147 AddFailedUIDInstance(sopInstanceUid);
159 LOG(ERROR) << "Get SCP: storeSCU: No presentation context for: (" 148 LOG(ERROR) << "Get SCP: storeSCU: No presentation context for: ("
160 << dcmSOPClassUIDToModality(sopClass, "OT") << ") " << sopClass; 149 << dcmSOPClassUIDToModality(sopClassUid.c_str(), "OT") << ") " << sopClassUid;
161 return DIMSE_NOVALIDPRESENTATIONCONTEXTID; 150 return DIMSE_NOVALIDPRESENTATIONCONTEXTID;
162 } 151 }
163 else 152 else
164 { 153 {
165 // make sure that we can send images in this presentation context 154 // make sure that we can send images in this presentation context
169 if ((pc.acceptedRole != ASC_SC_ROLE_SCP) && 158 if ((pc.acceptedRole != ASC_SC_ROLE_SCP) &&
170 (pc.acceptedRole != ASC_SC_ROLE_SCUSCP)) 159 (pc.acceptedRole != ASC_SC_ROLE_SCUSCP))
171 { 160 {
172 // the role is not appropriate 161 // the role is not appropriate
173 nFailed_++; 162 nFailed_++;
174 AddFailedUIDInstance(sopInstance); 163 AddFailedUIDInstance(sopInstanceUid);
175 LOG(ERROR) <<"Get SCP: storeSCU: [No presentation context with requestor SCP role for: (" 164 LOG(ERROR) <<"Get SCP: storeSCU: [No presentation context with requestor SCP role for: ("
176 << dcmSOPClassUIDToModality(sopClass, "OT") << ") " << sopClass; 165 << dcmSOPClassUIDToModality(sopClassUid.c_str(), "OT") << ") " << sopClassUid;
177 return DIMSE_NOVALIDPRESENTATIONCONTEXTID; 166 return DIMSE_NOVALIDPRESENTATIONCONTEXTID;
178 } 167 }
179 } 168 }
180 169
170 const DIC_US msgId = assoc->nextMsgID++;
171
172 T_DIMSE_C_StoreRQ req;
173 memset(&req, 0, sizeof(req));
181 req.MessageID = msgId; 174 req.MessageID = msgId;
182 strcpy(req.AffectedSOPClassUID, sopClass); 175 strncpy(req.AffectedSOPClassUID, sopClassUid.c_str(), DIC_UI_LEN);
183 strcpy(req.AffectedSOPInstanceUID, sopInstance); 176 strncpy(req.AffectedSOPInstanceUID, sopInstanceUid.c_str(), DIC_UI_LEN);
184 req.DataSetType = DIMSE_DATASET_PRESENT; 177 req.DataSetType = DIMSE_DATASET_PRESENT;
185 req.Priority = priority_; 178 req.Priority = DIMSE_PRIORITY_MEDIUM;
186 req.opts = 0; 179 req.opts = 0;
187 180
181 T_DIMSE_C_StoreRSP rsp;
182 memset(&rsp, 0, sizeof(rsp));
183
188 LOG(INFO) << "Store SCU RQ: MsgID " << msgId << ", (" 184 LOG(INFO) << "Store SCU RQ: MsgID " << msgId << ", ("
189 << dcmSOPClassUIDToModality(sopClass, "OT") << ")"; 185 << dcmSOPClassUIDToModality(sopClassUid.c_str(), "OT") << ")";
190 186
191 T_DIMSE_DetectedCancelParameters cancelParameters; 187 T_DIMSE_DetectedCancelParameters cancelParameters;
192 188 memset(&cancelParameters, 0, sizeof(cancelParameters));
193 cond = DIMSE_storeUser(assoc, presId, &req, 189
194 NULL, dataset, getSubOpProgressCallback, this, DIMSE_BLOCKING, 0, 190 std::unique_ptr<DcmDataset> stDetail;
195 &rsp, &stDetail, &cancelParameters); 191
192 OFCondition cond;
193
194 {
195 DcmDataset *stDetailTmp = NULL;
196 cond = DIMSE_storeUser(assoc, presId, &req, NULL /* imageFileName */, &dataset,
197 GetSubOpProgressCallback, this /* callbackData */,
198 (timeout_ > 0 ? DIMSE_NONBLOCKING : DIMSE_BLOCKING), timeout_,
199 &rsp, &stDetailTmp, &cancelParameters);
200 stDetail.reset(stDetailTmp);
201 }
196 202
197 if (cond.good()) 203 if (cond.good())
198 { 204 {
199 if (cancelParameters.cancelEncountered) 205 if (cancelParameters.cancelEncountered)
200 { 206 {
223 << DU_cstoreStatusString(rsp.DimseStatus); 229 << DU_cstoreStatusString(rsp.DimseStatus);
224 } 230 }
225 else 231 else
226 { 232 {
227 nFailed_++; 233 nFailed_++;
228 AddFailedUIDInstance(sopInstance); 234 AddFailedUIDInstance(sopInstanceUid);
229 // print a status message 235 // print a status message
230 LOG(ERROR) << "Get SCP: Store Failed: Response Status: " 236 LOG(ERROR) << "Get SCP: Store Failed: Response Status: "
231 << DU_cstoreStatusString(rsp.DimseStatus); 237 << DU_cstoreStatusString(rsp.DimseStatus);
232 } 238 }
233 } 239 }
234 else 240 else
235 { 241 {
236 nFailed_++; 242 nFailed_++;
237 AddFailedUIDInstance(sopInstance); 243 AddFailedUIDInstance(sopInstanceUid);
238 OFString temp_str; 244 OFString temp_str;
239 LOG(ERROR) << "Get SCP: storeSCU: Store Request Failed: " << DimseCondition::dump(temp_str, cond); 245 LOG(ERROR) << "Get SCP: storeSCU: Store Request Failed: " << DimseCondition::dump(temp_str, cond);
240 } 246 }
241 247
242 if (stDetail) 248 if (stDetail.get() != NULL)
243 { 249 {
244 LOG(INFO) << " Status Detail:" << OFendl << DcmObject::PrintHelper(*stDetail); 250 LOG(INFO) << " Status Detail:" << OFendl << DcmObject::PrintHelper(*stDetail);
245 delete stDetail;
246 } 251 }
247 252
248 return cond; 253 return cond;
249 } 254 }
250 255
251 bool OrthancGetRequestHandler::LookupIdentifiers(std::vector<std::string>& publicIds, 256 bool OrthancGetRequestHandler::LookupIdentifiers(std::list<std::string>& publicIds,
252 ResourceType level, 257 ResourceType level,
253 const DicomMap& input) 258 const DicomMap& input) const
254 { 259 {
255 DicomTag tag(0, 0); // Dummy initialization 260 DicomTag tag(0, 0); // Dummy initialization
256 261
257 switch (level) 262 switch (level)
258 { 263 {
288 { 293 {
289 return false; 294 return false;
290 } 295 }
291 else 296 else
292 { 297 {
293 const std::string& content = value.GetContent(); 298 std::vector<std::string> tokens;
294 context_.GetIndex().LookupIdentifierExact(publicIds, level, tag, content); 299 Toolbox::TokenizeString(tokens, value.GetContent(), '\\');
295 return true; 300
296 } 301 for (size_t i = 0; i < tokens.size(); i++)
297 } 302 {
298 303 std::vector<std::string> tmp;
299 304 context_.GetIndex().LookupIdentifierExact(tmp, level, tag, tokens[i]);
300 OrthancGetRequestHandler::OrthancGetRequestHandler(ServerContext& context) : 305
301 context_(context) 306 if (tmp.empty())
302 { 307 {
303 position_ = 0; 308 LOG(ERROR) << "C-GET: Cannot locate resource \"" << tokens[i]
304 nRemaining_ = 0; 309 << "\" at the " << EnumerationToString(level) << " level";
305 nCompleted_ = 0; 310 return false;
306 warningCount_ = 0; 311 }
307 nFailed_ = 0; 312 else
308 } 313 {
314 for (size_t i = 0; i < tmp.size(); i++)
315 {
316 publicIds.push_back(tmp[i]);
317 }
318 }
319 }
320
321 return true;
322 }
323 }
324
325
326 OrthancGetRequestHandler::OrthancGetRequestHandler(ServerContext& context) :
327 context_(context)
328 {
329 position_ = 0;
330 nRemaining_ = 0;
331 nCompleted_ = 0;
332 warningCount_ = 0;
333 nFailed_ = 0;
334 timeout_ = 0;
335 }
309 336
310 337
311 bool OrthancGetRequestHandler::Handle(const DicomMap& input, 338 bool OrthancGetRequestHandler::Handle(const DicomMap& input,
312 const std::string& originatorIp, 339 const std::string& originatorIp,
313 const std::string& originatorAet, 340 const std::string& originatorAet,
314 const std::string& calledAet) 341 const std::string& calledAet,
342 uint32_t timeout)
315 { 343 {
316 MetricsRegistry::Timer timer(context_.GetMetricsRegistry(), "orthanc_get_scp_duration_ms"); 344 MetricsRegistry::Timer timer(context_.GetMetricsRegistry(), "orthanc_get_scp_duration_ms");
317 345
318 LOG(WARNING) << "Get-SCU request received from AET \"" << originatorAet << "\""; 346 LOG(WARNING) << "Get-SCU request received from AET \"" << originatorAet << "\"";
319 347
342 370
343 /** 371 /**
344 * Lookup for the resource to be sent. 372 * Lookup for the resource to be sent.
345 **/ 373 **/
346 374
347 std::vector<std::string> publicIds; 375 std::list<std::string> publicIds;
348 376
349 if (!LookupIdentifiers(publicIds, level, input)) 377 if (!LookupIdentifiers(publicIds, level, input))
350 { 378 {
351 LOG(ERROR) << "Cannot determine what resources are requested by C-GET"; 379 LOG(ERROR) << "Cannot determine what resources are requested by C-GET";
352 return false; 380 return false;
353 } 381 }
354 382
355 localAet_ = context_.GetDefaultLocalApplicationEntityTitle(); 383 localAet_ = context_.GetDefaultLocalApplicationEntityTitle();
356 position_ = 0; 384 position_ = 0;
357 originatorAet_ = originatorAet; 385 originatorAet_ = originatorAet;
358 386
359 { 387 {
360 OrthancConfiguration::ReaderLock lock; 388 OrthancConfiguration::ReaderLock lock;
361 remote_ = lock.GetConfiguration().GetModalityUsingAet(originatorAet); 389 remote_ = lock.GetConfiguration().GetModalityUsingAet(originatorAet);
362 } 390 }
363 391
364 for (size_t i = 0; i < publicIds.size(); i++) 392 for (std::list<std::string>::const_iterator
365 { 393 resource = publicIds.begin(); resource != publicIds.end(); ++resource)
366 LOG(INFO) << "C-GET: Sending resource " << publicIds[i] 394 {
395 LOG(INFO) << "C-GET: Sending resource " << *resource
367 << " to modality \"" << originatorAet << "\""; 396 << " to modality \"" << originatorAet << "\"";
368 397
369 std::list<std::string> tmp; 398 std::list<std::string> tmp;
370 context_.GetIndex().GetChildInstances(tmp, publicIds[i]); 399 context_.GetIndex().GetChildInstances(tmp, *resource);
371 400
372 instances_.reserve(tmp.size()); 401 instances_.reserve(tmp.size());
373 for (std::list<std::string>::iterator it = tmp.begin(); it != tmp.end(); ++it) 402 for (std::list<std::string>::iterator it = tmp.begin(); it != tmp.end(); ++it)
374 { 403 {
375 instances_.push_back(*it); 404 instances_.push_back(*it);
376 } 405 }
377 } 406 }
378 407
379 failedUIDs_.clear(); 408 failedUIDs_.clear();
380 getCancelled_ = OFFalse; 409 getCancelled_ = OFFalse;
381 410
382 nRemaining_ = GetSubOperationCount(); 411 nRemaining_ = GetSubOperationCount();
383 nCompleted_ = 0; 412 nCompleted_ = 0;
384 nFailed_ = 0; 413 nFailed_ = 0;
385 warningCount_ = 0; 414 warningCount_ = 0;
415 timeout_ = timeout;
386 416
387 return true; 417 return true;
388 } 418 }
389 }; 419 };