PACS System 0.1.0
PACS DICOM system library
Loading...
Searching...
No Matches
secure_echo_scp.cpp
Go to the documentation of this file.
1
25
26#include <atomic>
27#include <chrono>
28#include <csignal>
29#include <cstdlib>
30#include <filesystem>
31#include <iomanip>
32#include <iostream>
33#include <string>
34
35namespace {
36
38std::atomic<kcenon::pacs::network::dicom_server*> g_server{nullptr};
39
41std::atomic<bool> g_running{true};
42
47void signal_handler(int signal) {
48 std::cout << "\nReceived signal " << signal << ", shutting down...\n";
49 g_running = false;
50
51 auto* server = g_server.load();
52 if (server) {
53 server->stop();
54 }
55}
56
60void install_signal_handlers() {
61 std::signal(SIGINT, signal_handler);
62 std::signal(SIGTERM, signal_handler);
63#ifndef _WIN32
64 std::signal(SIGHUP, signal_handler);
65#endif
66}
67
71struct tls_options {
72 std::filesystem::path cert_path;
73 std::filesystem::path key_path;
74 std::filesystem::path ca_path;
75 bool verify_peer = true;
76 std::string tls_version = "1.2";
77};
78
83void print_usage(const char* program_name) {
84 std::cout << R"(
85Secure Echo SCP - TLS-secured DICOM Connectivity Test Server
86
87Usage: )" << program_name << R"( <port> <ae_title> --cert <file> --key <file> [options]
88
89Arguments:
90 port Port number to listen on (typically 2762 for DICOM TLS)
91 ae_title Application Entity Title for this server (max 16 chars)
92
93Required TLS Options:
94 --cert <file> Server certificate file (PEM format)
95 --key <file> Server private key file (PEM format)
96
97Optional TLS Options:
98 --ca <file> CA certificate for client verification (PEM format)
99 --no-verify Disable client certificate verification
100 --tls-version <ver> Minimum TLS version: 1.2 or 1.3 (default: 1.2)
101
102Server Options:
103 --max-assoc <n> Maximum concurrent associations (default: 10)
104 --timeout <sec> Idle timeout in seconds (default: 300)
105 --help Show this help message
106
107Examples:
108 # Basic TLS server
109 )" << program_name << R"( 2762 MY_PACS --cert server.crt --key server.key
110
111 # With client certificate verification (mutual TLS)
112 )" << program_name << R"( 2762 MY_PACS --cert server.crt --key server.key --ca ca.crt
113
114 # TLS 1.3 only
115 )" << program_name << R"( 2762 MY_PACS --cert server.crt --key server.key --tls-version 1.3
116
117Notes:
118 - Standard DICOM TLS port is 2762
119 - Press Ctrl+C to stop the server gracefully
120 - Use generate_certs.sh to create test certificates
121
122Exit Codes:
123 0 Normal termination
124 1 Error - Failed to start server or invalid configuration
125)";
126}
127
139bool parse_arguments(
140 int argc,
141 char* argv[],
142 uint16_t& port,
143 std::string& ae_title,
144 tls_options& tls,
145 size_t& max_associations,
146 uint32_t& idle_timeout) {
147
148 if (argc < 5) {
149 return false;
150 }
151
152 // Check for help flag
153 for (int i = 1; i < argc; ++i) {
154 if (std::string(argv[i]) == "--help" || std::string(argv[i]) == "-h") {
155 return false;
156 }
157 }
158
159 // Parse port
160 try {
161 int port_int = std::stoi(argv[1]);
162 if (port_int < 1 || port_int > 65535) {
163 std::cerr << "Error: Port must be between 1 and 65535\n";
164 return false;
165 }
166 port = static_cast<uint16_t>(port_int);
167 } catch (const std::exception&) {
168 std::cerr << "Error: Invalid port number '" << argv[1] << "'\n";
169 return false;
170 }
171
172 // Parse AE title
173 ae_title = argv[2];
174 if (ae_title.length() > 16) {
175 std::cerr << "Error: AE title exceeds 16 characters\n";
176 return false;
177 }
178
179 // Default values
180 max_associations = 10;
181 idle_timeout = 300;
182 tls.verify_peer = true;
183 tls.tls_version = "1.2";
184
185 // Parse options
186 for (int i = 3; i < argc; ++i) {
187 std::string arg = argv[i];
188
189 if (arg == "--cert" && i + 1 < argc) {
190 tls.cert_path = argv[++i];
191 } else if (arg == "--key" && i + 1 < argc) {
192 tls.key_path = argv[++i];
193 } else if (arg == "--ca" && i + 1 < argc) {
194 tls.ca_path = argv[++i];
195 } else if (arg == "--no-verify") {
196 tls.verify_peer = false;
197 } else if (arg == "--tls-version" && i + 1 < argc) {
198 tls.tls_version = argv[++i];
199 if (tls.tls_version != "1.2" && tls.tls_version != "1.3") {
200 std::cerr << "Error: Invalid TLS version (use 1.2 or 1.3)\n";
201 return false;
202 }
203 } else if (arg == "--max-assoc" && i + 1 < argc) {
204 try {
205 int val = std::stoi(argv[++i]);
206 if (val < 1) {
207 std::cerr << "Error: max-assoc must be positive\n";
208 return false;
209 }
210 max_associations = static_cast<size_t>(val);
211 } catch (const std::exception&) {
212 std::cerr << "Error: Invalid max-assoc value\n";
213 return false;
214 }
215 } else if (arg == "--timeout" && i + 1 < argc) {
216 try {
217 int val = std::stoi(argv[++i]);
218 if (val < 0) {
219 std::cerr << "Error: timeout cannot be negative\n";
220 return false;
221 }
222 idle_timeout = static_cast<uint32_t>(val);
223 } catch (const std::exception&) {
224 std::cerr << "Error: Invalid timeout value\n";
225 return false;
226 }
227 } else if (arg[0] == '-') {
228 std::cerr << "Error: Unknown option '" << arg << "'\n";
229 return false;
230 }
231 }
232
233 // Validate required TLS options
234 if (tls.cert_path.empty()) {
235 std::cerr << "Error: --cert is required\n";
236 return false;
237 }
238 if (tls.key_path.empty()) {
239 std::cerr << "Error: --key is required\n";
240 return false;
241 }
242
243 return true;
244}
245
251bool validate_tls_files(const tls_options& tls) {
252 // Check certificate file
253 if (!std::filesystem::exists(tls.cert_path)) {
254 std::cerr << "Error: Certificate file not found: " << tls.cert_path << "\n";
255 return false;
256 }
257
258 // Check key file
259 if (!std::filesystem::exists(tls.key_path)) {
260 std::cerr << "Error: Key file not found: " << tls.key_path << "\n";
261 return false;
262 }
263
264 // Check CA file if specified
265 if (!tls.ca_path.empty() && !std::filesystem::exists(tls.ca_path)) {
266 std::cerr << "Error: CA certificate file not found: " << tls.ca_path << "\n";
267 return false;
268 }
269
270 return true;
271}
272
277std::string current_timestamp() {
278 auto now = std::chrono::system_clock::now();
279 auto time_t_now = std::chrono::system_clock::to_time_t(now);
280 auto ms = std::chrono::duration_cast<std::chrono::milliseconds>(
281 now.time_since_epoch()) % 1000;
282
283 std::ostringstream oss;
284 oss << std::put_time(std::localtime(&time_t_now), "%Y-%m-%d %H:%M:%S");
285 oss << '.' << std::setfill('0') << std::setw(3) << ms.count();
286 return oss.str();
287}
288
292bool run_server(
293 uint16_t port,
294 const std::string& ae_title,
295 const tls_options& tls,
296 size_t max_associations,
297 uint32_t idle_timeout) {
298
299 using namespace kcenon::pacs::network;
300 using namespace kcenon::pacs::services;
301 using namespace kcenon::pacs::integration;
302
303 std::cout << "\nStarting Secure Echo SCP...\n";
304 std::cout << " AE Title: " << ae_title << "\n";
305 std::cout << " Port: " << port << "\n";
306 std::cout << " Max Associations: " << max_associations << "\n";
307 std::cout << " Idle Timeout: " << idle_timeout << " seconds\n";
308 std::cout << "\n";
309 std::cout << " TLS Configuration:\n";
310 std::cout << " Certificate: " << tls.cert_path << "\n";
311 std::cout << " Private Key: " << tls.key_path << "\n";
312 if (!tls.ca_path.empty()) {
313 std::cout << " CA Certificate: " << tls.ca_path << "\n";
314 }
315 std::cout << " Verify Peer: " << (tls.verify_peer ? "Yes" : "No") << "\n";
316 std::cout << " Min TLS Version: " << tls.tls_version << "\n\n";
317
318 // Configure TLS
319 tls_config tls_cfg;
320 tls_cfg.enabled = true;
321 tls_cfg.cert_path = tls.cert_path;
322 tls_cfg.key_path = tls.key_path;
323 tls_cfg.ca_path = tls.ca_path;
324 tls_cfg.verify_peer = tls.verify_peer;
325 tls_cfg.min_version = (tls.tls_version == "1.3")
326 ? tls_config::tls_version::v1_3
327 : tls_config::tls_version::v1_2;
328
329 // Validate TLS configuration
330 auto tls_result = network_adapter::configure_tls(tls_cfg);
331 if (tls_result.is_err()) {
332 std::cerr << "TLS configuration error: " << tls_result.error().message << "\n";
333 return false;
334 }
335
336 // Configure server
337 server_config config;
338 config.ae_title = ae_title;
339 config.port = port;
340 config.max_associations = max_associations;
341 config.idle_timeout = std::chrono::seconds{idle_timeout};
342 config.implementation_class_uid = "1.2.826.0.1.3680043.2.1545.1";
343 config.implementation_version_name = "SECURE_SCP_001";
344
345 // Create server with TLS configuration
346 auto server_ptr = network_adapter::create_server(config, tls_cfg);
347 if (!server_ptr) {
348 std::cerr << "Failed to create secure server\n";
349 return false;
350 }
351
352 auto& server = *server_ptr;
353 g_server = &server;
354
355 // Register verification service (handles C-ECHO)
356 server.register_service(std::make_shared<verification_scp>());
357
358 // Set up callbacks for logging
359 server.on_association_established([](const association& assoc) {
360 std::cout << "[" << current_timestamp() << "] "
361 << "[TLS] Association established from: " << assoc.calling_ae()
362 << " -> " << assoc.called_ae() << "\n";
363 });
364
365 server.on_association_released([](const association& assoc) {
366 std::cout << "[" << current_timestamp() << "] "
367 << "[TLS] Association released: " << assoc.calling_ae() << "\n";
368 });
369
370 server.on_error([](const std::string& error) {
371 std::cerr << "[" << current_timestamp() << "] "
372 << "[TLS] Error: " << error << "\n";
373 });
374
375 // Start server
376 auto result = server.start();
377 if (result.is_err()) {
378 std::cerr << "Failed to start server: " << result.error().message << "\n";
379 g_server = nullptr;
380 return false;
381 }
382
383 std::cout << "=================================================\n";
384 std::cout << " Secure Echo SCP is running on port " << port << " (TLS)\n";
385 std::cout << " Press Ctrl+C to stop\n";
386 std::cout << "=================================================\n\n";
387
388 // Wait for shutdown
389 server.wait_for_shutdown();
390
391 // Print final statistics
392 auto stats = server.get_statistics();
393 std::cout << "\n";
394 std::cout << "=================================================\n";
395 std::cout << " Server Statistics (TLS-Secured)\n";
396 std::cout << "=================================================\n";
397 std::cout << " Total Associations: " << stats.total_associations << "\n";
398 std::cout << " Rejected Associations: " << stats.rejected_associations << "\n";
399 std::cout << " Messages Processed: " << stats.messages_processed << "\n";
400 std::cout << " Bytes Received: " << stats.bytes_received << "\n";
401 std::cout << " Bytes Sent: " << stats.bytes_sent << "\n";
402 std::cout << " Uptime: " << stats.uptime().count() << " seconds\n";
403 std::cout << "=================================================\n";
404
405 g_server = nullptr;
406 return true;
407}
408
409} // namespace
410
411int main(int argc, char* argv[]) {
412 std::cout << R"(
413 ____ _____ ____ _ _ ____ _____ _____ ____ ____
414 / ___|| ____/ ___| | | | _ \| ____| | ____/ ___/ ___|
415 \___ \| _|| | | | | | |_) | _| | _|| | \___ \
416 ___) | |__| |___| |_| | _ <| |___ | |__| |___ ___) |
417 |____/|_____\____|\___/|_| \_\_____| |_____\____|____/
418 ____ ____ ____
419 / ___| / ___| _ \
420 \___ \| | | |_) |
421 ___) | |___| __/
422 |____/ \____|_|
423
424 TLS-Secured DICOM Connectivity Test Server
425)" << "\n";
426
427 uint16_t port = 0;
428 std::string ae_title;
429 tls_options tls;
430 size_t max_associations = 0;
431 uint32_t idle_timeout = 0;
432
433 if (!parse_arguments(argc, argv, port, ae_title, tls, max_associations, idle_timeout)) {
434 print_usage(argv[0]);
435 return 1;
436 }
437
438 // Validate TLS files exist
439 if (!validate_tls_files(tls)) {
440 return 1;
441 }
442
443 // Install signal handlers
444 install_signal_handlers();
445
446 bool success = run_server(port, ae_title, tls, max_associations, idle_timeout);
447
448 std::cout << "\nSecure Echo SCP terminated\n";
449 return success ? 0 : 1;
450}
Multi-threaded DICOM server for handling multiple associations.
int main()
Definition main.cpp:84
@ error
Node returned an error.
@ tls
TLS over TCP (RFC 5425) — Secure.
Adapter for integrating network_system for DICOM protocol.
DICOM Server configuration structures.
DICOM Verification SCP service (C-ECHO handler)