0
|
1 /**
|
62
|
2 * Orthanc - A Lightweight, RESTful DICOM Store
|
0
|
3 * Copyright (C) 2012 Medical Physics Department, CHU of Liege,
|
|
4 * Belgium
|
|
5 *
|
|
6 * This program is free software: you can redistribute it and/or
|
|
7 * modify it under the terms of the GNU General Public License as
|
|
8 * published by the Free Software Foundation, either version 3 of the
|
|
9 * License, or (at your option) any later version.
|
|
10 *
|
|
11 * This program is distributed in the hope that it will be useful, but
|
|
12 * WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
14 * General Public License for more details.
|
|
15 *
|
|
16 * You should have received a copy of the GNU General Public License
|
|
17 * along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
18 **/
|
|
19
|
|
20
|
|
21 #include "CommandDispatcher.h"
|
|
22
|
|
23 #include "FindScp.h"
|
|
24 #include "StoreScp.h"
|
|
25 #include "MoveScp.h"
|
101
|
26 #include "DcmtkLogging.h"
|
0
|
27 #include "../../Core/Toolbox.h"
|
|
28
|
|
29 #include <dcmtk/dcmnet/dcasccfg.h> /* for class DcmAssociationConfiguration */
|
101
|
30 #include <boost/lexical_cast.hpp>
|
0
|
31
|
|
32 static OFBool opt_rejectWithoutImplementationUID = OFFalse;
|
|
33
|
|
34
|
62
|
35 namespace Orthanc
|
0
|
36 {
|
|
37 namespace Internals
|
|
38 {
|
|
39 OFCondition AssociationCleanup(T_ASC_Association *assoc)
|
|
40 {
|
|
41 OFString temp_str;
|
|
42 OFCondition cond = ASC_dropSCPAssociation(assoc);
|
|
43 if (cond.bad())
|
|
44 {
|
101
|
45 LOG4CPP_FATAL(Internals::GetLogger(), std::string(cond.text()));
|
0
|
46 return cond;
|
|
47 }
|
|
48
|
|
49 cond = ASC_destroyAssociation(&assoc);
|
|
50 if (cond.bad())
|
|
51 {
|
101
|
52 LOG4CPP_FATAL(Internals::GetLogger(), std::string(cond.text()));
|
0
|
53 return cond;
|
|
54 }
|
|
55
|
|
56 return cond;
|
|
57 }
|
|
58
|
|
59
|
|
60
|
|
61 CommandDispatcher* AcceptAssociation(const DicomServer& server, T_ASC_Network *net)
|
|
62 {
|
|
63 DcmAssociationConfiguration asccfg;
|
|
64 char buf[BUFSIZ];
|
|
65 T_ASC_Association *assoc;
|
|
66 OFCondition cond;
|
|
67 OFString sprofile;
|
|
68 OFString temp_str;
|
|
69
|
|
70 std::vector<const char*> knownAbstractSyntaxes;
|
|
71
|
|
72 // For C-STORE
|
|
73 if (server.HasStoreRequestHandlerFactory())
|
|
74 {
|
|
75 knownAbstractSyntaxes.push_back(UID_VerificationSOPClass);
|
|
76 }
|
|
77
|
|
78 // For C-FIND
|
|
79 if (server.HasFindRequestHandlerFactory())
|
|
80 {
|
|
81 knownAbstractSyntaxes.push_back(UID_FINDPatientRootQueryRetrieveInformationModel);
|
|
82 knownAbstractSyntaxes.push_back(UID_FINDStudyRootQueryRetrieveInformationModel);
|
|
83 }
|
|
84
|
|
85 // For C-MOVE
|
|
86 if (server.HasMoveRequestHandlerFactory())
|
|
87 {
|
|
88 knownAbstractSyntaxes.push_back(UID_MOVEStudyRootQueryRetrieveInformationModel);
|
|
89 }
|
|
90
|
|
91 const char* transferSyntaxes[] = { NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL };
|
|
92 int numTransferSyntaxes = 0;
|
|
93
|
|
94 cond = ASC_receiveAssociation(net, &assoc,
|
|
95 /*opt_maxPDU*/ ASC_DEFAULTMAXPDU,
|
|
96 NULL, NULL,
|
|
97 /*opt_secureConnection*/ OFFalse,
|
|
98 DUL_NOBLOCK, 1);
|
|
99
|
|
100 if (cond == DUL_NOASSOCIATIONREQUEST)
|
|
101 {
|
|
102 // Timeout
|
|
103 AssociationCleanup(assoc);
|
|
104 return NULL;
|
|
105 }
|
|
106
|
|
107 // if some kind of error occured, take care of it
|
|
108 if (cond.bad())
|
|
109 {
|
101
|
110 LOG4CPP_ERROR(Internals::GetLogger(), "Receiving Association failed: " + std::string(cond.text()));
|
0
|
111 // no matter what kind of error occurred, we need to do a cleanup
|
|
112 AssociationCleanup(assoc);
|
|
113 return NULL;
|
|
114 }
|
|
115
|
101
|
116 LOG4CPP_INFO(Internals::GetLogger(), "Association Received");
|
0
|
117
|
|
118 transferSyntaxes[0] = UID_LittleEndianExplicitTransferSyntax;
|
|
119 transferSyntaxes[1] = UID_BigEndianExplicitTransferSyntax;
|
|
120 transferSyntaxes[2] = UID_LittleEndianImplicitTransferSyntax;
|
|
121 numTransferSyntaxes = 3;
|
|
122
|
|
123 /* accept the Verification SOP Class if presented */
|
|
124 cond = ASC_acceptContextsWithPreferredTransferSyntaxes( assoc->params, &knownAbstractSyntaxes[0], knownAbstractSyntaxes.size(), transferSyntaxes, numTransferSyntaxes);
|
|
125 if (cond.bad())
|
|
126 {
|
101
|
127 LOG4CPP_DEBUG(Internals::GetLogger(), std::string(cond.text()));
|
0
|
128 AssociationCleanup(assoc);
|
|
129 return NULL;
|
|
130 }
|
|
131
|
|
132 /* the array of Storage SOP Class UIDs comes from dcuid.h */
|
|
133 cond = ASC_acceptContextsWithPreferredTransferSyntaxes( assoc->params, dcmAllStorageSOPClassUIDs, numberOfAllDcmStorageSOPClassUIDs, transferSyntaxes, numTransferSyntaxes);
|
|
134 if (cond.bad())
|
|
135 {
|
101
|
136 LOG4CPP_DEBUG(Internals::GetLogger(), std::string(cond.text()));
|
0
|
137 AssociationCleanup(assoc);
|
|
138 return NULL;
|
|
139 }
|
|
140
|
|
141 /* set our app title */
|
|
142 ASC_setAPTitles(assoc->params, NULL, NULL, server.GetApplicationEntityTitle().c_str());
|
|
143
|
|
144 /* acknowledge or reject this association */
|
|
145 cond = ASC_getApplicationContextName(assoc->params, buf);
|
|
146 if ((cond.bad()) || strcmp(buf, UID_StandardApplicationContext) != 0)
|
|
147 {
|
|
148 /* reject: the application context name is not supported */
|
|
149 T_ASC_RejectParameters rej =
|
|
150 {
|
|
151 ASC_RESULT_REJECTEDPERMANENT,
|
|
152 ASC_SOURCE_SERVICEUSER,
|
|
153 ASC_REASON_SU_APPCONTEXTNAMENOTSUPPORTED
|
|
154 };
|
|
155
|
101
|
156 LOG4CPP_INFO(Internals::GetLogger(), "Association Rejected: Bad Application Context Name: " + std::string(buf));
|
0
|
157 cond = ASC_rejectAssociation(assoc, &rej);
|
|
158 if (cond.bad())
|
|
159 {
|
101
|
160 LOG4CPP_DEBUG(Internals::GetLogger(), std::string(cond.text()));
|
0
|
161 }
|
|
162 AssociationCleanup(assoc);
|
|
163 return NULL;
|
|
164 }
|
|
165
|
|
166 /* check the AETs */
|
|
167 {
|
|
168 DIC_AE callingTitle_C;
|
|
169 DIC_AE calledTitle_C;
|
|
170 DIC_AE callingIP_C;
|
|
171 DIC_AE calledIP_C;
|
|
172 if (ASC_getAPTitles(assoc->params, callingTitle_C, calledTitle_C, NULL).bad() ||
|
|
173 ASC_getPresentationAddresses(assoc->params, callingIP_C, calledIP_C).bad())
|
|
174 {
|
|
175 T_ASC_RejectParameters rej =
|
|
176 {
|
|
177 ASC_RESULT_REJECTEDPERMANENT,
|
|
178 ASC_SOURCE_SERVICEUSER,
|
|
179 ASC_REASON_SU_NOREASON
|
|
180 };
|
|
181 ASC_rejectAssociation(assoc, &rej);
|
|
182 AssociationCleanup(assoc);
|
|
183 return NULL;
|
|
184 }
|
|
185
|
101
|
186 std::string callingIP(/*OFSTRING_GUARD*/(callingIP_C));
|
|
187 std::string callingTitle(/*OFSTRING_GUARD*/(callingTitle_C));
|
|
188 std::string calledTitle(/*OFSTRING_GUARD*/(calledTitle_C));
|
0
|
189 Toolbox::ToUpperCase(callingIP);
|
|
190 Toolbox::ToUpperCase(callingTitle);
|
|
191 Toolbox::ToUpperCase(calledTitle);
|
|
192
|
|
193 if (server.HasCalledApplicationEntityTitleCheck() &&
|
|
194 calledTitle != server.GetApplicationEntityTitle())
|
|
195 {
|
|
196 T_ASC_RejectParameters rej =
|
|
197 {
|
|
198 ASC_RESULT_REJECTEDPERMANENT,
|
|
199 ASC_SOURCE_SERVICEUSER,
|
|
200 ASC_REASON_SU_CALLEDAETITLENOTRECOGNIZED
|
|
201 };
|
|
202 ASC_rejectAssociation(assoc, &rej);
|
|
203 AssociationCleanup(assoc);
|
|
204 return NULL;
|
|
205 }
|
|
206
|
|
207 if (server.HasApplicationEntityFilter() &&
|
|
208 !server.GetApplicationEntityFilter().IsAllowed(callingIP, callingTitle))
|
|
209 {
|
|
210 T_ASC_RejectParameters rej =
|
|
211 {
|
|
212 ASC_RESULT_REJECTEDPERMANENT,
|
|
213 ASC_SOURCE_SERVICEUSER,
|
|
214 ASC_REASON_SU_CALLINGAETITLENOTRECOGNIZED
|
|
215 };
|
|
216 ASC_rejectAssociation(assoc, &rej);
|
|
217 AssociationCleanup(assoc);
|
|
218 return NULL;
|
|
219 }
|
|
220 }
|
|
221
|
|
222 if (opt_rejectWithoutImplementationUID && strlen(assoc->params->theirImplementationClassUID) == 0)
|
|
223 {
|
|
224 /* reject: the no implementation Class UID provided */
|
|
225 T_ASC_RejectParameters rej =
|
|
226 {
|
|
227 ASC_RESULT_REJECTEDPERMANENT,
|
|
228 ASC_SOURCE_SERVICEUSER,
|
|
229 ASC_REASON_SU_NOREASON
|
|
230 };
|
|
231
|
101
|
232 LOG4CPP_INFO(Internals::GetLogger(), "Association Rejected: No Implementation Class UID provided");
|
0
|
233 cond = ASC_rejectAssociation(assoc, &rej);
|
|
234 if (cond.bad())
|
|
235 {
|
101
|
236 LOG4CPP_DEBUG(Internals::GetLogger(), std::string(cond.text()));
|
0
|
237 }
|
|
238 AssociationCleanup(assoc);
|
|
239 return NULL;
|
|
240 }
|
|
241
|
|
242 {
|
|
243 cond = ASC_acknowledgeAssociation(assoc);
|
|
244 if (cond.bad())
|
|
245 {
|
101
|
246 LOG4CPP_ERROR(Internals::GetLogger(), std::string(cond.text()));
|
0
|
247 AssociationCleanup(assoc);
|
|
248 return NULL;
|
|
249 }
|
101
|
250 LOG4CPP_INFO(Internals::GetLogger(), "Association Acknowledged (Max Send PDV: " + boost::lexical_cast<std::string>(assoc->sendPDVLength) + ")");
|
0
|
251 if (ASC_countAcceptedPresentationContexts(assoc->params) == 0)
|
101
|
252 LOG4CPP_INFO(Internals::GetLogger(), " (but no valid presentation contexts)");
|
0
|
253 }
|
|
254
|
|
255 return new CommandDispatcher(server, assoc);
|
|
256 }
|
|
257
|
|
258 bool CommandDispatcher::Step()
|
|
259 /*
|
|
260 * This function receives DIMSE commmands over the network connection
|
|
261 * and handles these commands correspondingly. Note that in case of
|
|
262 * storscp only C-ECHO-RQ and C-STORE-RQ commands can be processed.
|
|
263 */
|
|
264 {
|
|
265 bool finished = false;
|
|
266
|
|
267 // receive a DIMSE command over the network, with a timeout of 1 second
|
|
268 DcmDataset *statusDetail = NULL;
|
|
269 T_ASC_PresentationContextID presID = 0;
|
|
270 T_DIMSE_Message msg;
|
|
271
|
|
272 OFCondition cond = DIMSE_receiveCommand(assoc_, DIMSE_NONBLOCKING, 1, &presID, &msg, &statusDetail);
|
|
273 elapsedTimeSinceLastCommand_++;
|
|
274
|
|
275 // if the command which was received has extra status
|
|
276 // detail information, dump this information
|
|
277 if (statusDetail != NULL)
|
|
278 {
|
101
|
279 //LOG4CPP_WARN(Internals::GetLogger(), "Status Detail:" << OFendl << DcmObject::PrintHelper(*statusDetail));
|
0
|
280 delete statusDetail;
|
|
281 }
|
|
282
|
|
283 if (cond == DIMSE_OUTOFRESOURCES)
|
|
284 {
|
|
285 finished = true;
|
|
286 }
|
|
287 else if (cond == DIMSE_NODATAAVAILABLE)
|
|
288 {
|
|
289 // Timeout due to DIMSE_NONBLOCKING
|
|
290 if (clientTimeout_ != 0 &&
|
|
291 elapsedTimeSinceLastCommand_ >= clientTimeout_)
|
|
292 {
|
|
293 // This timeout is actually a client timeout
|
|
294 finished = true;
|
|
295 }
|
|
296 }
|
|
297 else if (cond == EC_Normal)
|
|
298 {
|
|
299 // Reset the client timeout counter
|
|
300 elapsedTimeSinceLastCommand_ = 0;
|
|
301
|
|
302 // in case we received a valid message, process this command
|
|
303 // note that storescp can only process a C-ECHO-RQ and a C-STORE-RQ
|
|
304 switch (msg.CommandField)
|
|
305 {
|
|
306 case DIMSE_C_ECHO_RQ:
|
|
307 // process C-ECHO-Request
|
|
308 cond = EchoScp(assoc_, &msg, presID);
|
|
309 break;
|
|
310
|
|
311 case DIMSE_C_STORE_RQ:
|
|
312 // process C-STORE-Request
|
|
313 if (server_.HasStoreRequestHandlerFactory())
|
|
314 {
|
|
315 std::auto_ptr<IStoreRequestHandler> handler
|
|
316 (server_.GetStoreRequestHandlerFactory().ConstructStoreRequestHandler());
|
|
317 cond = Internals::storeScp(assoc_, &msg, presID, *handler);
|
|
318 }
|
|
319 else
|
|
320 cond = DIMSE_BADCOMMANDTYPE; // Should never happen
|
|
321 break;
|
|
322
|
|
323 case DIMSE_C_MOVE_RQ:
|
|
324 // process C-MOVE-Request
|
|
325 if (server_.HasMoveRequestHandlerFactory())
|
|
326 {
|
|
327 std::auto_ptr<IMoveRequestHandler> handler
|
|
328 (server_.GetMoveRequestHandlerFactory().ConstructMoveRequestHandler());
|
|
329 cond = Internals::moveScp(assoc_, &msg, presID, *handler);
|
|
330 }
|
|
331 else
|
|
332 cond = DIMSE_BADCOMMANDTYPE; // Should never happen
|
|
333 break;
|
|
334
|
|
335 case DIMSE_C_FIND_RQ:
|
|
336 // process C-FIND-Request
|
|
337 if (server_.HasFindRequestHandlerFactory())
|
|
338 {
|
|
339 std::auto_ptr<IFindRequestHandler> handler
|
|
340 (server_.GetFindRequestHandlerFactory().ConstructFindRequestHandler());
|
|
341 cond = Internals::findScp(assoc_, &msg, presID, *handler);
|
|
342 }
|
|
343 else
|
|
344 cond = DIMSE_BADCOMMANDTYPE; // Should never happen
|
|
345 break;
|
|
346
|
|
347 default:
|
|
348 // we cannot handle this kind of message
|
|
349 cond = DIMSE_BADCOMMANDTYPE;
|
101
|
350 LOG4CPP_ERROR(Internals::GetLogger(), "cannot handle command: 0x"
|
|
351 + boost::lexical_cast<std::string>(hex)
|
|
352 + boost::lexical_cast<std::string>(msg.CommandField));
|
0
|
353 break;
|
|
354 }
|
|
355 }
|
|
356 else
|
|
357 {
|
|
358 // Bad status, which indicates the closing of the connection by
|
|
359 // the peer or a network error
|
|
360 finished = true;
|
|
361 }
|
|
362
|
|
363 if (finished)
|
|
364 {
|
|
365 if (cond == DUL_PEERREQUESTEDRELEASE)
|
|
366 {
|
101
|
367 LOG4CPP_INFO(Internals::GetLogger(), "Association Release");
|
0
|
368 ASC_acknowledgeRelease(assoc_);
|
|
369 }
|
|
370 else if (cond == DUL_PEERABORTEDASSOCIATION)
|
|
371 {
|
101
|
372 LOG4CPP_INFO(Internals::GetLogger(), "Association Aborted");
|
0
|
373 }
|
|
374 else
|
|
375 {
|
|
376 OFString temp_str;
|
101
|
377 LOG4CPP_ERROR(Internals::GetLogger(), "DIMSE failure (aborting association): " + std::string(cond.text()));
|
0
|
378 /* some kind of error so abort the association */
|
|
379 ASC_abortAssociation(assoc_);
|
|
380 }
|
|
381 }
|
|
382
|
|
383 return !finished;
|
|
384 }
|
|
385
|
|
386
|
|
387 OFCondition EchoScp( T_ASC_Association * assoc, T_DIMSE_Message * msg, T_ASC_PresentationContextID presID)
|
|
388 {
|
|
389 OFString temp_str;
|
101
|
390 LOG4CPP_INFO(Internals::GetLogger(), "Received Echo Request");
|
|
391 //LOG4CPP_DEBUG(Internals::GetLogger(), DIMSE_dumpMessage(temp_str, msg->msg.CEchoRQ, DIMSE_INCOMING, NULL, presID));
|
0
|
392
|
|
393 /* the echo succeeded !! */
|
|
394 OFCondition cond = DIMSE_sendEchoResponse(assoc, presID, &msg->msg.CEchoRQ, STATUS_Success, NULL);
|
|
395 if (cond.bad())
|
|
396 {
|
101
|
397 LOG4CPP_ERROR(Internals::GetLogger(), "Echo SCP Failed: " + std::string(cond.text()));
|
0
|
398 }
|
|
399 return cond;
|
|
400 }
|
|
401 }
|
|
402 }
|