comparison PalantirServer/Internals/CommandDispatcher.cpp @ 0:3959d33612cc

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