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 }