ctkDcmSCU.cc 94 KB

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