Mercurial > hg > orthanc
annotate OrthancServer/Internals/CommandDispatcher.cpp @ 105:5bae1ac18ded
fixes
author | jodogne |
---|---|
date | Wed, 03 Oct 2012 17:14:52 +0200 |
parents | 7593b57dc1bf |
children | fe180eae201d |
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. | |
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 */ | |
101 | 29 #include <boost/lexical_cast.hpp> |
102
7593b57dc1bf
switch to google log
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
101
diff
changeset
|
30 #include <glog/logging.h> |
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 { | |
102
7593b57dc1bf
switch to google log
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
101
diff
changeset
|
45 LOG(FATAL) << cond.text(); |
0 | 46 return cond; |
47 } | |
48 | |
49 cond = ASC_destroyAssociation(&assoc); | |
50 if (cond.bad()) | |
51 { | |
102
7593b57dc1bf
switch to google log
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
101
diff
changeset
|
52 LOG(FATAL) << 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 { | |
102
7593b57dc1bf
switch to google log
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
101
diff
changeset
|
110 LOG(ERROR) << "Receiving Association failed: " << 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 | |
102
7593b57dc1bf
switch to google log
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
101
diff
changeset
|
116 LOG(INFO) << "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 { | |
102
7593b57dc1bf
switch to google log
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
101
diff
changeset
|
127 LOG(INFO) << 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 { | |
102
7593b57dc1bf
switch to google log
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
101
diff
changeset
|
136 LOG(INFO) << 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 | |
102
7593b57dc1bf
switch to google log
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
101
diff
changeset
|
156 LOG(INFO) << "Association Rejected: Bad Application Context Name: " << buf; |
0 | 157 cond = ASC_rejectAssociation(assoc, &rej); |
158 if (cond.bad()) | |
159 { | |
102
7593b57dc1bf
switch to google log
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
101
diff
changeset
|
160 LOG(INFO) << 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 | |
102
7593b57dc1bf
switch to google log
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
101
diff
changeset
|
232 LOG(INFO) << "Association Rejected: No Implementation Class UID provided"; |
0 | 233 cond = ASC_rejectAssociation(assoc, &rej); |
234 if (cond.bad()) | |
235 { | |
102
7593b57dc1bf
switch to google log
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
101
diff
changeset
|
236 LOG(INFO) << cond.text(); |
0 | 237 } |
238 AssociationCleanup(assoc); | |
239 return NULL; | |
240 } | |
241 | |
242 { | |
243 cond = ASC_acknowledgeAssociation(assoc); | |
244 if (cond.bad()) | |
245 { | |
102
7593b57dc1bf
switch to google log
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
101
diff
changeset
|
246 LOG(ERROR) << cond.text(); |
0 | 247 AssociationCleanup(assoc); |
248 return NULL; | |
249 } | |
102
7593b57dc1bf
switch to google log
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
101
diff
changeset
|
250 LOG(INFO) << "Association Acknowledged (Max Send PDV: " << assoc->sendPDVLength << ")"; |
0 | 251 if (ASC_countAcceptedPresentationContexts(assoc->params) == 0) |
102
7593b57dc1bf
switch to google log
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
101
diff
changeset
|
252 LOG(INFO) << " (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; | |
102
7593b57dc1bf
switch to google log
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
101
diff
changeset
|
350 LOG(ERROR) << "cannot handle command: 0x" << std::hex << msg.CommandField; |
0 | 351 break; |
352 } | |
353 } | |
354 else | |
355 { | |
356 // Bad status, which indicates the closing of the connection by | |
357 // the peer or a network error | |
358 finished = true; | |
359 } | |
360 | |
361 if (finished) | |
362 { | |
363 if (cond == DUL_PEERREQUESTEDRELEASE) | |
364 { | |
102
7593b57dc1bf
switch to google log
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
101
diff
changeset
|
365 LOG(INFO) << "Association Release"; |
0 | 366 ASC_acknowledgeRelease(assoc_); |
367 } | |
368 else if (cond == DUL_PEERABORTEDASSOCIATION) | |
369 { | |
102
7593b57dc1bf
switch to google log
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
101
diff
changeset
|
370 LOG(INFO) << "Association Aborted"; |
0 | 371 } |
372 else | |
373 { | |
374 OFString temp_str; | |
102
7593b57dc1bf
switch to google log
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
101
diff
changeset
|
375 LOG(ERROR) << "DIMSE failure (aborting association): " << cond.text(); |
0 | 376 /* some kind of error so abort the association */ |
377 ASC_abortAssociation(assoc_); | |
378 } | |
379 } | |
380 | |
381 return !finished; | |
382 } | |
383 | |
384 | |
385 OFCondition EchoScp( T_ASC_Association * assoc, T_DIMSE_Message * msg, T_ASC_PresentationContextID presID) | |
386 { | |
387 OFString temp_str; | |
102
7593b57dc1bf
switch to google log
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
101
diff
changeset
|
388 LOG(INFO) << "Received Echo Request"; |
7593b57dc1bf
switch to google log
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
101
diff
changeset
|
389 //LOG(DEBUG) << DIMSE_dumpMessage(temp_str, msg->msg.CEchoRQ, DIMSE_INCOMING, NULL, presID)); |
0 | 390 |
391 /* the echo succeeded !! */ | |
392 OFCondition cond = DIMSE_sendEchoResponse(assoc, presID, &msg->msg.CEchoRQ, STATUS_Success, NULL); | |
393 if (cond.bad()) | |
394 { | |
102
7593b57dc1bf
switch to google log
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
101
diff
changeset
|
395 LOG(ERROR) << "Echo SCP Failed: " << cond.text(); |
0 | 396 } |
397 return cond; | |
398 } | |
399 } | |
400 } |