Mercurial > hg > orthanc
comparison Core/DicomNetworking/DicomUserConnection.cpp @ 3799:320a2d224902
merge
author | Alain Mazy <alain@mazy.be> |
---|---|
date | Wed, 01 Apr 2020 10:15:33 +0200 |
parents | c38b82bb6fd3 c6658187e4b1 |
children | 38b0f51781aa |
comparison
equal
deleted
inserted
replaced
3798:c38b82bb6fd3 | 3799:320a2d224902 |
---|---|
84 | 84 |
85 #if !defined(DCMTK_VERSION_NUMBER) | 85 #if !defined(DCMTK_VERSION_NUMBER) |
86 # error The macro DCMTK_VERSION_NUMBER must be defined | 86 # error The macro DCMTK_VERSION_NUMBER must be defined |
87 #endif | 87 #endif |
88 | 88 |
89 #include "../Compatibility.h" | |
89 #include "../DicomFormat/DicomArray.h" | 90 #include "../DicomFormat/DicomArray.h" |
90 #include "../Logging.h" | 91 #include "../Logging.h" |
91 #include "../OrthancException.h" | 92 #include "../OrthancException.h" |
92 #include "../DicomParsing/FromDcmtkBridge.h" | 93 #include "../DicomParsing/FromDcmtkBridge.h" |
93 #include "../DicomParsing/ToDcmtkBridge.h" | 94 #include "../DicomParsing/ToDcmtkBridge.h" |
156 return assoc_ != NULL; | 157 return assoc_ != NULL; |
157 } | 158 } |
158 | 159 |
159 void CheckIsOpen() const; | 160 void CheckIsOpen() const; |
160 | 161 |
161 void Store(DcmInputStream& is, | 162 void Store(std::string& sopClassUidOut /* out */, |
163 std::string& sopInstanceUidOut /* out */, | |
164 DcmInputStream& is, | |
162 DicomUserConnection& connection, | 165 DicomUserConnection& connection, |
163 const std::string& moveOriginatorAET, | 166 const std::string& moveOriginatorAET, |
164 uint16_t moveOriginatorID); | 167 uint16_t moveOriginatorID); |
165 }; | 168 }; |
166 | 169 |
250 presentationContextId += 2; | 253 presentationContextId += 2; |
251 } | 254 } |
252 } | 255 } |
253 | 256 |
254 | 257 |
255 void DicomUserConnection::SetupPresentationContexts(const std::string& preferredTransferSyntax) | 258 void DicomUserConnection::SetupPresentationContexts(Mode mode, |
259 const std::string& preferredTransferSyntax) | |
256 { | 260 { |
257 // Flatten an array with the preferred transfer syntax | 261 // Flatten an array with the preferred transfer syntax |
258 const char* asPreferred[1] = { preferredTransferSyntax.c_str() }; | 262 const char* asPreferred[1] = { preferredTransferSyntax.c_str() }; |
259 | 263 |
260 // Setup the fallback transfer syntaxes | 264 // Setup the fallback transfer syntaxes |
272 { | 276 { |
273 asFallback.push_back(it->c_str()); | 277 asFallback.push_back(it->c_str()); |
274 } | 278 } |
275 | 279 |
276 CheckStorageSOPClassesInvariant(); | 280 CheckStorageSOPClassesInvariant(); |
277 unsigned int presentationContextId = 1; | 281 |
278 | 282 switch (mode) |
279 for (std::list<std::string>::const_iterator it = reservedStorageSOPClasses_.begin(); | 283 { |
280 it != reservedStorageSOPClasses_.end(); ++it) | 284 case Mode_Generic: |
281 { | 285 { |
282 RegisterStorageSOPClass(pimpl_->params_, presentationContextId, | 286 unsigned int presentationContextId = 1; |
283 *it, asPreferred, asFallback, remoteAet_); | 287 |
284 } | 288 for (std::list<std::string>::const_iterator it = reservedStorageSOPClasses_.begin(); |
285 | 289 it != reservedStorageSOPClasses_.end(); ++it) |
286 for (std::set<std::string>::const_iterator it = storageSOPClasses_.begin(); | 290 { |
287 it != storageSOPClasses_.end(); ++it) | 291 RegisterStorageSOPClass(pimpl_->params_, presentationContextId, |
288 { | 292 *it, asPreferred, asFallback, remoteAet_); |
289 RegisterStorageSOPClass(pimpl_->params_, presentationContextId, | 293 } |
290 *it, asPreferred, asFallback, remoteAet_); | 294 |
291 } | 295 for (std::set<std::string>::const_iterator it = storageSOPClasses_.begin(); |
292 | 296 it != storageSOPClasses_.end(); ++it) |
293 for (std::set<std::string>::const_iterator it = defaultStorageSOPClasses_.begin(); | 297 { |
294 it != defaultStorageSOPClasses_.end(); ++it) | 298 RegisterStorageSOPClass(pimpl_->params_, presentationContextId, |
295 { | 299 *it, asPreferred, asFallback, remoteAet_); |
296 RegisterStorageSOPClass(pimpl_->params_, presentationContextId, | 300 } |
297 *it, asPreferred, asFallback, remoteAet_); | 301 |
298 } | 302 for (std::set<std::string>::const_iterator it = defaultStorageSOPClasses_.begin(); |
299 } | 303 it != defaultStorageSOPClasses_.end(); ++it) |
300 | 304 { |
305 RegisterStorageSOPClass(pimpl_->params_, presentationContextId, | |
306 *it, asPreferred, asFallback, remoteAet_); | |
307 } | |
308 | |
309 break; | |
310 } | |
311 | |
312 case Mode_RequestStorageCommitment: | |
313 case Mode_ReportStorageCommitment: | |
314 { | |
315 const char* as = UID_StorageCommitmentPushModelSOPClass; | |
316 | |
317 std::vector<const char*> ts; | |
318 ts.push_back(UID_LittleEndianExplicitTransferSyntax); | |
319 ts.push_back(UID_LittleEndianImplicitTransferSyntax); | |
320 | |
321 T_ASC_SC_ROLE role; | |
322 switch (mode) | |
323 { | |
324 case Mode_RequestStorageCommitment: | |
325 role = ASC_SC_ROLE_DEFAULT; | |
326 break; | |
327 | |
328 case Mode_ReportStorageCommitment: | |
329 role = ASC_SC_ROLE_SCP; | |
330 break; | |
331 | |
332 default: | |
333 throw OrthancException(ErrorCode_InternalError); | |
334 } | |
335 | |
336 Check(ASC_addPresentationContext(pimpl_->params_, 1 /*presentationContextId*/, | |
337 as, &ts[0], ts.size(), role), | |
338 remoteAet_, "initializing"); | |
339 | |
340 break; | |
341 } | |
342 | |
343 default: | |
344 throw OrthancException(ErrorCode_InternalError); | |
345 } | |
346 } | |
347 | |
301 | 348 |
302 static bool IsGenericTransferSyntax(const std::string& syntax) | 349 static bool IsGenericTransferSyntax(const std::string& syntax) |
303 { | 350 { |
304 return (syntax == UID_LittleEndianExplicitTransferSyntax || | 351 return (syntax == UID_LittleEndianExplicitTransferSyntax || |
305 syntax == UID_BigEndianExplicitTransferSyntax || | 352 syntax == UID_BigEndianExplicitTransferSyntax || |
306 syntax == UID_LittleEndianImplicitTransferSyntax); | 353 syntax == UID_LittleEndianImplicitTransferSyntax); |
307 } | 354 } |
308 | 355 |
309 | 356 |
310 void DicomUserConnection::PImpl::Store(DcmInputStream& is, | 357 void DicomUserConnection::PImpl::Store(std::string& sopClassUidOut, |
358 std::string& sopInstanceUidOut, | |
359 DcmInputStream& is, | |
311 DicomUserConnection& connection, | 360 DicomUserConnection& connection, |
312 const std::string& moveOriginatorAET, | 361 const std::string& moveOriginatorAET, |
313 uint16_t moveOriginatorID) | 362 uint16_t moveOriginatorID) |
314 { | 363 { |
315 DcmFileFormat dcmff; | 364 DcmFileFormat dcmff; |
387 { | 436 { |
388 throw OrthancException(ErrorCode_NoSopClassOrInstance, | 437 throw OrthancException(ErrorCode_NoSopClassOrInstance, |
389 "Unable to determine the SOP class/instance for C-STORE with AET " + | 438 "Unable to determine the SOP class/instance for C-STORE with AET " + |
390 connection.remoteAet_); | 439 connection.remoteAet_); |
391 } | 440 } |
441 | |
442 sopClassUidOut.assign(sopClass); | |
443 sopInstanceUidOut.assign(sopInstance); | |
392 | 444 |
393 // Figure out which of the accepted presentation contexts should be used | 445 // Figure out which of the accepted presentation contexts should be used |
394 int presID = ASC_findAcceptedPresentationContextID(assoc_, sopClass); | 446 int presID = ASC_findAcceptedPresentationContextID(assoc_, sopClass); |
395 if (presID == 0) | 447 if (presID == 0) |
396 { | 448 { |
1058 Close(); | 1110 Close(); |
1059 remotePort_ = port; | 1111 remotePort_ = port; |
1060 } | 1112 } |
1061 } | 1113 } |
1062 | 1114 |
1063 void DicomUserConnection::Open() | 1115 void DicomUserConnection::OpenInternal(Mode mode) |
1064 { | 1116 { |
1065 if (IsOpen()) | 1117 if (IsOpen()) |
1066 { | 1118 { |
1067 // Don't reopen the connection | 1119 // Don't reopen the connection |
1068 return; | 1120 return; |
1098 | 1150 |
1099 // Set various options | 1151 // Set various options |
1100 Check(ASC_setTransportLayerType(pimpl_->params_, /*opt_secureConnection*/ false), | 1152 Check(ASC_setTransportLayerType(pimpl_->params_, /*opt_secureConnection*/ false), |
1101 remoteAet_, "connecting"); | 1153 remoteAet_, "connecting"); |
1102 | 1154 |
1103 SetupPresentationContexts(preferredTransferSyntax_); | 1155 SetupPresentationContexts(mode, preferredTransferSyntax_); |
1104 | 1156 |
1105 // Do the association | 1157 // Do the association |
1106 Check(ASC_requestAssociation(pimpl_->net_, pimpl_->params_, &pimpl_->assoc_), | 1158 Check(ASC_requestAssociation(pimpl_->net_, pimpl_->params_, &pimpl_->assoc_), |
1107 remoteAet_, "connecting"); | 1159 remoteAet_, "connecting"); |
1108 | 1160 |
1142 bool DicomUserConnection::IsOpen() const | 1194 bool DicomUserConnection::IsOpen() const |
1143 { | 1195 { |
1144 return pimpl_->IsOpen(); | 1196 return pimpl_->IsOpen(); |
1145 } | 1197 } |
1146 | 1198 |
1147 void DicomUserConnection::Store(const char* buffer, | 1199 void DicomUserConnection::Store(std::string& sopClassUid /* out */, |
1200 std::string& sopInstanceUid /* out */, | |
1201 const char* buffer, | |
1148 size_t size, | 1202 size_t size, |
1149 const std::string& moveOriginatorAET, | 1203 const std::string& moveOriginatorAET, |
1150 uint16_t moveOriginatorID) | 1204 uint16_t moveOriginatorID) |
1151 { | 1205 { |
1152 // Prepare an input stream for the memory buffer | 1206 // Prepare an input stream for the memory buffer |
1153 DcmInputBufferStream is; | 1207 DcmInputBufferStream is; |
1154 if (size > 0) | 1208 if (size > 0) |
1155 is.setBuffer(buffer, size); | 1209 is.setBuffer(buffer, size); |
1156 is.setEos(); | 1210 is.setEos(); |
1157 | 1211 |
1158 pimpl_->Store(is, *this, moveOriginatorAET, moveOriginatorID); | 1212 pimpl_->Store(sopClassUid, sopInstanceUid, is, *this, moveOriginatorAET, moveOriginatorID); |
1159 } | 1213 } |
1160 | 1214 |
1161 void DicomUserConnection::Store(const std::string& buffer, | 1215 void DicomUserConnection::Store(std::string& sopClassUid /* out */, |
1216 std::string& sopInstanceUid /* out */, | |
1217 const std::string& buffer, | |
1162 const std::string& moveOriginatorAET, | 1218 const std::string& moveOriginatorAET, |
1163 uint16_t moveOriginatorID) | 1219 uint16_t moveOriginatorID) |
1164 { | 1220 { |
1165 if (buffer.size() > 0) | 1221 if (buffer.size() > 0) |
1166 Store(&buffer[0], buffer.size(), moveOriginatorAET, moveOriginatorID); | 1222 Store(sopClassUid, sopInstanceUid, &buffer[0], buffer.size(), |
1223 moveOriginatorAET, moveOriginatorID); | |
1167 else | 1224 else |
1168 Store(NULL, 0, moveOriginatorAET, moveOriginatorID); | 1225 Store(sopClassUid, sopInstanceUid, NULL, 0, moveOriginatorAET, moveOriginatorID); |
1169 } | 1226 } |
1170 | 1227 |
1171 void DicomUserConnection::StoreFile(const std::string& path, | 1228 void DicomUserConnection::StoreFile(std::string& sopClassUid /* out */, |
1229 std::string& sopInstanceUid /* out */, | |
1230 const std::string& path, | |
1172 const std::string& moveOriginatorAET, | 1231 const std::string& moveOriginatorAET, |
1173 uint16_t moveOriginatorID) | 1232 uint16_t moveOriginatorID) |
1174 { | 1233 { |
1175 // Prepare an input stream for the file | 1234 // Prepare an input stream for the file |
1176 DcmInputFileStream is(path.c_str()); | 1235 DcmInputFileStream is(path.c_str()); |
1177 pimpl_->Store(is, *this, moveOriginatorAET, moveOriginatorID); | 1236 pimpl_->Store(sopClassUid, sopInstanceUid, is, *this, moveOriginatorAET, moveOriginatorID); |
1178 } | 1237 } |
1179 | 1238 |
1180 bool DicomUserConnection::Echo() | 1239 bool DicomUserConnection::Echo() |
1181 { | 1240 { |
1182 CheckIsOpen(); | 1241 CheckIsOpen(); |
1403 remoteAet_ == remote.GetApplicationEntityTitle() && | 1462 remoteAet_ == remote.GetApplicationEntityTitle() && |
1404 remoteHost_ == remote.GetHost() && | 1463 remoteHost_ == remote.GetHost() && |
1405 remotePort_ == remote.GetPortNumber() && | 1464 remotePort_ == remote.GetPortNumber() && |
1406 manufacturer_ == remote.GetManufacturer()); | 1465 manufacturer_ == remote.GetManufacturer()); |
1407 } | 1466 } |
1467 | |
1468 | |
1469 static void FillSopSequence(DcmDataset& dataset, | |
1470 const DcmTagKey& tag, | |
1471 const std::vector<std::string>& sopClassUids, | |
1472 const std::vector<std::string>& sopInstanceUids, | |
1473 const std::vector<StorageCommitmentFailureReason>& failureReasons, | |
1474 bool hasFailureReasons) | |
1475 { | |
1476 assert(sopClassUids.size() == sopInstanceUids.size() && | |
1477 (hasFailureReasons ? | |
1478 failureReasons.size() == sopClassUids.size() : | |
1479 failureReasons.empty())); | |
1480 | |
1481 if (sopInstanceUids.empty()) | |
1482 { | |
1483 // Add an empty sequence | |
1484 if (!dataset.insertEmptyElement(tag).good()) | |
1485 { | |
1486 throw OrthancException(ErrorCode_InternalError); | |
1487 } | |
1488 } | |
1489 else | |
1490 { | |
1491 for (size_t i = 0; i < sopClassUids.size(); i++) | |
1492 { | |
1493 std::unique_ptr<DcmItem> item(new DcmItem); | |
1494 if (!item->putAndInsertString(DCM_ReferencedSOPClassUID, sopClassUids[i].c_str()).good() || | |
1495 !item->putAndInsertString(DCM_ReferencedSOPInstanceUID, sopInstanceUids[i].c_str()).good() || | |
1496 (hasFailureReasons && | |
1497 !item->putAndInsertUint16(DCM_FailureReason, failureReasons[i]).good()) || | |
1498 !dataset.insertSequenceItem(tag, item.release()).good()) | |
1499 { | |
1500 throw OrthancException(ErrorCode_InternalError); | |
1501 } | |
1502 } | |
1503 } | |
1504 } | |
1505 | |
1506 | |
1507 | |
1508 | |
1509 void DicomUserConnection::ReportStorageCommitment( | |
1510 const std::string& transactionUid, | |
1511 const std::vector<std::string>& sopClassUids, | |
1512 const std::vector<std::string>& sopInstanceUids, | |
1513 const std::vector<StorageCommitmentFailureReason>& failureReasons) | |
1514 { | |
1515 if (sopClassUids.size() != sopInstanceUids.size() || | |
1516 sopClassUids.size() != failureReasons.size()) | |
1517 { | |
1518 throw OrthancException(ErrorCode_ParameterOutOfRange); | |
1519 } | |
1520 | |
1521 if (IsOpen()) | |
1522 { | |
1523 Close(); | |
1524 } | |
1525 | |
1526 std::vector<std::string> successSopClassUids, successSopInstanceUids, failedSopClassUids, failedSopInstanceUids; | |
1527 std::vector<StorageCommitmentFailureReason> failedReasons; | |
1528 | |
1529 successSopClassUids.reserve(sopClassUids.size()); | |
1530 successSopInstanceUids.reserve(sopClassUids.size()); | |
1531 failedSopClassUids.reserve(sopClassUids.size()); | |
1532 failedSopInstanceUids.reserve(sopClassUids.size()); | |
1533 failedReasons.reserve(sopClassUids.size()); | |
1534 | |
1535 for (size_t i = 0; i < sopClassUids.size(); i++) | |
1536 { | |
1537 switch (failureReasons[i]) | |
1538 { | |
1539 case StorageCommitmentFailureReason_Success: | |
1540 successSopClassUids.push_back(sopClassUids[i]); | |
1541 successSopInstanceUids.push_back(sopInstanceUids[i]); | |
1542 break; | |
1543 | |
1544 case StorageCommitmentFailureReason_ProcessingFailure: | |
1545 case StorageCommitmentFailureReason_NoSuchObjectInstance: | |
1546 case StorageCommitmentFailureReason_ResourceLimitation: | |
1547 case StorageCommitmentFailureReason_ReferencedSOPClassNotSupported: | |
1548 case StorageCommitmentFailureReason_ClassInstanceConflict: | |
1549 case StorageCommitmentFailureReason_DuplicateTransactionUID: | |
1550 failedSopClassUids.push_back(sopClassUids[i]); | |
1551 failedSopInstanceUids.push_back(sopInstanceUids[i]); | |
1552 failedReasons.push_back(failureReasons[i]); | |
1553 break; | |
1554 | |
1555 default: | |
1556 { | |
1557 char buf[16]; | |
1558 sprintf(buf, "%04xH", failureReasons[i]); | |
1559 throw OrthancException(ErrorCode_ParameterOutOfRange, | |
1560 "Unsupported failure reason for storage commitment: " + std::string(buf)); | |
1561 } | |
1562 } | |
1563 } | |
1564 | |
1565 try | |
1566 { | |
1567 OpenInternal(Mode_ReportStorageCommitment); | |
1568 | |
1569 /** | |
1570 * N-EVENT-REPORT | |
1571 * http://dicom.nema.org/medical/dicom/2019a/output/chtml/part04/sect_J.3.3.html | |
1572 * http://dicom.nema.org/medical/dicom/2019a/output/chtml/part07/chapter_10.html#table_10.1-1 | |
1573 * | |
1574 * Status code: | |
1575 * http://dicom.nema.org/medical/dicom/2019a/output/chtml/part07/chapter_10.html#sect_10.1.1.1.8 | |
1576 **/ | |
1577 | |
1578 /** | |
1579 * Send the "EVENT_REPORT_RQ" request | |
1580 **/ | |
1581 | |
1582 LOG(INFO) << "Reporting modality \"" << remoteAet_ | |
1583 << "\" about storage commitment transaction: " << transactionUid | |
1584 << " (" << successSopClassUids.size() << " successes, " | |
1585 << failedSopClassUids.size() << " failures)"; | |
1586 const DIC_US messageId = pimpl_->assoc_->nextMsgID++; | |
1587 | |
1588 { | |
1589 T_DIMSE_Message message; | |
1590 memset(&message, 0, sizeof(message)); | |
1591 message.CommandField = DIMSE_N_EVENT_REPORT_RQ; | |
1592 | |
1593 T_DIMSE_N_EventReportRQ& content = message.msg.NEventReportRQ; | |
1594 content.MessageID = messageId; | |
1595 strncpy(content.AffectedSOPClassUID, UID_StorageCommitmentPushModelSOPClass, DIC_UI_LEN); | |
1596 strncpy(content.AffectedSOPInstanceUID, UID_StorageCommitmentPushModelSOPInstance, DIC_UI_LEN); | |
1597 content.DataSetType = DIMSE_DATASET_PRESENT; | |
1598 | |
1599 DcmDataset dataset; | |
1600 if (!dataset.putAndInsertString(DCM_TransactionUID, transactionUid.c_str()).good()) | |
1601 { | |
1602 throw OrthancException(ErrorCode_InternalError); | |
1603 } | |
1604 | |
1605 { | |
1606 std::vector<StorageCommitmentFailureReason> empty; | |
1607 FillSopSequence(dataset, DCM_ReferencedSOPSequence, successSopClassUids, | |
1608 successSopInstanceUids, empty, false); | |
1609 } | |
1610 | |
1611 // http://dicom.nema.org/medical/dicom/2019a/output/chtml/part04/sect_J.3.3.html | |
1612 if (failedSopClassUids.empty()) | |
1613 { | |
1614 content.EventTypeID = 1; // "Storage Commitment Request Successful" | |
1615 } | |
1616 else | |
1617 { | |
1618 content.EventTypeID = 2; // "Storage Commitment Request Complete - Failures Exist" | |
1619 | |
1620 // Failure reason | |
1621 // http://dicom.nema.org/medical/dicom/2019a/output/chtml/part03/sect_C.14.html#sect_C.14.1.1 | |
1622 FillSopSequence(dataset, DCM_FailedSOPSequence, failedSopClassUids, | |
1623 failedSopInstanceUids, failedReasons, true); | |
1624 } | |
1625 | |
1626 int presID = ASC_findAcceptedPresentationContextID( | |
1627 pimpl_->assoc_, UID_StorageCommitmentPushModelSOPClass); | |
1628 if (presID == 0) | |
1629 { | |
1630 throw OrthancException(ErrorCode_NetworkProtocol, "Storage commitment - " | |
1631 "Unable to send N-EVENT-REPORT request to AET: " + remoteAet_); | |
1632 } | |
1633 | |
1634 if (!DIMSE_sendMessageUsingMemoryData( | |
1635 pimpl_->assoc_, presID, &message, NULL /* status detail */, | |
1636 &dataset, NULL /* callback */, NULL /* callback context */, | |
1637 NULL /* commandSet */).good()) | |
1638 { | |
1639 throw OrthancException(ErrorCode_NetworkProtocol); | |
1640 } | |
1641 } | |
1642 | |
1643 /** | |
1644 * Read the "EVENT_REPORT_RSP" response | |
1645 **/ | |
1646 | |
1647 { | |
1648 T_ASC_PresentationContextID presID = 0; | |
1649 T_DIMSE_Message message; | |
1650 | |
1651 const int timeout = pimpl_->dimseTimeout_; | |
1652 if (!DIMSE_receiveCommand(pimpl_->assoc_, | |
1653 (timeout ? DIMSE_NONBLOCKING : DIMSE_BLOCKING), timeout, | |
1654 &presID, &message, NULL /* no statusDetail */).good() || | |
1655 message.CommandField != DIMSE_N_EVENT_REPORT_RSP) | |
1656 { | |
1657 throw OrthancException(ErrorCode_NetworkProtocol, "Storage commitment - " | |
1658 "Unable to read N-EVENT-REPORT response from AET: " + remoteAet_); | |
1659 } | |
1660 | |
1661 const T_DIMSE_N_EventReportRSP& content = message.msg.NEventReportRSP; | |
1662 if (content.MessageIDBeingRespondedTo != messageId || | |
1663 !(content.opts & O_NEVENTREPORT_AFFECTEDSOPCLASSUID) || | |
1664 !(content.opts & O_NEVENTREPORT_AFFECTEDSOPINSTANCEUID) || | |
1665 //(content.opts & O_NEVENTREPORT_EVENTTYPEID) || // Pedantic test - The "content.EventTypeID" is not used by Orthanc | |
1666 std::string(content.AffectedSOPClassUID) != UID_StorageCommitmentPushModelSOPClass || | |
1667 std::string(content.AffectedSOPInstanceUID) != UID_StorageCommitmentPushModelSOPInstance || | |
1668 content.DataSetType != DIMSE_DATASET_NULL) | |
1669 { | |
1670 throw OrthancException(ErrorCode_NetworkProtocol, "Storage commitment - " | |
1671 "Badly formatted N-EVENT-REPORT response from AET: " + remoteAet_); | |
1672 } | |
1673 | |
1674 if (content.DimseStatus != 0 /* success */) | |
1675 { | |
1676 throw OrthancException(ErrorCode_NetworkProtocol, "Storage commitment - " | |
1677 "The request cannot be handled by remote AET: " + remoteAet_); | |
1678 } | |
1679 } | |
1680 | |
1681 Close(); | |
1682 } | |
1683 catch (OrthancException&) | |
1684 { | |
1685 Close(); | |
1686 throw; | |
1687 } | |
1688 } | |
1689 | |
1690 | |
1691 | |
1692 void DicomUserConnection::RequestStorageCommitment( | |
1693 const std::string& transactionUid, | |
1694 const std::vector<std::string>& sopClassUids, | |
1695 const std::vector<std::string>& sopInstanceUids) | |
1696 { | |
1697 if (sopClassUids.size() != sopInstanceUids.size()) | |
1698 { | |
1699 throw OrthancException(ErrorCode_ParameterOutOfRange); | |
1700 } | |
1701 | |
1702 for (size_t i = 0; i < sopClassUids.size(); i++) | |
1703 { | |
1704 if (sopClassUids[i].empty() || | |
1705 sopInstanceUids[i].empty()) | |
1706 { | |
1707 throw OrthancException(ErrorCode_ParameterOutOfRange, | |
1708 "The SOP class/instance UIDs cannot be empty, found: \"" + | |
1709 sopClassUids[i] + "\" / \"" + sopInstanceUids[i] + "\""); | |
1710 } | |
1711 } | |
1712 | |
1713 if (transactionUid.size() < 5 || | |
1714 transactionUid.substr(0, 5) != "2.25.") | |
1715 { | |
1716 throw OrthancException(ErrorCode_ParameterOutOfRange); | |
1717 } | |
1718 | |
1719 if (IsOpen()) | |
1720 { | |
1721 Close(); | |
1722 } | |
1723 | |
1724 try | |
1725 { | |
1726 OpenInternal(Mode_RequestStorageCommitment); | |
1727 | |
1728 /** | |
1729 * N-ACTION | |
1730 * http://dicom.nema.org/medical/dicom/2019a/output/chtml/part04/sect_J.3.2.html | |
1731 * http://dicom.nema.org/medical/dicom/2019a/output/chtml/part07/chapter_10.html#table_10.1-4 | |
1732 * | |
1733 * Status code: | |
1734 * http://dicom.nema.org/medical/dicom/2019a/output/chtml/part07/chapter_10.html#sect_10.1.1.1.8 | |
1735 **/ | |
1736 | |
1737 /** | |
1738 * Send the "N_ACTION_RQ" request | |
1739 **/ | |
1740 | |
1741 LOG(INFO) << "Request to modality \"" << remoteAet_ | |
1742 << "\" about storage commitment for " << sopClassUids.size() | |
1743 << " instances, with transaction UID: " << transactionUid; | |
1744 const DIC_US messageId = pimpl_->assoc_->nextMsgID++; | |
1745 | |
1746 { | |
1747 T_DIMSE_Message message; | |
1748 memset(&message, 0, sizeof(message)); | |
1749 message.CommandField = DIMSE_N_ACTION_RQ; | |
1750 | |
1751 T_DIMSE_N_ActionRQ& content = message.msg.NActionRQ; | |
1752 content.MessageID = messageId; | |
1753 strncpy(content.RequestedSOPClassUID, UID_StorageCommitmentPushModelSOPClass, DIC_UI_LEN); | |
1754 strncpy(content.RequestedSOPInstanceUID, UID_StorageCommitmentPushModelSOPInstance, DIC_UI_LEN); | |
1755 content.ActionTypeID = 1; // "Request Storage Commitment" | |
1756 content.DataSetType = DIMSE_DATASET_PRESENT; | |
1757 | |
1758 DcmDataset dataset; | |
1759 if (!dataset.putAndInsertString(DCM_TransactionUID, transactionUid.c_str()).good()) | |
1760 { | |
1761 throw OrthancException(ErrorCode_InternalError); | |
1762 } | |
1763 | |
1764 { | |
1765 std::vector<StorageCommitmentFailureReason> empty; | |
1766 FillSopSequence(dataset, DCM_ReferencedSOPSequence, sopClassUids, sopInstanceUids, empty, false); | |
1767 } | |
1768 | |
1769 int presID = ASC_findAcceptedPresentationContextID( | |
1770 pimpl_->assoc_, UID_StorageCommitmentPushModelSOPClass); | |
1771 if (presID == 0) | |
1772 { | |
1773 throw OrthancException(ErrorCode_NetworkProtocol, "Storage commitment - " | |
1774 "Unable to send N-ACTION request to AET: " + remoteAet_); | |
1775 } | |
1776 | |
1777 if (!DIMSE_sendMessageUsingMemoryData( | |
1778 pimpl_->assoc_, presID, &message, NULL /* status detail */, | |
1779 &dataset, NULL /* callback */, NULL /* callback context */, | |
1780 NULL /* commandSet */).good()) | |
1781 { | |
1782 throw OrthancException(ErrorCode_NetworkProtocol); | |
1783 } | |
1784 } | |
1785 | |
1786 /** | |
1787 * Read the "N_ACTION_RSP" response | |
1788 **/ | |
1789 | |
1790 { | |
1791 T_ASC_PresentationContextID presID = 0; | |
1792 T_DIMSE_Message message; | |
1793 | |
1794 const int timeout = pimpl_->dimseTimeout_; | |
1795 if (!DIMSE_receiveCommand(pimpl_->assoc_, | |
1796 (timeout ? DIMSE_NONBLOCKING : DIMSE_BLOCKING), timeout, | |
1797 &presID, &message, NULL /* no statusDetail */).good() || | |
1798 message.CommandField != DIMSE_N_ACTION_RSP) | |
1799 { | |
1800 throw OrthancException(ErrorCode_NetworkProtocol, "Storage commitment - " | |
1801 "Unable to read N-ACTION response from AET: " + remoteAet_); | |
1802 } | |
1803 | |
1804 const T_DIMSE_N_ActionRSP& content = message.msg.NActionRSP; | |
1805 if (content.MessageIDBeingRespondedTo != messageId || | |
1806 !(content.opts & O_NACTION_AFFECTEDSOPCLASSUID) || | |
1807 !(content.opts & O_NACTION_AFFECTEDSOPINSTANCEUID) || | |
1808 //(content.opts & O_NACTION_ACTIONTYPEID) || // Pedantic test - The "content.ActionTypeID" is not used by Orthanc | |
1809 std::string(content.AffectedSOPClassUID) != UID_StorageCommitmentPushModelSOPClass || | |
1810 std::string(content.AffectedSOPInstanceUID) != UID_StorageCommitmentPushModelSOPInstance || | |
1811 content.DataSetType != DIMSE_DATASET_NULL) | |
1812 { | |
1813 throw OrthancException(ErrorCode_NetworkProtocol, "Storage commitment - " | |
1814 "Badly formatted N-ACTION response from AET: " + remoteAet_); | |
1815 } | |
1816 | |
1817 if (content.DimseStatus != 0 /* success */) | |
1818 { | |
1819 throw OrthancException(ErrorCode_NetworkProtocol, "Storage commitment - " | |
1820 "The request cannot be handled by remote AET: " + remoteAet_); | |
1821 } | |
1822 } | |
1823 | |
1824 Close(); | |
1825 } | |
1826 catch (OrthancException&) | |
1827 { | |
1828 Close(); | |
1829 throw; | |
1830 } | |
1831 } | |
1408 } | 1832 } |