Mercurial > hg > orthanc
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 } |