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