38std::atomic<kcenon::pacs::network::dicom_server*> g_server{
nullptr};
41std::atomic<bool> g_running{
true};
47void signal_handler(
int signal) {
48 std::cout <<
"\nReceived signal " << signal <<
", shutting down...\n";
51 auto* server = g_server.load();
60void install_signal_handlers() {
61 std::signal(SIGINT, signal_handler);
62 std::signal(SIGTERM, signal_handler);
64 std::signal(SIGHUP, signal_handler);
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";
83void print_usage(
const char* program_name) {
85Secure Echo SCP - TLS-secured DICOM Connectivity Test Server
87Usage: )" << program_name << R"( <port> <ae_title> --cert <file> --key <file> [options]
90 port Port number to listen on (typically 2762 for DICOM TLS)
91 ae_title Application Entity Title for this server (max 16 chars)
94 --cert <file> Server certificate file (PEM format)
95 --key <file> Server private key file (PEM format)
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)
103 --max-assoc <n> Maximum concurrent associations (default: 10)
104 --timeout <sec> Idle timeout in seconds (default: 300)
105 --help Show this help message
109 )" << program_name << R"( 2762 MY_PACS --cert server.crt --key server.key
111 # With client certificate verification (mutual TLS)
112 )" << program_name << R"( 2762 MY_PACS --cert server.crt --key server.key --ca ca.crt
115 )" << program_name << R"( 2762 MY_PACS --cert server.crt --key server.key --tls-version 1.3
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
124 1 Error - Failed to start server or invalid configuration
143 std::string& ae_title,
145 size_t& max_associations,
146 uint32_t& idle_timeout) {
153 for (
int i = 1; i < argc; ++i) {
154 if (std::string(argv[i]) ==
"--help" || std::string(argv[i]) ==
"-h") {
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";
166 port =
static_cast<uint16_t
>(port_int);
167 }
catch (
const std::exception&) {
168 std::cerr <<
"Error: Invalid port number '" << argv[1] <<
"'\n";
174 if (ae_title.length() > 16) {
175 std::cerr <<
"Error: AE title exceeds 16 characters\n";
180 max_associations = 10;
182 tls.verify_peer =
true;
183 tls.tls_version =
"1.2";
186 for (
int i = 3; i < argc; ++i) {
187 std::string arg = argv[i];
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";
203 }
else if (arg ==
"--max-assoc" && i + 1 < argc) {
205 int val = std::stoi(argv[++i]);
207 std::cerr <<
"Error: max-assoc must be positive\n";
210 max_associations =
static_cast<size_t>(val);
211 }
catch (
const std::exception&) {
212 std::cerr <<
"Error: Invalid max-assoc value\n";
215 }
else if (arg ==
"--timeout" && i + 1 < argc) {
217 int val = std::stoi(argv[++i]);
219 std::cerr <<
"Error: timeout cannot be negative\n";
222 idle_timeout =
static_cast<uint32_t
>(val);
223 }
catch (
const std::exception&) {
224 std::cerr <<
"Error: Invalid timeout value\n";
227 }
else if (arg[0] ==
'-') {
228 std::cerr <<
"Error: Unknown option '" << arg <<
"'\n";
234 if (
tls.cert_path.empty()) {
235 std::cerr <<
"Error: --cert is required\n";
238 if (
tls.key_path.empty()) {
239 std::cerr <<
"Error: --key is required\n";
251bool validate_tls_files(
const tls_options& tls) {
253 if (!std::filesystem::exists(
tls.cert_path)) {
254 std::cerr <<
"Error: Certificate file not found: " <<
tls.cert_path <<
"\n";
259 if (!std::filesystem::exists(
tls.key_path)) {
260 std::cerr <<
"Error: Key file not found: " <<
tls.key_path <<
"\n";
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";
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;
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();
294 const std::string& ae_title,
295 const tls_options& tls,
296 size_t max_associations,
297 uint32_t idle_timeout) {
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";
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";
315 std::cout <<
" Verify Peer: " << (
tls.verify_peer ?
"Yes" :
"No") <<
"\n";
316 std::cout <<
" Min TLS Version: " <<
tls.tls_version <<
"\n\n";
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;
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";
337 server_config config;
338 config.ae_title = ae_title;
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";
346 auto server_ptr = network_adapter::create_server(config, tls_cfg);
348 std::cerr <<
"Failed to create secure server\n";
352 auto& server = *server_ptr;
356 server.register_service(std::make_shared<verification_scp>());
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";
365 server.on_association_released([](
const association& assoc) {
366 std::cout <<
"[" << current_timestamp() <<
"] "
367 <<
"[TLS] Association released: " << assoc.calling_ae() <<
"\n";
370 server.on_error([](
const std::string& error) {
371 std::cerr <<
"[" << current_timestamp() <<
"] "
372 <<
"[TLS] Error: " <<
error <<
"\n";
376 auto result = server.start();
377 if (result.is_err()) {
378 std::cerr <<
"Failed to start server: " << result.error().message <<
"\n";
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";
389 server.wait_for_shutdown();
392 auto stats = server.get_statistics();
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";
411int main(
int argc,
char* argv[]) {
413 ____ _____ ____ _ _ ____ _____ _____ ____ ____
414 / ___|| ____/ ___| | | | _ \| ____| | ____/ ___/ ___|
415 \___ \| _|| | | | | | |_) | _| | _|| | \___ \
416 ___) | |__| |___| |_| | _ <| |___ | |__| |___ ___) |
417 |____/|_____\____|\___/|_| \_\_____| |_____\____|____/
424 TLS-Secured DICOM Connectivity Test Server
428 std::string ae_title;
430 size_t max_associations = 0;
431 uint32_t idle_timeout = 0;
433 if (!parse_arguments(argc, argv, port, ae_title, tls, max_associations, idle_timeout)) {
434 print_usage(argv[0]);
439 if (!validate_tls_files(tls)) {
444 install_signal_handlers();
446 bool success = run_server(port, ae_title, tls, max_associations, idle_timeout);
448 std::cout <<
"\nSecure Echo SCP terminated\n";
449 return success ? 0 : 1;
Multi-threaded DICOM server for handling multiple associations.
@ 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)