0
|
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 }
|