ctkDcmSCU.cc 86 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277227822792280228122822283228422852286228722882289229022912292229322942295229622972298229923002301230223032304230523062307230823092310231123122313231423152316231723182319232023212322232323242325232623272328232923302331233223332334233523362337233823392340234123422343234423452346234723482349235023512352235323542355235623572358235923602361236223632364236523662367236823692370237123722373237423752376237723782379238023812382238323842385238623872388238923902391239223932394239523962397239823992400240124022403240424052406240724082409241024112412241324142415241624172418241924202421242224232424242524262427242824292430243124322433
  1. /*
  2. *
  3. * Copyright (C) 2008-2012, OFFIS e.V.
  4. * All rights reserved. See COPYRIGHT file for details.
  5. *
  6. * This software and supporting documentation were developed by
  7. *
  8. * OFFIS e.V.
  9. * R&D Division Health
  10. * Escherweg 2
  11. * D-26121 Oldenburg, Germany
  12. *
  13. *
  14. * Module: dcmnet
  15. *
  16. * Author: Michael Onken
  17. *
  18. * Purpose: Base class for Service Class Users (SCUs)
  19. *
  20. */
  21. // just a hack for debian, has to be sorted out
  22. // see http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=637687
  23. //
  24. #define HAVE_CONFIG_H
  25. #include "dcmtk/config/osconfig.h" /* make sure OS specific configuration is included first */
  26. #include "ctkDcmSCU.h"
  27. #include "dcmtk/dcmnet/diutil.h" /* for dcmnet logger */
  28. #include "dcmtk/dcmdata/dcuid.h" /* for dcmFindUIDName() */
  29. #include "dcmtk/dcmdata/dcostrmf.h" /* for class DcmOutputFileStream */
  30. #ifdef WITH_ZLIB
  31. #include <zlib.h> /* for zlibVersion() */
  32. #endif
  33. /* Remove below if changing to more current DCMTK */
  34. const OFConditionConst ECE_AlreadyConnected( OFM_dcmnet, ECC_AlreadyConnected, OF_error, "Already Connected");
  35. const OFConditionConst ECE_NoAcceptablePresentationContexts( OFM_dcmnet, ECC_NoAcceptablePresentationContexts, OF_error, "No Acceptable Presentation Contexts");
  36. const OFConditionConst ECE_NoPresentationContextsDefined( OFM_dcmnet, ECC_NoPresentationContextsDefined, OF_error, "No Presentation Contexts defined");
  37. const OFConditionConst ECE_InvalidSOPClassUID( OFM_dcmnet, ECC_InvalidSOPClassUID, OF_error, "Invalid SOP Class UID");
  38. const OFConditionConst ECE_InvalidSOPInstanceUID( OFM_dcmnet, ECC_InvalidSOPInstanceUID, OF_error, "Invalid SOP Instance UID");
  39. const OFConditionConst ECE_UnknownTransferSyntax( OFM_dcmnet, ECC_UnknownTransferSyntax, OF_error, "Unknown Transfer Syntax");
  40. const OFCondition NET_EC_AlreadyConnected( ECE_AlreadyConnected);
  41. const OFCondition NET_EC_NoAcceptablePresentationContext( ECE_NoAcceptablePresentationContexts);
  42. const OFCondition NET_EC_NoPresentationContextsDefined( ECE_NoPresentationContextsDefined);
  43. /* Remove above if changing to more current DCMTK */
  44. DcmSCU::DcmSCU() :
  45. m_assoc(NULL),
  46. m_net(NULL),
  47. m_params(NULL),
  48. m_assocConfigFilename(),
  49. m_assocConfigProfile(),
  50. m_presContexts(),
  51. m_assocConfigFile(),
  52. m_openDIMSERequest(NULL),
  53. m_maxReceivePDULength(ASC_DEFAULTMAXPDU),
  54. m_blockMode(DIMSE_BLOCKING),
  55. m_ourAETitle("ANY-SCU"),
  56. m_peer(),
  57. m_peerAETitle("ANY-SCP"),
  58. m_peerPort(104),
  59. m_dimseTimeout(0),
  60. m_acseTimeout(30),
  61. m_storageDir(),
  62. m_storageMode(DCMSCU_STORAGE_DISK),
  63. m_verbosePCMode(OFFalse),
  64. m_datasetConversionMode(OFFalse),
  65. m_progressNotificationMode(OFTrue)
  66. {
  67. #ifdef HAVE_GUSI_H
  68. GUSISetup(GUSIwithSIOUXSockets);
  69. GUSISetup(GUSIwithInternetSockets);
  70. #endif
  71. #ifdef HAVE_WINSOCK_H
  72. WSAData winSockData;
  73. /* we need at least version 1.1 */
  74. WORD winSockVersionNeeded = MAKEWORD( 1, 1 );
  75. WSAStartup(winSockVersionNeeded, &winSockData); // TODO: check with multiple SCU instances whether this is harmful
  76. #endif
  77. }
  78. void DcmSCU::freeNetwork()
  79. {
  80. if ((m_assoc != NULL) || (m_net != NULL) || (m_params != NULL))
  81. DCMNET_DEBUG("Cleaning up internal association and network structures");
  82. /* destroy association parameters, i.e. free memory of T_ASC_Parameters.
  83. Usually this is done in ASC_destroyAssociation; however, if we already
  84. have association parameters but not yet an association (e.g. after calling
  85. initNetwork() and negotiateAssociation()), the latter approach may fail.
  86. */
  87. if (m_params)
  88. {
  89. ASC_destroyAssociationParameters(&m_params);
  90. m_params = NULL;
  91. // make sure destroyAssocation does not try to free params a second time
  92. // (happens in case we have already have an association structure)
  93. if (m_assoc)
  94. m_assoc->params = NULL;
  95. }
  96. // destroy the association, i.e. free memory of T_ASC_Association* structure.
  97. ASC_destroyAssociation(&m_assoc);
  98. // drop the network, i.e. free memory of T_ASC_Network* structure.
  99. ASC_dropNetwork(&m_net);
  100. // Cleanup old DIMSE request if any
  101. delete m_openDIMSERequest;
  102. m_openDIMSERequest = NULL;
  103. }
  104. DcmSCU::~DcmSCU()
  105. {
  106. // abort association (if any) and destroy dcmnet data structures
  107. if (isConnected())
  108. {
  109. closeAssociation(DCMSCU_ABORT_ASSOCIATION); // also frees network
  110. } else {
  111. freeNetwork();
  112. }
  113. #ifdef HAVE_WINSOCK_H
  114. WSACleanup(); // TODO: check with multiple SCU instances whether this is harmful
  115. #endif
  116. }
  117. OFCondition DcmSCU::initNetwork()
  118. {
  119. /* Return if SCU is already connected */
  120. if (isConnected())
  121. return NET_EC_AlreadyConnected;
  122. /* Be sure internal network structures are clean (delete old) */
  123. freeNetwork();
  124. OFString tempStr;
  125. /* initialize network, i.e. create an instance of T_ASC_Network*. */
  126. OFCondition cond = ASC_initializeNetwork(NET_REQUESTOR, 0, m_acseTimeout, &m_net);
  127. if (cond.bad())
  128. {
  129. DimseCondition::dump(tempStr, cond);
  130. DCMNET_ERROR(tempStr);
  131. return cond;
  132. }
  133. /* initialize asscociation parameters, i.e. create an instance of T_ASC_Parameters*. */
  134. cond = ASC_createAssociationParameters(&m_params, m_maxReceivePDULength);
  135. if (cond.bad())
  136. {
  137. DCMNET_ERROR(DimseCondition::dump(tempStr, cond));
  138. return cond;
  139. }
  140. /* sets this application's title and the called application's title in the params */
  141. /* structure. The default values are "ANY-SCU" and "ANY-SCP". */
  142. ASC_setAPTitles(m_params, m_ourAETitle.c_str(), m_peerAETitle.c_str(), NULL);
  143. /* Figure out the presentation addresses and copy the */
  144. /* corresponding values into the association parameters.*/
  145. DIC_NODENAME localHost;
  146. DIC_NODENAME peerHost;
  147. gethostname(localHost, sizeof(localHost) - 1);
  148. /* Since the underlying dcmnet structures reserve only 64 bytes for peer
  149. as well as local host name, we check here for buffer overflow.
  150. */
  151. if ((m_peer.length() + 5 /* max 65535 */) + 1 /* for ":" */ > 63)
  152. {
  153. DCMNET_ERROR("Maximum length of peer host name '" << m_peer << "' is longer than maximum of 57 characters");
  154. return EC_IllegalCall; // TODO: need to find better error code
  155. }
  156. if (strlen(localHost) + 1 > 63)
  157. {
  158. DCMNET_ERROR("Maximum length of local host name '" << localHost << "' is longer than maximum of 62 characters");
  159. return EC_IllegalCall; // TODO: need to find better error code
  160. }
  161. sprintf(peerHost, "%s:%d", m_peer.c_str(), OFstatic_cast(int, m_peerPort));
  162. ASC_setPresentationAddresses(m_params, localHost, peerHost);
  163. /* Add presentation contexts */
  164. // First, import from config file, if specified
  165. OFCondition result;
  166. if (!m_assocConfigFilename.empty())
  167. {
  168. DcmAssociationConfiguration assocConfig;
  169. result = DcmAssociationConfigurationFile::initialize(assocConfig, m_assocConfigFilename.c_str());
  170. if (result.bad())
  171. {
  172. DCMNET_WARN("Unable to parse association configuration file " << m_assocConfigFilename
  173. << " (ignored): " << result.text());
  174. return result;
  175. }
  176. else
  177. {
  178. /* perform name mangling for config file key */
  179. OFString profileName;
  180. const unsigned char *c = OFreinterpret_cast(const unsigned char *, m_assocConfigProfile.c_str());
  181. while (*c)
  182. {
  183. if (! isspace(*c)) profileName += OFstatic_cast(char, toupper(*c));
  184. ++c;
  185. }
  186. result = assocConfig.setAssociationParameters(profileName.c_str(), *m_params);
  187. if (result.bad())
  188. {
  189. DCMNET_WARN("Unable to apply association configuration file " << m_assocConfigFilename
  190. <<" (ignored): " << result.text());
  191. return result;
  192. }
  193. }
  194. }
  195. // Adapt presentation context ID to existing presentation contexts.
  196. // It's important that presentation context IDs are numerated 1,3,5,7...!
  197. Uint32 nextFreePresID = 257;
  198. Uint32 numContexts = ASC_countPresentationContexts(m_params);
  199. if (numContexts <= 127)
  200. {
  201. // Need Uint16 to avoid overflow in currPresID (unsigned char)
  202. nextFreePresID = 2 * numContexts + 1; /* add 1 to point to the next free ID*/
  203. }
  204. // Print warning if number of overall presentation contexts exceeds 128
  205. if ((numContexts + m_presContexts.size()) > 128)
  206. {
  207. DCMNET_WARN("Number of presentation contexts exceeds 128 (" << numContexts + m_presContexts.size()
  208. << "). Some contexts will not be negotiated");
  209. }
  210. else
  211. {
  212. DCMNET_TRACE("Configured " << numContexts << " presentation contexts from config file");
  213. if (m_presContexts.size() > 0)
  214. DCMNET_TRACE("Adding another " << m_presContexts.size() << " presentation contexts configured for SCU");
  215. }
  216. // Add presentation contexts not originating from config file
  217. OFListIterator(DcmSCUPresContext) contIt = m_presContexts.begin();
  218. OFListConstIterator(DcmSCUPresContext) endOfContList = m_presContexts.end();
  219. while ((contIt != endOfContList) && (nextFreePresID <= 255))
  220. {
  221. const Uint16 numTransferSyntaxes = OFstatic_cast(Uint16, (*contIt).transferSyntaxes.size());
  222. const char** transferSyntaxes = new const char*[numTransferSyntaxes];
  223. // Iterate over transfer syntaxes within one presentation context
  224. OFListIterator(OFString) syntaxIt = (*contIt).transferSyntaxes.begin();
  225. OFListIterator(OFString) endOfSyntaxList = (*contIt).transferSyntaxes.end();
  226. Uint16 sNum = 0;
  227. // copy all transfer syntaxes to array
  228. while (syntaxIt != endOfSyntaxList)
  229. {
  230. transferSyntaxes[sNum] = (*syntaxIt).c_str();
  231. ++syntaxIt;
  232. ++sNum;
  233. }
  234. // add the presentation context
  235. cond = ASC_addPresentationContext(m_params, OFstatic_cast(Uint8, nextFreePresID),
  236. (*contIt).abstractSyntaxName.c_str(), transferSyntaxes, numTransferSyntaxes,(*contIt).roleSelect);
  237. // if adding was successfull, prepare presentation context ID for next addition
  238. delete[] transferSyntaxes;
  239. transferSyntaxes = NULL;
  240. if (cond.bad())
  241. return cond;
  242. contIt++;
  243. // goto next free number, only odd presentation context IDs permitted
  244. nextFreePresID += 2;
  245. }
  246. numContexts = ASC_countPresentationContexts(m_params);
  247. if (numContexts == 0)
  248. {
  249. DCMNET_ERROR("Cannot initialize network: No presentation contexts defined");
  250. return NET_EC_NoPresentationContextsDefined;
  251. }
  252. DCMNET_DEBUG("Configured a total of " << numContexts << " presentation contexts for SCU");
  253. return cond;
  254. }
  255. OFCondition DcmSCU::negotiateAssociation()
  256. {
  257. /* Return error if SCU is already connected */
  258. if (isConnected())
  259. return NET_EC_AlreadyConnected;
  260. /* dump presentation contexts if required */
  261. OFString tempStr;
  262. if (m_verbosePCMode)
  263. DCMNET_INFO("Request Parameters:" << OFendl << ASC_dumpParameters(tempStr, m_params, ASC_ASSOC_RQ));
  264. else
  265. DCMNET_DEBUG("Request Parameters:" << OFendl << ASC_dumpParameters(tempStr, m_params, ASC_ASSOC_RQ));
  266. /* create association, i.e. try to establish a network connection to another */
  267. /* DICOM application. This call creates an instance of T_ASC_Association*. */
  268. DCMNET_INFO("Requesting Association");
  269. OFCondition cond = ASC_requestAssociation(m_net, m_params, &m_assoc);
  270. if (cond.bad())
  271. {
  272. if (cond == DUL_ASSOCIATIONREJECTED)
  273. {
  274. T_ASC_RejectParameters rej;
  275. ASC_getRejectParameters(m_params, &rej);
  276. DCMNET_DEBUG("Association Rejected:" << OFendl << ASC_printRejectParameters(tempStr, &rej));
  277. return cond;
  278. }
  279. else
  280. {
  281. DCMNET_DEBUG("Association Request Failed: " << DimseCondition::dump(tempStr, cond));
  282. return cond;
  283. }
  284. }
  285. /* dump the presentation contexts which have been accepted/refused */
  286. if (m_verbosePCMode)
  287. DCMNET_INFO("Association Parameters Negotiated:" << OFendl << ASC_dumpParameters(tempStr, m_params, ASC_ASSOC_AC));
  288. else
  289. DCMNET_DEBUG("Association Parameters Negotiated:" << OFendl << ASC_dumpParameters(tempStr, m_params, ASC_ASSOC_AC));
  290. /* count the presentation contexts which have been accepted by the SCP */
  291. /* If there are none, finish the execution */
  292. if (ASC_countAcceptedPresentationContexts(m_params) == 0)
  293. {
  294. DCMNET_ERROR("No Acceptable Presentation Contexts");
  295. return NET_EC_NoAcceptablePresentationContexts;
  296. }
  297. /* dump general information concerning the establishment of the network connection if required */
  298. DCMNET_INFO("Association Accepted (Max Send PDV: " << OFstatic_cast(unsigned long, m_assoc->sendPDVLength) << ")");
  299. return EC_Normal;
  300. }
  301. OFCondition DcmSCU::addPresentationContext(const OFString &abstractSyntax,
  302. const OFList<OFString> &xferSyntaxes,
  303. const T_ASC_SC_ROLE role)
  304. {
  305. DcmSCUPresContext presContext;
  306. presContext.abstractSyntaxName = abstractSyntax;
  307. OFListConstIterator(OFString) it = xferSyntaxes.begin();
  308. OFListConstIterator(OFString) endOfList = xferSyntaxes.end();
  309. while (it != endOfList)
  310. {
  311. presContext.transferSyntaxes.push_back(*it);
  312. it++;
  313. }
  314. presContext.roleSelect = role;
  315. m_presContexts.push_back(presContext);
  316. return EC_Normal;
  317. }
  318. OFCondition DcmSCU::useSecureConnection(DcmTransportLayer *tlayer)
  319. {
  320. OFCondition cond = ASC_setTransportLayer(m_net, tlayer, OFFalse /* do not take over ownership */);
  321. if (cond.good())
  322. cond = ASC_setTransportLayerType(m_params, OFTrue /* use TLS */);
  323. return cond;
  324. }
  325. void DcmSCU::clearPresentationContexts()
  326. {
  327. m_presContexts.clear();
  328. m_assocConfigFilename.clear();
  329. m_assocConfigProfile.clear();
  330. }
  331. // Returns usable presentation context ID for a given abstract syntax UID and
  332. // transfer syntax UID. 0 if none matches.
  333. T_ASC_PresentationContextID DcmSCU::findPresentationContextID(const OFString &abstractSyntax,
  334. const OFString &transferSyntax)
  335. {
  336. if (!isConnected())
  337. return 0;
  338. DUL_PRESENTATIONCONTEXT *pc;
  339. LST_HEAD **l;
  340. OFBool found = OFFalse;
  341. if (abstractSyntax.empty()) return 0;
  342. /* first of all we look for a presentation context
  343. * matching both abstract and transfer syntax
  344. */
  345. l = &m_assoc->params->DULparams.acceptedPresentationContext;
  346. pc = (DUL_PRESENTATIONCONTEXT*) LST_Head(l);
  347. (void)LST_Position(l, (LST_NODE*)pc);
  348. while (pc && !found)
  349. {
  350. found = (strcmp(pc->abstractSyntax, abstractSyntax.c_str()) == 0);
  351. found &= (pc->result == ASC_P_ACCEPTANCE);
  352. if (!transferSyntax.empty()) // ignore transfer syntax if not specified
  353. found &= (strcmp(pc->acceptedTransferSyntax, transferSyntax.c_str()) == 0);
  354. if (!found) pc = (DUL_PRESENTATIONCONTEXT*) LST_Next(l);
  355. }
  356. if (found)
  357. return pc->presentationContextID;
  358. return 0; /* not found */
  359. }
  360. // Returns the presentation context ID that best matches the given abstract syntax UID and
  361. // transfer syntax UID.
  362. T_ASC_PresentationContextID DcmSCU::findAnyPresentationContextID(const OFString &abstractSyntax,
  363. const OFString &transferSyntax)
  364. {
  365. if (m_assoc == NULL)
  366. return 0;
  367. DUL_PRESENTATIONCONTEXT *pc;
  368. LST_HEAD **l;
  369. OFBool found = OFFalse;
  370. if (abstractSyntax.empty()) return 0;
  371. /* first of all we look for a presentation context
  372. * matching both abstract and transfer syntax
  373. */
  374. l = &m_assoc->params->DULparams.acceptedPresentationContext;
  375. pc = (DUL_PRESENTATIONCONTEXT*) LST_Head(l);
  376. (void)LST_Position(l, (LST_NODE*)pc);
  377. while (pc && !found)
  378. {
  379. found = (strcmp(pc->abstractSyntax, abstractSyntax.c_str()) == 0);
  380. found &= (pc->result == ASC_P_ACCEPTANCE);
  381. if (!transferSyntax.empty()) // ignore transfer syntax if not specified
  382. found &= (strcmp(pc->acceptedTransferSyntax, transferSyntax.c_str()) == 0);
  383. if (!found) pc = (DUL_PRESENTATIONCONTEXT*) LST_Next(l);
  384. }
  385. if (found) return pc->presentationContextID;
  386. /* now we look for an explicit VR uncompressed PC. */
  387. l = &m_assoc->params->DULparams.acceptedPresentationContext;
  388. pc = (DUL_PRESENTATIONCONTEXT*) LST_Head(l);
  389. (void)LST_Position(l, (LST_NODE*)pc);
  390. while (pc && !found)
  391. {
  392. found = (strcmp(pc->abstractSyntax, abstractSyntax.c_str()) == 0)
  393. && (pc->result == ASC_P_ACCEPTANCE)
  394. && ((strcmp(pc->acceptedTransferSyntax, UID_LittleEndianExplicitTransferSyntax) == 0)
  395. || (strcmp(pc->acceptedTransferSyntax, UID_BigEndianExplicitTransferSyntax) == 0));
  396. if (!found) pc = (DUL_PRESENTATIONCONTEXT*) LST_Next(l);
  397. }
  398. if (found) return pc->presentationContextID;
  399. /* now we look for an implicit VR uncompressed PC. */
  400. l = &m_assoc->params->DULparams.acceptedPresentationContext;
  401. pc = (DUL_PRESENTATIONCONTEXT*) LST_Head(l);
  402. (void)LST_Position(l, (LST_NODE*)pc);
  403. while (pc && !found)
  404. {
  405. found = (strcmp(pc->abstractSyntax, abstractSyntax.c_str()) == 0)
  406. && (pc->result == ASC_P_ACCEPTANCE)
  407. && (strcmp(pc->acceptedTransferSyntax, UID_LittleEndianImplicitTransferSyntax) == 0);
  408. if (!found) pc = (DUL_PRESENTATIONCONTEXT*) LST_Next(l);
  409. }
  410. if (found) return pc->presentationContextID;
  411. /* finally we accept everything we get.
  412. returns 0 if abstract syntax is not supported
  413. */
  414. return ASC_findAcceptedPresentationContextID(m_assoc, abstractSyntax.c_str());
  415. }
  416. void DcmSCU::findPresentationContext(const T_ASC_PresentationContextID presID,
  417. OFString &abstractSyntax,
  418. OFString &transferSyntax)
  419. {
  420. transferSyntax.clear();
  421. abstractSyntax.clear();
  422. if (m_assoc == NULL)
  423. return;
  424. DUL_PRESENTATIONCONTEXT *pc;
  425. LST_HEAD **l;
  426. /* we look for a presentation context matching
  427. * both abstract and transfer syntax
  428. */
  429. l = &m_assoc->params->DULparams.acceptedPresentationContext;
  430. pc = (DUL_PRESENTATIONCONTEXT*) LST_Head(l);
  431. (void)LST_Position(l, (LST_NODE*)pc);
  432. while (pc)
  433. {
  434. if (presID == pc->presentationContextID)
  435. {
  436. if (pc->result == ASC_P_ACCEPTANCE)
  437. {
  438. // found a match
  439. transferSyntax = pc->acceptedTransferSyntax;
  440. abstractSyntax = pc->abstractSyntax;
  441. }
  442. break;
  443. }
  444. pc = (DUL_PRESENTATIONCONTEXT*) LST_Next(l);
  445. }
  446. }
  447. Uint16 DcmSCU::nextMessageID()
  448. {
  449. if (!isConnected())
  450. return 0;
  451. else
  452. return m_assoc->nextMsgID++;
  453. }
  454. void DcmSCU::closeAssociation(const DcmCloseAssociationType closeType)
  455. {
  456. if (!isConnected())
  457. {
  458. DCMNET_WARN("Closing of association request but no association active (ignored)");
  459. return;
  460. }
  461. OFCondition cond;
  462. OFString tempStr;
  463. /* tear down association, i.e. terminate network connection to SCP */
  464. switch (closeType)
  465. {
  466. case DCMSCU_RELEASE_ASSOCIATION:
  467. /* release association */
  468. DCMNET_INFO("Releasing Association");
  469. cond = ASC_releaseAssociation(m_assoc);
  470. if (cond.bad())
  471. {
  472. DCMNET_ERROR("Association Release Failed: " << DimseCondition::dump(tempStr, cond));
  473. return; // TODO: do we really need this?
  474. }
  475. break;
  476. case DCMSCU_ABORT_ASSOCIATION:
  477. /* abort association */
  478. DCMNET_INFO("Aborting Association");
  479. cond = ASC_abortAssociation(m_assoc);
  480. if (cond.bad())
  481. {
  482. DCMNET_ERROR("Association Abort Failed: " << DimseCondition::dump(tempStr, cond));
  483. }
  484. break;
  485. case DCMSCU_PEER_REQUESTED_RELEASE:
  486. /* peer requested release */
  487. DCMNET_ERROR("Protocol Error: Peer requested release (Aborting)");
  488. DCMNET_INFO("Aborting Association");
  489. cond = ASC_abortAssociation(m_assoc);
  490. if (cond.bad())
  491. {
  492. DCMNET_ERROR("Association Abort Failed: " << DimseCondition::dump(tempStr, cond));
  493. }
  494. break;
  495. case DCMSCU_PEER_ABORTED_ASSOCIATION:
  496. /* peer aborted association */
  497. DCMNET_INFO("Peer Aborted Association");
  498. break;
  499. }
  500. // destroy and free memory of internal association and network structures
  501. freeNetwork();
  502. }
  503. /* ************************************************************************* */
  504. /* C-ECHO functionality */
  505. /* ************************************************************************* */
  506. // Sends C-ECHO request to another DICOM application
  507. OFCondition DcmSCU::sendECHORequest(const T_ASC_PresentationContextID presID)
  508. {
  509. if (!isConnected())
  510. return DIMSE_ILLEGALASSOCIATION;
  511. OFCondition cond;
  512. T_ASC_PresentationContextID pcid = presID;
  513. /* If necessary, find appropriate presentation context */
  514. if (pcid == 0)
  515. pcid = findPresentationContextID(UID_VerificationSOPClass, UID_LittleEndianExplicitTransferSyntax);
  516. if (pcid == 0)
  517. pcid = findPresentationContextID(UID_VerificationSOPClass, UID_BigEndianExplicitTransferSyntax);
  518. if (pcid == 0)
  519. pcid = findPresentationContextID(UID_VerificationSOPClass, UID_LittleEndianImplicitTransferSyntax);
  520. if (pcid == 0)
  521. {
  522. DCMNET_ERROR("No presentation context found for sending C-ECHO with SOP Class / Transfer Syntax: "
  523. << dcmFindNameOfUID(UID_VerificationSOPClass, "") << " / "
  524. << DcmXfer(UID_LittleEndianImplicitTransferSyntax).getXferName());
  525. return DIMSE_NOVALIDPRESENTATIONCONTEXTID;
  526. }
  527. /* Now, assemble DIMSE message */
  528. T_DIMSE_Message msg;
  529. T_DIMSE_C_EchoRQ* req = &(msg.msg.CEchoRQ);
  530. // Set type of message
  531. msg.CommandField = DIMSE_C_ECHO_RQ;
  532. // Set message ID
  533. req->MessageID = nextMessageID();
  534. // Announce no dataset
  535. req->DataSetType = DIMSE_DATASET_NULL;
  536. // Set affected SOP Class UID (always Verification SOP Class)
  537. OFStandard::strlcpy(req->AffectedSOPClassUID, UID_VerificationSOPClass, sizeof(req->AffectedSOPClassUID));
  538. /* Send request */
  539. OFString tempStr;
  540. if (DCM_dcmnetGetLogger().isEnabledFor(OFLogger::DEBUG_LOG_LEVEL))
  541. {
  542. DCMNET_INFO("Sending C-ECHO Request");
  543. DCMNET_DEBUG(DIMSE_dumpMessage(tempStr, msg, DIMSE_OUTGOING, NULL, pcid));
  544. } else {
  545. DCMNET_INFO("Sending C-ECHO Request (MsgID " << req->MessageID << ")");
  546. }
  547. cond = sendDIMSEMessage(pcid, &msg, NULL /*dataObject*/);
  548. if (cond.bad())
  549. {
  550. DCMNET_ERROR("Failed sending C-ECHO request: " << DimseCondition::dump(tempStr, cond));
  551. return cond;
  552. }
  553. /* Receive response */
  554. T_DIMSE_Message rsp;
  555. DcmDataset* statusDetail = NULL;
  556. cond = receiveDIMSECommand(&pcid, &rsp, &statusDetail, NULL /* not interested in the command set */);
  557. if (cond.bad())
  558. {
  559. DCMNET_ERROR("Failed receiving DIMSE response: " << DimseCondition::dump(tempStr, cond));
  560. return cond;
  561. }
  562. /* Check whether we received C-ECHO response, otherwise print error */
  563. if (rsp.CommandField == DIMSE_C_ECHO_RSP)
  564. {
  565. if (DCM_dcmnetGetLogger().isEnabledFor(OFLogger::DEBUG_LOG_LEVEL))
  566. {
  567. DCMNET_INFO("Received C-ECHO Response");
  568. DCMNET_DEBUG(DIMSE_dumpMessage(tempStr, rsp, DIMSE_INCOMING, NULL, pcid));
  569. } else {
  570. DCMNET_INFO("Received C-ECHO Response (" << rsp.msg.CEchoRSP.DimseStatus << ")");
  571. }
  572. } else {
  573. DCMNET_ERROR("Expected C-ECHO response but received DIMSE command 0x"
  574. << STD_NAMESPACE hex << STD_NAMESPACE setfill('0') << STD_NAMESPACE setw(4)
  575. << OFstatic_cast(unsigned int, rsp.CommandField));
  576. DCMNET_DEBUG(DIMSE_dumpMessage(tempStr, rsp, DIMSE_INCOMING, NULL, pcid));
  577. delete statusDetail;
  578. return DIMSE_BADCOMMANDTYPE;
  579. }
  580. /* Print status detail if it was received */
  581. if (statusDetail != NULL)
  582. {
  583. DCMNET_DEBUG("Response has status detail:" << OFendl << DcmObject::PrintHelper(*statusDetail));
  584. delete statusDetail;
  585. }
  586. return EC_Normal;
  587. }
  588. /* ************************************************************************* */
  589. /* C-STORE functionality */
  590. /* ************************************************************************* */
  591. // Sends C-STORE request to another DICOM application
  592. OFCondition DcmSCU::sendSTORERequest(const T_ASC_PresentationContextID presID,
  593. const OFString &dicomFile,
  594. DcmDataset *dataset,
  595. Uint16 &rspStatusCode)
  596. {
  597. // Do some basic validity checks
  598. if (!isConnected())
  599. return DIMSE_ILLEGALASSOCIATION;
  600. OFCondition cond;
  601. OFString tempStr;
  602. T_ASC_PresentationContextID pcid = presID;
  603. DcmDataset* statusDetail = NULL;
  604. T_DIMSE_Message msg;
  605. T_DIMSE_C_StoreRQ* req = &(msg.msg.CStoreRQ);
  606. // Set type of message
  607. msg.CommandField = DIMSE_C_STORE_RQ;
  608. /* Set message ID */
  609. req->MessageID = nextMessageID();
  610. /* Load file if necessary */
  611. DcmFileFormat *fileformat = NULL;
  612. if (!dicomFile.empty())
  613. {
  614. fileformat = new DcmFileFormat();
  615. if (fileformat == NULL)
  616. return EC_MemoryExhausted;
  617. cond = fileformat->loadFile(dicomFile.c_str());
  618. if (cond.bad())
  619. {
  620. delete fileformat;
  621. return cond;
  622. }
  623. dataset = fileformat->getDataset();
  624. }
  625. /* Fill message according to dataset to be sent */
  626. OFString sopClassUID;
  627. OFString sopInstanceUID;
  628. E_TransferSyntax xferSyntax = EXS_Unknown;
  629. cond = getDatasetInfo(dataset, sopClassUID, sopInstanceUID, xferSyntax);
  630. DcmXfer xfer(xferSyntax);
  631. /* Check whether the information is sufficient */
  632. if (sopClassUID.empty() || sopInstanceUID.empty() || ((pcid == 0) && (xferSyntax == EXS_Unknown)))
  633. {
  634. DCMNET_ERROR("Cannot send SOP instance, missing information:");
  635. if (!dicomFile.empty())
  636. DCMNET_ERROR(" DICOM Filename : " << dicomFile);
  637. DCMNET_ERROR(" SOP Class UID : " << sopClassUID);
  638. DCMNET_ERROR(" SOP Instance UID : " << sopInstanceUID);
  639. DCMNET_ERROR(" Transfer Syntax : " << xfer.getXferName());
  640. if (pcid == 0)
  641. DCMNET_ERROR(" Pres. Context ID : 0 (find via SOP Class and Transfer Syntax)");
  642. else
  643. DCMNET_ERROR(" Pres. Context ID : " << OFstatic_cast(unsigned int, pcid));
  644. delete fileformat;
  645. return cond;
  646. }
  647. OFStandard::strlcpy(req->AffectedSOPClassUID, sopClassUID.c_str(), sizeof(req->AffectedSOPClassUID));
  648. OFStandard::strlcpy(req->AffectedSOPInstanceUID, sopInstanceUID.c_str(), sizeof(req->AffectedSOPInstanceUID));
  649. req->DataSetType = DIMSE_DATASET_PRESENT;
  650. req->Priority = DIMSE_PRIORITY_LOW;
  651. /* If no presentation context is specified by the caller ... */
  652. if (pcid == 0)
  653. {
  654. /* ... try to find an appropriate presentation context automatically */
  655. pcid = findPresentationContextID(sopClassUID, xfer.getXferID());
  656. }
  657. else if (m_datasetConversionMode)
  658. {
  659. /* Convert dataset to network transfer syntax (if required) */
  660. OFString abstractSyntax, transferSyntax;
  661. findPresentationContext(pcid, abstractSyntax, transferSyntax);
  662. /* Check whether given presentation context was accepted by the peer */
  663. if (abstractSyntax.empty() || transferSyntax.empty())
  664. {
  665. /* Mark presentation context as invalid */
  666. pcid = 0;
  667. } else {
  668. if (abstractSyntax != sopClassUID)
  669. {
  670. DCMNET_WARN("Inappropriate presentation context with ID " << OFstatic_cast(unsigned int, pcid)
  671. << ": abstract syntax does not match SOP class UID");
  672. }
  673. /* Try to convert to the negotiated transfer syntax */
  674. DcmXfer netXfer = DcmXfer(transferSyntax.c_str()).getXfer();
  675. if (netXfer.getXfer() != xferSyntax)
  676. {
  677. DCMNET_INFO("Converting transfer syntax: " << xfer.getXferName() << " -> "
  678. << netXfer.getXferName());
  679. dataset->chooseRepresentation(netXfer.getXfer(), NULL);
  680. }
  681. }
  682. }
  683. /* No appropriate presentation context for sending */
  684. if (pcid == 0)
  685. {
  686. OFString sopClassName = dcmFindNameOfUID(sopClassUID.c_str(), sopClassUID.c_str());
  687. OFString xferName = xfer.getXferName();
  688. DCMNET_ERROR("No presentation context found for sending C-STORE with SOP Class / Transfer Syntax: "
  689. << sopClassName << " / " << xferName);
  690. return DIMSE_NOVALIDPRESENTATIONCONTEXTID;
  691. }
  692. /* Send request */
  693. if (DCM_dcmnetGetLogger().isEnabledFor(OFLogger::DEBUG_LOG_LEVEL))
  694. {
  695. DCMNET_INFO("Sending C-STORE Request");
  696. DCMNET_DEBUG(DIMSE_dumpMessage(tempStr, msg, DIMSE_OUTGOING, NULL, pcid));
  697. } else {
  698. DCMNET_INFO("Sending C-STORE Request (MsgID " << req->MessageID << ", "
  699. << dcmSOPClassUIDToModality(sopClassUID.c_str(), "OT") << ")");
  700. }
  701. cond = sendDIMSEMessage(pcid, &msg, dataset);
  702. delete fileformat;
  703. fileformat = NULL;
  704. if (cond.bad())
  705. {
  706. DCMNET_ERROR("Failed sending C-STORE request: " << DimseCondition::dump(tempStr, cond));
  707. return cond;
  708. }
  709. /* Receive response */
  710. T_DIMSE_Message rsp;
  711. cond = receiveDIMSECommand(&pcid, &rsp, &statusDetail, NULL /* not interested in the command set */);
  712. if (cond.bad())
  713. {
  714. DCMNET_ERROR("Failed receiving DIMSE response: " << DimseCondition::dump(tempStr, cond));
  715. return cond;
  716. }
  717. if (rsp.CommandField == DIMSE_C_STORE_RSP)
  718. {
  719. if (DCM_dcmnetGetLogger().isEnabledFor(OFLogger::DEBUG_LOG_LEVEL))
  720. {
  721. DCMNET_INFO("Received C-STORE Response");
  722. DCMNET_DEBUG(DIMSE_dumpMessage(tempStr, rsp, DIMSE_INCOMING, NULL, pcid));
  723. } else {
  724. DCMNET_INFO("Received C-STORE Response (" << DU_cstoreStatusString(rsp.msg.CStoreRSP.DimseStatus) << ")");
  725. }
  726. } else {
  727. DCMNET_ERROR("Expected C-STORE response but received DIMSE command 0x"
  728. << STD_NAMESPACE hex << STD_NAMESPACE setfill('0') << STD_NAMESPACE setw(4)
  729. << OFstatic_cast(unsigned int, rsp.CommandField));
  730. DCMNET_DEBUG(DIMSE_dumpMessage(tempStr, rsp, DIMSE_INCOMING, NULL, pcid));
  731. delete statusDetail;
  732. return DIMSE_BADCOMMANDTYPE;
  733. }
  734. T_DIMSE_C_StoreRSP storeRsp = rsp.msg.CStoreRSP;
  735. rspStatusCode = storeRsp.DimseStatus;
  736. if (statusDetail != NULL)
  737. {
  738. DCMNET_DEBUG("Response has status detail:" << OFendl << DcmObject::PrintHelper(*statusDetail));
  739. delete statusDetail;
  740. }
  741. return cond;
  742. }
  743. /* ************************************************************************* */
  744. /* C-MOVE functionality */
  745. /* ************************************************************************* */
  746. // Sends a C-MOVE Request on given presentation context
  747. OFCondition DcmSCU::sendMOVERequest(const T_ASC_PresentationContextID presID,
  748. const OFString &moveDestinationAETitle,
  749. DcmDataset *dataset,
  750. OFList<RetrieveResponse*> *responses)
  751. {
  752. // Do some basic validity checks
  753. if (!isConnected())
  754. return DIMSE_ILLEGALASSOCIATION;
  755. if (dataset == NULL)
  756. return DIMSE_NULLKEY;
  757. /* Prepare DIMSE data structures for issuing request */
  758. OFCondition cond;
  759. OFString tempStr;
  760. T_ASC_PresentationContextID pcid = presID;
  761. T_DIMSE_Message msg;
  762. DcmDataset* statusDetail = NULL;
  763. T_DIMSE_C_MoveRQ* req = &(msg.msg.CMoveRQ);
  764. // Set type of message
  765. msg.CommandField = DIMSE_C_MOVE_RQ;
  766. // Set message ID
  767. req->MessageID = nextMessageID();
  768. // Announce dataset
  769. req->DataSetType = DIMSE_DATASET_PRESENT;
  770. // Set target for embedded C-Store's
  771. OFStandard::strlcpy(req->MoveDestination, moveDestinationAETitle.c_str(), sizeof(req->MoveDestination));
  772. // Set priority (mandatory)
  773. req->Priority = DIMSE_PRIORITY_LOW;
  774. /* Determine SOP Class from presentation context */
  775. OFString abstractSyntax, transferSyntax;
  776. findPresentationContext(pcid, abstractSyntax, transferSyntax);
  777. if (abstractSyntax.empty() || transferSyntax.empty())
  778. return DIMSE_NOVALIDPRESENTATIONCONTEXTID;
  779. OFStandard::strlcpy(req->AffectedSOPClassUID, abstractSyntax.c_str(), sizeof(req->AffectedSOPClassUID));
  780. /* Send request */
  781. if (DCM_dcmnetGetLogger().isEnabledFor(OFLogger::DEBUG_LOG_LEVEL))
  782. {
  783. DCMNET_INFO("Sending C-MOVE Request");
  784. DCMNET_DEBUG(DIMSE_dumpMessage(tempStr, msg, DIMSE_OUTGOING, dataset, pcid));
  785. } else {
  786. DCMNET_INFO("Sending C-MOVE Request (MsgID " << req->MessageID << ")");
  787. }
  788. cond = sendDIMSEMessage(pcid, &msg, dataset);
  789. if (cond.bad())
  790. {
  791. DCMNET_ERROR("Failed sending C-MOVE request: " << DimseCondition::dump(tempStr, cond));
  792. return cond;
  793. }
  794. /* Receive and handle C-MOVE response messages */
  795. OFBool waitForNextResponse = OFTrue;
  796. while (waitForNextResponse)
  797. {
  798. T_DIMSE_Message rsp;
  799. statusDetail = NULL;
  800. // Receive command set
  801. cond = receiveDIMSECommand(&pcid, &rsp, &statusDetail, NULL /* not interested in the command set */);
  802. if (cond.bad())
  803. {
  804. DCMNET_ERROR("Failed receiving DIMSE response: " << DimseCondition::dump(tempStr, cond));
  805. delete statusDetail;
  806. break;
  807. }
  808. if (rsp.CommandField == DIMSE_C_MOVE_RSP)
  809. {
  810. if (DCM_dcmnetGetLogger().isEnabledFor(OFLogger::DEBUG_LOG_LEVEL))
  811. {
  812. DCMNET_INFO("Received C-MOVE Response");
  813. DCMNET_DEBUG(DIMSE_dumpMessage(tempStr, rsp, DIMSE_INCOMING, NULL, pcid));
  814. } else {
  815. DCMNET_INFO("Received C-MOVE Response (" << DU_cmoveStatusString(rsp.msg.CMoveRSP.DimseStatus) << ")");
  816. }
  817. } else {
  818. DCMNET_ERROR("Expected C-MOVE response but received DIMSE command 0x"
  819. << STD_NAMESPACE hex << STD_NAMESPACE setfill('0') << STD_NAMESPACE setw(4)
  820. << OFstatic_cast(unsigned int, rsp.CommandField));
  821. DCMNET_DEBUG(DIMSE_dumpMessage(tempStr, rsp, DIMSE_INCOMING, NULL, pcid));
  822. delete statusDetail;
  823. cond = DIMSE_BADCOMMANDTYPE;
  824. break;
  825. }
  826. // Prepare response package for response handler
  827. RetrieveResponse *moveRSP = new RetrieveResponse();
  828. moveRSP->m_affectedSOPClassUID = rsp.msg.CMoveRSP.AffectedSOPClassUID;
  829. moveRSP->m_messageIDRespondedTo = rsp.msg.CMoveRSP.MessageIDBeingRespondedTo;
  830. moveRSP->m_status = rsp.msg.CMoveRSP.DimseStatus;
  831. moveRSP->m_numberOfRemainingSubops = rsp.msg.CMoveRSP.NumberOfRemainingSubOperations;
  832. moveRSP->m_numberOfCompletedSubops = rsp.msg.CMoveRSP.NumberOfCompletedSubOperations;
  833. moveRSP->m_numberOfFailedSubops = rsp.msg.CMoveRSP.NumberOfFailedSubOperations;
  834. moveRSP->m_numberOfWarningSubops = rsp.msg.CMoveRSP.NumberOfWarningSubOperations;
  835. moveRSP->m_statusDetail = statusDetail;
  836. //DCMNET_DEBUG("C-MOVE response has status 0x"
  837. // << STD_NAMESPACE hex << STD_NAMESPACE setfill('0') << STD_NAMESPACE setw(4)
  838. // << moveRSP->m_status);
  839. if (statusDetail != NULL)
  840. {
  841. DCMNET_DEBUG("Response has status detail:" << OFendl << DcmObject::PrintHelper(*statusDetail));
  842. }
  843. // Receive dataset if there is one (status PENDING)
  844. DcmDataset *rspDataset = NULL;
  845. // Check if dataset is announced correctly
  846. if (rsp.msg.CMoveRSP.DataSetType != DIMSE_DATASET_NULL) // Some of the sub operations have failed, thus a dataset with a list of them is attached
  847. {
  848. // Receive dataset
  849. cond = receiveDIMSEDataset(&pcid, &rspDataset);
  850. if (cond.bad())
  851. {
  852. DCMNET_ERROR("Unable to receive C-MOVE dataset on presentation context "
  853. << OFstatic_cast(unsigned int, pcid) << ": " << DimseCondition::dump(tempStr, cond));
  854. delete moveRSP; // includes statusDetail
  855. break;
  856. }
  857. moveRSP->m_dataset = rspDataset;
  858. }
  859. // Handle C-MOVE response (has to handle all possible status flags)
  860. cond = handleMOVEResponse(pcid, moveRSP, waitForNextResponse);
  861. if (cond.bad())
  862. {
  863. DCMNET_WARN("Unable to handle C-MOVE response correctly: " << cond.text() << " (ignored)");
  864. delete moveRSP; // includes statusDetail
  865. // don't return here but trust the "waitForNextResponse" variable
  866. }
  867. // if response could be handled successfully, add it to response list
  868. else
  869. {
  870. if (responses != NULL) // only add if desired by caller
  871. responses->push_back(moveRSP);
  872. else
  873. delete moveRSP; // includes statusDetail
  874. }
  875. }
  876. /* All responses received or break signal occured */
  877. return cond;
  878. }
  879. // Standard handler for C-MOVE message responses
  880. OFCondition DcmSCU::handleMOVEResponse( const T_ASC_PresentationContextID /* presID */,
  881. RetrieveResponse *response,
  882. OFBool &waitForNextResponse )
  883. {
  884. // Do some basic validity checks
  885. if (!isConnected())
  886. return DIMSE_ILLEGALASSOCIATION;
  887. if (response == NULL)
  888. return DIMSE_NULLKEY;
  889. DCMNET_DEBUG("Handling C-MOVE Response");
  890. switch (response->m_status) {
  891. case STATUS_MOVE_Failed_IdentifierDoesNotMatchSOPClass:
  892. waitForNextResponse = OFFalse;
  893. DCMNET_ERROR("Identifier does not match SOP class in C-MOVE response");
  894. break;
  895. case STATUS_MOVE_Failed_MoveDestinationUnknown:
  896. waitForNextResponse = OFFalse;
  897. DCMNET_ERROR("Move destination unknown");
  898. break;
  899. case STATUS_MOVE_Failed_UnableToProcess:
  900. waitForNextResponse = OFFalse;
  901. DCMNET_ERROR("Unable to process C-Move response");
  902. break;
  903. case STATUS_MOVE_Cancel_SubOperationsTerminatedDueToCancelIndication:
  904. waitForNextResponse = OFFalse;
  905. DCMNET_DEBUG("Suboperations canceled by server due to CANCEL indication");
  906. break;
  907. case STATUS_MOVE_Warning_SubOperationsCompleteOneOrMoreFailures:
  908. waitForNextResponse = OFFalse;
  909. DCMNET_WARN("Suboperations of C-MOVE completed with one or more failures");
  910. break;
  911. case STATUS_Pending:
  912. /* in this case the current C-MOVE-RSP indicates that */
  913. /* there will be some more results */
  914. waitForNextResponse = OFTrue;
  915. DCMNET_DEBUG("One or more pending C-MOVE responses");
  916. break;
  917. case STATUS_Success:
  918. /* in this case, we received the last C-MOVE-RSP so there */
  919. /* will be no other responses we have to wait for. */
  920. waitForNextResponse = OFFalse;
  921. DCMNET_DEBUG("Received final C-MOVE response, no more C-MOVE responses expected");
  922. break;
  923. default:
  924. /* in all other cases, don't expect further responses to come */
  925. waitForNextResponse = OFFalse;
  926. DCMNET_WARN("Status is 0x"
  927. << STD_NAMESPACE hex << STD_NAMESPACE setfill('0') << STD_NAMESPACE setw(4)
  928. << response->m_status << " (unknown)");
  929. DCMNET_WARN("Will not wait for further C-MOVE responses");
  930. break;
  931. } //switch
  932. return EC_Normal;
  933. }
  934. /* ************************************************************************* */
  935. /* C-GET and acommpanying C-STORE functionality */
  936. /* ************************************************************************* */
  937. // Sends a C-GET Request on given presentation context
  938. OFCondition DcmSCU::sendCGETRequest(const T_ASC_PresentationContextID presID,
  939. DcmDataset *dataset,
  940. OFList<RetrieveResponse*> *responses)
  941. {
  942. // Do some basic validity checks
  943. if (!isConnected())
  944. return DIMSE_ILLEGALASSOCIATION;
  945. if (dataset == NULL)
  946. return DIMSE_NULLKEY;
  947. /* Prepare DIMSE data structures for issuing request */
  948. OFCondition cond;
  949. OFString tempStr;
  950. T_ASC_PresentationContextID pcid = presID;
  951. T_DIMSE_Message msg;
  952. T_DIMSE_C_GetRQ* req = &(msg.msg.CGetRQ);
  953. // Set type of message
  954. msg.CommandField = DIMSE_C_GET_RQ;
  955. // Set message ID
  956. req->MessageID = nextMessageID();
  957. // Announce dataset
  958. req->DataSetType = DIMSE_DATASET_PRESENT;
  959. // Specify priority
  960. req->Priority = DIMSE_PRIORITY_LOW;
  961. // Determine SOP Class from presentation context
  962. OFString abstractSyntax, transferSyntax;
  963. findPresentationContext(pcid, abstractSyntax, transferSyntax);
  964. if (abstractSyntax.empty() || transferSyntax.empty())
  965. return DIMSE_NOVALIDPRESENTATIONCONTEXTID;
  966. OFStandard::strlcpy(req->AffectedSOPClassUID, abstractSyntax.c_str(), sizeof(req->AffectedSOPClassUID));
  967. /* Send request */
  968. if (DCM_dcmnetGetLogger().isEnabledFor(OFLogger::DEBUG_LOG_LEVEL))
  969. {
  970. DCMNET_INFO("Sending C-GET Request");
  971. DCMNET_DEBUG(DIMSE_dumpMessage(tempStr, msg, DIMSE_OUTGOING, dataset, pcid));
  972. } else {
  973. DCMNET_INFO("Sending C-GET Request (MsgID " << req->MessageID << ")");
  974. }
  975. cond = sendDIMSEMessage(pcid, &msg, dataset);
  976. if (cond.bad())
  977. {
  978. DCMNET_ERROR("Failed sending C-GET request: " << DimseCondition::dump(tempStr, cond));
  979. return cond;
  980. }
  981. cond = handleCGETSession(pcid, dataset, responses);
  982. return cond;
  983. }
  984. // Does the logic for switching between C-GET Response and C-STORE Requests
  985. OFCondition DcmSCU::handleCGETSession(const T_ASC_PresentationContextID /* presID */,
  986. DcmDataset * /* dataset */,
  987. OFList<RetrieveResponse*> *responses)
  988. {
  989. OFCondition result;
  990. OFBool continueSession = OFTrue;
  991. OFString tempStr;
  992. // As long we want to continue (usually, as long as we receive more objects,
  993. // i.e. the final C-GET reponse has not arrived yet)
  994. while (continueSession)
  995. {
  996. T_DIMSE_Message rsp;
  997. DcmDataset *statusDetail = NULL;
  998. T_ASC_PresentationContextID pcid = 0;
  999. // Receive command set
  1000. result = receiveDIMSECommand(&pcid, &rsp, &statusDetail, NULL /* not interested in the command set */);
  1001. if (result.bad())
  1002. {
  1003. DCMNET_ERROR("Failed receiving DIMSE command: " << DimseCondition::dump(tempStr, result));
  1004. delete statusDetail;
  1005. break;
  1006. }
  1007. // Handle C-GET Response
  1008. if (rsp.CommandField == DIMSE_C_GET_RSP)
  1009. {
  1010. if (DCM_dcmnetGetLogger().isEnabledFor(OFLogger::DEBUG_LOG_LEVEL))
  1011. {
  1012. DCMNET_INFO("Received C-GET Response");
  1013. DCMNET_DEBUG(DIMSE_dumpMessage(tempStr, rsp, DIMSE_INCOMING, NULL, pcid));
  1014. } else {
  1015. DCMNET_INFO("Received C-GET Response (" << DU_cgetStatusString(rsp.msg.CGetRSP.DimseStatus) << ")");
  1016. }
  1017. // Prepare response package for response handler
  1018. RetrieveResponse *getRSP = new RetrieveResponse();
  1019. getRSP->m_affectedSOPClassUID = rsp.msg.CGetRSP.AffectedSOPClassUID;
  1020. getRSP->m_messageIDRespondedTo = rsp.msg.CGetRSP.MessageIDBeingRespondedTo;
  1021. getRSP->m_status = rsp.msg.CGetRSP.DimseStatus;
  1022. getRSP->m_numberOfRemainingSubops = rsp.msg.CGetRSP.NumberOfRemainingSubOperations;
  1023. getRSP->m_numberOfCompletedSubops = rsp.msg.CGetRSP.NumberOfCompletedSubOperations;
  1024. getRSP->m_numberOfFailedSubops = rsp.msg.CGetRSP.NumberOfFailedSubOperations;
  1025. getRSP->m_numberOfWarningSubops = rsp.msg.CGetRSP.NumberOfWarningSubOperations;
  1026. getRSP->m_statusDetail = statusDetail;
  1027. if (statusDetail != NULL)
  1028. {
  1029. DCMNET_DEBUG("Response has status detail:" << OFendl << DcmObject::PrintHelper(*statusDetail));
  1030. statusDetail = NULL; // forget reference to status detail, will be deleted with getRSP
  1031. }
  1032. result = handleCGETResponse(pcid, getRSP, continueSession);
  1033. if (result.bad())
  1034. {
  1035. DCMNET_WARN("Unable to handle C-GET response correctly: " << result.text() << " (ignored)");
  1036. delete getRSP; // includes statusDetail
  1037. // don't return here but trust the "continueSession" variable
  1038. }
  1039. // if response could be handled successfully, add it to response list
  1040. else {
  1041. if (responses != NULL) // only add if desired by caller
  1042. responses->push_back(getRSP);
  1043. else
  1044. delete getRSP; // includes statusDetail
  1045. }
  1046. }
  1047. // Handle C-STORE Request
  1048. else if (rsp.CommandField == DIMSE_C_STORE_RQ)
  1049. {
  1050. if (DCM_dcmnetGetLogger().isEnabledFor(OFLogger::DEBUG_LOG_LEVEL))
  1051. {
  1052. DCMNET_INFO("Received C-STORE Request");
  1053. DCMNET_DEBUG(DIMSE_dumpMessage(tempStr, rsp, DIMSE_INCOMING, NULL, pcid));
  1054. } else {
  1055. DCMNET_INFO("Received C-STORE Request (MsgID " << rsp.msg.CStoreRQ.MessageID << ")");
  1056. }
  1057. // Receive dataset if there is one (status PENDING)
  1058. DcmDataset *rspDataset = NULL;
  1059. // Check if dataset is announced correctly
  1060. if (rsp.msg.CStoreRQ.DataSetType == DIMSE_DATASET_NULL)
  1061. {
  1062. DCMNET_WARN("Incoming C-STORE with no dataset, trying to receive one anyway");
  1063. }
  1064. Uint16 desiredCStoreReturnStatus = 0;
  1065. // handle normal storage mode, i.e. receive in memory and store to disk
  1066. if (m_storageMode == DCMSCU_STORAGE_DISK)
  1067. {
  1068. // Receive dataset
  1069. result = receiveDIMSEDataset(&pcid, &rspDataset);
  1070. if (result.bad())
  1071. {
  1072. result = DIMSE_NULLKEY;
  1073. desiredCStoreReturnStatus = STATUS_STORE_Error_CannotUnderstand;
  1074. } else {
  1075. result = handleSTORERequest(pcid, rspDataset, continueSession, desiredCStoreReturnStatus);
  1076. }
  1077. }
  1078. // handle bit preserving storage mode, i.e. receive directly to disk
  1079. else if (m_storageMode == DCMSCU_STORAGE_BIT_PRESERVING)
  1080. {
  1081. OFString storageFilename;
  1082. OFStandard::combineDirAndFilename(storageFilename, m_storageDir, rsp.msg.CStoreRQ.AffectedSOPInstanceUID, OFTrue);
  1083. result = handleSTORERequestFile(&pcid, storageFilename, &(rsp.msg.CStoreRQ));
  1084. if (result.good())
  1085. {
  1086. notifyInstanceStored(storageFilename, rsp.msg.CStoreRQ.AffectedSOPClassUID, rsp.msg.CStoreRQ.AffectedSOPInstanceUID);
  1087. }
  1088. }
  1089. // handle ignore storage mode, i.e. ignore received dataset and do not store at all
  1090. else
  1091. {
  1092. result = ignoreSTORERequest(pcid, rsp.msg.CStoreRQ);
  1093. }
  1094. // Evaluate result from C-STORE request handling and send response
  1095. if (result.bad())
  1096. {
  1097. desiredCStoreReturnStatus = STATUS_STORE_Error_CannotUnderstand;
  1098. continueSession = OFFalse;
  1099. }
  1100. result = sendSTOREResponse(pcid, desiredCStoreReturnStatus, rsp.msg.CStoreRQ);
  1101. if (result.bad())
  1102. {
  1103. continueSession = OFFalse;
  1104. }
  1105. delete rspDataset; // should be NULL if not existing
  1106. }
  1107. // Handle other DIMSE command (error since other command than GET/STORE not expected)
  1108. else
  1109. {
  1110. DCMNET_ERROR("Expected C-GET response or C-STORE request but received DIMSE command 0x"
  1111. << STD_NAMESPACE hex << STD_NAMESPACE setfill('0') << STD_NAMESPACE setw(4)
  1112. << OFstatic_cast(unsigned int, rsp.CommandField));
  1113. DCMNET_DEBUG(DIMSE_dumpMessage(tempStr, rsp, DIMSE_INCOMING, NULL, pcid));
  1114. result = DIMSE_BADCOMMANDTYPE;
  1115. continueSession = OFFalse;
  1116. }
  1117. delete statusDetail; // should be NULL if not existing or added to response list
  1118. statusDetail = NULL;
  1119. }
  1120. /* All responses received or break signal occured */
  1121. return result;
  1122. }
  1123. // Handles single C-GET Response
  1124. OFCondition DcmSCU::handleCGETResponse(const T_ASC_PresentationContextID /* presID */,
  1125. RetrieveResponse* response,
  1126. OFBool& continueCGETSession)
  1127. {
  1128. // Do some basic validity checks
  1129. if (!isConnected())
  1130. return DIMSE_ILLEGALASSOCIATION;
  1131. if (response == NULL)
  1132. return DIMSE_NULLKEY;
  1133. DCMNET_DEBUG("Handling C-GET Response");
  1134. /* First, perform separate check for 0xCxxx error codes */
  1135. Uint16 highNibble = response->m_status & 0xf000;
  1136. if (highNibble == STATUS_GET_Failed_UnableToProcess)
  1137. {
  1138. continueCGETSession = OFFalse;
  1139. DCMNET_ERROR("Unable to Process");
  1140. DCMNET_WARN("Full status is 0x"
  1141. << STD_NAMESPACE hex << STD_NAMESPACE setfill('0') << STD_NAMESPACE setw(4)
  1142. << response->m_status);
  1143. return EC_Normal;
  1144. }
  1145. /* Check for other error codes */
  1146. switch (response->m_status) {
  1147. case STATUS_GET_Refused_OutOfResourcesNumberOfMatches:
  1148. continueCGETSession = OFFalse;
  1149. DCMNET_ERROR("Out of Resouces - Unable to calculate number of matches");
  1150. break;
  1151. case STATUS_GET_Refused_OutOfResourcesSubOperations:
  1152. continueCGETSession = OFFalse;
  1153. DCMNET_ERROR("Out of Resouces - Unable to perform sub-operations");
  1154. break;
  1155. case STATUS_GET_Failed_IdentifierDoesNotMatchSOPClass:
  1156. continueCGETSession = OFFalse;
  1157. DCMNET_ERROR("Identifier does not match SOP class");
  1158. break;
  1159. case STATUS_GET_Cancel_SubOperationsTerminatedDueToCancelIndication:
  1160. continueCGETSession = OFFalse;
  1161. DCMNET_DEBUG("Suboperations canceled by server due to CANCEL indication");
  1162. break;
  1163. case STATUS_GET_Warning_SubOperationsCompleteOneOrMoreFailures:
  1164. continueCGETSession = OFFalse;
  1165. DCMNET_WARN("Suboperations of C-GET completed with one or more failures");
  1166. break;
  1167. case STATUS_Pending:
  1168. /* in this case the current C-MOVE-RSP indicates that */
  1169. /* there will be some more results */
  1170. continueCGETSession = OFTrue;
  1171. DCMNET_DEBUG("One or more pending C-GET responses");
  1172. break;
  1173. case STATUS_Success:
  1174. /* in this case, we received the last C-MOVE-RSP so there */
  1175. /* will be no other responses we have to wait for. */
  1176. continueCGETSession = OFFalse;
  1177. DCMNET_DEBUG("Received final C-GET response, no more C-GET responses expected");
  1178. break;
  1179. default:
  1180. /* in all other cases, don't expect further responses to come */
  1181. continueCGETSession = OFFalse;
  1182. DCMNET_WARN("Status is 0x"
  1183. << STD_NAMESPACE hex << STD_NAMESPACE setfill('0') << STD_NAMESPACE setw(4)
  1184. << response->m_status << " (unknown)");
  1185. DCMNET_WARN("Will not wait for further C-GET responses");
  1186. break;
  1187. } //switch
  1188. return EC_Normal;
  1189. }
  1190. // Handles single C-STORE Request received during C-GET session
  1191. OFCondition DcmSCU::handleSTORERequest(const T_ASC_PresentationContextID /* presID */,
  1192. DcmDataset *incomingObject,
  1193. OFBool& /* continueCGETSession */,
  1194. Uint16& cStoreReturnStatus)
  1195. {
  1196. if (incomingObject == NULL)
  1197. return DIMSE_NULLKEY;
  1198. OFString sopClassUID;
  1199. OFString sopInstanceUID;
  1200. OFCondition result = incomingObject->findAndGetOFString(DCM_SOPClassUID, sopClassUID);
  1201. if (result.good())
  1202. result = incomingObject->findAndGetOFString(DCM_SOPInstanceUID, sopInstanceUID);
  1203. if (result.bad())
  1204. {
  1205. DCMNET_ERROR("Cannot store received object: either SOP Instance or SOP Class UID not present");
  1206. cStoreReturnStatus = STATUS_STORE_Error_DataSetDoesNotMatchSOPClass;
  1207. return EC_TagNotFound;
  1208. }
  1209. OFString filename = createStorageFilename(incomingObject);
  1210. result = incomingObject->saveFile(filename.c_str());
  1211. if (result.good())
  1212. {
  1213. E_TransferSyntax xferSyntax;
  1214. getDatasetInfo(incomingObject, sopClassUID, sopInstanceUID, xferSyntax);
  1215. notifyInstanceStored(filename, sopClassUID, sopInstanceUID);
  1216. cStoreReturnStatus = STATUS_Success;
  1217. }
  1218. else
  1219. {
  1220. cStoreReturnStatus = STATUS_STORE_Refused_OutOfResources;
  1221. }
  1222. return result;
  1223. }
  1224. OFCondition DcmSCU::handleSTORERequestFile(T_ASC_PresentationContextID *presID,
  1225. const OFString& filename,
  1226. T_DIMSE_C_StoreRQ* request)
  1227. {
  1228. if (filename.empty())
  1229. return EC_IllegalParameter;
  1230. /* in the following, we want to receive data over the network and write it to a file */
  1231. /* exactly the way it was received over the network. Hence, a filestream will be created and the data */
  1232. /* set will be received and written to the file through the call to DIMSE_receiveDataSetInFile(...).*/
  1233. /* create filestream */
  1234. DcmOutputFileStream *filestream = NULL;
  1235. OFCondition cond = DIMSE_createFilestream(filename.c_str(), request, m_assoc, *presID, OFTrue, &filestream);
  1236. if (cond.good())
  1237. {
  1238. if (m_progressNotificationMode)
  1239. {
  1240. cond = DIMSE_receiveDataSetInFile(m_assoc, m_blockMode, m_dimseTimeout, presID, filestream,
  1241. callbackRECEIVEProgress, this /*callbackData*/);
  1242. } else {
  1243. cond = DIMSE_receiveDataSetInFile(m_assoc, m_blockMode, m_dimseTimeout, presID, filestream,
  1244. NULL /*callback*/, NULL /*callbackData*/);
  1245. }
  1246. delete filestream;
  1247. if (cond != EC_Normal)
  1248. {
  1249. unlink(filename.c_str());
  1250. }
  1251. DCMNET_DEBUG("Received dataset on presentation context " << OFstatic_cast(unsigned int, *presID));
  1252. }
  1253. else
  1254. {
  1255. OFString tempStr;
  1256. DCMNET_ERROR("Unable to receive and store dataset on presentation context "
  1257. << OFstatic_cast(unsigned int, *presID) << ": " << DimseCondition::dump(tempStr, cond));
  1258. }
  1259. return cond;
  1260. }
  1261. OFCondition DcmSCU::sendSTOREResponse(T_ASC_PresentationContextID presID,
  1262. Uint16 status,
  1263. const T_DIMSE_C_StoreRQ& request)
  1264. {
  1265. // Send back response
  1266. T_DIMSE_Message response;
  1267. T_DIMSE_C_StoreRSP &storeRsp = response.msg.CStoreRSP;
  1268. response.CommandField = DIMSE_C_STORE_RSP;
  1269. storeRsp.MessageIDBeingRespondedTo = request.MessageID;
  1270. storeRsp.DimseStatus = status;
  1271. storeRsp.DataSetType = DIMSE_DATASET_NULL;
  1272. storeRsp.opts = 0;
  1273. /* Following information is optional and normally not sent by the underlying
  1274. * dcmnet routines. However, maybe this could be changed later, so insert it.
  1275. */
  1276. OFStandard::strlcpy(storeRsp.AffectedSOPClassUID, request.AffectedSOPClassUID, sizeof(storeRsp.AffectedSOPClassUID));
  1277. OFStandard::strlcpy(storeRsp.AffectedSOPInstanceUID, request.AffectedSOPInstanceUID, sizeof(storeRsp.AffectedSOPInstanceUID));
  1278. OFString tempStr;
  1279. if (DCM_dcmnetGetLogger().isEnabledFor(OFLogger::DEBUG_LOG_LEVEL))
  1280. {
  1281. DCMNET_INFO("Sending C-STORE Response");
  1282. DCMNET_DEBUG(DIMSE_dumpMessage(tempStr, response, DIMSE_OUTGOING, NULL, presID));
  1283. } else {
  1284. DCMNET_INFO("Sending C-STORE Response (" << DU_cstoreStatusString(status) << ")");
  1285. }
  1286. OFCondition cond = sendDIMSEMessage(presID, &response, NULL /*dataObject*/);
  1287. if (cond.bad())
  1288. {
  1289. DCMNET_ERROR("Failed sending C-STORE response: " << DimseCondition::dump(tempStr, cond));
  1290. }
  1291. return cond;
  1292. }
  1293. OFString DcmSCU::createStorageFilename(DcmDataset *dataset)
  1294. {
  1295. OFString sopClassUID, sopInstanceUID;
  1296. E_TransferSyntax dummy;
  1297. getDatasetInfo(dataset, sopClassUID, sopInstanceUID, dummy);
  1298. // Create unique filename
  1299. if (sopClassUID.empty() || sopInstanceUID.empty())
  1300. return "";
  1301. OFString name = dcmSOPClassUIDToModality(sopClassUID.c_str(), "UNKNOWN");
  1302. name += ".";
  1303. name += sopInstanceUID;
  1304. OFString returnStr;
  1305. OFStandard::combineDirAndFilename(returnStr, m_storageDir, name, OFTrue);
  1306. return returnStr;
  1307. }
  1308. OFCondition DcmSCU::ignoreSTORERequest(T_ASC_PresentationContextID presID,
  1309. const T_DIMSE_C_StoreRQ& request)
  1310. {
  1311. /* We cannot create the filestream, so ignore the incoming dataset and return an out-of-resources error to the SCU */
  1312. DIC_UL bytesRead = 0;
  1313. DIC_UL pdvCount=0;
  1314. DCMNET_DEBUG("Ignoring incoming C-STORE dataset on presentation context "
  1315. << OFstatic_cast(unsigned int, presID)
  1316. << " with Affected SOP Instance UID: " << request.AffectedSOPInstanceUID );
  1317. OFCondition result = DIMSE_ignoreDataSet(m_assoc, m_blockMode, m_dimseTimeout, &bytesRead, &pdvCount);
  1318. if (result.good())
  1319. {
  1320. DCMNET_TRACE("Successfully skipped " << bytesRead << " bytes in " << pdvCount << " PDVs");
  1321. }
  1322. return result;
  1323. }
  1324. void DcmSCU::notifyInstanceStored(const OFString& filename,
  1325. const OFString& sopClassUID,
  1326. const OFString& sopInstanceUID) const
  1327. {
  1328. DCMNET_DEBUG("Stored instance to disk:");
  1329. DCMNET_DEBUG(" Filename: " << filename);
  1330. DCMNET_DEBUG(" SOP Class UID: " << sopClassUID);
  1331. DCMNET_DEBUG(" SOP Instance UID: " << sopInstanceUID);
  1332. }
  1333. /* ************************************************************************* */
  1334. /* C-FIND functionality */
  1335. /* ************************************************************************* */
  1336. // Sends a C-FIND Request on given presentation context
  1337. OFCondition DcmSCU::sendFINDRequest(const T_ASC_PresentationContextID presID,
  1338. DcmDataset *queryKeys,
  1339. OFList<QRResponse*> *responses)
  1340. {
  1341. // Do some basic validity checks
  1342. if (!isConnected())
  1343. return DIMSE_ILLEGALASSOCIATION;
  1344. if (queryKeys == NULL)
  1345. return DIMSE_NULLKEY;
  1346. /* Prepare DIMSE data structures for issuing request */
  1347. OFCondition cond;
  1348. OFString tempStr;
  1349. T_ASC_PresentationContextID pcid = presID;
  1350. T_DIMSE_Message msg;
  1351. DcmDataset* statusDetail = NULL;
  1352. T_DIMSE_C_FindRQ* req = &(msg.msg.CFindRQ);
  1353. // Set type of message
  1354. msg.CommandField = DIMSE_C_FIND_RQ;
  1355. // Set message ID
  1356. req->MessageID = nextMessageID();
  1357. // Announce dataset
  1358. req->DataSetType = DIMSE_DATASET_PRESENT;
  1359. // Specify priority
  1360. req->Priority = DIMSE_PRIORITY_LOW;
  1361. // Determine SOP Class from presentation context
  1362. OFString abstractSyntax, transferSyntax;
  1363. findPresentationContext(pcid, abstractSyntax, transferSyntax);
  1364. if (abstractSyntax.empty() || transferSyntax.empty())
  1365. return DIMSE_NOVALIDPRESENTATIONCONTEXTID;
  1366. OFStandard::strlcpy(req->AffectedSOPClassUID, abstractSyntax.c_str(), sizeof(req->AffectedSOPClassUID));
  1367. /* Send request */
  1368. if (DCM_dcmnetGetLogger().isEnabledFor(OFLogger::DEBUG_LOG_LEVEL))
  1369. {
  1370. DCMNET_INFO("Sending C-FIND Request");
  1371. DCMNET_DEBUG(DIMSE_dumpMessage(tempStr, msg, DIMSE_OUTGOING, queryKeys, pcid));
  1372. } else {
  1373. DCMNET_INFO("Sending C-FIND Request (MsgID " << req->MessageID << ")");
  1374. }
  1375. cond = sendDIMSEMessage(pcid, &msg, queryKeys);
  1376. if (cond.bad())
  1377. {
  1378. DCMNET_ERROR("Failed sending C-FIND request: " << DimseCondition::dump(tempStr, cond));
  1379. return cond;
  1380. }
  1381. /* Receive and handle response */
  1382. OFBool waitForNextResponse = OFTrue;
  1383. while (waitForNextResponse)
  1384. {
  1385. T_DIMSE_Message rsp;
  1386. statusDetail = NULL;
  1387. // Receive command set
  1388. cond = receiveDIMSECommand(&pcid, &rsp, &statusDetail, NULL /* not interested in the command set */);
  1389. if (cond.bad())
  1390. {
  1391. DCMNET_ERROR("Failed receiving DIMSE response: " << DimseCondition::dump(tempStr, cond));
  1392. return cond;
  1393. }
  1394. if (rsp.CommandField == DIMSE_C_FIND_RSP)
  1395. {
  1396. if (DCM_dcmnetGetLogger().isEnabledFor(OFLogger::DEBUG_LOG_LEVEL))
  1397. {
  1398. DCMNET_INFO("Received C-FIND Response");
  1399. DCMNET_DEBUG(DIMSE_dumpMessage(tempStr, rsp, DIMSE_INCOMING, NULL, pcid));
  1400. } else {
  1401. DCMNET_INFO("Received C-FIND Response (" << DU_cfindStatusString(rsp.msg.CFindRSP.DimseStatus) << ")");
  1402. }
  1403. } else {
  1404. DCMNET_ERROR("Expected C-FIND response but received DIMSE command 0x"
  1405. << STD_NAMESPACE hex << STD_NAMESPACE setfill('0') << STD_NAMESPACE setw(4)
  1406. << OFstatic_cast(unsigned int, rsp.CommandField));
  1407. DCMNET_DEBUG(DIMSE_dumpMessage(tempStr, rsp, DIMSE_INCOMING, NULL, pcid));
  1408. delete statusDetail;
  1409. return DIMSE_BADCOMMANDTYPE;
  1410. }
  1411. // Prepare response package for response handler
  1412. QRResponse *findRSP = new QRResponse();
  1413. findRSP->m_affectedSOPClassUID = rsp.msg.CFindRSP.AffectedSOPClassUID;
  1414. findRSP->m_messageIDRespondedTo = rsp.msg.CFindRSP.MessageIDBeingRespondedTo;
  1415. findRSP->m_status = rsp.msg.CFindRSP.DimseStatus;
  1416. findRSP->m_statusDetail = statusDetail;
  1417. // Receive dataset if there is one (status PENDING)
  1418. DcmDataset *rspDataset = NULL;
  1419. if (DICOM_PENDING_STATUS(findRSP->m_status))
  1420. {
  1421. // Check if dataset is announced correctly
  1422. if (rsp.msg.CFindRSP.DataSetType == DIMSE_DATASET_NULL)
  1423. {
  1424. DCMNET_ERROR("Received C-FIND response with PENDING status but no dataset announced, aborting");
  1425. delete findRSP; // includes statusDetail
  1426. return DIMSE_BADMESSAGE;
  1427. }
  1428. // Receive dataset
  1429. cond = receiveDIMSEDataset(&pcid, &rspDataset);
  1430. if (cond.bad())
  1431. {
  1432. delete findRSP; // includes statusDetail
  1433. return DIMSE_BADDATA;
  1434. }
  1435. findRSP->m_dataset = rspDataset;
  1436. }
  1437. // Handle C-FIND response (has to handle all possible status flags)
  1438. cond = handleFINDResponse(pcid, findRSP, waitForNextResponse);
  1439. if (cond.bad())
  1440. {
  1441. DCMNET_WARN("Unable to handle C-FIND response correctly: " << cond.text() << " (ignored)");
  1442. delete findRSP; // includes statusDetail and rspDataset
  1443. // don't return here but trust the "waitForNextResponse" variable
  1444. }
  1445. // if response could be handled successfully, add it to response list
  1446. else
  1447. {
  1448. if (responses != NULL) // only add if desired by caller
  1449. responses->push_back(findRSP);
  1450. else
  1451. delete findRSP; // includes statusDetail and rspDataset
  1452. }
  1453. }
  1454. /* All responses received or break signal occured */
  1455. return EC_Normal;
  1456. }
  1457. // Standard handler for C-FIND message responses
  1458. OFCondition DcmSCU::handleFINDResponse(const T_ASC_PresentationContextID /* presID */,
  1459. QRResponse *response,
  1460. OFBool &waitForNextResponse)
  1461. {
  1462. if (!isConnected())
  1463. return DIMSE_ILLEGALASSOCIATION;
  1464. if (response == NULL)
  1465. return DIMSE_NULLKEY;
  1466. DCMNET_DEBUG("Handling C-FIND Response");
  1467. switch (response->m_status) {
  1468. case STATUS_Pending:
  1469. case STATUS_FIND_Pending_WarningUnsupportedOptionalKeys:
  1470. /* in this case the current C-FIND-RSP indicates that */
  1471. /* there will be some more results */
  1472. waitForNextResponse = OFTrue;
  1473. DCMNET_DEBUG("One or more pending C-FIND responses");
  1474. break;
  1475. case STATUS_Success:
  1476. /* in this case the current C-FIND-RSP indicates that */
  1477. /* there are no more records that match the search mask */
  1478. waitForNextResponse = OFFalse;
  1479. DCMNET_DEBUG("Received final C-FIND response, no more C-FIND responses expected");
  1480. break;
  1481. default:
  1482. /* in all other cases, don't expect further responses to come */
  1483. waitForNextResponse = OFFalse;
  1484. DCMNET_DEBUG("Status tells not to wait for further C-FIND responses");
  1485. break;
  1486. } //switch
  1487. return EC_Normal;
  1488. }
  1489. /* ************************************************************************* */
  1490. /* C-CANCEL functionality */
  1491. /* ************************************************************************* */
  1492. // Send C-CANCEL-REQ and, therefore, ends current C-FIND, -MOVE or -GET session
  1493. OFCondition DcmSCU::sendCANCELRequest(const T_ASC_PresentationContextID presID)
  1494. {
  1495. if (!isConnected())
  1496. return DIMSE_ILLEGALASSOCIATION;
  1497. /* Prepare DIMSE data structures for issuing request */
  1498. OFCondition cond;
  1499. OFString tempStr;
  1500. T_ASC_PresentationContextID pcid = presID;
  1501. T_DIMSE_Message msg;
  1502. T_DIMSE_C_CancelRQ* req = &(msg.msg.CCancelRQ);
  1503. // Set type of message
  1504. msg.CommandField = DIMSE_C_CANCEL_RQ;
  1505. /* Set message ID responded to. A new message ID is _not_ needed so
  1506. we do not increment the message ID here but instead have to give the
  1507. message ID that was used last.
  1508. Note that that it is required to actually use the message ID of the last
  1509. C-FIND/GET/MOVE that was issued on this presentation context channel.
  1510. However, since we only support synchronous association mode so far,
  1511. it is enough to take the last message ID used at all.
  1512. For asynchronous operation, we would have to lookup the message ID
  1513. of the last C-FIND/GET/MOVE request issued and thus, store this
  1514. information after sending it.
  1515. */
  1516. req->MessageIDBeingRespondedTo = m_assoc->nextMsgID - 1;
  1517. // Announce dataset
  1518. req->DataSetType = DIMSE_DATASET_NULL;
  1519. /* We do not care about the transfer syntax since no
  1520. dataset is transported at all, i.e. we trust that the user provided
  1521. the correct presentation context ID (could be private one).
  1522. */
  1523. if (DCM_dcmnetGetLogger().isEnabledFor(OFLogger::DEBUG_LOG_LEVEL))
  1524. {
  1525. DCMNET_INFO("Sending C-CANCEL Request");
  1526. DCMNET_DEBUG(DIMSE_dumpMessage(tempStr, msg, DIMSE_OUTGOING, NULL, pcid));
  1527. } else {
  1528. DCMNET_INFO("Sending C-CANCEL Request (MsgID " << req->MessageIDBeingRespondedTo
  1529. << ", PresID " << OFstatic_cast(unsigned int, pcid) << ")");
  1530. }
  1531. cond = sendDIMSEMessage(pcid, &msg, NULL /*dataObject*/);
  1532. if (cond.bad())
  1533. {
  1534. DCMNET_ERROR("Failed sending C-CANCEL request: " << DimseCondition::dump(tempStr, cond));
  1535. }
  1536. DCMNET_TRACE("There is no C-CANCEL response in DICOM, so none expected");
  1537. return cond;
  1538. }
  1539. /* ************************************************************************* */
  1540. /* N-ACTION functionality */
  1541. /* ************************************************************************* */
  1542. // Sends N-ACTION request to another DICOM application
  1543. OFCondition DcmSCU::sendACTIONRequest(const T_ASC_PresentationContextID presID,
  1544. const OFString &sopInstanceUID,
  1545. const Uint16 actionTypeID,
  1546. DcmDataset *reqDataset,
  1547. Uint16 &rspStatusCode)
  1548. {
  1549. // Do some basic validity checks
  1550. if (!isConnected())
  1551. return DIMSE_ILLEGALASSOCIATION;
  1552. if (sopInstanceUID.empty() || (reqDataset == NULL))
  1553. return DIMSE_NULLKEY;
  1554. // Prepare DIMSE data structures for issuing request
  1555. OFCondition cond;
  1556. OFString tempStr;
  1557. T_ASC_PresentationContextID pcid = presID;
  1558. T_DIMSE_Message request;
  1559. T_DIMSE_N_ActionRQ &actionReq = request.msg.NActionRQ;
  1560. DcmDataset *statusDetail = NULL;
  1561. request.CommandField = DIMSE_N_ACTION_RQ;
  1562. actionReq.MessageID = nextMessageID();
  1563. actionReq.DataSetType = DIMSE_DATASET_PRESENT;
  1564. actionReq.ActionTypeID = actionTypeID;
  1565. // Determine SOP Class from presentation context
  1566. OFString abstractSyntax, transferSyntax;
  1567. findPresentationContext(pcid, abstractSyntax, transferSyntax);
  1568. if (abstractSyntax.empty() || transferSyntax.empty())
  1569. return DIMSE_NOVALIDPRESENTATIONCONTEXTID;
  1570. OFStandard::strlcpy(actionReq.RequestedSOPClassUID, abstractSyntax.c_str(), sizeof(actionReq.RequestedSOPClassUID));
  1571. OFStandard::strlcpy(actionReq.RequestedSOPInstanceUID, sopInstanceUID.c_str(), sizeof(actionReq.RequestedSOPInstanceUID));
  1572. // Send request
  1573. if (DCM_dcmnetGetLogger().isEnabledFor(OFLogger::DEBUG_LOG_LEVEL))
  1574. {
  1575. DCMNET_INFO("Sending N-ACTION Request");
  1576. // Output dataset only if trace level is enabled
  1577. if (DCM_dcmnetGetLogger().isEnabledFor(OFLogger::TRACE_LOG_LEVEL))
  1578. DCMNET_DEBUG(DIMSE_dumpMessage(tempStr, request, DIMSE_OUTGOING, reqDataset, pcid));
  1579. else
  1580. DCMNET_DEBUG(DIMSE_dumpMessage(tempStr, request, DIMSE_OUTGOING, NULL, pcid));
  1581. } else {
  1582. DCMNET_INFO("Sending N-ACTION Request (MsgID " << actionReq.MessageID << ")");
  1583. }
  1584. cond = sendDIMSEMessage(pcid, &request, reqDataset);
  1585. if (cond.bad())
  1586. {
  1587. DCMNET_ERROR("Failed sending N-ACTION request: " << DimseCondition::dump(tempStr, cond));
  1588. return cond;
  1589. }
  1590. // Receive response
  1591. T_DIMSE_Message response;
  1592. cond = receiveDIMSECommand(&pcid, &response, &statusDetail, NULL /* commandSet */);
  1593. if (cond.bad())
  1594. {
  1595. DCMNET_ERROR("Failed receiving DIMSE response: " << DimseCondition::dump(tempStr, cond));
  1596. return cond;
  1597. }
  1598. // Check command set
  1599. if (response.CommandField == DIMSE_N_ACTION_RSP)
  1600. {
  1601. if (DCM_dcmnetGetLogger().isEnabledFor(OFLogger::DEBUG_LOG_LEVEL))
  1602. {
  1603. DCMNET_INFO("Received N-ACTION Response");
  1604. DCMNET_DEBUG(DIMSE_dumpMessage(tempStr, response, DIMSE_INCOMING, NULL, pcid));
  1605. } else {
  1606. DCMNET_INFO("Received N-ACTION Response (" << DU_nactionStatusString(response.msg.NActionRSP.DimseStatus) << ")");
  1607. }
  1608. } else {
  1609. DCMNET_ERROR("Expected N-ACTION response but received DIMSE command 0x"
  1610. << STD_NAMESPACE hex << STD_NAMESPACE setfill('0') << STD_NAMESPACE setw(4)
  1611. << OFstatic_cast(unsigned int, response.CommandField));
  1612. DCMNET_DEBUG(DIMSE_dumpMessage(tempStr, response, DIMSE_INCOMING, NULL, pcid));
  1613. delete statusDetail;
  1614. return DIMSE_BADCOMMANDTYPE;
  1615. }
  1616. if (statusDetail != NULL)
  1617. {
  1618. DCMNET_DEBUG("Response has status detail:" << OFendl << DcmObject::PrintHelper(*statusDetail));
  1619. delete statusDetail;
  1620. }
  1621. // Set return value
  1622. T_DIMSE_N_ActionRSP &actionRsp = response.msg.NActionRSP;
  1623. rspStatusCode = actionRsp.DimseStatus;
  1624. // Check whether there is a dataset to be received
  1625. if (actionRsp.DataSetType == DIMSE_DATASET_PRESENT)
  1626. {
  1627. // this should never happen
  1628. DcmDataset *tempDataset = NULL;
  1629. T_ASC_PresentationContextID tempID;
  1630. DCMNET_WARN("Trying to retrieve unexpected dataset in N-ACTION response");
  1631. cond = receiveDIMSEDataset(&tempID, &tempDataset);
  1632. if (cond.good())
  1633. {
  1634. DCMNET_WARN("Received unexpected dataset after N-ACTION response, ignoring");
  1635. delete tempDataset;
  1636. } else {
  1637. return DIMSE_BADDATA;
  1638. }
  1639. }
  1640. if (actionRsp.MessageIDBeingRespondedTo != actionReq.MessageID)
  1641. {
  1642. // since we only support synchronous communication, the message ID in the response
  1643. // should be identical to the one in the request
  1644. DCMNET_ERROR("Received response with wrong message ID (" << actionRsp.MessageIDBeingRespondedTo
  1645. << " instead of " << actionReq.MessageID << ")");
  1646. return DIMSE_BADMESSAGE;
  1647. }
  1648. return cond;
  1649. }
  1650. /* ************************************************************************* */
  1651. /* N-EVENT REPORT functionality */
  1652. /* ************************************************************************* */
  1653. // Sends N-EVENT-REPORT request and receives N-EVENT-REPORT response
  1654. OFCondition DcmSCU::sendEVENTREPORTRequest(const T_ASC_PresentationContextID presID,
  1655. const OFString &sopInstanceUID,
  1656. const Uint16 eventTypeID,
  1657. DcmDataset *reqDataset,
  1658. Uint16 &rspStatusCode)
  1659. {
  1660. // Do some basic validity checks
  1661. if (!isConnected())
  1662. return DIMSE_ILLEGALASSOCIATION;
  1663. if (sopInstanceUID.empty() || (reqDataset == NULL))
  1664. return DIMSE_NULLKEY;
  1665. // Prepare DIMSE data structures for issuing request
  1666. OFCondition cond;
  1667. OFString tempStr;
  1668. T_ASC_PresentationContextID pcid = presID;
  1669. T_DIMSE_Message request;
  1670. T_DIMSE_N_EventReportRQ &eventReportReq = request.msg.NEventReportRQ;
  1671. DcmDataset *statusDetail = NULL;
  1672. request.CommandField = DIMSE_N_EVENT_REPORT_RQ;
  1673. // Generate a new message ID
  1674. eventReportReq.MessageID = nextMessageID();
  1675. eventReportReq.DataSetType = DIMSE_DATASET_PRESENT;
  1676. eventReportReq.EventTypeID = eventTypeID;
  1677. // Determine SOP Class from presentation context
  1678. OFString abstractSyntax, transferSyntax;
  1679. findPresentationContext(pcid, abstractSyntax, transferSyntax);
  1680. if (abstractSyntax.empty() || transferSyntax.empty())
  1681. return DIMSE_NOVALIDPRESENTATIONCONTEXTID;
  1682. OFStandard::strlcpy(eventReportReq.AffectedSOPClassUID, abstractSyntax.c_str(), sizeof(eventReportReq.AffectedSOPClassUID));
  1683. OFStandard::strlcpy(eventReportReq.AffectedSOPInstanceUID, sopInstanceUID.c_str(), sizeof(eventReportReq.AffectedSOPInstanceUID));
  1684. // Send request
  1685. if (DCM_dcmnetGetLogger().isEnabledFor(OFLogger::DEBUG_LOG_LEVEL))
  1686. {
  1687. DCMNET_INFO("Sending N-EVENT-REPORT Request");
  1688. // Output dataset only if trace level is enabled
  1689. if (DCM_dcmnetGetLogger().isEnabledFor(OFLogger::TRACE_LOG_LEVEL))
  1690. DCMNET_DEBUG(DIMSE_dumpMessage(tempStr, request, DIMSE_OUTGOING, reqDataset, pcid));
  1691. else
  1692. DCMNET_DEBUG(DIMSE_dumpMessage(tempStr, request, DIMSE_OUTGOING, NULL, pcid));
  1693. } else {
  1694. DCMNET_INFO("Sending N-EVENT-REPORT Request (MsgID " << eventReportReq.MessageID << ")");
  1695. }
  1696. cond = sendDIMSEMessage(pcid, &request, reqDataset);
  1697. if (cond.bad())
  1698. {
  1699. DCMNET_ERROR("Failed sending N-EVENT-REPORT request: " << DimseCondition::dump(tempStr, cond));
  1700. return cond;
  1701. }
  1702. // Receive response
  1703. T_DIMSE_Message response;
  1704. cond = receiveDIMSECommand(&pcid, &response, &statusDetail, NULL /* commandSet */);
  1705. if (cond.bad())
  1706. {
  1707. DCMNET_ERROR("Failed receiving DIMSE response: " << DimseCondition::dump(tempStr, cond));
  1708. return cond;
  1709. }
  1710. // Check command set
  1711. if (response.CommandField == DIMSE_N_EVENT_REPORT_RSP)
  1712. {
  1713. if (DCM_dcmnetGetLogger().isEnabledFor(OFLogger::DEBUG_LOG_LEVEL))
  1714. {
  1715. DCMNET_INFO("Received N-EVENT-REPORT Response");
  1716. DCMNET_DEBUG(DIMSE_dumpMessage(tempStr, response, DIMSE_INCOMING, NULL, pcid));
  1717. } else {
  1718. DCMNET_INFO("Received N-EVENT-REPORT Response (" << DU_neventReportStatusString(response.msg.NEventReportRSP.DimseStatus) << ")");
  1719. }
  1720. } else {
  1721. DCMNET_ERROR("Expected N-EVENT-REPORT response but received DIMSE command 0x"
  1722. << STD_NAMESPACE hex << STD_NAMESPACE setfill('0') << STD_NAMESPACE setw(4)
  1723. << OFstatic_cast(unsigned int, response.CommandField));
  1724. DCMNET_DEBUG(DIMSE_dumpMessage(tempStr, response, DIMSE_INCOMING, NULL, pcid));
  1725. delete statusDetail;
  1726. return DIMSE_BADCOMMANDTYPE;
  1727. }
  1728. if (statusDetail != NULL)
  1729. {
  1730. DCMNET_DEBUG("Response has status detail:" << OFendl << DcmObject::PrintHelper(*statusDetail));
  1731. delete statusDetail;
  1732. }
  1733. // Set return value
  1734. T_DIMSE_N_EventReportRSP &eventReportRsp = response.msg.NEventReportRSP;
  1735. rspStatusCode = eventReportRsp.DimseStatus;
  1736. // Check whether there is a dataset to be received
  1737. if (eventReportRsp.DataSetType == DIMSE_DATASET_PRESENT)
  1738. {
  1739. // this should never happen
  1740. DcmDataset *tempDataset = NULL;
  1741. T_ASC_PresentationContextID tempID;
  1742. cond = receiveDIMSEDataset(&tempID, &tempDataset);
  1743. if (cond.good())
  1744. {
  1745. DCMNET_WARN("Received unexpected dataset after N-EVENT-REPORT response, ignoring");
  1746. delete tempDataset;
  1747. } else {
  1748. DCMNET_ERROR("Failed receiving unexpected dataset after N-EVENT-REPORT response: "
  1749. << DimseCondition::dump(tempStr, cond));
  1750. return DIMSE_BADDATA;
  1751. }
  1752. }
  1753. // Check whether the message ID being responded to is equal to the message ID of the request
  1754. if (eventReportRsp.MessageIDBeingRespondedTo != eventReportReq.MessageID)
  1755. {
  1756. DCMNET_ERROR("Received response with wrong message ID (" << eventReportRsp.MessageIDBeingRespondedTo
  1757. << " instead of " << eventReportReq.MessageID << ")");
  1758. return DIMSE_BADMESSAGE;
  1759. }
  1760. return cond;
  1761. }
  1762. // Receives N-EVENT-REPORT request
  1763. OFCondition DcmSCU::handleEVENTREPORTRequest(DcmDataset *&reqDataset,
  1764. Uint16 &eventTypeID,
  1765. const int timeout)
  1766. {
  1767. // Do some basic validity checks
  1768. if (!isConnected())
  1769. return DIMSE_ILLEGALASSOCIATION;
  1770. OFCondition cond;
  1771. OFString tempStr;
  1772. T_ASC_PresentationContextID presID;
  1773. T_ASC_PresentationContextID presIDdset;
  1774. T_DIMSE_Message request;
  1775. T_DIMSE_N_EventReportRQ &eventReportReq = request.msg.NEventReportRQ;
  1776. DcmDataset *dataset = NULL;
  1777. DcmDataset *statusDetail = NULL;
  1778. Uint16 statusCode = 0;
  1779. if (timeout > 0)
  1780. DCMNET_DEBUG("Handle N-EVENT-REPORT request, waiting up to " << timeout << " seconds (only for N-EVENT-REPORT message)");
  1781. else if ((m_dimseTimeout > 0) && (m_blockMode == DIMSE_NONBLOCKING))
  1782. DCMNET_DEBUG("Handle N-EVENT-REPORT request, waiting up to " << m_dimseTimeout << " seconds (default for all DIMSE messages)");
  1783. else
  1784. DCMNET_DEBUG("Handle N-EVENT-REPORT request, waiting an unlimited period of time");
  1785. // Receive request, use specific timeout (if defined)
  1786. cond = receiveDIMSECommand(&presID, &request, &statusDetail, NULL /* commandSet */, timeout);
  1787. if (cond.bad())
  1788. {
  1789. if (cond != DIMSE_NODATAAVAILABLE)
  1790. DCMNET_ERROR("Failed receiving DIMSE request: " << DimseCondition::dump(tempStr, cond));
  1791. return cond;
  1792. }
  1793. // Check command set
  1794. if (request.CommandField == DIMSE_N_EVENT_REPORT_RQ)
  1795. {
  1796. if (DCM_dcmnetGetLogger().isEnabledFor(OFLogger::DEBUG_LOG_LEVEL))
  1797. DCMNET_INFO("Received N-EVENT-REPORT Request");
  1798. else
  1799. DCMNET_INFO("Received N-EVENT-REPORT Request (MsgID " << eventReportReq.MessageID << ")");
  1800. } else {
  1801. DCMNET_DEBUG(DIMSE_dumpMessage(tempStr, request, DIMSE_INCOMING, NULL, presID));
  1802. DCMNET_ERROR("Expected N-EVENT-REPORT request but received DIMSE command 0x"
  1803. << STD_NAMESPACE hex << STD_NAMESPACE setfill('0') << STD_NAMESPACE setw(4)
  1804. << OFstatic_cast(unsigned int, request.CommandField));
  1805. delete statusDetail;
  1806. return DIMSE_BADCOMMANDTYPE;
  1807. }
  1808. if (statusDetail != NULL)
  1809. {
  1810. DCMNET_DEBUG("Request has status detail:" << OFendl << DcmObject::PrintHelper(*statusDetail));
  1811. delete statusDetail;
  1812. }
  1813. // Check if dataset is announced correctly
  1814. if (eventReportReq.DataSetType == DIMSE_DATASET_NULL)
  1815. {
  1816. DCMNET_DEBUG(DIMSE_dumpMessage(tempStr, request, DIMSE_INCOMING, NULL, presID));
  1817. DCMNET_ERROR("Received N-EVENT-REPORT request but no dataset announced, aborting");
  1818. return DIMSE_BADMESSAGE;
  1819. }
  1820. // Receive dataset
  1821. cond = receiveDIMSEDataset(&presIDdset, &dataset);
  1822. if (cond.bad())
  1823. {
  1824. DCMNET_DEBUG(DIMSE_dumpMessage(tempStr, request, DIMSE_INCOMING, NULL, presID));
  1825. return DIMSE_BADDATA;
  1826. }
  1827. // Output dataset only if trace level is enabled
  1828. if (DCM_dcmnetGetLogger().isEnabledFor(OFLogger::TRACE_LOG_LEVEL))
  1829. DCMNET_DEBUG(DIMSE_dumpMessage(tempStr, request, DIMSE_INCOMING, dataset, presID));
  1830. else
  1831. DCMNET_DEBUG(DIMSE_dumpMessage(tempStr, request, DIMSE_INCOMING, NULL, presID));
  1832. // Compare presentation context ID of command and data set
  1833. if (presIDdset != presID)
  1834. {
  1835. DCMNET_ERROR("Presentation Context ID of command (" << OFstatic_cast(unsigned int, presID)
  1836. << ") and data set (" << OFstatic_cast(unsigned int, presIDdset) << ") differ");
  1837. delete dataset;
  1838. return makeDcmnetCondition(DIMSEC_INVALIDPRESENTATIONCONTEXTID, OF_error,
  1839. "DIMSE: Presentation Contexts of Command and Data Set differ");
  1840. }
  1841. // Check the request dataset and return the DIMSE status code to be used
  1842. statusCode = checkEVENTREPORTRequest(eventReportReq, dataset);
  1843. // Send back response
  1844. T_DIMSE_Message response;
  1845. T_DIMSE_N_EventReportRSP &eventReportRsp = response.msg.NEventReportRSP;
  1846. response.CommandField = DIMSE_N_EVENT_REPORT_RSP;
  1847. eventReportRsp.MessageIDBeingRespondedTo = eventReportReq.MessageID;
  1848. eventReportRsp.DimseStatus = statusCode;
  1849. eventReportRsp.DataSetType = DIMSE_DATASET_NULL;
  1850. eventReportRsp.opts = 0;
  1851. eventReportRsp.AffectedSOPClassUID[0] = 0;
  1852. eventReportRsp.AffectedSOPInstanceUID[0] = 0;
  1853. if (DCM_dcmnetGetLogger().isEnabledFor(OFLogger::DEBUG_LOG_LEVEL))
  1854. {
  1855. DCMNET_INFO("Sending N-EVENT-REPORT Response");
  1856. DCMNET_DEBUG(DIMSE_dumpMessage(tempStr, response, DIMSE_OUTGOING, NULL, presID));
  1857. } else {
  1858. DCMNET_INFO("Sending N-EVENT-REPORT Response (" << DU_neventReportStatusString(statusCode) << ")");
  1859. }
  1860. cond = sendDIMSEMessage(presID, &response, NULL /*dataObject*/);
  1861. if (cond.bad())
  1862. {
  1863. DCMNET_ERROR("Failed sending N-EVENT-REPORT response: " << DimseCondition::dump(tempStr, cond));
  1864. delete dataset;
  1865. return cond;
  1866. }
  1867. // Set return values
  1868. reqDataset = dataset;
  1869. eventTypeID = eventReportReq.EventTypeID;
  1870. return cond;
  1871. }
  1872. Uint16 DcmSCU::checkEVENTREPORTRequest(T_DIMSE_N_EventReportRQ & /*eventReportReq*/,
  1873. DcmDataset * /*reqDataset*/)
  1874. {
  1875. // we default to success
  1876. return STATUS_Success;
  1877. }
  1878. /* ************************************************************************* */
  1879. /* General message handling */
  1880. /* ************************************************************************* */
  1881. void DcmSCU::notifySENDProgress(const unsigned long byteCount)
  1882. {
  1883. DCMNET_TRACE("Bytes sent: " << byteCount);
  1884. }
  1885. void DcmSCU::notifyRECEIVEProgress(const unsigned long byteCount)
  1886. {
  1887. DCMNET_TRACE("Bytes received: " << byteCount);
  1888. }
  1889. /* ************************************************************************* */
  1890. /* Various helpers */
  1891. /* ************************************************************************* */
  1892. // Sends a DIMSE command and possibly also instance data to the configured peer DICOM application
  1893. OFCondition DcmSCU::sendDIMSEMessage(const T_ASC_PresentationContextID presID,
  1894. T_DIMSE_Message *msg,
  1895. DcmDataset *dataObject,
  1896. DcmDataset **commandSet)
  1897. {
  1898. if (!isConnected())
  1899. return DIMSE_ILLEGALASSOCIATION;
  1900. if (msg == NULL)
  1901. return DIMSE_NULLKEY;
  1902. OFCondition cond;
  1903. /* call the corresponding DIMSE function to send the message */
  1904. if (m_progressNotificationMode)
  1905. {
  1906. cond = DIMSE_sendMessageUsingMemoryData(m_assoc, presID, msg, NULL /*statusDetail*/, dataObject,
  1907. callbackSENDProgress, this /*callbackData*/, commandSet);
  1908. } else {
  1909. cond = DIMSE_sendMessageUsingMemoryData(m_assoc, presID, msg, NULL /*statusDetail*/, dataObject,
  1910. NULL /*callback*/, NULL /*callbackData*/, commandSet);
  1911. }
  1912. #if 0
  1913. // currently disabled because it is not (yet) needed
  1914. if (cond.good())
  1915. {
  1916. /* create a copy of the current DIMSE command message */
  1917. delete m_openDIMSERequest;
  1918. m_openDIMSERequest = new T_DIMSE_Message;
  1919. memcpy((char*)m_openDIMSERequest, msg, sizeof(*m_openDIMSERequest));
  1920. }
  1921. #endif
  1922. return cond;
  1923. }
  1924. // Receive DIMSE command (excluding dataset!) over the currently open association
  1925. OFCondition DcmSCU::receiveDIMSECommand(T_ASC_PresentationContextID *presID,
  1926. T_DIMSE_Message *msg,
  1927. DcmDataset **statusDetail,
  1928. DcmDataset **commandSet,
  1929. const Uint32 timeout)
  1930. {
  1931. if (!isConnected())
  1932. return DIMSE_ILLEGALASSOCIATION;
  1933. OFCondition cond;
  1934. if (timeout > 0)
  1935. {
  1936. /* call the corresponding DIMSE function to receive the command (use specified timeout)*/
  1937. cond = DIMSE_receiveCommand(m_assoc, DIMSE_NONBLOCKING, timeout, presID,
  1938. msg, statusDetail, commandSet);
  1939. } else {
  1940. /* call the corresponding DIMSE function to receive the command (use default timeout) */
  1941. cond = DIMSE_receiveCommand(m_assoc, m_blockMode, m_dimseTimeout, presID,
  1942. msg, statusDetail, commandSet);
  1943. }
  1944. return cond;
  1945. }
  1946. // Receives one dataset (of instance data) via network from another DICOM application
  1947. OFCondition DcmSCU::receiveDIMSEDataset(T_ASC_PresentationContextID *presID,
  1948. DcmDataset **dataObject)
  1949. {
  1950. if (!isConnected())
  1951. return DIMSE_ILLEGALASSOCIATION;
  1952. OFCondition cond;
  1953. /* call the corresponding DIMSE function to receive the dataset */
  1954. if (m_progressNotificationMode)
  1955. {
  1956. cond = DIMSE_receiveDataSetInMemory(m_assoc, m_blockMode, m_dimseTimeout, presID, dataObject,
  1957. callbackRECEIVEProgress, this /*callbackData*/);
  1958. } else {
  1959. cond = DIMSE_receiveDataSetInMemory(m_assoc, m_blockMode, m_dimseTimeout, presID, dataObject,
  1960. NULL /*callback*/, NULL /*callbackData*/);
  1961. }
  1962. if (cond.good())
  1963. {
  1964. DCMNET_DEBUG("Received dataset on presentation context " << OFstatic_cast(unsigned int, *presID));
  1965. }
  1966. else
  1967. {
  1968. OFString tempStr;
  1969. DCMNET_ERROR("Unable to receive dataset on presentation context "
  1970. << OFstatic_cast(unsigned int, *presID) << ": " << DimseCondition::dump(tempStr, cond));
  1971. }
  1972. return cond;
  1973. }
  1974. void DcmSCU::setMaxReceivePDULength(const unsigned long maxRecPDU)
  1975. {
  1976. m_maxReceivePDULength = maxRecPDU;
  1977. }
  1978. void DcmSCU::setDIMSEBlockingMode(const T_DIMSE_BlockingMode blockingMode)
  1979. {
  1980. m_blockMode = blockingMode;
  1981. }
  1982. void DcmSCU::setAETitle(const OFString &myAETtitle)
  1983. {
  1984. m_ourAETitle = myAETtitle;
  1985. }
  1986. void DcmSCU::setPeerHostName(const OFString &peerHostName)
  1987. {
  1988. m_peer = peerHostName;
  1989. }
  1990. void DcmSCU::setPeerAETitle(const OFString &peerAETitle)
  1991. {
  1992. m_peerAETitle = peerAETitle;
  1993. }
  1994. void DcmSCU::setPeerPort(const Uint16 peerPort)
  1995. {
  1996. m_peerPort = peerPort;
  1997. }
  1998. void DcmSCU::setDIMSETimeout(const Uint32 dimseTimeout)
  1999. {
  2000. m_dimseTimeout = dimseTimeout;
  2001. }
  2002. void DcmSCU::setACSETimeout(const Uint32 acseTimeout)
  2003. {
  2004. m_acseTimeout = acseTimeout;
  2005. }
  2006. void DcmSCU::setAssocConfigFileAndProfile(const OFString &filename,
  2007. const OFString &profile)
  2008. {
  2009. m_assocConfigFilename = filename;
  2010. m_assocConfigProfile = profile;
  2011. }
  2012. void DcmSCU::setStorageDir(const OFString& storeDir)
  2013. {
  2014. m_storageDir = storeDir;
  2015. }
  2016. void DcmSCU::setStorageMode(const DcmStorageMode storageMode)
  2017. {
  2018. m_storageMode = storageMode;
  2019. }
  2020. void DcmSCU::setVerbosePCMode(const OFBool mode)
  2021. {
  2022. m_verbosePCMode = mode;
  2023. }
  2024. void DcmSCU::setDatasetConversionMode(const OFBool mode)
  2025. {
  2026. m_datasetConversionMode = mode;
  2027. }
  2028. void DcmSCU::setProgressNotificationMode(const OFBool mode)
  2029. {
  2030. m_progressNotificationMode = mode;
  2031. }
  2032. /* Get methods */
  2033. OFBool DcmSCU::isConnected() const
  2034. {
  2035. return (m_assoc != NULL) && (m_assoc->DULassociation != NULL);
  2036. }
  2037. Uint32 DcmSCU::getMaxReceivePDULength() const
  2038. {
  2039. return m_maxReceivePDULength;
  2040. }
  2041. OFBool DcmSCU::getTLSEnabled() const
  2042. {
  2043. return OFFalse;
  2044. }
  2045. T_DIMSE_BlockingMode DcmSCU::getDIMSEBlockingMode() const
  2046. {
  2047. return m_blockMode;
  2048. }
  2049. const OFString &DcmSCU::getAETitle() const
  2050. {
  2051. return m_ourAETitle;
  2052. }
  2053. const OFString &DcmSCU::getPeerHostName() const
  2054. {
  2055. return m_peer;
  2056. }
  2057. const OFString &DcmSCU::getPeerAETitle() const
  2058. {
  2059. return m_peerAETitle;
  2060. }
  2061. Uint16 DcmSCU::getPeerPort() const
  2062. {
  2063. return m_peerPort;
  2064. }
  2065. Uint32 DcmSCU::getDIMSETimeout() const
  2066. {
  2067. return m_dimseTimeout;
  2068. }
  2069. Uint32 DcmSCU::getACSETimeout() const
  2070. {
  2071. return m_acseTimeout;
  2072. }
  2073. OFString DcmSCU::getStorageDir() const
  2074. {
  2075. return m_storageDir;
  2076. }
  2077. DcmStorageMode DcmSCU::getStorageMode() const
  2078. {
  2079. return m_storageMode;
  2080. }
  2081. OFBool DcmSCU::getVerbosePCMode() const
  2082. {
  2083. return m_verbosePCMode;
  2084. }
  2085. OFBool DcmSCU::getDatasetConversionMode() const
  2086. {
  2087. return m_datasetConversionMode;
  2088. }
  2089. OFBool DcmSCU::getProgressNotificationMode() const
  2090. {
  2091. return m_progressNotificationMode;
  2092. }
  2093. OFCondition DcmSCU::getDatasetInfo(DcmDataset *dataset,
  2094. OFString &sopClassUID,
  2095. OFString &sopInstanceUID,
  2096. E_TransferSyntax &transferSyntax)
  2097. {
  2098. OFCondition status = EC_IllegalParameter;
  2099. sopClassUID.clear();
  2100. sopInstanceUID.clear();
  2101. transferSyntax = EXS_Unknown;
  2102. if (dataset != NULL)
  2103. {
  2104. // ignore returned condition codes (e.g. EC_TagNotFound)
  2105. dataset->findAndGetOFString(DCM_SOPClassUID, sopClassUID);
  2106. dataset->findAndGetOFString(DCM_SOPInstanceUID, sopInstanceUID);
  2107. transferSyntax = dataset->getOriginalXfer();
  2108. // check return values for validity
  2109. if (sopClassUID.empty())
  2110. status = NET_EC_InvalidSOPClassUID;
  2111. else if (sopInstanceUID.empty())
  2112. status = NET_EC_InvalidSOPInstanceUID;
  2113. else if (transferSyntax == EXS_Unknown)
  2114. status = NET_EC_UnknownTransferSyntax;
  2115. else
  2116. status = EC_Normal;
  2117. }
  2118. return status;
  2119. }
  2120. /* ************************************************************************* */
  2121. /* Callback functions */
  2122. /* ************************************************************************* */
  2123. void DcmSCU::callbackSENDProgress(void *callbackContext,
  2124. const unsigned long byteCount)
  2125. {
  2126. if (callbackContext != NULL)
  2127. OFreinterpret_cast(DcmSCU *, callbackContext)->notifySENDProgress(byteCount);
  2128. }
  2129. void DcmSCU::callbackRECEIVEProgress(void *callbackContext,
  2130. const unsigned long byteCount)
  2131. {
  2132. if (callbackContext != NULL)
  2133. OFreinterpret_cast(DcmSCU *, callbackContext)->notifyRECEIVEProgress(byteCount);
  2134. }
  2135. /* ************************************************************************* */
  2136. /* class RetrieveResponse */
  2137. /* ************************************************************************* */
  2138. void RetrieveResponse::print()
  2139. {
  2140. DCMNET_INFO(" Number of Remaining Suboperations : " << m_numberOfRemainingSubops);
  2141. DCMNET_INFO(" Number of Completed Suboperations : " << m_numberOfCompletedSubops);
  2142. DCMNET_INFO(" Number of Failed Suboperations : " << m_numberOfFailedSubops);
  2143. DCMNET_INFO(" Number of Warning Suboperations : " << m_numberOfWarningSubops);
  2144. }