comparison Core/DicomNetworking/Internals/StoreScp.cpp @ 2382:7284093111b0

big reorganization to cleanly separate framework vs. server
author Sebastien Jodogne <s.jodogne@gmail.com>
date Tue, 29 Aug 2017 21:17:35 +0200
parents OrthancServer/Internals/StoreScp.cpp@b8969010b534
children e4045b3c9772
comparison
equal deleted inserted replaced
2381:b8969010b534 2382:7284093111b0
1 /**
2 * Orthanc - A Lightweight, RESTful DICOM Store
3 * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
4 * Department, University Hospital of Liege, Belgium
5 * Copyright (C) 2017 Osimis, Belgium
6 *
7 * This program is free software: you can redistribute it and/or
8 * modify it under the terms of the GNU General Public License as
9 * published by the Free Software Foundation, either version 3 of the
10 * License, or (at your option) any later version.
11 *
12 * In addition, as a special exception, the copyright holders of this
13 * program give permission to link the code of its release with the
14 * OpenSSL project's "OpenSSL" library (or with modified versions of it
15 * that use the same license as the "OpenSSL" library), and distribute
16 * the linked executables. You must obey the GNU General Public License
17 * in all respects for all of the code used other than "OpenSSL". If you
18 * modify file(s) with this exception, you may extend this exception to
19 * your version of the file(s), but you are not obligated to do so. If
20 * you do not wish to do so, delete this exception statement from your
21 * version. If you delete this exception statement from all source files
22 * in the program, then also delete it here.
23 *
24 * This program is distributed in the hope that it will be useful, but
25 * WITHOUT ANY WARRANTY; without even the implied warranty of
26 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
27 * General Public License for more details.
28 *
29 * You should have received a copy of the GNU General Public License
30 * along with this program. If not, see <http://www.gnu.org/licenses/>.
31 **/
32
33
34
35
36 /*=========================================================================
37
38 This file is based on portions of the following project:
39
40 Program: DCMTK 3.6.0
41 Module: http://dicom.offis.de/dcmtk.php.en
42
43 Copyright (C) 1994-2011, OFFIS e.V.
44 All rights reserved.
45
46 This software and supporting documentation were developed by
47
48 OFFIS e.V.
49 R&D Division Health
50 Escherweg 2
51 26121 Oldenburg, Germany
52
53 Redistribution and use in source and binary forms, with or without
54 modification, are permitted provided that the following conditions
55 are met:
56
57 - Redistributions of source code must retain the above copyright
58 notice, this list of conditions and the following disclaimer.
59
60 - Redistributions in binary form must reproduce the above copyright
61 notice, this list of conditions and the following disclaimer in the
62 documentation and/or other materials provided with the distribution.
63
64 - Neither the name of OFFIS nor the names of its contributors may be
65 used to endorse or promote products derived from this software
66 without specific prior written permission.
67
68 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
69 "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
70 LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
71 A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
72 HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
73 SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
74 LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
75 DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
76 THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
77 (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
78 OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
79
80 =========================================================================*/
81
82
83 #include "../../PrecompiledHeaders.h"
84 #include "StoreScp.h"
85
86 #include "../../DicomParsing/FromDcmtkBridge.h"
87 #include "../../DicomParsing/ToDcmtkBridge.h"
88 #include "../../OrthancException.h"
89 #include "../../Logging.h"
90
91 #include <dcmtk/dcmdata/dcfilefo.h>
92 #include <dcmtk/dcmdata/dcmetinf.h>
93 #include <dcmtk/dcmdata/dcostrmb.h>
94 #include <dcmtk/dcmdata/dcdeftag.h>
95 #include <dcmtk/dcmnet/diutil.h>
96
97
98 namespace Orthanc
99 {
100 namespace
101 {
102 struct StoreCallbackData
103 {
104 IStoreRequestHandler* handler;
105 const std::string* remoteIp;
106 const char* remoteAET;
107 const char* calledAET;
108 const char* modality;
109 const char* affectedSOPInstanceUID;
110 uint32_t messageID;
111 };
112
113
114 static void
115 storeScpCallback(
116 void *callbackData,
117 T_DIMSE_StoreProgress *progress,
118 T_DIMSE_C_StoreRQ *req,
119 char * /*imageFileName*/, DcmDataset **imageDataSet,
120 T_DIMSE_C_StoreRSP *rsp,
121 DcmDataset **statusDetail)
122 /*
123 * This function.is used to indicate progress when storescp receives instance data over the
124 * network. On the final call to this function (identified by progress->state == DIMSE_StoreEnd)
125 * this function will store the data set which was received over the network to a file.
126 * Earlier calls to this function will simply cause some information to be dumped to stdout.
127 *
128 * Parameters:
129 * callbackData - [in] data for this callback function
130 * progress - [in] The state of progress. (identifies if this is the initial or final call
131 * to this function, or a call in between these two calls.
132 * req - [in] The original store request message.
133 * imageFileName - [in] The path to and name of the file the information shall be written to.
134 * imageDataSet - [in] The data set which shall be stored in the image file
135 * rsp - [inout] the C-STORE-RSP message (will be sent after the call to this function)
136 * statusDetail - [inout] This variable can be used to capture detailed information with regard to
137 * the status information which is captured in the status element (0000,0900). Note
138 * that this function does specify any such information, the pointer will be set to NULL.
139 */
140 {
141 StoreCallbackData *cbdata = OFstatic_cast(StoreCallbackData *, callbackData);
142
143 DIC_UI sopClass;
144 DIC_UI sopInstance;
145
146 // if this is the final call of this function, save the data which was received to a file
147 // (note that we could also save the image somewhere else, put it in database, etc.)
148 if (progress->state == DIMSE_StoreEnd)
149 {
150 OFString tmpStr;
151
152 // do not send status detail information
153 *statusDetail = NULL;
154
155 // Concerning the following line: an appropriate status code is already set in the resp structure,
156 // it need not be success. For example, if the caller has already detected an out of resources problem
157 // then the status will reflect this. The callback function is still called to allow cleanup.
158 //rsp->DimseStatus = STATUS_Success;
159
160 // we want to write the received information to a file only if this information
161 // is present and the options opt_bitPreserving and opt_ignore are not set.
162 if ((imageDataSet != NULL) && (*imageDataSet != NULL))
163 {
164 DicomMap summary;
165 Json::Value dicomJson;
166 std::string buffer;
167
168 try
169 {
170 FromDcmtkBridge::ExtractDicomSummary(summary, **imageDataSet);
171 FromDcmtkBridge::ExtractDicomAsJson(dicomJson, **imageDataSet);
172
173 if (!FromDcmtkBridge::SaveToMemoryBuffer(buffer, **imageDataSet))
174 {
175 LOG(ERROR) << "cannot write DICOM file to memory";
176 rsp->DimseStatus = STATUS_STORE_Refused_OutOfResources;
177 }
178 }
179 catch (...)
180 {
181 rsp->DimseStatus = STATUS_STORE_Refused_OutOfResources;
182 }
183
184 // check the image to make sure it is consistent, i.e. that its sopClass and sopInstance correspond
185 // to those mentioned in the request. If not, set the status in the response message variable.
186 if (rsp->DimseStatus == STATUS_Success)
187 {
188 // which SOP class and SOP instance ?
189 if (!DU_findSOPClassAndInstanceInDataSet(*imageDataSet, sopClass, sopInstance, /*opt_correctUIDPadding*/ OFFalse))
190 {
191 //LOG4CPP_ERROR(Internals::GetLogger(), "bad DICOM file: " << fileName);
192 rsp->DimseStatus = STATUS_STORE_Error_CannotUnderstand;
193 }
194 else if (strcmp(sopClass, req->AffectedSOPClassUID) != 0)
195 {
196 rsp->DimseStatus = STATUS_STORE_Error_DataSetDoesNotMatchSOPClass;
197 }
198 else if (strcmp(sopInstance, req->AffectedSOPInstanceUID) != 0)
199 {
200 rsp->DimseStatus = STATUS_STORE_Error_DataSetDoesNotMatchSOPClass;
201 }
202 else
203 {
204 try
205 {
206 cbdata->handler->Handle(buffer, summary, dicomJson, *cbdata->remoteIp, cbdata->remoteAET, cbdata->calledAET);
207 }
208 catch (OrthancException& e)
209 {
210 rsp->DimseStatus = STATUS_STORE_Refused_OutOfResources;
211
212 if (e.GetErrorCode() == ErrorCode_InexistentTag)
213 {
214 summary.LogMissingTagsForStore();
215 }
216 else
217 {
218 LOG(ERROR) << "Exception while storing DICOM: " << e.What();
219 }
220 }
221 }
222 }
223 }
224 }
225 }
226 }
227
228 /*
229 * This function processes a DIMSE C-STORE-RQ commmand that was
230 * received over the network connection.
231 *
232 * Parameters:
233 * assoc - [in] The association (network connection to another DICOM application).
234 * msg - [in] The DIMSE C-STORE-RQ message that was received.
235 * presID - [in] The ID of the presentation context which was specified in the PDV which contained
236 * the DIMSE command.
237 */
238 OFCondition Internals::storeScp(T_ASC_Association * assoc,
239 T_DIMSE_Message * msg,
240 T_ASC_PresentationContextID presID,
241 IStoreRequestHandler& handler,
242 const std::string& remoteIp)
243 {
244 OFCondition cond = EC_Normal;
245 T_DIMSE_C_StoreRQ *req;
246
247 // assign the actual information of the C-STORE-RQ command to a local variable
248 req = &msg->msg.CStoreRQ;
249
250 // intialize some variables
251 StoreCallbackData data;
252 data.handler = &handler;
253 data.remoteIp = &remoteIp;
254 data.modality = dcmSOPClassUIDToModality(req->AffectedSOPClassUID/*, "UNKNOWN"*/);
255 if (data.modality == NULL)
256 data.modality = "UNKNOWN";
257
258 data.affectedSOPInstanceUID = req->AffectedSOPInstanceUID;
259 data.messageID = req->MessageID;
260 if (assoc && assoc->params)
261 {
262 data.remoteAET = assoc->params->DULparams.callingAPTitle;
263 data.calledAET = assoc->params->DULparams.calledAPTitle;
264 }
265 else
266 {
267 data.remoteAET = "";
268 data.calledAET = "";
269 }
270
271 DcmFileFormat dcmff;
272
273 // store SourceApplicationEntityTitle in metaheader
274 if (assoc && assoc->params)
275 {
276 const char *aet = assoc->params->DULparams.callingAPTitle;
277 if (aet) dcmff.getMetaInfo()->putAndInsertString(DCM_SourceApplicationEntityTitle, aet);
278 }
279
280 // define an address where the information which will be received over the network will be stored
281 DcmDataset *dset = dcmff.getDataset();
282
283 cond = DIMSE_storeProvider(assoc, presID, req, NULL, /*opt_useMetaheader*/OFFalse, &dset,
284 storeScpCallback, &data,
285 /*opt_blockMode*/ DIMSE_BLOCKING,
286 /*opt_dimse_timeout*/ 0);
287
288 // if some error occured, dump corresponding information and remove the outfile if necessary
289 if (cond.bad())
290 {
291 OFString temp_str;
292 LOG(ERROR) << "Store SCP Failed: " << cond.text();
293 }
294
295 // return return value
296 return cond;
297 }
298 }